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 BINARY_NAME=statup
GOPATH:=$(GOPATH) GOPATH:=$(GOPATH)
GOCMD=go GOCMD=go

View File

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/hunterlong/statup/core" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/handlers"
"github.com/hunterlong/statup/source" "github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
@ -44,6 +45,8 @@ func CatchCLI(args []string) error {
LoadDotEnvs() LoadDotEnvs()
switch args[1] { switch args[1] {
case "app":
handlers.DesktopInit(ipAddress, port)
case "version": case "version":
if COMMIT != "" { if COMMIT != "" {
fmt.Printf("Statup v%v (%v)\n", VERSION, 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() { func (u *Service) DeleteFailures() {
var fails []*Failure _, err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, u.Id)
col := DbSession.Collection("failures") if err != nil {
col.Find("service", u.Id).All(&fails) utils.Log(3, fmt.Sprintf("failed to delete all failures: %v", err))
for _, fail := range fails {
fail.Delete()
} }
u.Failures = nil u.Failures = nil
} }

View File

@ -16,7 +16,10 @@
package handlers package handlers
import ( import (
"fmt"
"github.com/hunterlong/statup/core" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils"
"net/http" "net/http"
) )
@ -31,3 +34,61 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
} }
ExecuteResponse(w, r, "index.html", core.CoreApp) 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("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler))
r.Handle("/metrics", http.HandlerFunc(PrometheusHandler)) r.Handle("/metrics", http.HandlerFunc(PrometheusHandler))
r.NotFoundHandler = http.HandlerFunc(Error404Handler) r.NotFoundHandler = http.HandlerFunc(Error404Handler)
r.Handle("/tray", http.HandlerFunc(TrayHandler))
return r return r
} }
func resetRouter() { func ResetRouter() {
router = Router() router = Router()
httpServer.Handler = router httpServer.Handler = router
} }

View File

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

View File

@ -354,22 +354,29 @@ HTML, BODY {
box-shadow: 0 0 7px #47d337; box-shadow: 0 0 7px #47d337;
animation: glow-grow 2s ease-out infinite; } animation: glow-grow 2s ease-out infinite; }
.sortable { .sortable_drag {
background-color: #0000000f; }
.drag_icon {
cursor: move; cursor: move;
/* fallback if grab cursor is unsupported */ /* fallback if grab cursor is unsupported */
cursor: grab; cursor: grab;
cursor: -moz-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. */ /* (Optional) Apply a "closed-hand" cursor during drag operation. */
.sortable:active { .drag_icon:active {
cursor: grabbing; cursor: grabbing;
cursor: -moz-grabbing; cursor: -moz-grabbing;
cursor: -webkit-grabbing; } cursor: -webkit-grabbing; }
.sortable_drag {
background-color: #0000000f; }
@media (max-width: 767px) { @media (max-width: 767px) {
HTML, BODY { HTML, BODY {
background-color: #fcfcfc; } background-color: #fcfcfc; }

View File

@ -15,61 +15,63 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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 id = $(this).attr('data-id');
var position = $("#service_id_"+id).offset(); var position = $('#service_id_' + id).offset();
window.scroll(0,position.top-23); window.scroll(0, position.top - 23);
return false; return false;
}); });
$('form').submit(function() { $('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() { $('select#service_type').on('change', function() {
var selected = $('#service_type option:selected').val(); var selected = $('#service_type option:selected').val();
if (selected === "tcp") { if (selected === 'tcp') {
$("#service_port").parent().parent().removeClass("d-none"); $('#service_port').parent().parent().removeClass('d-none');
$("#service_check_type").parent().parent().addClass("d-none"); $('#service_check_type').parent().parent().addClass('d-none');
$("#service_url").attr("placeholder", "localhost"); $('#service_url').attr('placeholder', 'localhost');
$("#post_data").parent().parent().addClass("d-none"); $('#post_data').parent().parent().addClass('d-none');
$("#service_response").parent().parent().addClass("d-none"); $('#service_response').parent().parent().addClass('d-none');
$("#service_response_code").parent().parent().addClass("d-none"); $('#service_response_code').parent().parent().addClass('d-none');
} else { } else {
$("#post_data").parent().parent().removeClass("d-none"); $('#post_data').parent().parent().removeClass('d-none');
$("#service_response").parent().parent().removeClass("d-none"); $('#service_response').parent().parent().removeClass('d-none');
$("#service_response_code").parent().parent().removeClass("d-none"); $('#service_response_code').parent().parent().removeClass('d-none');
$("#service_check_type").parent().parent().removeClass("d-none"); $('#service_check_type').parent().parent().removeClass('d-none');
$("#service_url").attr("placeholder", "https://google.com"); $('#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() { $('select#service_check_type').on('change', function() {
var selected = $('#service_check_type option:selected').val(); var selected = $('#service_check_type option:selected').val();
if (selected === "POST") { if (selected === 'POST') {
$("#post_data").parent().parent().removeClass("d-none"); $('#post_data').parent().parent().removeClass('d-none');
} else { } else {
$("#post_data").parent().parent().addClass("d-none"); $('#post_data').parent().parent().addClass('d-none');
} }
}); });
$(function() { $(function() {
var pathname = window.location.pathname; var pathname = window.location.pathname;
if (pathname==="/logs") { if (pathname === '/logs') {
var lastline; var lastline;
var logArea = $("#live_logs"); var logArea = $('#live_logs');
setInterval(function() { setInterval(function() {
$.get("/logs/line", function(data, status){ $.get('/logs/line', function(data, status) {
if (lastline !== data) { if (lastline !== data) {
var curr = $.trim(logArea.text()); var curr = $.trim(logArea.text());
var line = data.replace(/(\r\n|\n|\r)/gm, " "); var line = data.replace(/(\r\n|\n|\r)/gm, ' ');
line = line + "\n"; line = line + '\n';
logArea.text(line + curr); logArea.text(line + curr);
lastline = data; lastline = data;
} }
@ -79,9 +81,9 @@ $(function() {
}); });
$(".confirm-btn").on('click', function() { $('.confirm-btn').on('click', function() {
var r = confirm("Are you sure you want to delete?"); var r = confirm('Are you sure you want to delete?');
if (r == true) { if (r === true) {
return true; return true;
} else { } else {
return false; return false;
@ -89,30 +91,64 @@ $(".confirm-btn").on('click', function() {
}); });
$(".select-input").on("click", function () { $('.select-input').on('click', function() {
$(this).select(); $(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 ranVar = false;
var ranTheme = false; var ranTheme = false;
$('a[data-toggle="pill"]').on('shown.bs.tab', function (e) { var ranMobile = false;
var target = $(e.target).attr("href"); $('a[data-toggle=pill]').on('shown.bs.tab', function(e) {
if (target==="#v-pills-style" && !ranVar) { var target = $(e.target).attr('href');
var sass_vars = CodeMirror.fromTextArea(document.getElementById("sass_vars"), { if (target === '#v-pills-style' && !ranVar) {
var sass_vars = CodeMirror.fromTextArea(document.getElementById('sass_vars'), {
lineNumbers: true, lineNumbers: true,
matchBrackets: true, matchBrackets: true,
mode: "text/x-scss", mode: 'text/x-scss',
colorpicker : true colorpicker: true
}); });
sass_vars.setSize(null, 900);
ranVar = true; ranVar = true;
} else if (target==="#pills-theme" && !ranTheme) { } else if (target === '#pills-theme' && !ranTheme) {
var theme_css = CodeMirror.fromTextArea(document.getElementById("theme_css"), { var theme_css = CodeMirror.fromTextArea(document.getElementById('theme_css'), {
lineNumbers: true, lineNumbers: true,
matchBrackets: true, matchBrackets: true,
mode: "text/x-scss", mode: 'text/x-scss',
colorpicker : true colorpicker: true
}); });
theme_css.setSize(null, 900);
ranTheme = true; 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; animation: glow-grow 2s ease-out infinite;
} }
.sortable { .sortable_drag {
background-color: #0000000f;
}
.drag_icon {
cursor: move; /* fallback if grab cursor is unsupported */ cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab; cursor: grab;
cursor: -moz-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. */ /* (Optional) Apply a "closed-hand" cursor during drag operation. */
.sortable:active { .drag_icon:active {
cursor: grabbing; cursor: grabbing;
cursor: -moz-grabbing; cursor: -moz-grabbing;
cursor: -webkit-grabbing; cursor: -webkit-grabbing;
} }
.sortable_drag {
background-color: #0000000f;
}
@import 'mobile'; @import 'mobile';

View File

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

View File

@ -64,7 +64,7 @@
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start"> <a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between"> <div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{.ParseError}}</h5> <h5 class="mb-1">{{.ParseError}}</h5>
<small>Reported {{.Ago}}</small> <small>{{.Ago}}</small>
</div> </div>
<p class="mb-1">{{.Issue}}</p> <p class="mb-1">{{.Issue}}</p>
</a> </a>
@ -109,10 +109,13 @@
<select name="method" class="form-control" id="service_check_type" value="{{.Method}}"> <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="GET" {{if eq .Method "GET"}}selected{{end}}>GET</option>
<option value="POST" {{if eq .Method "POST"}}selected{{end}}>POST</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> </select>
</div> </div>
</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> <label for="post_data" class="col-sm-4 col-form-label">Optional Post Data (JSON)</label>
<div class="col-sm-8"> <div class="col-sm-8">
<textarea name="post_data" class="form-control" id="post_data" rows="3" autocapitalize="false" spellcheck="false">{{.PostData}}</textarea> <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"> <tbody class="sortable">
{{range .}} {{range .}}
<tr id="{{.Id}}"> <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="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"> <td class="text-right">
<div class="btn-group"> <div class="btn-group">
@ -77,6 +77,9 @@
<select name="method" class="form-control" id="service_check_type"> <select name="method" class="form-control" id="service_check_type">
<option value="GET" selected>GET</option> <option value="GET" selected>GET</option>
<option value="POST">POST</option> <option value="POST">POST</option>
<option value="DELETE">DELETE</option>
<option value="PATCH">PATCH</option>
<option value="PUT">PUT</option>
</select> </select>
</div> </div>
</div> </div>
@ -152,7 +155,8 @@
<script> <script>
sortable('.sortable', { sortable('.sortable', {
forcePlaceholderSize: true, forcePlaceholderSize: true,
hoverClass: 'sortable_drag' hoverClass: 'sortable_drag',
handle: '.drag_icon'
}); });
sortable('.sortable')[0].addEventListener('sortupdate', function(e) { sortable('.sortable')[0].addEventListener('sortupdate', function(e) {
var i = 0; var i = 0;

View File

@ -26,7 +26,7 @@
<div class="row"> <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"> <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> <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>
<div class="col-sm-5 mt-4"> <div class="col-sm-5 mt-4">
<span class="switch"> <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> <label for="switch-normal">Enable CDN</label>
</span> </span>
</div> </div>
@ -101,7 +101,7 @@
<div class="tab-pane" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab"> <div class="tab-pane" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab">
{{if not .UsingAssets }} {{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 }} {{ else }}
<form method="POST" action="/settings/css"> <form method="POST" action="/settings/css">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist"> <ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
@ -111,6 +111,9 @@
<li class="nav-item col text-center"> <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> <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>
<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> </ul>
<div class="tab-content" id="pills-tabContent"> <div class="tab-content" id="pills-tabContent">
<div class="tab-pane show active" id="pills-vars" role="tabpanel" aria-labelledby="pills-vars-tab"> <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"> <div class="tab-pane" id="pills-theme" role="tabpanel" aria-labelledby="pills-theme-tab">
<textarea name="theme" id="theme_css">{{ .BaseSASS }}</textarea> <textarea name="theme" id="theme_css">{{ .BaseSASS }}</textarea>
</div> </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> </div>
<button type="submit" class="btn btn-primary btn-block mt-2">Save Style</button> <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> <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>