UI changes - sortable services

pull/57/head v0.51
Hunter Long 2018-08-23 00:28:48 -07:00
parent 40b31d3ccd
commit c24e073942
14 changed files with 297 additions and 69 deletions

View File

@ -1,4 +1,4 @@
VERSION=0.5
VERSION=0.51
BINARY_NAME=statup
GOPATH:=$(GOPATH)
GOCMD=go

View File

@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/handlers"
"github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
@ -44,6 +45,8 @@ func CatchCLI(args []string) error {
LoadDotEnvs()
switch args[1] {
case "app":
handlers.DesktopInit(ipAddress, port)
case "version":
if COMMIT != "" {
fmt.Printf("Statup v%v (%v)\n", VERSION, COMMIT)

View File

@ -56,11 +56,9 @@ func (s *Service) AllFailures() []*types.Failure {
}
func (u *Service) DeleteFailures() {
var fails []*Failure
col := DbSession.Collection("failures")
col.Find("service", u.Id).All(&fails)
for _, fail := range fails {
fail.Delete()
_, err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, u.Id)
if err != nil {
utils.Log(3, fmt.Sprintf("failed to delete all failures: %v", err))
}
u.Failures = nil
}

View File

@ -16,7 +16,10 @@
package handlers
import (
"fmt"
"github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"net/http"
)
@ -31,3 +34,61 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
}
ExecuteResponse(w, r, "index.html", core.CoreApp)
}
func TrayHandler(w http.ResponseWriter, r *http.Request) {
ExecuteResponse(w, r, "tray.html", core.CoreApp)
}
func DesktopInit(ip string, port int) {
config := &core.DbConfig{DbConfig: &types.DbConfig{
DbConn: "sqlite",
Project: "Statup",
Description: "Statup running as an App!",
Domain: "http://localhost",
Username: "admin",
Password: "admin",
Email: "user@email.com",
Error: nil,
Location: utils.Directory,
}}
fmt.Println(config)
err := config.Save()
if err != nil {
utils.Log(4, err)
}
if err != nil {
utils.Log(3, err)
return
}
core.Configs, err = core.LoadConfig()
if err != nil {
utils.Log(3, err)
config.Error = err
return
}
err = core.DbConnection(core.Configs.Connection, false, utils.Directory)
if err != nil {
utils.Log(3, err)
core.DeleteConfig()
config.Error = err
return
}
admin := core.ReturnUser(&types.User{
Username: config.Username,
Password: config.Password,
Email: config.Email,
Admin: true,
})
admin.Create()
core.LoadSampleData()
core.InitApp()
RunHTTPServer(ip, port)
}

View File

@ -101,10 +101,11 @@ func Router() *mux.Router {
r.Handle("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler))
r.Handle("/metrics", http.HandlerFunc(PrometheusHandler))
r.NotFoundHandler = http.HandlerFunc(Error404Handler)
r.Handle("/tray", http.HandlerFunc(TrayHandler))
return r
}
func resetRouter() {
func ResetRouter() {
router = Router()
httpServer.Handler = router
}

View File

@ -73,10 +73,12 @@ func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
theme := r.PostForm.Get("theme")
variables := r.PostForm.Get("variables")
mobile := r.PostForm.Get("mobile")
source.SaveAsset([]byte(theme), utils.Directory, "scss/base.scss")
source.SaveAsset([]byte(variables), utils.Directory, "scss/variables.scss")
source.SaveAsset([]byte(mobile), utils.Directory, "scss/mobile.scss")
source.CompileSASS(utils.Directory)
resetRouter()
ResetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp)
}
@ -96,7 +98,7 @@ func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
source.CopyToPublic(source.CssBox, dir+"/assets/css", "base.css")
utils.Log(2, "Default 'base.css' was insert because SASS did not work.")
}
resetRouter()
ResetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp)
}
@ -106,7 +108,7 @@ func DeleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
return
}
source.DeleteAllAssets(utils.Directory)
resetRouter()
ResetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp)
}

View File

@ -354,22 +354,29 @@ HTML, BODY {
box-shadow: 0 0 7px #47d337;
animation: glow-grow 2s ease-out infinite; }
.sortable {
.sortable_drag {
background-color: #0000000f; }
.drag_icon {
cursor: move;
/* fallback if grab cursor is unsupported */
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab; }
cursor: -webkit-grab;
width: 25px;
height: 25px;
display: inline-block;
margin-right: 5px;
margin-left: -10px;
text-align: center;
color: #b1b1b1; }
/* (Optional) Apply a "closed-hand" cursor during drag operation. */
.sortable:active {
.drag_icon:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing; }
.sortable_drag {
background-color: #0000000f; }
@media (max-width: 767px) {
HTML, BODY {
background-color: #fcfcfc; }

View File

@ -15,61 +15,63 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
$(".service_li").on('click', function() {
$('.service_li').on('click', function() {
var id = $(this).attr('data-id');
var position = $("#service_id_"+id).offset();
window.scroll(0,position.top-23);
var position = $('#service_id_' + id).offset();
window.scroll(0, position.top - 23);
return false;
});
$('form').submit(function() {
$(this).find("button[type='submit']").prop('disabled',true);
console.log(this);
$(this).find('button[type=submit]').prop('disabled', true);
});
$('select#service_type').on('change', function() {
var selected = $('#service_type option:selected').val();
if (selected === "tcp") {
$("#service_port").parent().parent().removeClass("d-none");
$("#service_check_type").parent().parent().addClass("d-none");
$("#service_url").attr("placeholder", "localhost");
if (selected === 'tcp') {
$('#service_port').parent().parent().removeClass('d-none');
$('#service_check_type').parent().parent().addClass('d-none');
$('#service_url').attr('placeholder', 'localhost');
$("#post_data").parent().parent().addClass("d-none");
$("#service_response").parent().parent().addClass("d-none");
$("#service_response_code").parent().parent().addClass("d-none");
$('#post_data').parent().parent().addClass('d-none');
$('#service_response').parent().parent().addClass('d-none');
$('#service_response_code').parent().parent().addClass('d-none');
} else {
$("#post_data").parent().parent().removeClass("d-none");
$("#service_response").parent().parent().removeClass("d-none");
$("#service_response_code").parent().parent().removeClass("d-none");
$("#service_check_type").parent().parent().removeClass("d-none");
$("#service_url").attr("placeholder", "https://google.com");
$('#post_data').parent().parent().removeClass('d-none');
$('#service_response').parent().parent().removeClass('d-none');
$('#service_response_code').parent().parent().removeClass('d-none');
$('#service_check_type').parent().parent().removeClass('d-none');
$('#service_url').attr('placeholder', 'https://google.com');
$("#service_port").parent().parent().addClass("d-none");
$('#service_port').parent().parent().addClass('d-none');
}
});
$('select#service_check_type').on('change', function() {
var selected = $('#service_check_type option:selected').val();
if (selected === "POST") {
$("#post_data").parent().parent().removeClass("d-none");
if (selected === 'POST') {
$('#post_data').parent().parent().removeClass('d-none');
} else {
$("#post_data").parent().parent().addClass("d-none");
$('#post_data').parent().parent().addClass('d-none');
}
});
$(function() {
var pathname = window.location.pathname;
if (pathname==="/logs") {
if (pathname === '/logs') {
var lastline;
var logArea = $("#live_logs");
var logArea = $('#live_logs');
setInterval(function() {
$.get("/logs/line", function(data, status){
$.get('/logs/line', function(data, status) {
if (lastline !== data) {
var curr = $.trim(logArea.text());
var line = data.replace(/(\r\n|\n|\r)/gm, " ");
line = line + "\n";
var line = data.replace(/(\r\n|\n|\r)/gm, ' ');
line = line + '\n';
logArea.text(line + curr);
lastline = data;
}
@ -79,9 +81,9 @@ $(function() {
});
$(".confirm-btn").on('click', function() {
var r = confirm("Are you sure you want to delete?");
if (r == true) {
$('.confirm-btn').on('click', function() {
var r = confirm('Are you sure you want to delete?');
if (r === true) {
return true;
} else {
return false;
@ -89,30 +91,64 @@ $(".confirm-btn").on('click', function() {
});
$(".select-input").on("click", function () {
$('.select-input').on('click', function() {
$(this).select();
});
// $('input[name=password], input[name=password_confirm]').on('change keyup input paste', function() {
// var password = $('input[name=password]'),
// repassword = $('input[name=password_confirm]'),
// both = password.add(repassword).removeClass('is-valid is-invalid');
//
// var btn = $(this).parents('form:first').find('button[type=submit]');
// password.addClass(
// password.val().length > 0 ? 'is-valid' : 'is-invalid'
// );
// repassword.addClass(
// password.val().length > 0 ? 'is-valid' : 'is-invalid'
// );
//
// if (password.val() !== repassword.val()) {
// both.addClass('is-invalid');
// btn.prop('disabled', true);
// } else {
// btn.prop('disabled', false);
// }
// });
var ranVar = false;
var ranTheme = false;
$('a[data-toggle="pill"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr("href");
if (target==="#v-pills-style" && !ranVar) {
var sass_vars = CodeMirror.fromTextArea(document.getElementById("sass_vars"), {
var ranMobile = false;
$('a[data-toggle=pill]').on('shown.bs.tab', function(e) {
var target = $(e.target).attr('href');
if (target === '#v-pills-style' && !ranVar) {
var sass_vars = CodeMirror.fromTextArea(document.getElementById('sass_vars'), {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-scss",
colorpicker : true
mode: 'text/x-scss',
colorpicker: true
});
sass_vars.setSize(null, 900);
ranVar = true;
} else if (target==="#pills-theme" && !ranTheme) {
var theme_css = CodeMirror.fromTextArea(document.getElementById("theme_css"), {
} else if (target === '#pills-theme' && !ranTheme) {
var theme_css = CodeMirror.fromTextArea(document.getElementById('theme_css'), {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-scss",
colorpicker : true
mode: 'text/x-scss',
colorpicker: true
});
theme_css.setSize(null, 900);
ranTheme = true;
} else if (target === '#pills-mobile' && !ranMobile) {
var mobile_css = CodeMirror.fromTextArea(document.getElementById('mobile_css'), {
lineNumbers: true,
matchBrackets: true,
mode: 'text/x-scss',
colorpicker: true
});
mobile_css.setSize(null, 900);
ranMobile = true;
}
});

View File

@ -410,22 +410,29 @@ HTML,BODY {
animation: glow-grow 2s ease-out infinite;
}
.sortable {
.sortable_drag {
background-color: #0000000f;
}
.drag_icon {
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
width: 25px;
height: 25px;
display: inline-block;
margin-right: 5px;
margin-left: -10px;
text-align: center;
color: #b1b1b1;
}
/* (Optional) Apply a "closed-hand" cursor during drag operation. */
.sortable:active {
.drag_icon:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
.sortable_drag {
background-color: #0000000f;
}
@import 'mobile';

View File

@ -66,7 +66,7 @@
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{.ParseError}}</h5>
<small>Reported {{.Ago}}</small>
<small>{{.Ago}}</small>
</div>
<p class="mb-1">{{.Issue}}</p>
</a>

View File

@ -64,7 +64,7 @@
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{.ParseError}}</h5>
<small>Reported {{.Ago}}</small>
<small>{{.Ago}}</small>
</div>
<p class="mb-1">{{.Issue}}</p>
</a>
@ -109,10 +109,13 @@
<select name="method" class="form-control" id="service_check_type" value="{{.Method}}">
<option value="GET" {{if eq .Method "GET"}}selected{{end}}>GET</option>
<option value="POST" {{if eq .Method "POST"}}selected{{end}}>POST</option>
<option value="DELETE" {{if eq .Method "DELETE"}}selected{{end}}>DELETE</option>
<option value="PATCH" {{if eq .Method "PATCH"}}selected{{end}}>PATCH</option>
<option value="PUT" {{if eq .Method "PUT"}}selected{{end}}>PUT</option>
</select>
</div>
</div>
<div class="form-group row{{if eq .Type "tcp"}} d-none{{end}}">
<div class="form-group row{{if ne .Method "POST"}} d-none{{end}}">
<label for="post_data" class="col-sm-4 col-form-label">Optional Post Data (JSON)</label>
<div class="col-sm-8">
<textarea name="post_data" class="form-control" id="post_data" rows="3" autocapitalize="false" spellcheck="false">{{.PostData}}</textarea>

View File

@ -34,7 +34,7 @@
<tbody class="sortable">
{{range .}}
<tr id="{{.Id}}">
<td>{{.Name}}</td>
<td><span class="drag_icon d-none d-md-inline">&#9776;</span> {{.Name}}</td>
<td class="d-none d-md-table-cell">{{if .Online}}<span class="badge badge-success">ONLINE</span>{{else}}<span class="badge badge-danger">OFFLINE</span>{{end}} </td>
<td class="text-right">
<div class="btn-group">
@ -77,6 +77,9 @@
<select name="method" class="form-control" id="service_check_type">
<option value="GET" selected>GET</option>
<option value="POST">POST</option>
<option value="DELETE">DELETE</option>
<option value="PATCH">PATCH</option>
<option value="PUT">PUT</option>
</select>
</div>
</div>
@ -152,7 +155,8 @@
<script>
sortable('.sortable', {
forcePlaceholderSize: true,
hoverClass: 'sortable_drag'
hoverClass: 'sortable_drag',
handle: '.drag_icon'
});
sortable('.sortable')[0].addEventListener('sortupdate', function(e) {
var i = 0;

View File

@ -26,7 +26,7 @@
<div class="row">
<div class="col-md-3 col-sm-12">
<div class="col-md-3 col-sm-12 mb-4 mb-md-0">
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a>
@ -65,7 +65,7 @@
</div>
<div class="col-sm-5 mt-4">
<span class="switch">
<input type="checkbox" name="enable_cdn" class="switch" id="switch-normal" {{if USE_CDN}}checked{{end}}>
<input type="checkbox" name="enable_cdn" class="switch" id="switch-normal" {{if USE_CDN}}checked{{end}}{{if .UsingAssets}} disabled{{end}}>
<label for="switch-normal">Enable CDN</label>
</span>
</div>
@ -101,7 +101,7 @@
<div class="tab-pane" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab">
{{if not .UsingAssets }}
<a href="/settings/build" class="btn btn-primary btn-block">Enable Local Assets</a>
<a href="/settings/build" class="btn btn-primary btn-block"{{if USE_CDN}} disabled{{end}}>Enable Local Assets</a>
{{ else }}
<form method="POST" action="/settings/css">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
@ -111,6 +111,9 @@
<li class="nav-item col text-center">
<a class="nav-link" id="pills-theme-tab" data-toggle="pill" href="#pills-theme" role="tab" aria-controls="pills-theme" aria-selected="false">Base Theme</a>
</li>
<li class="nav-item col text-center">
<a class="nav-link" id="pills-mobile-tab" data-toggle="pill" href="#pills-mobile" role="tab" aria-controls="pills-mobile" aria-selected="false">Mobile</a>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane show active" id="pills-vars" role="tabpanel" aria-labelledby="pills-vars-tab">
@ -119,6 +122,9 @@
<div class="tab-pane" id="pills-theme" role="tabpanel" aria-labelledby="pills-theme-tab">
<textarea name="theme" id="theme_css">{{ .BaseSASS }}</textarea>
</div>
<div class="tab-pane" id="pills-mobile" role="tabpanel" aria-labelledby="pills-mobile-tab">
<textarea name="mobile" id="mobile_css">{{ .MobileSASS }}</textarea>
</div>
</div>
<button type="submit" class="btn btn-primary btn-block mt-2">Save Style</button>
<a href="/settings/delete_assets" class="btn btn-danger btn-block confirm-btn">Delete All Assets</a>

100
source/tmpl/tray.html Normal file
View File

@ -0,0 +1,100 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-355, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
{{if USE_CDN}}
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statup.io/favicon.ico">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<link rel="stylesheet" href="https://assets.statup.io/base.css">
{{ else }}
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/base.css">
{{end}}
<title>{{.Name}} Status</title>
<style>
BODY::-webkit-scrollbar { width: 0 !important }
</style>
</head>
<body>
<div class="container col-12 sm-container" style="margin-top: 0 !important;">
<div class="col-12 full-col-12">
<div class="list-group online_list">
{{ range .Services }}
<a href="#" class="service_li list-group-item list-group-item-action text-truncate {{if not .Online}}bg-danger text-white{{ end }}" data-id="{{.Id}}">
{{ .Name }}
{{if .Online}}
<span class="badge bg-success float-right pulse-glow">ONLINE</span>
{{ else }}
<span class="badge bg-white text-black-50 float-right pulse">OFFLINE</span>
{{end}}
</a>
{{ end }}
</div>
</div>
<div class="col-12 full-col-12">
{{ range .Services }}
<div class="mt-4" id="service_id_{{.Id}}">
<div class="card">
<div class="card-body">
<div class="col-12">
<h4 class="mt-3"><a href="/service/{{.Id}}"{{if not .Online}} class="text-danger"{{end}}>{{ .Name }}</a>
{{if .Online}}
<span class="badge bg-success float-right">ONLINE</span>
{{ else }}
<span class="badge bg-danger float-right pulse">OFFLINE</span>
{{end}}</h4>
<div class="row stats_area mt-5 mb-5">
<div class="col-4">
<span class="lg_number">{{.Online24}}%</span>
Online last 24 Hours
</div>
<div class="col-4">
<span class="lg_number">{{.AvgTime}}ms</span>
Average Response
</div>
<div class="col-4">
<span class="lg_number">{{.AvgUptime}}%</span>
Total Uptime
</div>
</div>
</div>
</div>
{{ if .AvgTime }}
<div class="chart-container">
<canvas id="service_{{ .Id }}"></canvas>
</div>
{{ end }}
<div class="row lower_canvas full-col-12 text-white{{if not .Online}} bg-danger{{end}}">
<div class="col-10 text-truncate">
<span class="d-none d-md-inline">{{.SmallText}}</span>
</div>
<div class="col-sm-12 col-md-2">
</div>
</div>
</div>
</div>
{{ end }}
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<script src="https://assets.statup.io/main.js"></script>
<script src="/charts.js"></script>
</body>
</html>