mirror of https://github.com/statping/statping
updates
parent
fefc8a94fe
commit
f491cbd019
|
@ -19,6 +19,7 @@ public
|
|||
.env
|
||||
logs
|
||||
tmp
|
||||
source/dist
|
||||
/dev/test/node_modules
|
||||
dev/test/cypress/videos
|
||||
dev/test/cypress/screenshots
|
||||
|
|
6
Makefile
6
Makefile
|
@ -22,6 +22,8 @@ frontend:
|
|||
|
||||
frontend-build:
|
||||
cd frontend && rm -rf dist && yarn build
|
||||
rm -rf source/dist && cp -r frontend/dist source/
|
||||
cp -r source/tmpl/*.* source/dist/
|
||||
|
||||
# build and push the images to docker hub
|
||||
docker: docker-build-all docker-publish-all
|
||||
|
@ -93,10 +95,8 @@ watch:
|
|||
-i="Makefile,statping,statup.db,statup.db-journal,handlers/graphql/generated.go"
|
||||
|
||||
# compile assets using SASS and Rice. compiles scss -> css, and run rice embed-go
|
||||
compile: generate
|
||||
sass source/scss/base.scss source/css/base.css
|
||||
compile: frontend-build generate
|
||||
cd source && rice embed-go
|
||||
rm -rf .sass-cache
|
||||
|
||||
# benchmark testing
|
||||
benchmark:
|
||||
|
|
|
@ -99,11 +99,11 @@ func (c *Checkin) CreateFailure() (int64, error) {
|
|||
if len(c.Failures) > limitedFailures {
|
||||
c.Failures = c.Failures[1:]
|
||||
}
|
||||
return fail.Id, row.Error
|
||||
return fail.Id, row.Error()
|
||||
}
|
||||
|
||||
// LimitedHits will return the last amount of successful hits from a checkin
|
||||
func (c *Checkin) LimitedHits(amount int64) []*types.CheckinHit {
|
||||
func (c *Checkin) LimitedHits(amount int) []*types.CheckinHit {
|
||||
var hits []*types.CheckinHit
|
||||
checkinHitsDB().Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits)
|
||||
return hits
|
||||
|
@ -168,7 +168,7 @@ func (c *Checkin) AllHits() []*types.CheckinHit {
|
|||
}
|
||||
|
||||
// Hits returns all of the CheckinHits for a given Checkin
|
||||
func (c *Checkin) LimitedFailures(amount int64) []types.FailureInterface {
|
||||
func (c *Checkin) LimitedFailures(amount int) []types.FailureInterface {
|
||||
var failures []*Failure
|
||||
var failInterfaces []types.FailureInterface
|
||||
col := failuresDB().Where("checkin = ?", c.Id).Where("method = 'checkin'").Limit(amount).Order("id desc")
|
||||
|
@ -195,7 +195,7 @@ func (c *Checkin) Delete() error {
|
|||
slice := service.Checkins
|
||||
service.Checkins = append(slice[:i], slice[i+1:]...)
|
||||
row := checkinDB().Delete(&c)
|
||||
return row.Error
|
||||
return row.Error()
|
||||
}
|
||||
|
||||
// index returns a checkin index int for updating the *checkin.Service slice
|
||||
|
@ -212,25 +212,25 @@ func (c *Checkin) index() int {
|
|||
func (c *Checkin) Create() (int64, error) {
|
||||
c.ApiKey = utils.RandomString(7)
|
||||
row := checkinDB().Create(&c)
|
||||
if row.Error != nil {
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
if row.Error() != nil {
|
||||
log.Warnln(row.Error())
|
||||
return 0, row.Error()
|
||||
}
|
||||
service := SelectService(c.ServiceId)
|
||||
service.Checkins = append(service.Checkins, c)
|
||||
c.Start()
|
||||
go c.Routine()
|
||||
return c.Id, row.Error
|
||||
return c.Id, row.Error()
|
||||
}
|
||||
|
||||
// Update will update a Checkin
|
||||
func (c *Checkin) Update() (int64, error) {
|
||||
row := checkinDB().Update(&c)
|
||||
if row.Error != nil {
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
if row.Error() != nil {
|
||||
log.Warnln(row.Error())
|
||||
return 0, row.Error()
|
||||
}
|
||||
return c.Id, row.Error
|
||||
return c.Id, row.Error()
|
||||
}
|
||||
|
||||
// Create will create a new successful checkinHit
|
||||
|
@ -239,11 +239,11 @@ func (c *CheckinHit) Create() (int64, error) {
|
|||
c.CreatedAt = utils.Now()
|
||||
}
|
||||
row := checkinHitsDB().Create(&c)
|
||||
if row.Error != nil {
|
||||
log.Warnln(row.Error)
|
||||
return 0, row.Error
|
||||
if row.Error() != nil {
|
||||
log.Warnln(row.Error())
|
||||
return 0, row.Error()
|
||||
}
|
||||
return c.Id, row.Error
|
||||
return c.Id, row.Error()
|
||||
}
|
||||
|
||||
// Ago returns the duration of time between now and the last successful checkinHit
|
||||
|
|
|
@ -89,7 +89,7 @@ func InsertNotifierDB() error {
|
|||
// UpdateCore will update the CoreApp variable inside of the 'core' table in database
|
||||
func UpdateCore(c *Core) (*Core, error) {
|
||||
db := coreDB().Update(&c)
|
||||
return c, db.Error
|
||||
return c, db.Error()
|
||||
}
|
||||
|
||||
// CurrentTime will return the current local time
|
||||
|
@ -159,12 +159,12 @@ func SelectCore() (*Core, error) {
|
|||
return nil, errors.New("core database has not been setup yet.")
|
||||
}
|
||||
db := coreDB().First(&CoreApp)
|
||||
if db.Error != nil {
|
||||
return nil, db.Error
|
||||
if db.Error() != nil {
|
||||
return nil, db.Error()
|
||||
}
|
||||
CoreApp.Version = VERSION
|
||||
CoreApp.UseCdn = types.NewNullBool(os.Getenv("USE_CDN") == "true")
|
||||
return CoreApp, db.Error
|
||||
return CoreApp, db.Error()
|
||||
}
|
||||
|
||||
// GetLocalIP returns the non loopback local IP of the host
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
|
||||
var (
|
||||
// DbSession stores the Statping database session
|
||||
DbSession *gorm.DB
|
||||
DbSession types.Database
|
||||
DbModels []interface{}
|
||||
)
|
||||
|
||||
|
@ -48,62 +48,62 @@ func init() {
|
|||
type DbConfig types.DbConfig
|
||||
|
||||
// failuresDB returns the 'failures' database column
|
||||
func failuresDB() *gorm.DB {
|
||||
func failuresDB() types.Database {
|
||||
return DbSession.Model(&types.Failure{})
|
||||
}
|
||||
|
||||
// hitsDB returns the 'hits' database column
|
||||
func hitsDB() *gorm.DB {
|
||||
func hitsDB() types.Database {
|
||||
return DbSession.Model(&types.Hit{})
|
||||
}
|
||||
|
||||
// servicesDB returns the 'services' database column
|
||||
func servicesDB() *gorm.DB {
|
||||
func servicesDB() types.Database {
|
||||
return DbSession.Model(&types.Service{})
|
||||
}
|
||||
|
||||
// coreDB returns the single column 'core'
|
||||
func coreDB() *gorm.DB {
|
||||
func coreDB() types.Database {
|
||||
return DbSession.Table("core").Model(&CoreApp)
|
||||
}
|
||||
|
||||
// usersDB returns the 'users' database column
|
||||
func usersDB() *gorm.DB {
|
||||
func usersDB() types.Database {
|
||||
return DbSession.Model(&types.User{})
|
||||
}
|
||||
|
||||
// checkinDB returns the Checkin records for a service
|
||||
func checkinDB() *gorm.DB {
|
||||
func checkinDB() types.Database {
|
||||
return DbSession.Model(&types.Checkin{})
|
||||
}
|
||||
|
||||
// checkinHitsDB returns the Checkin Hits records for a service
|
||||
func checkinHitsDB() *gorm.DB {
|
||||
func checkinHitsDB() types.Database {
|
||||
return DbSession.Model(&types.CheckinHit{})
|
||||
}
|
||||
|
||||
// messagesDb returns the Checkin records for a service
|
||||
func messagesDb() *gorm.DB {
|
||||
func messagesDb() types.Database {
|
||||
return DbSession.Model(&types.Message{})
|
||||
}
|
||||
|
||||
// messagesDb returns the Checkin records for a service
|
||||
func groupsDb() *gorm.DB {
|
||||
func groupsDb() types.Database {
|
||||
return DbSession.Model(&types.Group{})
|
||||
}
|
||||
|
||||
// incidentsDB returns the 'incidents' database column
|
||||
func incidentsDB() *gorm.DB {
|
||||
func incidentsDB() types.Database {
|
||||
return DbSession.Model(&types.Incident{})
|
||||
}
|
||||
|
||||
// incidentsUpdatesDB returns the 'incidents updates' database column
|
||||
func incidentsUpdatesDB() *gorm.DB {
|
||||
func incidentsUpdatesDB() types.Database {
|
||||
return DbSession.Model(&types.IncidentUpdate{})
|
||||
}
|
||||
|
||||
// HitsBetween returns the gorm database query for a collection of service hits between a time range
|
||||
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) *gorm.DB {
|
||||
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) types.Database {
|
||||
selector := Dbtimestamp(group, column)
|
||||
if CoreApp.Config.DbConn == "postgres" {
|
||||
return hitsDB().Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
|
||||
|
@ -187,7 +187,7 @@ func (c *Core) InsertCore(db *types.DbConfig) (*Core, error) {
|
|||
Config: db,
|
||||
}}
|
||||
query := coreDB().Create(&CoreApp)
|
||||
return CoreApp, query.Error
|
||||
return CoreApp, query.Error()
|
||||
}
|
||||
|
||||
func findDbFile() string {
|
||||
|
@ -242,7 +242,7 @@ func (c *Core) Connect(retry bool, location string) error {
|
|||
conn = fmt.Sprintf("sqlserver://%v:%v@%v?database=%v", CoreApp.Config.DbUser, CoreApp.Config.DbPass, host, CoreApp.Config.DbData)
|
||||
}
|
||||
log.WithFields(utils.ToFields(c, conn)).Debugln("attempting to connect to database")
|
||||
dbSession, err := gorm.Open(dbType, conn)
|
||||
dbSession, err := types.Openw(dbType, conn)
|
||||
if err != nil {
|
||||
log.Debugln(fmt.Sprintf("Database connection error %v", err))
|
||||
if retry {
|
||||
|
@ -261,7 +261,7 @@ func (c *Core) Connect(retry bool, location string) error {
|
|||
if dbSession.DB().Ping() == nil {
|
||||
DbSession = dbSession
|
||||
if utils.VerboseMode >= 4 {
|
||||
DbSession.LogMode(true).Debug().SetLogger(log)
|
||||
DbSession.LogMode(true).Debug().SetLogger(gorm.Logger{log})
|
||||
}
|
||||
log.Infoln(fmt.Sprintf("Database %v connection was successful.", dbType))
|
||||
}
|
||||
|
@ -289,8 +289,8 @@ func DatabaseMaintence() {
|
|||
func DeleteAllSince(table string, date time.Time) {
|
||||
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02"))
|
||||
db := DbSession.Exec(sql)
|
||||
if db.Error != nil {
|
||||
log.Warnln(db.Error)
|
||||
if db.Error() != nil {
|
||||
log.Warnln(db.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,7 +346,7 @@ func (c *Core) CreateCore() *Core {
|
|||
MigrationId: time.Now().Unix(),
|
||||
}
|
||||
db := coreDB().Create(&newCore)
|
||||
if db.Error == nil {
|
||||
if db.Error() == nil {
|
||||
CoreApp = &Core{Core: newCore}
|
||||
}
|
||||
CoreApp, err := SelectCore()
|
||||
|
@ -370,7 +370,7 @@ func (c *Core) DropDatabase() error {
|
|||
err = DbSession.DropTableIfExists("messages")
|
||||
err = DbSession.DropTableIfExists("incidents")
|
||||
err = DbSession.DropTableIfExists("incident_updates")
|
||||
return err.Error
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// CreateDatabase will CREATE TABLES for each of the Statping elements
|
||||
|
@ -378,12 +378,12 @@ func (c *Core) CreateDatabase() error {
|
|||
var err error
|
||||
log.Infoln("Creating Database Tables...")
|
||||
for _, table := range DbModels {
|
||||
if err := DbSession.CreateTable(table); err.Error != nil {
|
||||
return err.Error
|
||||
if err := DbSession.CreateTable(table); err.Error() != nil {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
if err := DbSession.Table("core").CreateTable(&types.Core{}); err.Error != nil {
|
||||
return err.Error
|
||||
if err := DbSession.Table("core").CreateTable(&types.Core{}); err.Error() != nil {
|
||||
return err.Error()
|
||||
}
|
||||
log.Infoln("Statping Database Created")
|
||||
return err
|
||||
|
@ -401,17 +401,17 @@ func (c *Core) MigrateDatabase() error {
|
|||
}
|
||||
}()
|
||||
if tx.Error != nil {
|
||||
log.Errorln(tx.Error)
|
||||
return tx.Error
|
||||
log.Errorln(tx.Error())
|
||||
return tx.Error()
|
||||
}
|
||||
for _, table := range DbModels {
|
||||
tx = tx.AutoMigrate(table)
|
||||
}
|
||||
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error != nil {
|
||||
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error() != nil {
|
||||
tx.Rollback()
|
||||
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error))
|
||||
return tx.Error
|
||||
return tx.Error()
|
||||
}
|
||||
log.Infoln("Statping Database Migrated")
|
||||
return tx.Commit().Error
|
||||
return tx.Commit().Error()
|
||||
}
|
||||
|
|
|
@ -16,33 +16,11 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// ExportChartsJs renders the charts for the index page
|
||||
func ExportChartsJs() string {
|
||||
render, err := source.JsBox.String("charts.js")
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
t := template.New("charts")
|
||||
t.Funcs(template.FuncMap{
|
||||
"safe": func(html string) template.HTML {
|
||||
return template.HTML(html)
|
||||
},
|
||||
})
|
||||
t.Parse(render)
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, CoreApp.Services); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
result := tpl.String()
|
||||
return result
|
||||
}
|
||||
|
||||
type ExportData struct {
|
||||
Core *types.Core `json:"core"`
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -37,48 +38,51 @@ const (
|
|||
func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
|
||||
f.Service = s.Id
|
||||
row := failuresDB().Create(f)
|
||||
if row.Error != nil {
|
||||
log.Errorln(row.Error)
|
||||
return 0, row.Error
|
||||
if row.Error() != nil {
|
||||
log.Errorln(row.Error())
|
||||
return 0, row.Error()
|
||||
}
|
||||
sort.Sort(types.FailSort(s.Failures))
|
||||
//s.Failures = append(s.Failures, f)
|
||||
if len(s.Failures) > limitedFailures {
|
||||
s.Failures = s.Failures[1:]
|
||||
}
|
||||
return f.Id, row.Error
|
||||
return f.Id, row.Error()
|
||||
}
|
||||
|
||||
// AllFailures will return all failures attached to a service
|
||||
func (s *Service) AllFailures() []*types.Failure {
|
||||
var fails []*types.Failure
|
||||
col := failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc")
|
||||
err := col.Find(&fails)
|
||||
if err.Error != nil {
|
||||
func (s *Service) AllFailures() []types.Failure {
|
||||
var fails []types.Failure
|
||||
err := DbSession.Failures(s.Id).Find(&fails)
|
||||
if err.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
||||
return nil
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
func (s *Service) FailuresDb(r *http.Request) types.Database {
|
||||
return failuresDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
|
||||
}
|
||||
|
||||
// DeleteFailures will delete all failures for a service
|
||||
func (s *Service) DeleteFailures() {
|
||||
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
|
||||
if err.Error != nil {
|
||||
if err.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("failed to delete all failures: %v", err))
|
||||
}
|
||||
s.Failures = nil
|
||||
}
|
||||
|
||||
// LimitedFailures will return the last amount of failures from a service
|
||||
func (s *Service) LimitedFailures(amount int64) []*Failure {
|
||||
func (s *Service) LimitedFailures(amount int) []*Failure {
|
||||
var failArr []*Failure
|
||||
failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
|
||||
return failArr
|
||||
}
|
||||
|
||||
// LimitedFailures will return the last amount of failures from a service
|
||||
func (s *Service) LimitedCheckinFailures(amount int64) []*Failure {
|
||||
func (s *Service) LimitedCheckinFailures(amount int) []*Failure {
|
||||
var failArr []*Failure
|
||||
failuresDB().Where("service = ?", s.Id).Where("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
|
||||
return failArr
|
||||
|
@ -98,7 +102,7 @@ func (f *Failure) Select() *types.Failure {
|
|||
// Delete will remove a Failure record from the database
|
||||
func (f *Failure) Delete() error {
|
||||
db := failuresDB().Delete(f)
|
||||
return db.Error
|
||||
return db.Error()
|
||||
}
|
||||
|
||||
// Count24HFailures returns the amount of failures for a service within the last 24 hours
|
||||
|
@ -116,7 +120,7 @@ func (c *Core) Count24HFailures() uint64 {
|
|||
func CountFailures() uint64 {
|
||||
var count uint64
|
||||
err := failuresDB().Count(&count)
|
||||
if err.Error != nil {
|
||||
if err.Error() != nil {
|
||||
log.Warnln(err.Error)
|
||||
return 0
|
||||
}
|
||||
|
@ -130,7 +134,7 @@ func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) {
|
|||
dateend := ago.UTC().Format("2006-01-02") + " 23:59:59"
|
||||
rows := failuresDB().Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'")
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error
|
||||
return count, err.Error()
|
||||
}
|
||||
|
||||
// TotalFailures24 returns the amount of failures for a service within the last 24 hours
|
||||
|
@ -144,7 +148,7 @@ func (s *Service) TotalFailures() (uint64, error) {
|
|||
var count uint64
|
||||
rows := failuresDB().Where("service = ?", s.Id)
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error
|
||||
return count, err.Error()
|
||||
}
|
||||
|
||||
// FailuresDaysAgo returns the amount of failures since days ago
|
||||
|
@ -159,7 +163,7 @@ func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
|
|||
var count uint64
|
||||
rows := failuresDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Not("method = 'checkin'")
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error
|
||||
return count, err.Error()
|
||||
}
|
||||
|
||||
// ParseError returns a human readable error for a Failure
|
||||
|
|
|
@ -17,21 +17,21 @@ func (g *Group) Delete() error {
|
|||
s.Update(false)
|
||||
}
|
||||
err := groupsDb().Delete(g)
|
||||
return err.Error
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// Create will create a group and insert it into the database
|
||||
func (g *Group) Create() (int64, error) {
|
||||
g.CreatedAt = time.Now().UTC()
|
||||
db := groupsDb().Create(g)
|
||||
return g.Id, db.Error
|
||||
return g.Id, db.Error()
|
||||
}
|
||||
|
||||
// Update will update a group
|
||||
func (g *Group) Update() (int64, error) {
|
||||
g.UpdatedAt = time.Now().UTC()
|
||||
db := groupsDb().Update(g)
|
||||
return g.Id, db.Error
|
||||
return g.Id, db.Error()
|
||||
}
|
||||
|
||||
// Services returns all services belonging to a group
|
||||
|
|
33
core/hits.go
33
core/hits.go
|
@ -17,6 +17,7 @@ package core
|
|||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -27,11 +28,11 @@ type Hit struct {
|
|||
// CreateHit will create a new 'hit' record in the database for a successful/online service
|
||||
func (s *Service) CreateHit(h *types.Hit) (int64, error) {
|
||||
db := hitsDB().Create(&h)
|
||||
if db.Error != nil {
|
||||
log.Errorln(db.Error)
|
||||
return 0, db.Error
|
||||
if db.Error() != nil {
|
||||
log.Errorln(db.Error())
|
||||
return 0, db.Error()
|
||||
}
|
||||
return h.Id, db.Error
|
||||
return h.Id, db.Error()
|
||||
}
|
||||
|
||||
// CountHits returns a int64 for all hits for a service
|
||||
|
@ -39,7 +40,19 @@ func (s *Service) CountHits() (int64, error) {
|
|||
var hits int64
|
||||
col := hitsDB().Where("service = ?", s.Id)
|
||||
err := col.Count(&hits)
|
||||
return hits, err.Error
|
||||
return hits, err.Error()
|
||||
}
|
||||
|
||||
// Hits returns all successful hits for a service
|
||||
func (s *Service) HitsQuery(r *http.Request) ([]*types.Hit, error) {
|
||||
var hits []*types.Hit
|
||||
col := hitsDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
|
||||
err := col.Find(&hits)
|
||||
return hits, err.Error()
|
||||
}
|
||||
|
||||
func (s *Service) HitsDb(r *http.Request) types.Database {
|
||||
return hitsDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
|
||||
}
|
||||
|
||||
// Hits returns all successful hits for a service
|
||||
|
@ -47,15 +60,15 @@ func (s *Service) Hits() ([]*types.Hit, error) {
|
|||
var hits []*types.Hit
|
||||
col := hitsDB().Where("service = ?", s.Id).Order("id desc")
|
||||
err := col.Find(&hits)
|
||||
return hits, err.Error
|
||||
return hits, err.Error()
|
||||
}
|
||||
|
||||
// LimitedHits returns the last 1024 successful/online 'hit' records for a service
|
||||
func (s *Service) LimitedHits(amount int64) ([]*types.Hit, error) {
|
||||
func (s *Service) LimitedHits(amount int) ([]*types.Hit, error) {
|
||||
var hits []*types.Hit
|
||||
col := hitsDB().Where("service = ?", s.Id).Order("id desc").Limit(amount)
|
||||
err := col.Find(&hits)
|
||||
return reverseHits(hits), err.Error
|
||||
return reverseHits(hits), err.Error()
|
||||
}
|
||||
|
||||
// reverseHits will reverse the service's hit slice
|
||||
|
@ -70,14 +83,14 @@ func reverseHits(input []*types.Hit) []*types.Hit {
|
|||
func (s *Service) TotalHits() (uint64, error) {
|
||||
var count uint64
|
||||
col := hitsDB().Where("service = ?", s.Id).Count(&count)
|
||||
return count, col.Error
|
||||
return count, col.Error()
|
||||
}
|
||||
|
||||
// TotalHitsSince returns the total amount of hits based on a specific time/date
|
||||
func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) {
|
||||
var count uint64
|
||||
rows := hitsDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Count(&count)
|
||||
return count, rows.Error
|
||||
return count, rows.Error()
|
||||
}
|
||||
|
||||
// Sum returns the added value Latency for all of the services successful hits.
|
||||
|
|
|
@ -42,32 +42,32 @@ func (i *Incident) AllUpdates() []*IncidentUpdate {
|
|||
// Delete will remove a incident
|
||||
func (i *Incident) Delete() error {
|
||||
err := incidentsDB().Delete(i)
|
||||
return err.Error
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// Create will create a incident and insert it into the database
|
||||
func (i *Incident) Create() (int64, error) {
|
||||
i.CreatedAt = time.Now().UTC()
|
||||
db := incidentsDB().Create(i)
|
||||
return i.Id, db.Error
|
||||
return i.Id, db.Error()
|
||||
}
|
||||
|
||||
// Update will update a incident
|
||||
func (i *Incident) Update() (int64, error) {
|
||||
i.UpdatedAt = time.Now().UTC()
|
||||
db := incidentsDB().Update(i)
|
||||
return i.Id, db.Error
|
||||
return i.Id, db.Error()
|
||||
}
|
||||
|
||||
// Delete will remove a incident update
|
||||
func (i *IncidentUpdate) Delete() error {
|
||||
err := incidentsUpdatesDB().Delete(i)
|
||||
return err.Error
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// Create will create a incident update and insert it into the database
|
||||
func (i *IncidentUpdate) Create() (int64, error) {
|
||||
i.CreatedAt = time.Now().UTC()
|
||||
db := incidentsUpdatesDB().Create(i)
|
||||
return i.Id, db.Error
|
||||
return i.Id, db.Error()
|
||||
}
|
||||
|
|
|
@ -41,14 +41,14 @@ func ReturnMessage(m *types.Message) *Message {
|
|||
func SelectMessages() ([]*Message, error) {
|
||||
var messages []*Message
|
||||
db := messagesDb().Find(&messages).Order("id desc")
|
||||
return messages, db.Error
|
||||
return messages, db.Error()
|
||||
}
|
||||
|
||||
// SelectMessage returns a Message based on the ID passed
|
||||
func SelectMessage(id int64) (*Message, error) {
|
||||
var message Message
|
||||
db := messagesDb().Where("id = ?", id).Find(&message)
|
||||
return &message, db.Error
|
||||
return &message, db.Error()
|
||||
}
|
||||
|
||||
func (m *Message) Service() *Service {
|
||||
|
@ -62,9 +62,9 @@ func (m *Message) Service() *Service {
|
|||
func (m *Message) Create() (int64, error) {
|
||||
m.CreatedAt = time.Now().UTC()
|
||||
db := messagesDb().Create(m)
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||
return 0, db.Error
|
||||
if db.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error()))
|
||||
return 0, db.Error()
|
||||
}
|
||||
return m.Id, nil
|
||||
}
|
||||
|
@ -72,15 +72,15 @@ func (m *Message) Create() (int64, error) {
|
|||
// Delete will delete a Message from database
|
||||
func (m *Message) Delete() error {
|
||||
db := messagesDb().Delete(m)
|
||||
return db.Error
|
||||
return db.Error()
|
||||
}
|
||||
|
||||
// Update will update a Message in the database
|
||||
func (m *Message) Update() (*Message, error) {
|
||||
db := messagesDb().Update(m)
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error))
|
||||
return nil, db.Error
|
||||
if db.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error()))
|
||||
return nil, db.Error()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/jinzhu/gorm"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -31,7 +30,7 @@ var (
|
|||
// AllCommunications holds all the loaded notifiers
|
||||
AllCommunications []types.AllNotifiers
|
||||
// db holds the Statping database connection
|
||||
db *gorm.DB
|
||||
db types.Database
|
||||
timezone float32
|
||||
log = utils.Log.WithField("type", "notifier")
|
||||
)
|
||||
|
@ -112,12 +111,12 @@ func (n *Notification) CanTest() bool {
|
|||
}
|
||||
|
||||
// db will return the notifier database column/record
|
||||
func modelDb(n *Notification) *gorm.DB {
|
||||
func modelDb(n *Notification) types.Database {
|
||||
return db.Model(&Notification{}).Where("method = ?", n.Method).Find(n)
|
||||
}
|
||||
|
||||
// SetDB is called by core to inject the database for a notifier to use
|
||||
func SetDB(d *gorm.DB, zone float32) {
|
||||
func SetDB(d types.Database, zone float32) {
|
||||
db = d
|
||||
timezone = zone
|
||||
}
|
||||
|
@ -199,7 +198,7 @@ func isInDatabase(n Notifier) bool {
|
|||
func SelectNotification(n Notifier) (*Notification, error) {
|
||||
notifier := n.Select()
|
||||
err := db.Model(&Notification{}).Where("method = ?", notifier.Method).Scan(¬ifier)
|
||||
return notifier, err.Error
|
||||
return notifier, err.Error()
|
||||
}
|
||||
|
||||
// Update will update the notification into the database
|
||||
|
@ -213,7 +212,7 @@ func Update(n Notifier, notif *Notification) (*Notification, error) {
|
|||
} else {
|
||||
notif.close()
|
||||
}
|
||||
return notif, err.Error
|
||||
return notif, err.Error()
|
||||
}
|
||||
|
||||
// insertDatabase will create a new record into the database for the notifier
|
||||
|
@ -221,10 +220,10 @@ func insertDatabase(n Notifier) (int64, error) {
|
|||
noti := n.Select()
|
||||
noti.Limits = 3
|
||||
query := db.Create(noti)
|
||||
if query.Error != nil {
|
||||
return 0, query.Error
|
||||
if query.Error() != nil {
|
||||
return 0, query.Error()
|
||||
}
|
||||
return noti.Id, query.Error
|
||||
return noti.Id, query.Error()
|
||||
}
|
||||
|
||||
// SelectNotifier returns the Notification struct from the database
|
||||
|
|
|
@ -231,7 +231,7 @@ func InsertSampleHits() error {
|
|||
}()
|
||||
}
|
||||
sg.Wait()
|
||||
err := tx.Commit().Error
|
||||
err := tx.Commit().Error()
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ func insertSampleCore() error {
|
|||
UseCdn: types.NewNullBool(false),
|
||||
}
|
||||
query := coreDB().Create(core)
|
||||
return query.Error
|
||||
return query.Error()
|
||||
}
|
||||
|
||||
// insertSampleUsers will create 2 admin users for a seed database
|
||||
|
@ -457,13 +457,13 @@ func InsertLargeSampleData() error {
|
|||
}
|
||||
|
||||
// insertFailureRecords will create failures for 15 services from seed
|
||||
func insertFailureRecords(since time.Time, amount int64) {
|
||||
func insertFailureRecords(since time.Time, amount int) {
|
||||
for i := int64(14); i <= 15; i++ {
|
||||
service := SelectService(i)
|
||||
log.Infoln(fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
|
||||
createdAt := since
|
||||
|
||||
for fi := int64(1); fi <= amount; fi++ {
|
||||
for fi := 1; fi <= amount; fi++ {
|
||||
createdAt = createdAt.Add(2 * time.Minute)
|
||||
|
||||
failure := &types.Failure{
|
||||
|
@ -478,13 +478,13 @@ func insertFailureRecords(since time.Time, amount int64) {
|
|||
}
|
||||
|
||||
// insertHitRecords will create successful Hit records for 15 services
|
||||
func insertHitRecords(since time.Time, amount int64) {
|
||||
func insertHitRecords(since time.Time, amount int) {
|
||||
for i := int64(1); i <= 15; i++ {
|
||||
service := SelectService(i)
|
||||
log.Infoln(fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name))
|
||||
createdAt := since
|
||||
p := utils.NewPerlin(2, 2, 5, time.Now().UnixNano())
|
||||
for hi := int64(1); hi <= amount; hi++ {
|
||||
for hi := 1; hi <= amount; hi++ {
|
||||
latency := p.Noise1D(float64(hi / 10))
|
||||
createdAt = createdAt.Add(1 * time.Minute)
|
||||
hit := &types.Hit{
|
||||
|
|
|
@ -100,9 +100,9 @@ func (s *Service) AllCheckins() []*Checkin {
|
|||
func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
|
||||
var services []*Service
|
||||
db := servicesDB().Find(&services).Order("order_id desc")
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("service error: %v", db.Error))
|
||||
return nil, db.Error
|
||||
if db.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("service error: %v", db.Error()))
|
||||
return nil, db.Error()
|
||||
}
|
||||
CoreApp.Services = nil
|
||||
for _, service := range services {
|
||||
|
@ -123,7 +123,7 @@ func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
|
|||
CoreApp.Services = append(CoreApp.Services, service)
|
||||
}
|
||||
reorderServices()
|
||||
return services, db.Error
|
||||
return services, db.Error()
|
||||
}
|
||||
|
||||
// reorderServices will sort the services based on 'order_id'
|
||||
|
@ -170,17 +170,6 @@ func (s *Service) OnlineSince(ago time.Time) float32 {
|
|||
return s.Online24Hours
|
||||
}
|
||||
|
||||
// DateScan struct is for creating the charts.js graph JSON array
|
||||
type DateScan struct {
|
||||
CreatedAt string `json:"x,omitempty"`
|
||||
Value int64 `json:"y"`
|
||||
}
|
||||
|
||||
// DateScanObj struct is for creating the charts.js graph JSON array
|
||||
type DateScanObj struct {
|
||||
Array []DateScan `json:"data"`
|
||||
}
|
||||
|
||||
// lastFailure returns the last Failure a service had
|
||||
func (s *Service) lastFailure() *Failure {
|
||||
limited := s.LimitedFailures(1)
|
||||
|
@ -264,37 +253,20 @@ func (s *Service) Downtime() time.Duration {
|
|||
return since
|
||||
}
|
||||
|
||||
// DateScanObj struct is for creating the charts.js graph JSON array
|
||||
type DateScanObj struct {
|
||||
Array []*types.DateScan `json:"data"`
|
||||
}
|
||||
|
||||
// GraphDataRaw will return all the hits between 2 times for a Service
|
||||
func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group string, column string) *DateScanObj {
|
||||
var data []DateScan
|
||||
outgoing := new(DateScanObj)
|
||||
model := service.(*Service).HitsBetween(start, end, group, column)
|
||||
model = model.Order("timeframe asc", false).Group("timeframe")
|
||||
rows, err := model.Rows()
|
||||
outgoing, err := model.ToChart()
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Errorf("issue fetching service chart data: %v", err))
|
||||
log.Error(err)
|
||||
}
|
||||
for rows.Next() {
|
||||
var gd DateScan
|
||||
var createdAt string
|
||||
var value float64
|
||||
var createdTime time.Time
|
||||
var err error
|
||||
rows.Scan(&createdAt, &value)
|
||||
if CoreApp.Config.DbConn == "postgres" {
|
||||
createdTime, err = time.Parse(types.TIME_NANO, createdAt)
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Errorf("issue parsing time from database: %v to %v", createdAt, types.TIME_NANO))
|
||||
}
|
||||
} else {
|
||||
createdTime, err = time.Parse(types.TIME, createdAt)
|
||||
}
|
||||
gd.CreatedAt = utils.Timezoner(createdTime, CoreApp.Timezone).Format(types.CHART_TIME)
|
||||
gd.Value = int64(value * 1000)
|
||||
data = append(data, gd)
|
||||
}
|
||||
outgoing.Array = data
|
||||
return outgoing
|
||||
return &DateScanObj{outgoing}
|
||||
}
|
||||
|
||||
// ToString will convert the DateScanObj into a JSON string for the charts to render
|
||||
|
@ -370,24 +342,24 @@ func updateService(s *Service) {
|
|||
func (s *Service) Delete() error {
|
||||
i := s.index()
|
||||
err := servicesDB().Delete(s)
|
||||
if err.Error != nil {
|
||||
if err.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error))
|
||||
return err.Error
|
||||
return err.Error()
|
||||
}
|
||||
s.Close()
|
||||
slice := CoreApp.Services
|
||||
CoreApp.Services = append(slice[:i], slice[i+1:]...)
|
||||
reorderServices()
|
||||
notifier.OnDeletedService(s.Service)
|
||||
return err.Error
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// Update will update a service in the database, the service's checking routine can be restarted by passing true
|
||||
func (s *Service) Update(restart bool) error {
|
||||
err := servicesDB().Update(&s)
|
||||
if err.Error != nil {
|
||||
if err.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
|
||||
return err.Error
|
||||
return err.Error()
|
||||
}
|
||||
// clear the notification queue for a service
|
||||
if !s.AllowNotifications.Bool {
|
||||
|
@ -405,16 +377,16 @@ func (s *Service) Update(restart bool) error {
|
|||
reorderServices()
|
||||
updateService(s)
|
||||
notifier.OnUpdatedService(s.Service)
|
||||
return err.Error
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// Create will create a service and insert it into the database
|
||||
func (s *Service) Create(check bool) (int64, error) {
|
||||
s.CreatedAt = time.Now().UTC()
|
||||
db := servicesDB().Create(s)
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error))
|
||||
return 0, db.Error
|
||||
if db.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error()))
|
||||
return 0, db.Error()
|
||||
}
|
||||
s.Start()
|
||||
go s.CheckQueue(check)
|
||||
|
|
|
@ -43,7 +43,7 @@ func CountUsers() int64 {
|
|||
func SelectUser(id int64) (*User, error) {
|
||||
var user User
|
||||
err := usersDB().Where("id = ?", id).First(&user)
|
||||
return &user, err.Error
|
||||
return &user, err.Error()
|
||||
}
|
||||
|
||||
// SelectUsername returns the User based on the User's username
|
||||
|
@ -51,19 +51,19 @@ func SelectUsername(username string) (*User, error) {
|
|||
var user User
|
||||
res := usersDB().Where("username = ?", username)
|
||||
err := res.First(&user)
|
||||
return &user, err.Error
|
||||
return &user, err.Error()
|
||||
}
|
||||
|
||||
// Delete will remove the User record from the database
|
||||
func (u *User) Delete() error {
|
||||
return usersDB().Delete(u).Error
|
||||
return usersDB().Delete(u).Error()
|
||||
}
|
||||
|
||||
// Update will update the User's record in database
|
||||
func (u *User) Update() error {
|
||||
u.ApiKey = utils.NewSHA1Hash(5)
|
||||
u.ApiSecret = utils.NewSHA1Hash(10)
|
||||
return usersDB().Update(u).Error
|
||||
return usersDB().Update(u).Error()
|
||||
}
|
||||
|
||||
// Create will insert a new User into the database
|
||||
|
@ -73,25 +73,25 @@ func (u *User) Create() (int64, error) {
|
|||
u.ApiKey = utils.NewSHA1Hash(5)
|
||||
u.ApiSecret = utils.NewSHA1Hash(10)
|
||||
db := usersDB().Create(u)
|
||||
if db.Error != nil {
|
||||
return 0, db.Error
|
||||
if db.Error() != nil {
|
||||
return 0, db.Error()
|
||||
}
|
||||
if u.Id == 0 {
|
||||
log.Errorln(fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error))
|
||||
return 0, db.Error
|
||||
log.Errorln(fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error()))
|
||||
return 0, db.Error()
|
||||
}
|
||||
return u.Id, db.Error
|
||||
return u.Id, db.Error()
|
||||
}
|
||||
|
||||
// SelectAllUsers returns all users
|
||||
func SelectAllUsers() ([]*User, error) {
|
||||
var users []*User
|
||||
db := usersDB().Find(&users)
|
||||
if db.Error != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error))
|
||||
return nil, db.Error
|
||||
if db.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error()))
|
||||
return nil, db.Error()
|
||||
}
|
||||
return users, db.Error
|
||||
return users, db.Error()
|
||||
}
|
||||
|
||||
// AuthUser will return the User and a boolean if authentication was correct.
|
||||
|
|
|
@ -14,7 +14,7 @@ const webpackConfig = merge(commonConfig, {
|
|||
path: helpers.root('dist'),
|
||||
publicPath: '/',
|
||||
filename: 'js/[name].bundle.js',
|
||||
chunkFilename: 'js/[id].chunk.js'
|
||||
chunkFilename: 'js/[name].chunk.js'
|
||||
},
|
||||
optimization: {
|
||||
runtimeChunk: 'single',
|
||||
|
|
|
@ -16,8 +16,8 @@ const webpackConfig = merge(commonConfig, {
|
|||
output: {
|
||||
path: helpers.root('dist'),
|
||||
publicPath: '/',
|
||||
filename: 'js/[hash].js',
|
||||
chunkFilename: 'js/[id].[hash].chunk.js'
|
||||
filename: 'js/[name].js',
|
||||
chunkFilename: 'js/[name].chunk.js'
|
||||
},
|
||||
optimization: {
|
||||
runtimeChunk: 'single',
|
||||
|
@ -57,8 +57,8 @@ const webpackConfig = merge(commonConfig, {
|
|||
plugins: [
|
||||
new webpack.EnvironmentPlugin(environment),
|
||||
new MiniCSSExtractPlugin({
|
||||
filename: 'css/[name].[hash].css',
|
||||
chunkFilename: 'css/[id].[hash].css'
|
||||
filename: 'css/[name].css',
|
||||
chunkFilename: 'css/[name].[hash].css'
|
||||
}),
|
||||
new CompressionPlugin({
|
||||
filename: '[path].gz[query]',
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"build": "rm -rf dist && cross-env NODE_ENV=production webpack",
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --progress",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
|
@ -56,9 +56,9 @@
|
|||
"friendly-errors-webpack-plugin": "~1.7",
|
||||
"html-webpack-plugin": "~3.2",
|
||||
"mini-css-extract-plugin": "~0.5",
|
||||
"node-sass": "~4.12",
|
||||
"node-sass": "^4.13.1",
|
||||
"optimize-css-assets-webpack-plugin": "~5.0",
|
||||
"sass-loader": "~7.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"uglifyjs-webpack-plugin": "~1.2",
|
||||
"vue-loader": "~15.6",
|
||||
"vue-style-loader": "~4.1",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div id="app" v-if="ready">
|
||||
<div id="app" v-if="loaded">
|
||||
<router-view/>
|
||||
<Footer version="DEV" />
|
||||
</div>
|
||||
|
@ -14,27 +14,25 @@ export default {
|
|||
components: {
|
||||
Footer
|
||||
},
|
||||
computed: {
|
||||
ready () {
|
||||
return true
|
||||
data () {
|
||||
return {
|
||||
loaded: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.$store.getters.hasPublicData) {
|
||||
this.setAllObjects()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
async created () {
|
||||
await this.setAllObjects()
|
||||
this.loaded = true
|
||||
this.$store.commit('setHasPublicData', true)
|
||||
},
|
||||
methods: {
|
||||
async setAllObjects () {
|
||||
await this.setCore()
|
||||
await this.setToken()
|
||||
await this.setServices()
|
||||
await this.setGroups()
|
||||
await this.setMessages()
|
||||
await this.setToken()
|
||||
this.$store.commit('setHasPublicData', true)
|
||||
this.loaded = true
|
||||
},
|
||||
async setCore () {
|
||||
const core = await Api.core()
|
||||
|
@ -60,5 +58,9 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
@import "./assets/css/bootstrap.min.css";
|
||||
@import "./assets/scss/variables";
|
||||
@import "./assets/scss/base";
|
||||
@import "./assets/scss/mobile";
|
||||
</style>
|
||||
|
|
|
@ -137,7 +137,7 @@ HTML, BODY {
|
|||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 190px;
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
Before Width: | Height: | Size: 673 KiB After Width: | Height: | Size: 673 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 708 KiB After Width: | Height: | Size: 708 KiB |
|
@ -24,6 +24,14 @@ class Api {
|
|||
return axios.get('/api/services/'+id).then(response => (response.data))
|
||||
}
|
||||
|
||||
async service_create (data) {
|
||||
return axios.post('/api/services', data).then(response => (response.data))
|
||||
}
|
||||
|
||||
async service_update (data) {
|
||||
return axios.post('/api/services/'+data.id, data).then(response => (response.data))
|
||||
}
|
||||
|
||||
async service_hits (id, start, end, group) {
|
||||
return axios.get('/api/services/'+id+'/data?start=' + start + '&end=' + end + '&group=' + group).then(response => (response.data))
|
||||
}
|
||||
|
@ -122,6 +130,10 @@ class Api {
|
|||
}
|
||||
}
|
||||
|
||||
async allActions (...all) {
|
||||
await axios.all([all])
|
||||
}
|
||||
|
||||
}
|
||||
const api = new Api()
|
||||
export default api
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="(service, index) in $store.getters.servicesInOrder()" v-bind:key="index">
|
||||
<div v-for="(service, index) in services" v-bind:key="index">
|
||||
<ServiceInfo :service=service />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -29,6 +29,11 @@
|
|||
name: 'DashboardIndex',
|
||||
components: {
|
||||
ServiceInfo
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
services: this.$store.getters.servicesInOrder()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<template v-if="service">
|
||||
<div class="col-12 card mb-3" style="min-height: 260px">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><a href="service/7">{{service.name}}</a>
|
||||
|
@ -6,16 +6,16 @@
|
|||
</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div id="spark_service_7_1"></div>
|
||||
<ServiceSparkLine title="here" subtitle="Failures in 7 Days" :series="first"/>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div id="spark_service_7_2"></div>
|
||||
<ServiceSparkLine title="here" subtitle="Failures Last Month" :series="second"/>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div id="spark_service_7_3"></div>
|
||||
<ServiceSparkLine title="here" subtitle="Average Response" :series="third"/>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div id="spark_service_7_4"></div>
|
||||
<ServiceSparkLine title="here" subtitle="Ping Time" :series="fourth"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -23,13 +23,45 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ServiceSparkLine from "./ServiceSparkLine";
|
||||
import Api from "../API";
|
||||
|
||||
export default {
|
||||
name: 'ServiceInfo',
|
||||
components: {
|
||||
ServiceSparkLine
|
||||
},
|
||||
props: {
|
||||
service: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
first: [],
|
||||
second: [],
|
||||
third: [],
|
||||
fourth: []
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.first = await this.getFailures(7, "hour")
|
||||
this.second = await this.getFailures(30, "hour")
|
||||
this.third = await this.getHits(7, "hour")
|
||||
this.fourth = await this.getHits(30, "hour")
|
||||
},
|
||||
methods: {
|
||||
async getHits(days, group) {
|
||||
const start = this.ago(3600 * 24)
|
||||
const data = await Api.service_hits(this.service.id, start, this.now(), group)
|
||||
return [data]
|
||||
},
|
||||
async getFailures(days, group) {
|
||||
const start = this.ago(3600 * 24)
|
||||
const data = await Api.service_failures(this.service.id, start, this.now())
|
||||
return [data]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<template v-if="series.length">
|
||||
<apexchart width="100%" height="180" type="area" :options="chartOpts" :series="series"></apexchart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ServiceSparkLine',
|
||||
props: {
|
||||
series: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartOpts: {
|
||||
chart: {
|
||||
type: 'area',
|
||||
height: 180,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
},
|
||||
stroke: {
|
||||
curve: 'straight'
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
yaxis: {
|
||||
min: 0
|
||||
},
|
||||
colors: ['#DCE6EC'],
|
||||
tooltip: {
|
||||
enabled: false
|
||||
},
|
||||
title: {
|
||||
text: "title",
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '28px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
text: "subtitle",
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
|
@ -42,7 +42,8 @@
|
|||
},
|
||||
methods: {
|
||||
async chartHits() {
|
||||
this.data = await Api.service_hits(this.service.id, 0, 99999999999, "hour")
|
||||
const start = this.ago(3600 * 24)
|
||||
this.data = await Api.service_hits(this.service.id, start, this.now(), "hour")
|
||||
this.series = [{
|
||||
name: this.service.name,
|
||||
...this.data
|
||||
|
@ -126,7 +127,6 @@
|
|||
}
|
||||
},
|
||||
series: [{
|
||||
name: this.service.name,
|
||||
data: []
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
<template>
|
||||
<form @submit="login">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-2 col-form-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" v-model="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" v-model="password" name="password" class="form-control" id="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button @click="login" type="submit" class="btn btn-primary btn-block mb-3">Sign in</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from "../components/API";
|
||||
|
||||
export default {
|
||||
name: 'FormLogin',
|
||||
props: {
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
username: "",
|
||||
password: "",
|
||||
auth: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
async login (e) {
|
||||
e.preventDefault();
|
||||
const auth = await Api.login(this.username, this.password)
|
||||
if (auth.token !== null) {
|
||||
this.auth = Api.saveToken(this.username, auth.token)
|
||||
this.$store.commit('setToken', auth)
|
||||
this.$router.push('/dashboard')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
|
@ -63,7 +63,7 @@
|
|||
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">HTTP Headers</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.headers" class="form-control" autocapitalize="none" spellcheck="false" placeholder='Authorization=1010101,Content-Type=application/json' value="">
|
||||
<input v-model="service.headers" class="form-control" autocapitalize="none" spellcheck="false" placeholder='Authorization=1010101,Content-Type=application/json'>
|
||||
<small class="form-text text-muted">Comma delimited list of HTTP Headers (KEY=VALUE,KEY=VALUE)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -93,28 +93,28 @@
|
|||
<div class="form-group row">
|
||||
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.check_interval" type="number" class="form-control" value="60" min="1" id="service_interval" required>
|
||||
<input v-model="service.check_interval" type="number" class="form-control" min="1" id="service_interval" required>
|
||||
<small id="interval" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Timeout in Seconds</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.timeout" type="number" name="timeout" class="form-control" value="15" placeholder="15" min="1">
|
||||
<input v-model="service.timeout" type="number" name="timeout" class="form-control" placeholder="15" min="1">
|
||||
<small class="form-text text-muted">If the endpoint does not respond within this time it will be considered to be offline</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Permalink URL</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.permalink" type="text" name="permalink" class="form-control" value="" id="permalink" autocapitalize="none" spellcheck="true" placeholder='awesome_service'>
|
||||
<input v-model="service.permalink" type="text" name="permalink" class="form-control" id="permalink" autocapitalize="none" spellcheck="true" placeholder='awesome_service'>
|
||||
<small class="form-text text-muted">Use text for the service URL rather than the service number.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="order" class="col-sm-4 col-form-label">List Order</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.order" type="number" name="order" class="form-control" min="0" value="0" id="order">
|
||||
<input v-model="service.order" type="number" name="order" class="form-control" min="0" id="order">
|
||||
<small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -122,9 +122,8 @@
|
|||
<label for="order" class="col-sm-4 col-form-label">Verify SSL</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input v-model="service.verify_ssl" type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" checked>
|
||||
<input v-model="service.verify_ssl" type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" v-bind:checked="service.verify_ssl">
|
||||
<label for="switch-verify-ssl">Verify SSL Certificate for this service</label>
|
||||
<input type="hidden" name="verify_ssl" id="switch-verify-ssl-value" value="true">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -132,9 +131,8 @@
|
|||
<label for="order" class="col-sm-4 col-form-label">Notifications</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input v-model="service.allow_notifications" type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" checked>
|
||||
<input v-model="service.allow_notifications" type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" v-bind:checked="service.allow_notifications">
|
||||
<label for="switch-notifications">Allow notifications to be sent for this service</label>
|
||||
<input type="hidden" name="allow_notifications" id="switch-notifications-value" value="true">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -142,9 +140,8 @@
|
|||
<label for="order" class="col-sm-4 col-form-label">Visible</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input v-model="service.public" type="checkbox" name="public-option" class="switch" id="switch-public" checked>
|
||||
<input v-model="service.public" type="checkbox" name="public-option" class="switch" id="switch-public" v-bind:checked="service.public">
|
||||
<label for="switch-public">Show service details to the public</label>
|
||||
<input type="hidden" name="public" id="switch-public-value" value="true">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -196,8 +193,8 @@
|
|||
}
|
||||
},
|
||||
async created() {
|
||||
if (this.in_service) {
|
||||
this.service = this.in_service
|
||||
if (this.props.in_service) {
|
||||
this.service = this.props.in_service
|
||||
}
|
||||
if (!this.$store.getters.groups) {
|
||||
const groups = await Api.groups()
|
||||
|
@ -205,8 +202,16 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
saveService(e) {
|
||||
async saveService(e) {
|
||||
e.preventDefault()
|
||||
let s = this.service
|
||||
delete s.failures
|
||||
delete s.created_at
|
||||
delete s.updated_at
|
||||
delete s.last_success
|
||||
delete s.latency
|
||||
delete s.online_24_hours
|
||||
await Api.service_save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,111 +3,19 @@ import VueRouter from 'vue-router'
|
|||
import VueApexCharts from 'vue-apexcharts'
|
||||
|
||||
import App from '@/App.vue'
|
||||
import Api from './components/API'
|
||||
import store from './store'
|
||||
|
||||
import {library} from '@fortawesome/fontawesome-svg-core'
|
||||
import {fas} from '@fortawesome/fontawesome-free-solid';
|
||||
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
|
||||
import DashboardIndex from "./components/Dashboard/DashboardIndex";
|
||||
import DashboardUsers from "./components/Dashboard/DashboardUsers";
|
||||
import DashboardServices from "./components/Dashboard/DashboardServices";
|
||||
import DashboardMessages from "./components/Dashboard/DashboardMessages";
|
||||
import Settings from "./pages/Settings";
|
||||
import EditService from "./components/Dashboard/EditService";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import Index from "./pages/Index";
|
||||
import Login from "./pages/Login";
|
||||
import Service from "./pages/Service";
|
||||
import router from './routes'
|
||||
import "./mixin"
|
||||
|
||||
library.add(fas)
|
||||
|
||||
Vue.component('apexchart', VueApexCharts)
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon)
|
||||
|
||||
require("@/assets/css/bootstrap.min.css")
|
||||
require("@/assets/css/base.css")
|
||||
|
||||
|
||||
// require("./assets/js/bootstrap.min")
|
||||
// require("./assets/js/flatpickr")
|
||||
// require("./assets/js/inputTags.min")
|
||||
// require("./assets/js/rangePlugin")
|
||||
// require("./assets/js/sortable.min")
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Index',
|
||||
component: Index
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: Dashboard,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
},
|
||||
children: [{
|
||||
path: '',
|
||||
component: DashboardIndex
|
||||
},{
|
||||
path: 'users',
|
||||
component: DashboardUsers
|
||||
},{
|
||||
path: 'services',
|
||||
component: DashboardServices
|
||||
},{
|
||||
path: 'create_service',
|
||||
component: EditService
|
||||
},{
|
||||
path: 'edit_service/:id',
|
||||
component: EditService
|
||||
},{
|
||||
path: 'messages',
|
||||
component: DashboardMessages
|
||||
},{
|
||||
path: 'settings',
|
||||
component: Settings
|
||||
},{
|
||||
path: 'logs',
|
||||
component: DashboardUsers
|
||||
},{
|
||||
path: 'help',
|
||||
component: DashboardUsers
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login
|
||||
},
|
||||
{ path: '/logout', redirect: '/' },
|
||||
{
|
||||
path: '/service/:id',
|
||||
name: 'Service',
|
||||
component: Service,
|
||||
props: true
|
||||
}
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (Api.token()) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
next('/login')
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(require('vue-moment'));
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import Vue from "vue";
|
||||
|
||||
export default Vue.mixin({
|
||||
methods: {
|
||||
now() {
|
||||
return Math.round(new Date().getTime() / 1000)
|
||||
},
|
||||
ago(seconds) {
|
||||
return this.now() - seconds
|
||||
},
|
||||
hour(){ return 3600 },
|
||||
day() { return 3600 * 24 }
|
||||
}
|
||||
});
|
|
@ -8,6 +8,7 @@
|
|||
<script>
|
||||
import Login from "./Login";
|
||||
import TopNav from "../components/Dashboard/TopNav";
|
||||
import Api from "../components/API";
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
|
@ -17,7 +18,6 @@
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
view: "DashboardIndex",
|
||||
authenticated: false
|
||||
}
|
||||
},
|
||||
|
@ -27,7 +27,10 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
async setServices () {
|
||||
const services = await Api.services()
|
||||
this.$store.commit('setServices', services)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,28 +2,69 @@
|
|||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
<div class="col-10 offset-1 col-md-8 offset-md-2 mt-md-2">
|
||||
<div class="col-12 col-md-8 offset-md-2 mb-4">
|
||||
<img class="col-12 mt-5 mt-md-0" src="/public/img/banner.png">
|
||||
<img class="col-12 mt-5 mt-md-0" src="/public/banner.png">
|
||||
</div>
|
||||
<FormLogin/>
|
||||
|
||||
<form @submit="login">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-2 col-form-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" v-model="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" v-model="password" name="password" class="form-control" id="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button @click="login" type="submit" class="btn btn-block mb-3" :class="{'btn-primary': !loading, 'btn-default': loading}" v-bind:disabled="loading">
|
||||
{{loading ? "Loading" : "Sign in"}}
|
||||
</button>
|
||||
<div v-if="error" class="alert alert-danger" role="alert">
|
||||
Incorrect username or password
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormLogin from "../forms/Login";
|
||||
import Api from "../components/API";
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
components: {
|
||||
FormLogin
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
||||
username: "",
|
||||
password: "",
|
||||
auth: {},
|
||||
loading: false,
|
||||
error: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
async login (e) {
|
||||
e.preventDefault();
|
||||
this.loading = true
|
||||
this.error = false
|
||||
const auth = await Api.login(this.username, this.password)
|
||||
if (auth.error) {
|
||||
this.error = true
|
||||
} else if (auth.token) {
|
||||
this.auth = Api.saveToken(this.username, auth.token)
|
||||
this.$store.commit('setToken', auth)
|
||||
this.$router.push('/dashboard')
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import Index from "./pages/Index";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import DashboardIndex from "./components/Dashboard/DashboardIndex";
|
||||
import DashboardUsers from "./components/Dashboard/DashboardUsers";
|
||||
import DashboardServices from "./components/Dashboard/DashboardServices";
|
||||
import EditService from "./components/Dashboard/EditService";
|
||||
import DashboardMessages from "./components/Dashboard/DashboardMessages";
|
||||
import Settings from "./pages/Settings";
|
||||
import Login from "./pages/Login";
|
||||
import Service from "./pages/Service";
|
||||
import VueRouter from "vue-router";
|
||||
import Api from "./components/API";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Index',
|
||||
component: Index
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: Dashboard,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
},
|
||||
children: [{
|
||||
path: '',
|
||||
component: DashboardIndex,
|
||||
},{
|
||||
path: 'users',
|
||||
component: DashboardUsers
|
||||
},{
|
||||
path: 'services',
|
||||
component: DashboardServices
|
||||
},{
|
||||
path: 'create_service',
|
||||
component: EditService
|
||||
},{
|
||||
path: 'edit_service/:id',
|
||||
component: EditService
|
||||
},{
|
||||
path: 'messages',
|
||||
component: DashboardMessages
|
||||
},{
|
||||
path: 'settings',
|
||||
component: Settings
|
||||
},{
|
||||
path: 'logs',
|
||||
component: DashboardUsers
|
||||
},{
|
||||
path: 'help',
|
||||
component: DashboardUsers
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login
|
||||
},
|
||||
{ path: '/logout', redirect: '/' },
|
||||
{
|
||||
path: '/service/:id',
|
||||
name: 'Service',
|
||||
component: Service,
|
||||
props: true
|
||||
}
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
const tk = Api.token()
|
||||
if (tk === null) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
if (to.path !== '/login') {
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
|
@ -47,7 +47,7 @@ export default new Vuex.Store({
|
|||
return state.services.filter(s => s.group_id === id)
|
||||
},
|
||||
servicesInOrder: (state) => () => {
|
||||
return state.services.sort((a, b) => a.order_id - b.order_id)
|
||||
return state.services
|
||||
},
|
||||
onlineServices: (state) => (online) => {
|
||||
return state.services.filter(s => s.online === online)
|
||||
|
@ -79,7 +79,7 @@ export default new Vuex.Store({
|
|||
state.token = token
|
||||
},
|
||||
setServices(state, services) {
|
||||
state.services = services
|
||||
state.services = services.sort((a, b) => a.order_id - b.order_id)
|
||||
},
|
||||
setGroups(state, groups) {
|
||||
state.groups = groups
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module.exports = {
|
||||
assetsDir: 'assets',
|
||||
filenameHashing: false,
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
|
|
|
@ -2060,15 +2060,14 @@ cliui@^4.0.0:
|
|||
strip-ansi "^4.0.0"
|
||||
wrap-ansi "^2.0.0"
|
||||
|
||||
clone-deep@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
|
||||
integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==
|
||||
clone-deep@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
||||
integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
|
||||
dependencies:
|
||||
for-own "^1.0.0"
|
||||
is-plain-object "^2.0.4"
|
||||
kind-of "^6.0.0"
|
||||
shallow-clone "^1.0.0"
|
||||
kind-of "^6.0.2"
|
||||
shallow-clone "^3.0.0"
|
||||
|
||||
clone@^1.0.2:
|
||||
version "1.0.4"
|
||||
|
@ -3579,23 +3578,11 @@ follow-redirects@^1.0.0:
|
|||
dependencies:
|
||||
debug "^3.0.0"
|
||||
|
||||
for-in@^0.1.3:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
|
||||
integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=
|
||||
|
||||
for-in@^1.0.1, for-in@^1.0.2:
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
|
||||
|
||||
for-own@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
|
||||
integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=
|
||||
dependencies:
|
||||
for-in "^1.0.1"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
|
@ -4798,7 +4785,7 @@ loader-utils@^0.2.16:
|
|||
json5 "^0.5.0"
|
||||
object-assign "^4.0.1"
|
||||
|
||||
loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
|
||||
loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
||||
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
|
||||
|
@ -4840,11 +4827,6 @@ lodash.memoize@^4.1.2:
|
|||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||
|
||||
lodash.tail@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
|
||||
integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
|
||||
|
||||
lodash.uniq@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
|
@ -5165,14 +5147,6 @@ mixin-deep@^1.2.0:
|
|||
for-in "^1.0.2"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
mixin-object@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
|
||||
integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=
|
||||
dependencies:
|
||||
for-in "^0.1.3"
|
||||
is-extendable "^0.1.1"
|
||||
|
||||
mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
|
@ -5347,10 +5321,10 @@ node-releases@^1.1.44:
|
|||
dependencies:
|
||||
semver "^6.3.0"
|
||||
|
||||
node-sass@~4.12:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017"
|
||||
integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==
|
||||
node-sass@^4.13.1:
|
||||
version "4.13.1"
|
||||
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3"
|
||||
integrity sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==
|
||||
dependencies:
|
||||
async-foreach "^0.1.3"
|
||||
chalk "^1.1.1"
|
||||
|
@ -5359,7 +5333,7 @@ node-sass@~4.12:
|
|||
get-stdin "^4.0.1"
|
||||
glob "^7.0.3"
|
||||
in-publish "^2.0.0"
|
||||
lodash "^4.17.11"
|
||||
lodash "^4.17.15"
|
||||
meow "^3.7.0"
|
||||
mkdirp "^0.5.1"
|
||||
nan "^2.13.2"
|
||||
|
@ -6806,17 +6780,16 @@ sass-graph@^2.2.4:
|
|||
scss-tokenizer "^0.2.3"
|
||||
yargs "^7.0.0"
|
||||
|
||||
sass-loader@~7.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d"
|
||||
integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==
|
||||
sass-loader@^8.0.2:
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d"
|
||||
integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==
|
||||
dependencies:
|
||||
clone-deep "^2.0.1"
|
||||
loader-utils "^1.0.1"
|
||||
lodash.tail "^4.1.1"
|
||||
neo-async "^2.5.0"
|
||||
pify "^3.0.0"
|
||||
semver "^5.5.0"
|
||||
clone-deep "^4.0.1"
|
||||
loader-utils "^1.2.3"
|
||||
neo-async "^2.6.1"
|
||||
schema-utils "^2.6.1"
|
||||
semver "^6.3.0"
|
||||
|
||||
sax@~1.2.4:
|
||||
version "1.2.4"
|
||||
|
@ -6840,7 +6813,7 @@ schema-utils@^1.0.0:
|
|||
ajv-errors "^1.0.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
schema-utils@^2.0.0, schema-utils@^2.5.0:
|
||||
schema-utils@^2.0.0, schema-utils@^2.5.0, schema-utils@^2.6.1:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.4.tgz#a27efbf6e4e78689d91872ee3ccfa57d7bdd0f53"
|
||||
integrity sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==
|
||||
|
@ -6978,14 +6951,12 @@ sha.js@^2.4.0, sha.js@^2.4.8:
|
|||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
shallow-clone@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571"
|
||||
integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==
|
||||
shallow-clone@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
|
||||
integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
|
||||
dependencies:
|
||||
is-extendable "^0.1.1"
|
||||
kind-of "^5.0.0"
|
||||
mixin-object "^2.0.1"
|
||||
kind-of "^6.0.2"
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
|
|
|
@ -31,7 +31,6 @@ import (
|
|||
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
)
|
||||
|
||||
|
@ -46,7 +45,6 @@ var (
|
|||
usingSSL bool
|
||||
mainTmpl = `{{define "main" }} {{ template "base" . }} {{ end }}`
|
||||
templates = []string{"base.gohtml", "head.gohtml", "nav.gohtml", "footer.gohtml", "scripts.gohtml", "form_service.gohtml", "form_notifier.gohtml", "form_integration.gohtml", "form_group.gohtml", "form_user.gohtml", "form_checkin.gohtml", "form_message.gohtml"}
|
||||
javascripts = []string{"charts.js", "chart_index.js"}
|
||||
)
|
||||
|
||||
// RunHTTPServer will start a HTTP server on a specific IP and port
|
||||
|
@ -152,7 +150,6 @@ func IsFullAuthenticated(r *http.Request) bool {
|
|||
func getJwtToken(r *http.Request) (JwtClaim, error) {
|
||||
c, err := r.Cookie(cookieKey)
|
||||
if err != nil {
|
||||
utils.Log.Errorln(err)
|
||||
if err == http.ErrNoCookie {
|
||||
return JwtClaim{}, err
|
||||
}
|
||||
|
@ -164,14 +161,12 @@ func getJwtToken(r *http.Request) (JwtClaim, error) {
|
|||
return []byte(jwtKey), nil
|
||||
})
|
||||
if err != nil {
|
||||
utils.Log.Errorln("error getting jwt token: ", err)
|
||||
if err == jwt.ErrSignatureInvalid {
|
||||
return JwtClaim{}, err
|
||||
}
|
||||
return JwtClaim{}, err
|
||||
}
|
||||
if !tkn.Valid {
|
||||
utils.Log.Errorln("token is not valid")
|
||||
return claims, errors.New("token is not valid")
|
||||
}
|
||||
return claims, err
|
||||
|
@ -237,15 +232,6 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) (*template.Template, e
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
// render all javascript files
|
||||
for _, temp := range javascripts {
|
||||
tmp, _ := source.JsBox.String(temp)
|
||||
mainTemplate, err = mainTemplate.Parse(tmp)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return mainTemplate, err
|
||||
}
|
||||
|
||||
|
@ -278,28 +264,28 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
|
|||
|
||||
// executeJSResponse will render a Javascript response
|
||||
func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
|
||||
render, err := source.JsBox.String(file)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
if usingSSL {
|
||||
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
|
||||
}
|
||||
t := template.New("charts")
|
||||
t.Funcs(template.FuncMap{
|
||||
"safe": func(html string) template.HTML {
|
||||
return template.HTML(html)
|
||||
},
|
||||
"Services": func() []types.ServiceInterface {
|
||||
return core.CoreApp.Services
|
||||
},
|
||||
})
|
||||
if _, err := t.Parse(render); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
//render, err := source.JsBox.String(file)
|
||||
//if err != nil {
|
||||
// log.Errorln(err)
|
||||
//}
|
||||
//if usingSSL {
|
||||
// w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
|
||||
//}
|
||||
//t := template.New("charts")
|
||||
//t.Funcs(template.FuncMap{
|
||||
// "safe": func(html string) template.HTML {
|
||||
// return template.HTML(html)
|
||||
// },
|
||||
// "Services": func() []types.ServiceInterface {
|
||||
// return core.CoreApp.Services
|
||||
// },
|
||||
//})
|
||||
//if _, err := t.Parse(render); err != nil {
|
||||
// log.Errorln(err)
|
||||
//}
|
||||
//if err := t.Execute(w, data); err != nil {
|
||||
// log.Errorln(err)
|
||||
//}
|
||||
}
|
||||
|
||||
func returnJson(d interface{}, w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -313,5 +299,5 @@ func error404Handler(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
ExecuteResponse(w, r, "error_404.gohtml", nil, nil)
|
||||
ExecuteResponse(w, r, "index.html", nil, nil)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.Redirect(w, r, "/setup", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
ExecuteResponse(w, r, "index.gohtml", core.CoreApp, nil)
|
||||
ExecuteResponse(w, r, "index.html", core.CoreApp, nil)
|
||||
}
|
||||
|
||||
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -16,6 +19,30 @@ var (
|
|||
authPass string
|
||||
)
|
||||
|
||||
// Gzip Compression
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func Gzip(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
handler.ServeHTTP(gzw, r)
|
||||
})
|
||||
}
|
||||
|
||||
// basicAuthHandler is a middleware to implement HTTP basic authentication using
|
||||
// AUTH_USERNAME and AUTH_PASSWORD environment variables
|
||||
func basicAuthHandler(next http.Handler) http.Handler {
|
||||
|
|
|
@ -17,10 +17,8 @@ package handlers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/99designs/gqlgen/handler"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/handlers/graphql"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
|
@ -56,65 +54,26 @@ func Router() *mux.Router {
|
|||
r.Use(sendLog)
|
||||
if source.UsingAssets(dir) {
|
||||
indexHandler := http.FileServer(http.Dir(dir + "/assets/"))
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix(basePath+"css/", http.FileServer(http.Dir(dir+"/assets/css"))))
|
||||
r.PathPrefix("/css/").Handler(Gzip(http.StripPrefix(basePath+"css/", http.FileServer(http.Dir(dir+"/assets/css")))))
|
||||
r.PathPrefix("/font/").Handler(http.StripPrefix(basePath+"font/", http.FileServer(http.Dir(dir+"/assets/font"))))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix(basePath+"js/", http.FileServer(http.Dir(dir+"/assets/js"))))
|
||||
r.PathPrefix("/js/").Handler(Gzip(http.StripPrefix(basePath+"js/", http.FileServer(http.Dir(dir+"/assets/js")))))
|
||||
r.PathPrefix("/robots.txt").Handler(http.StripPrefix(basePath, indexHandler))
|
||||
r.PathPrefix("/favicon.ico").Handler(http.StripPrefix(basePath, indexHandler))
|
||||
r.PathPrefix("/banner.png").Handler(http.StripPrefix(basePath, indexHandler))
|
||||
} else {
|
||||
r.PathPrefix("/css/").Handler(http.StripPrefix(basePath+"css/", http.FileServer(source.CssBox.HTTPBox())))
|
||||
r.PathPrefix("/font/").Handler(http.StripPrefix(basePath+"font/", http.FileServer(source.FontBox.HTTPBox())))
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix(basePath+"js/", http.FileServer(source.JsBox.HTTPBox())))
|
||||
//r.PathPrefix("/").Handler(http.StripPrefix(basePath+"/", http.FileServer(source.TmplBox.HTTPBox())))
|
||||
r.PathPrefix("/css/").Handler(Gzip(http.FileServer(source.TmplBox.HTTPBox())))
|
||||
r.PathPrefix("/font/").Handler(http.FileServer(source.TmplBox.HTTPBox()))
|
||||
r.PathPrefix("/js/").Handler(Gzip(http.FileServer(source.TmplBox.HTTPBox())))
|
||||
r.PathPrefix("/robots.txt").Handler(http.StripPrefix(basePath, http.FileServer(source.TmplBox.HTTPBox())))
|
||||
r.PathPrefix("/favicon.ico").Handler(http.StripPrefix(basePath, http.FileServer(source.TmplBox.HTTPBox())))
|
||||
r.PathPrefix("/banner.png").Handler(http.StripPrefix(basePath, http.FileServer(source.TmplBox.HTTPBox())))
|
||||
}
|
||||
r.Handle("/charts.js", http.HandlerFunc(renderServiceChartsHandler))
|
||||
r.Handle("/setup", http.HandlerFunc(setupHandler)).Methods("GET")
|
||||
r.Handle("/setup", http.HandlerFunc(processSetupHandler)).Methods("POST")
|
||||
r.Handle("/dashboard", http.HandlerFunc(dashboardHandler)).Methods("GET")
|
||||
r.Handle("/dashboard", http.HandlerFunc(loginHandler)).Methods("POST")
|
||||
r.Handle("/logout", http.HandlerFunc(logoutHandler))
|
||||
r.Handle("/plugins/download/{name}", authenticated(pluginsDownloadHandler, true))
|
||||
r.Handle("/plugins/{name}/save", authenticated(pluginSavedHandler, true)).Methods("POST")
|
||||
r.Handle("/help", authenticated(helpHandler, true))
|
||||
r.Handle("/logs", authenticated(logsHandler, true))
|
||||
r.Handle("/logs/line", readOnly(logsLineHandler, true))
|
||||
|
||||
// GRAPHQL Route
|
||||
r.Handle("/graphql", authenticated(handler.GraphQL(graphql.NewExecutableSchema(graphql.Config{Resolvers: &graphql.Resolver{}})), true))
|
||||
|
||||
// USER Routes
|
||||
r.Handle("/users", readOnly(usersHandler, true)).Methods("GET")
|
||||
r.Handle("/user/{id}", authenticated(usersEditHandler, true)).Methods("GET")
|
||||
|
||||
// MESSAGES Routes
|
||||
r.Handle("/messages", authenticated(messagesHandler, true)).Methods("GET")
|
||||
r.Handle("/message/{id}", authenticated(viewMessageHandler, true)).Methods("GET")
|
||||
|
||||
// SETTINGS Routes
|
||||
r.Handle("/settings", authenticated(settingsHandler, true)).Methods("GET")
|
||||
r.Handle("/settings", authenticated(saveSettingsHandler, true)).Methods("POST")
|
||||
r.Handle("/settings/css", authenticated(saveSASSHandler, true)).Methods("POST")
|
||||
r.Handle("/settings/build", authenticated(saveAssetsHandler, true)).Methods("GET")
|
||||
r.Handle("/settings/delete_assets", authenticated(deleteAssetsHandler, true)).Methods("GET")
|
||||
r.Handle("/settings/export", authenticated(exportHandler, true)).Methods("GET")
|
||||
r.Handle("/settings/bulk_import", authenticated(bulkImportHandler, true)).Methods("POST")
|
||||
r.Handle("/settings/integrator/{name}", authenticated(integratorHandler, true)).Methods("POST")
|
||||
|
||||
// SERVICE Routes
|
||||
r.Handle("/services", authenticated(servicesHandler, true)).Methods("GET")
|
||||
r.Handle("/service/create", authenticated(createServiceHandler, true)).Methods("GET")
|
||||
r.Handle("/service/{id}", readOnly(servicesViewHandler, true)).Methods("GET")
|
||||
r.Handle("/service/{id}/edit", authenticated(servicesViewHandler, true)).Methods("GET")
|
||||
r.Handle("/service/{id}/delete_failures", authenticated(servicesDeleteFailuresHandler, true)).Methods("GET")
|
||||
|
||||
r.Handle("/group/{id}", http.HandlerFunc(groupViewHandler)).Methods("GET")
|
||||
|
||||
// API Routes
|
||||
r.Handle("/api", scoped(apiIndexHandler))
|
||||
r.Handle("/api/login", http.HandlerFunc(apiLoginHandler)).Methods("POST")
|
||||
r.Handle("/api/setup", http.HandlerFunc(processSetupHandler)).Methods("POST")
|
||||
r.Handle("/api/logout", http.HandlerFunc(logoutHandler))
|
||||
r.Handle("/api/renew", authenticated(apiRenewHandler, false))
|
||||
r.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false))
|
||||
|
@ -177,6 +136,8 @@ func Router() *mux.Router {
|
|||
r.Handle("/api/checkin/{api}", authenticated(checkinDeleteHandler, false)).Methods("DELETE")
|
||||
r.Handle("/checkin/{api}", http.HandlerFunc(checkinHitHandler))
|
||||
|
||||
//r.PathPrefix("/").Handler(http.HandlerFunc(indexHandler))
|
||||
|
||||
// Static Files Routes
|
||||
r.PathPrefix("/files/postman.json").Handler(http.StripPrefix("/files/", http.FileServer(source.TmplBox.HTTPBox())))
|
||||
r.PathPrefix("/files/swagger.json").Handler(http.StripPrefix("/files/", http.FileServer(source.TmplBox.HTTPBox())))
|
||||
|
|
|
@ -322,7 +322,11 @@ func apiServiceFailuresHandler(r *http.Request) interface{} {
|
|||
if servicer == nil {
|
||||
return errors.New("service not found")
|
||||
}
|
||||
return servicer.AllFailures()
|
||||
fails, err := servicer.FailuresDb(r).Fails()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
func apiServiceHitsHandler(r *http.Request) interface{} {
|
||||
|
@ -331,8 +335,7 @@ func apiServiceHitsHandler(r *http.Request) interface{} {
|
|||
if servicer == nil {
|
||||
return errors.New("service not found")
|
||||
}
|
||||
|
||||
hits, err := servicer.Hits()
|
||||
hits, err := servicer.HitsDb(r).Hits()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
if err := source.CompileSASS(dir); err != nil {
|
||||
source.CopyToPublic(source.CssBox, dir+"/assets/css", "base.css")
|
||||
source.CopyToPublic(source.TmplBox, dir+"/assets/css", "base.css")
|
||||
log.Errorln("Default 'base.css' was inserted because SASS did not work.")
|
||||
}
|
||||
resetRouter()
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,137 +0,0 @@
|
|||
{{define "chartIndex"}}
|
||||
var ctx_{{js .Id}} = document.getElementById("service_{{js .Id}}").getContext('2d');
|
||||
var chartdata_{{js .Id}} = new Chart(ctx_{{js .Id}}, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [{
|
||||
label: 'Response Time (Milliseconds)',
|
||||
data: [],
|
||||
backgroundColor: ['{{if .Online}}rgba(47, 206, 30, 0.92){{else}}rgb(221, 53, 69){{end}}'],
|
||||
borderColor: ['{{if .Online}}rgb(47, 171, 34){{else}}rgb(183, 32, 47){{end}}'],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: !1,
|
||||
scaleShowValues: !0,
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: -10
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
animationDuration: 0,
|
||||
},
|
||||
responsiveAnimationDuration: 0,
|
||||
animation: {
|
||||
duration: 3500,
|
||||
onComplete: function() {
|
||||
var chartInstance = this.chart,
|
||||
ctx = chartInstance.ctx;
|
||||
var controller = this.chart.controller;
|
||||
var xAxis = controller.scales['x-axis-0'];
|
||||
var yAxis = controller.scales['y-axis-0'];
|
||||
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
var numTicks = xAxis.ticks.length;
|
||||
var yOffsetStart = xAxis.width / numTicks;
|
||||
var halfBarWidth = (xAxis.width / (numTicks * 2));
|
||||
xAxis.ticks.forEach(function(value, index) {
|
||||
var xOffset = 20;
|
||||
var yOffset = (yOffsetStart * index) + halfBarWidth;
|
||||
ctx.fillStyle = '#e2e2e2';
|
||||
ctx.fillText(value, yOffset, xOffset)
|
||||
});
|
||||
this.data.datasets.forEach(function(dataset, i) {
|
||||
var meta = chartInstance.controller.getDatasetMeta(i);
|
||||
var hxH = 0;
|
||||
var hyH = 0;
|
||||
var hxL = 0;
|
||||
var hyL = 0;
|
||||
var highestNum = 0;
|
||||
var lowestnum = 999999999999;
|
||||
meta.data.forEach(function(bar, index) {
|
||||
var data = dataset.data[index];
|
||||
if (lowestnum > data.y) {
|
||||
lowestnum = data.y;
|
||||
hxL = bar._model.x;
|
||||
hyL = bar._model.y
|
||||
}
|
||||
if (data.y > highestNum) {
|
||||
highestNum = data.y;
|
||||
hxH = bar._model.x;
|
||||
hyH = bar._model.y
|
||||
}
|
||||
});
|
||||
if (hxH >= 820) {
|
||||
hxH = 820
|
||||
} else if (50 >= hxH) {
|
||||
hxH = 50
|
||||
}
|
||||
if (hxL >= 820) {
|
||||
hxL = 820
|
||||
} else if (70 >= hxL) {
|
||||
hxL = 70
|
||||
}
|
||||
ctx.fillStyle = '#ffa7a2';
|
||||
ctx.fillText(highestNum + "ms", hxH - 40, hyH + 15);
|
||||
ctx.fillStyle = '#45d642';
|
||||
ctx.fillText(lowestnum + "ms", hxL, hyL + 10);
|
||||
})
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: !1
|
||||
},
|
||||
tooltips: {
|
||||
enabled: !1
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
display: !1,
|
||||
ticks: {
|
||||
fontSize: 20,
|
||||
display: !1,
|
||||
beginAtZero: !1
|
||||
},
|
||||
gridLines: {
|
||||
display: !1
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
distribution: 'series',
|
||||
autoSkip: !1,
|
||||
time: {
|
||||
displayFormats: {
|
||||
'hour': 'MMM DD hA'
|
||||
},
|
||||
source: 'auto'
|
||||
},
|
||||
gridLines: {
|
||||
display: !1
|
||||
},
|
||||
ticks: {
|
||||
source: 'auto',
|
||||
stepSize: 1,
|
||||
min: 0,
|
||||
fontColor: "white",
|
||||
fontSize: 20,
|
||||
display: !1
|
||||
}
|
||||
}]
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AjaxChart(chartdata_{{js .Id}},{{js .Id}},0,99999999999,"hour");
|
||||
{{end}}
|
|
@ -1,141 +0,0 @@
|
|||
{{define "charts"}}
|
||||
{{$start := .Start}}
|
||||
{{$end := .End}}
|
||||
|
||||
const axisOptions = {
|
||||
labels: {
|
||||
show: false
|
||||
},
|
||||
crosshairs: {
|
||||
show: false
|
||||
},
|
||||
lines: {
|
||||
show: false
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false
|
||||
},
|
||||
axisTicks: {
|
||||
show: false
|
||||
},
|
||||
grid: {
|
||||
show: false
|
||||
},
|
||||
marker: {
|
||||
show: false
|
||||
}
|
||||
};
|
||||
|
||||
const annotationColor = {
|
||||
strokeDashArray: 0,
|
||||
borderColor: "#d0222d",
|
||||
label: {
|
||||
show: false,
|
||||
}
|
||||
};
|
||||
|
||||
let annotation = {
|
||||
annotations: {
|
||||
xaxis: [
|
||||
{
|
||||
// in a datetime series, the x value should be a timestamp, just like it is generated below
|
||||
x: new Date("01/29/2019").getTime(),
|
||||
...annotationColor
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
let options = {
|
||||
chart: {
|
||||
height: 210,
|
||||
width: "100%",
|
||||
type: "area",
|
||||
animations: {
|
||||
enabled: false,
|
||||
initialAnimation: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
selection: {
|
||||
enabled: false
|
||||
},
|
||||
zoom: {
|
||||
enabled: false
|
||||
},
|
||||
toolbar: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
padding: {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: -10,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
marker: {
|
||||
show: false,
|
||||
},
|
||||
x: {
|
||||
show: false,
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
floating: true,
|
||||
axisTicks: {
|
||||
show: false
|
||||
},
|
||||
axisBorder: {
|
||||
show: false
|
||||
},
|
||||
fill: {
|
||||
colors: ["#48d338"],
|
||||
opacity: 1,
|
||||
type: 'solid'
|
||||
},
|
||||
stroke: {
|
||||
show: true,
|
||||
curve: 'smooth',
|
||||
lineCap: 'butt',
|
||||
colors: ["#3aa82d"],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "Response Time",
|
||||
data: [],
|
||||
}
|
||||
],
|
||||
xaxis: {
|
||||
type: "datetime",
|
||||
...axisOptions
|
||||
},
|
||||
yaxis: {
|
||||
...axisOptions
|
||||
},
|
||||
};
|
||||
|
||||
const startOn = UTCTime() - (86400 * 14);
|
||||
|
||||
async function RenderCharts() {
|
||||
{{ range .Services }}
|
||||
options.fill.colors = {{if .Online}}["#48d338"]{{else}}["#dd3545"]{{end}};
|
||||
options.stroke.colors = {{if .Online}}["#3aa82d"]{{else}}["#c23342"]{{end}};
|
||||
|
||||
let chart{{.Id}} = new ApexCharts(document.querySelector("#service_{{js .Id}}"), options);
|
||||
|
||||
await RenderChart(chart{{js .Id}}, {{js .Id}}, startOn);{{end}}
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
RenderCharts()
|
||||
});
|
||||
{{end}}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,492 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
$('.service_li').on('click', function() {
|
||||
var id = $(this).attr('data-id');
|
||||
var position = $('#service_id_' + id).offset();
|
||||
window.scroll(0, position.top - 23);
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.test_notifier').on('click', function(e) {
|
||||
var btn = $(this);
|
||||
var form = $(this).parents('form:first');
|
||||
var values = form.serialize();
|
||||
var notifier = form.find('input[name=method]').val();
|
||||
var success = $('#'+notifier+'-success');
|
||||
var error = $('#'+notifier+'-error');
|
||||
Spinner(btn);
|
||||
$.ajax({
|
||||
url: form.attr("action")+"/test",
|
||||
type: 'POST',
|
||||
data: values,
|
||||
success: function(data) {
|
||||
if (data === 'ok') {
|
||||
success.removeClass('d-none');
|
||||
setTimeout(function() {
|
||||
success.addClass('d-none');
|
||||
}, 5000)
|
||||
} else {
|
||||
error.removeClass('d-none');
|
||||
error.html(data);
|
||||
setTimeout(function() {
|
||||
error.addClass('d-none');
|
||||
}, 8000)
|
||||
}
|
||||
Spinner(btn, true);
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$('.spin_form').on('submit', function() {
|
||||
Spinner($(this).find('button[type=submit]'));
|
||||
});
|
||||
|
||||
function Spinner(btn, off = false) {
|
||||
btn.prop('disabled', !off);
|
||||
if (off) {
|
||||
let pastVal = btn.attr("data-past");
|
||||
btn.text(pastVal);
|
||||
btn.removeAttr("data-past");
|
||||
} else {
|
||||
let pastVal = btn.text();
|
||||
btn.attr("data-past", pastVal);
|
||||
btn.html('<i class="fa fa-spinner fa-spin"></i>');
|
||||
}
|
||||
}
|
||||
|
||||
function SaveNotifier(data) {
|
||||
let button = data.element.find('button[type=submit]');
|
||||
button.text('Saved!')
|
||||
button.removeClass('btn-primary')
|
||||
button.addClass('btn-success')
|
||||
}
|
||||
|
||||
$('.scrollclick').on('click',function(e) {
|
||||
let element = $(this).attr("data-id");
|
||||
$('html, body').animate({
|
||||
scrollTop: $("#"+element).offset().top - 15
|
||||
}, 500);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$('.toggle-service').on('click',function(e) {
|
||||
let obj = $(this);
|
||||
let serviceId = obj.attr("data-id");
|
||||
let online = obj.attr("data-online");
|
||||
let d = confirm("Do you want to "+(eval(online) ? "stop" : "start")+" checking this service?");
|
||||
if (d) {
|
||||
$.ajax({
|
||||
url: "api/services/" + serviceId + "/running",
|
||||
type: 'POST',
|
||||
success: function (data) {
|
||||
if (online === "true") {
|
||||
obj.removeClass("fa-toggle-on text-success");
|
||||
obj.addClass("fa-toggle-off text-black-50");
|
||||
} else {
|
||||
obj.removeClass("fa-toggle-off text-black-50");
|
||||
obj.addClass("fa-toggle-on text-success");
|
||||
}
|
||||
obj.attr("data-online", online !== "true");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('select#service_type').on('change', function() {
|
||||
var selected = $('#service_type option:selected').val();
|
||||
var typeLabel = $('#service_type_label');
|
||||
if (selected === 'tcp' || selected === 'udp') {
|
||||
if (selected === 'tcp') {
|
||||
typeLabel.html('TCP Port')
|
||||
} else {
|
||||
typeLabel.html('UDP Port')
|
||||
}
|
||||
$('#service_port').parent().parent().removeClass('d-none');
|
||||
$('#service_check_type').parent().parent().addClass('d-none');
|
||||
$('#service_url').attr('placeholder', '192.168.1.1');
|
||||
$('#post_data').parent().parent().addClass('d-none');
|
||||
$('#service_response').parent().parent().addClass('d-none');
|
||||
$('#service_response_code').parent().parent().addClass('d-none');
|
||||
$('#headers').parent().parent().addClass('d-none');
|
||||
} else if (selected === 'icmp') {
|
||||
$('#service_port').parent().parent().removeClass('d-none');
|
||||
$('#headers').parent().parent().addClass('d-none');
|
||||
$('#service_check_type').parent().parent().addClass('d-none');
|
||||
$('#service_url').attr('placeholder', '192.168.1.1');
|
||||
$('#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');
|
||||
$('#service_port').parent().parent().addClass('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
async function RenderChart(chart, service, start=0, end=9999999999, group="hour", retry=true) {
|
||||
if (!chart.el) {
|
||||
return
|
||||
}
|
||||
let chartData = await ChartLatency(service, start, end, group, retry);
|
||||
if (!chartData) {
|
||||
chartData = await ChartLatency(service, start, end, "minute", retry);
|
||||
}
|
||||
chart.render();
|
||||
chart.updateSeries([{
|
||||
data: chartData || []
|
||||
}]);
|
||||
}
|
||||
|
||||
|
||||
function UTCTime() {
|
||||
var now = new Date();
|
||||
now = new Date(now.toUTCString());
|
||||
return Math.floor(now.getTime() / 1000);
|
||||
}
|
||||
|
||||
function ChartLatency(service, start=0, end=9999999999, group="hour", retry=true) {
|
||||
let url = "api/services/" + service + "/data?start=" + start + "&end=" + end + "&group=" + group;
|
||||
return new Promise(resolve => {
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
success: function (data) {
|
||||
resolve(data.data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function ChartHeatmap(service) {
|
||||
return new Promise(resolve => {
|
||||
$.ajax({
|
||||
url: "api/services/" + service + "/heatmap",
|
||||
type: 'GET',
|
||||
success: function (data) {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function FailureAnnotations(chart, service, start=0, end=9999999999, group="hour", retry=true) {
|
||||
const annotationColor = {
|
||||
strokeDashArray: 0,
|
||||
borderColor: "#d0222d",
|
||||
label: {
|
||||
show: false,
|
||||
}
|
||||
};
|
||||
var dataArr = [];
|
||||
$.ajax({
|
||||
url: "api/services/"+service+"/failures?start="+start+"&end="+end+"&group="+group,
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
data.forEach(function (d) {
|
||||
dataArr.push({x: d.created_at, ...annotationColor})
|
||||
});
|
||||
chart.addXaxisAnnotation(dataArr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$('input[id=service_name]').on('keyup', function() {
|
||||
var url = $(this).val();
|
||||
url = url.replace(/[^\w\s]/gi, '').replace(/\s+/g, '-').toLowerCase();
|
||||
$('#permalink').val(url);
|
||||
});
|
||||
|
||||
$('input[type=checkbox]').on('change', function() {
|
||||
var element = $(this).attr('id');
|
||||
$("#"+element+"-value").val(this.checked ? "true" : "false")
|
||||
});
|
||||
|
||||
function PingAjaxChart(chart, service, start=0, end=9999999999, group="hour") {
|
||||
$.ajax({
|
||||
url: "api/services/"+service+"/ping?start="+start+"&end="+end+"&group="+group,
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
chart.data.labels.pop();
|
||||
chart.data.datasets.push({
|
||||
label: "Ping Time",
|
||||
backgroundColor: "#bababa"
|
||||
});
|
||||
chart.update();
|
||||
data.data.forEach(function(d) {
|
||||
chart.data.datasets[1].data.push(d);
|
||||
});
|
||||
chart.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('.confirm_btn').on('click', function() {
|
||||
let msg = $(this).attr('data-msg');
|
||||
var r = confirm(msg);
|
||||
if (r !== true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
$('.ajax_delete').on('click', function() {
|
||||
var r = confirm('Are you sure you want to delete?');
|
||||
if (r !== true) {
|
||||
return false;
|
||||
}
|
||||
let obj = $(this);
|
||||
let id = obj.attr('data-id');
|
||||
let element = obj.attr('data-obj');
|
||||
let url = obj.attr('href');
|
||||
let method = obj.attr('data-method');
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: method,
|
||||
data: JSON.stringify({id: id}),
|
||||
success: function (data) {
|
||||
if (data.status === 'error') {
|
||||
alert(data.error)
|
||||
} else {
|
||||
console.log(data);
|
||||
$('#' + element).remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
return false
|
||||
});
|
||||
|
||||
|
||||
$('form.ajax_form').on('submit', function() {
|
||||
const form = $(this);
|
||||
let values = form.serializeArray();
|
||||
let method = form.attr('method');
|
||||
let action = form.attr('action');
|
||||
let func = form.attr('data-func');
|
||||
let redirect = form.attr('data-redirect');
|
||||
let button = form.find('button[type=submit]');
|
||||
let alerter = form.find('#alerter');
|
||||
var arrayData = [];
|
||||
let newArr = {};
|
||||
Spinner(button);
|
||||
values.forEach(function(k, v) {
|
||||
if (k.name === "password_confirm" || k.value === "" || k.name === "enabled-option") {
|
||||
return
|
||||
}
|
||||
if (k.value === "on") {
|
||||
k.value = (k.value === "on")
|
||||
}
|
||||
if (k.value === "false" || k.value === "true") {
|
||||
k.value = (k.value === "true")
|
||||
}
|
||||
if($.isNumeric(k.value)){
|
||||
if (k.name !== "password") {
|
||||
k.value = parseInt(k.value)
|
||||
}
|
||||
}
|
||||
if (k.name === "var1" || k.name === "var2" || k.name === "host" || k.name === "username" || k.name === "password" || k.name === "api_key" || k.name === "api_secret") {
|
||||
k.value = k.value.toString()
|
||||
}
|
||||
newArr[k.name] = k.value;
|
||||
arrayData.push(newArr)
|
||||
});
|
||||
let sendData = JSON.stringify(newArr);
|
||||
$.ajax({
|
||||
url: action,
|
||||
type: method,
|
||||
data: sendData,
|
||||
success: function (data) {
|
||||
setTimeout(function () {
|
||||
if (data.status === 'error') {
|
||||
let alerter = form.find('#alerter');
|
||||
alerter.html(data.error);
|
||||
alerter.removeClass("d-none");
|
||||
Spinner(button, true);
|
||||
} else {
|
||||
Spinner(button, true);
|
||||
if (func) {
|
||||
let fn = window[func];
|
||||
if (typeof fn === "function") fn({element: form, form: newArr, data: data});
|
||||
}
|
||||
if (redirect) {
|
||||
window.location.href = redirect;
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
function CreateService(output) {
|
||||
let form = output.form;
|
||||
let data = output.data.output;
|
||||
let objTbl = `<tr id="service_${data.id}">
|
||||
<td><span class="drag_icon d-none d-md-inline"><i class="fas fa-bars"></i></span> ${form.name}</td>
|
||||
<td class="d-none d-md-table-cell">${data.online}<span class="badge badge-success">ONLINE</span></td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="service/${data.id}" class="btn btn-outline-secondary"><i class="fas fa-chart-area"></i> View</a>
|
||||
<a href="api/services/${data.id}" class="ajax_delete btn btn-danger confirm-btn" data-method="DELETE" data-obj="service_${data.id}" data-id="${data.id}"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
$('#services_table').append(objTbl);
|
||||
}
|
||||
|
||||
function CreateUser(output) {
|
||||
console.log('creating user', output)
|
||||
let form = output.form;
|
||||
let data = output.data.output;
|
||||
let objTbl = `<tr id="user_${data.id}">
|
||||
<td>${form.username}</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<a href="user/${data.id}" class="btn btn-outline-secondary"><i class="fas fa-user-edit"></i> Edit</a>
|
||||
<a href="api/users/${data.id}" class="ajax_delete btn btn-danger confirm-btn" data-method="DELETE" data-obj="user_${data.id}" data-id="${data.id}"><i class="fas fa-times"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
$('#users_table').append(objTbl);
|
||||
}
|
||||
|
||||
$('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');
|
||||
} else {
|
||||
$('#post_data').parent().parent().addClass('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$(function() {
|
||||
var pathname = window.location.pathname;
|
||||
if (pathname === '/logs') {
|
||||
var lastline;
|
||||
var logArea = $('#live_logs');
|
||||
setInterval(function() {
|
||||
$.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';
|
||||
logArea.text(line + curr);
|
||||
lastline = data;
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('.confirm-btn').on('click', function() {
|
||||
var r = confirm('Are you sure you want to delete?');
|
||||
let obj = $(this);
|
||||
let redirect = obj.attr('data-redirect');
|
||||
let href = obj.attr('href');
|
||||
let method = obj.attr('data-method');
|
||||
let data = obj.attr('data-object');
|
||||
if (r === true) {
|
||||
$.ajax({
|
||||
url: href,
|
||||
type: method,
|
||||
data: data ? data : null,
|
||||
success: function (data) {
|
||||
console.log("send to url: ", href);
|
||||
if (redirect) {
|
||||
window.location.href = redirect;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('.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;
|
||||
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
|
||||
});
|
||||
sass_vars.setSize(null, 900);
|
||||
ranVar = true;
|
||||
} 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
|
||||
});
|
||||
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;
|
||||
}
|
||||
});
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Statping
|
||||
* Copyright (C) 2018. Hunter Long and the project contributors
|
||||
* Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
*
|
||||
* https://github.com/hunterlong/statping
|
||||
*
|
||||
* The licenses for most software and other practical works are designed
|
||||
* to take away your freedom to share and change the works. By contrast,
|
||||
* the GNU General Public License is intended to guarantee your freedom to
|
||||
* share and change all versions of a program--to make sure it remains free
|
||||
* software for all its users.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var currentLocation = window.location;
|
||||
var domain = $("#domain_input");
|
||||
if (domain.val() === "") {
|
||||
domain.val(currentLocation.origin);
|
||||
}
|
||||
|
||||
$('select#database_type').on('change', function(){
|
||||
var selected = $('#database_type option:selected').val();
|
||||
if (selected=="sqlite") {
|
||||
$("#db_host").hide();
|
||||
$("#db_password").hide();
|
||||
$("#db_port").hide();
|
||||
$("#db_user").hide();
|
||||
$("#db_database").hide();
|
||||
} else {
|
||||
$("#db_host").show();
|
||||
$("#db_password").show();
|
||||
$("#db_port").show();
|
||||
$("#db_user").show();
|
||||
$("#db_database").show();
|
||||
}
|
||||
if (selected=="mysql") {
|
||||
$("#db_port_in").val('3306');
|
||||
} else if (selected=="postgres") {
|
||||
$("#db_port_in").val('5432');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$("#setup_form").submit(function() {
|
||||
$("#setup_button").prop("disabled", true);
|
||||
$("#setup_button").text("Creating Statping...");
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
$('form').submit(function() {
|
||||
$(this).find("button[type='submit']").prop('disabled',true);
|
||||
$(this).find("button[type='submit']").text('Loading...');
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -29,20 +29,12 @@ import (
|
|||
|
||||
var (
|
||||
log = utils.Log.WithField("type", "source")
|
||||
CssBox *rice.Box // CSS files from the 'source/css' directory, this will be loaded into '/assets/css'
|
||||
ScssBox *rice.Box // SCSS files from the 'source/scss' directory, this will be loaded into '/assets/scss'
|
||||
JsBox *rice.Box // JS files from the 'source/js' directory, this will be loaded into '/assets/js'
|
||||
TmplBox *rice.Box // HTML and other small files from the 'source/tmpl' directory, this will be loaded into '/assets'
|
||||
FontBox *rice.Box // HTML and other small files from the 'source/tmpl' directory, this will be loaded into '/assets'
|
||||
)
|
||||
|
||||
// Assets will load the Rice boxes containing the CSS, SCSS, JS, and HTML files.
|
||||
func Assets() error {
|
||||
CssBox = rice.MustFindBox("css")
|
||||
ScssBox = rice.MustFindBox("scss")
|
||||
JsBox = rice.MustFindBox("js")
|
||||
TmplBox = rice.MustFindBox("tmpl")
|
||||
FontBox = rice.MustFindBox("font")
|
||||
TmplBox = rice.MustFindBox("dist")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -93,7 +85,7 @@ func UsingAssets(folder string) bool {
|
|||
CreateAllAssets(folder)
|
||||
err := CompileSASS(folder)
|
||||
if err != nil {
|
||||
CopyToPublic(CssBox, folder+"/css", "base.css")
|
||||
//CopyToPublic(CssBox, folder+"/css", "base.css")
|
||||
log.Warnln("Default 'base.css' was insert because SASS did not work.")
|
||||
return true
|
||||
}
|
||||
|
@ -135,11 +127,7 @@ func CreateAllAssets(folder string) error {
|
|||
MakePublicFolder(folder + "/assets/font")
|
||||
MakePublicFolder(folder + "/assets/files")
|
||||
log.Infoln("Inserting scss, css, and javascript files into assets folder")
|
||||
CopyAllToPublic(ScssBox, "scss")
|
||||
CopyAllToPublic(FontBox, "font")
|
||||
CopyAllToPublic(CssBox, "css")
|
||||
CopyAllToPublic(JsBox, "js")
|
||||
CopyToPublic(FontBox, folder+"/assets/font", "all.css")
|
||||
CopyAllToPublic(TmplBox, folder+"/assets")
|
||||
CopyToPublic(TmplBox, folder+"/assets", "robots.txt")
|
||||
CopyToPublic(TmplBox, folder+"/assets", "banner.png")
|
||||
CopyToPublic(TmplBox, folder+"/assets", "favicon.ico")
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
name,domain,expected,expected_status,interval,type,method,post_data,port,timeout,order,allow_notifications,public,group_id,headers,permalink,verify_ssl
|
||||
Bulk Upload,http://google.com,,200,60s,http,get,,,60s,1,TRUE,TRUE,,Authorization=example,bulk_example,FALSE
|
||||
JSON Post,https://jsonplaceholder.typicode.com/posts,,200,1m,http,post,"{""id"": 1, ""title"": 'foo', ""body"": 'bar', ""userId"": 1}",,15s,2,TRUE,TRUE,,Content-Type=application/json,json_post_example,FALSE
|
||||
Google DNS,8.8.8.8,,,60s,tcp,,,53,10s,3,TRUE,TRUE,,,google_dns_example,FALSE
|
||||
Google DNS UDP,8.8.8.8,,,60s,udp,,,53,10s,4,TRUE,TRUE,,,google_dns_udp_example,FALSE
|
||||
Statping Demo Page,https://demo.statping.com/health,"(\""online\"": true)",200,30s,http,get,,,10s,5,TRUE,TRUE,,,demo_link,FALSE
|
||||
Statping MySQL Page,https://mysql.statping.com/health,"(\""online\"": true)",200,30s,http,get,,,10s,6,TRUE,TRUE,,,mysql_demo_link,FALSE
|
||||
Statping SQLite Page,https://sqlite.statping.com/health,"(\""online\"": true)",200,30s,http,get,,,10s,7,TRUE,TRUE,,,sqlite_demo_link,FALSE
|
||||
Token Balance,https://status.tokenbalance.com/health,"(\""online\"": true)",200,30s,http,get,,,10s,8,TRUE,TRUE,,,token_balance,FALSE
|
||||
CloudFlare DNS,1.1.1.1,,,60s,tcp,,,53,10s,9,TRUE,TRUE,,,cloudflare_dns_example,FALSE
|
||||
Verisign DNS,64.6.64.4,,,60s,tcp,,,53,10s,10,TRUE,TRUE,,,verisign_dns_example,FALSE
|
|
|
@ -1,208 +0,0 @@
|
|||
{{define "title"}}Statping | Dashboard{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
{{template "nav" }}
|
||||
<div class="col-12 mt-3">
|
||||
<div class="row stats_area mb-5">
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{ len Services }}</span>
|
||||
Total Services
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{ CoreApp.Count24HFailures }}</span>
|
||||
Failures last 24 Hours
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{ CoreApp.CountOnline }}</span>
|
||||
Online Services
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ range Services }}
|
||||
<div class="col-12 card mb-3" style="min-height: 260px">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><a href="service/{{.Id}}">{{.Name}}</a> {{if .Online}}<span class="badge float-right badge-success">ONLINE</span>{{else}}<span class="badge float-right badge-danger">OFFLINE</span>{{end}}</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div id="spark_service_{{.Id}}_1"></div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div id="spark_service_{{.Id}}_2"></div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div id="spark_service_{{.Id}}_3"></div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div id="spark_service_{{.Id}}_4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
{{if eq (len CoreApp.Services) 0}}
|
||||
<div class="jumbotron jumbotron-fluid">
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">No Services!</h1>
|
||||
<a class="lead">You don't have any websites or applications being monitored by your Statping server. <p><a href="service/create" class="btn btn-secondary mt-3">Add Service</a></p></p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "extra_scripts"}}
|
||||
<script>
|
||||
function AddTotal(arr) {
|
||||
var total = 0;
|
||||
arr.forEach(function(d) {
|
||||
total += d
|
||||
});
|
||||
return total.toString();
|
||||
}
|
||||
|
||||
function Average(arr) {
|
||||
var total = AddTotal(arr);
|
||||
if (arr.length === 0) {
|
||||
return total+"ms"
|
||||
}
|
||||
return (total / arr.length).toFixed(0)+"ms";
|
||||
}
|
||||
|
||||
const sparkOption = {
|
||||
chart: {
|
||||
type: 'area',
|
||||
height: 180,
|
||||
sparkline: {
|
||||
enabled: true
|
||||
},
|
||||
},
|
||||
stroke: {
|
||||
curve: 'straight'
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
yaxis: {
|
||||
min: 0
|
||||
},
|
||||
colors: ['#DCE6EC'],
|
||||
tooltip: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
{{ range Services }}
|
||||
var sparklineData_{{js .Id}}_1 = {{js (.SparklineDayFailures 7)}};
|
||||
var sparklineData_{{js .Id}}_2 = {{js (.SparklineDayFailures 30)}};
|
||||
var sparklineData_{{js .Id}}_3 = {{js (.SparklineHourResponse 30 "latency")}};
|
||||
var sparklineData_{{js .Id}}_4 = {{js (.SparklineHourResponse 30 "ping_time")}};
|
||||
|
||||
var options_{{js .Id}}_1 = {
|
||||
...sparkOption,
|
||||
title: {
|
||||
text: AddTotal(sparklineData_{{js .Id}}_1),
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '28px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
text: 'Failures Last 7 Days',
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
data: sparklineData_{{js .Id}}_1
|
||||
}],
|
||||
};
|
||||
var options_{{js .Id}}_2 = {
|
||||
...sparkOption,
|
||||
title: {
|
||||
text: AddTotal(sparklineData_{{js .Id}}_2),
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '28px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
text: 'Failures Last Month',
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
data: sparklineData_{{js .Id}}_2
|
||||
}],
|
||||
};
|
||||
var options_{{js .Id}}_3 = {
|
||||
...sparkOption,
|
||||
title: {
|
||||
text: Average(sparklineData_{{js .Id}}_3),
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '28px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
text: 'Average Response',
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
data: sparklineData_{{js .Id}}_3
|
||||
}],
|
||||
};
|
||||
var options_{{js .Id}}_4 = {
|
||||
...sparkOption,
|
||||
title: {
|
||||
text: Average(sparklineData_{{js .Id}}_4),
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '28px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
text: 'Ping Time',
|
||||
offsetX: 0,
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
cssClass: 'apexcharts-yaxis-title'
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
data: sparklineData_{{js .Id}}_4
|
||||
}],
|
||||
};
|
||||
{{end}}
|
||||
|
||||
{{ range Services }}
|
||||
var spark_{{js .Id}}_1 = new ApexCharts(document.querySelector("#spark_service_{{.Id}}_1"), options_{{js .Id}}_1);
|
||||
var spark_{{js .Id}}_2 = new ApexCharts(document.querySelector("#spark_service_{{.Id}}_2"), options_{{js .Id}}_2);
|
||||
var spark_{{js .Id}}_3 = new ApexCharts(document.querySelector("#spark_service_{{.Id}}_3"), options_{{js .Id}}_3);
|
||||
var spark_{{js .Id}}_4 = new ApexCharts(document.querySelector("#spark_service_{{.Id}}_4"), options_{{js .Id}}_4);
|
||||
spark_{{js .Id}}_1.render();
|
||||
spark_{{js .Id}}_2.render();
|
||||
spark_{{js .Id}}_3.render();
|
||||
spark_{{js .Id}}_4.render();
|
||||
{{end}}
|
||||
|
||||
</script>
|
||||
{{end}}
|
|
@ -1,10 +0,0 @@
|
|||
{{define "title"}}Statping Page Not Found{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
<div class="col-12 mt-3">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
Sorry, this page doesn't seem to exist.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,9 +0,0 @@
|
|||
{{ define "footer"}}
|
||||
<div class="footer text-center mb-4 p-2">
|
||||
{{ if CoreApp.Footer.String }}
|
||||
{{ safe CoreApp.Footer.String }}
|
||||
{{ else }}
|
||||
<a href="https://github.com/hunterlong/statping" target="_blank">Statping {{VERSION}} made with <i class="text-danger fas fa-heart"></i></a> | <a href="dashboard">Dashboard</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
|
@ -1,27 +0,0 @@
|
|||
{{define "form_checkin"}}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form class="ajax_form" action="api/checkin" data-redirect="/service/{{.Id}}" method="POST">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3">
|
||||
<label for="checkin_interval" class="col-form-label">Checkin Name</label>
|
||||
<input type="text" name="name" class="form-control" id="checkin_name" placeholder="New Checkin">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label for="checkin_interval" class="col-form-label">Interval (seconds)</label>
|
||||
<input type="number" name="interval" class="form-control" id="checkin_interval" placeholder="60">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label for="grace_period" class="col-form-label">Grace Period</label>
|
||||
<input type="number" name="grace" class="form-control" id="grace_period" placeholder="10">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label for="submit" class="col-form-label"></label>
|
||||
<input type="hidden" name="service_id" class="form-control" id="service_id" value="{{.Id}}">
|
||||
<button type="submit" id="submit" class="btn btn-success d-block" style="margin-top: 14px;">Save Checkin</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,31 +0,0 @@
|
|||
{{define "form_group"}}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{$message := .}}
|
||||
<form class="ajax_form" action="api/groups{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="services" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Group Name</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="name" class="form-control" value="{{.Name}}" id="title" placeholder="Group Name" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="order" class="col-sm-4 col-form-label">Public Group</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input type="checkbox" name="public" class="switch" id="switch-group-public" {{if .Public.Bool}}checked{{end}}>
|
||||
<label for="switch-group-public">Show group services to the public</label>
|
||||
<input type="hidden" name="public" id="switch-group-public-value" value="{{if .Public.Bool}}true{{else}}false{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update Group{{else}}Create Group{{end}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,84 +0,0 @@
|
|||
{{define "form_incident"}}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{$message := .}}
|
||||
<form class="ajax_form" action="api/messages{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="/messages" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Title</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="title" class="form-control" value="{{.Title}}" id="title" placeholder="Message Title" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Description</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea rows="5" name="description" class="form-control" id="description" required>{{.Description}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Message Date Range</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="{{ParseTime .StartOn "2006-01-02T15:04:05Z"}}" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="{{ParseTime .EndOn "2006-01-02T15:04:05Z"}}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="service_id" class="col-sm-4 col-form-label">Service</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="form-control" name="service" id="service_id">
|
||||
<option value="0" {{if eq (ToString .ServiceId) "0"}}selected{{end}}>Global Message</option>
|
||||
{{range Services}}
|
||||
{{$s := .Select}}
|
||||
<option value="{{$s.Id}}" {{if eq $message.ServiceId $s.Id}}selected{{end}}>{{$s.Name}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="notify_method" class="form-control" id="notify_method" value="{{.NotifyMethod}}" placeholder="email">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label>
|
||||
<div class="col-sm-8">
|
||||
<span class="switch">
|
||||
<input type="checkbox" name="notify_users-value" class="switch" id="switch-normal"{{if .NotifyUsers.Bool}} checked{{end}}>
|
||||
<label for="switch-normal">Notify Users Before Scheduled Time</label>
|
||||
<input type="hidden" name="notify_users" id="switch-normal-value" value="{{if .NotifyUsers.Bool}}true{{else}}false{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="form-inline">
|
||||
<input type="number" name="notify_before" class="col-4 form-control" id="notify_before" value="{{.NotifyBefore.Int64}}">
|
||||
<select class="ml-2 col-7 form-control" name="notify_before_scale" id="notify_before_scale">
|
||||
<option value="minute"{{if ne .Id 0}} selected{{else}}{{if eq .NotifyBeforeScale "minute"}}selected{{end}}{{end}}>Minutes</option>
|
||||
<option value="hour"{{if eq .NotifyBeforeScale "hour"}} selected{{end}}>Hours</option>
|
||||
<option value="day"{{if eq .NotifyBeforeScale "day"}} selected{{end}}>Days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update Message{{else}}Create Message{{end}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,32 +0,0 @@
|
|||
{{define "form_integration"}}
|
||||
{{$i := .Get}}
|
||||
<form class="integration_{{underscore $i.ShortName }}" action="settings/integrator/{{ $i.ShortName }}" method="POST">
|
||||
<input type="hidden" name="integrator" class="form-control" value="{{ $i.ShortName }}">
|
||||
{{if $i.ShortName}}<h4 class="text-capitalize">{{$i.ShortName}}</h4>{{end}}
|
||||
{{if $i.Description}}<p class="small text-muted">{{safe $i.Description}}</p>{{end}}
|
||||
|
||||
{{range $i.Fields}}
|
||||
<div class="form-group">
|
||||
<label class="text-capitalize" for="{{underscore .Name}}">{{.Name}}</label>
|
||||
{{if eq .Type "textarea"}}
|
||||
<textarea rows="3" class="form-control" name="{{underscore .Name}}" id="{{underscore .Name}}">{{ .Value }}</textarea>
|
||||
{{else if eq .Type "text"}}
|
||||
<input type="text" name="{{underscore .Name}}" class="form-control" value="{{ .Value }}" id="{{underscore .Name}}">
|
||||
{{else if eq .Type "password"}}
|
||||
<input type="password" name="{{underscore .Name}}" class="form-control" value="{{ .Value }}" id="{{underscore .Name}}">
|
||||
{{else if eq .Type "integer"}}
|
||||
<input type="number" name="{{underscore .Name}}" class="form-control" value="{{ .Value }}" id="{{underscore .Name}}">
|
||||
{{else if eq .Type "file"}}
|
||||
<input type="file" name="{{underscore .Name}}" class="form-control" value="{{ .Value }}" id="{{underscore .Name}}">
|
||||
{{end}}
|
||||
{{if .Description}}
|
||||
<small class="form-text text-muted">{{safe .Description}}</small>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<button type="submit" class="btn btn-block btn-info fetch_integrator">Fetch Services</button>
|
||||
|
||||
<div class="alert alert-danger d-none" id="integration_alerter" role="alert"></div>
|
||||
</form>
|
||||
{{end}}
|
|
@ -1,84 +0,0 @@
|
|||
{{define "form_message"}}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{$message := .}}
|
||||
<form class="ajax_form" action="api/messages{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="messages" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Title</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="title" class="form-control" value="{{.Title}}" id="title" placeholder="Message Title" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Description</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea rows="5" name="description" class="form-control" id="description" required>{{.Description}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Message Date Range</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" name="start_on" class="form-control form-control-plaintext" id="start_on" value="{{ParseTime .StartOn "2006-01-02T15:04:05Z"}}" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" name="end_on" class="form-control form-control-plaintext" id="end_on" value="{{ParseTime .EndOn "2006-01-02T15:04:05Z"}}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="service_id" class="col-sm-4 col-form-label">Service</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="form-control" name="service" id="service_id">
|
||||
<option value="0" {{if eq (ToString .ServiceId) "0"}}selected{{end}}>Global Message</option>
|
||||
{{range Services}}
|
||||
{{$s := .Select}}
|
||||
<option value="{{$s.Id}}" {{if eq $message.ServiceId $s.Id}}selected{{end}}>{{$s.Name}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="notify_method" class="form-control" id="notify_method" value="{{.NotifyMethod}}" placeholder="email">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label>
|
||||
<div class="col-sm-8">
|
||||
<span class="switch">
|
||||
<input type="checkbox" name="notify_users-value" class="switch" id="switch-normal"{{if .NotifyUsers.Bool}} checked{{end}}>
|
||||
<label for="switch-normal">Notify Users Before Scheduled Time</label>
|
||||
<input type="hidden" name="notify_users" id="switch-normal-value" value="{{if .NotifyUsers.Bool}}true{{else}}false{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="form-inline">
|
||||
<input type="number" name="notify_before" class="col-4 form-control" id="notify_before" value="{{.NotifyBefore.Int64}}">
|
||||
<select class="ml-2 col-7 form-control" name="notify_before_scale" id="notify_before_scale">
|
||||
<option value="minute"{{if ne .Id 0}} selected{{else}}{{if eq .NotifyBeforeScale "minute"}}selected{{end}}{{end}}>Minutes</option>
|
||||
<option value="hour"{{if eq .NotifyBeforeScale "hour"}} selected{{end}}>Hours</option>
|
||||
<option value="day"{{if eq .NotifyBeforeScale "day"}} selected{{end}}>Days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update Message{{else}}Create Message{{end}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,73 +0,0 @@
|
|||
{{define "form_notifier"}}
|
||||
{{$n := .Select}}
|
||||
<form class="ajax_form {{underscore $n.Method }}" data-func="SaveNotifier" action="api/notifier/{{ $n.Method }}" method="POST">
|
||||
{{if $n.Title}}<h4 class="text-capitalize">{{$n.Title}}</h4>{{end}}
|
||||
{{if $n.Description}}<p class="small text-muted">{{safe $n.Description}}</p>{{end}}
|
||||
|
||||
{{range $n.Form}}
|
||||
<div class="form-group">
|
||||
<label class="text-capitalize{{if .IsHidden}} d-none{{end}}" for="{{underscore .Title}}">{{.Title}}</label>
|
||||
{{if eq .Type "textarea"}}
|
||||
<textarea rows="3" class="form-control{{if .IsHidden}} d-none{{end}}" name="{{underscore .DbField}}" id="{{underscore .Title}}">{{ $n.GetValue .DbField }}</textarea>
|
||||
{{else}}
|
||||
<input type="{{.Type}}" name="{{underscore .DbField}}" class="form-control{{if .IsHidden}} d-none{{end}}" value="{{ $n.GetValue .DbField }}" id="{{underscore .Title}}" placeholder="{{.Placeholder}}" {{if .Required}}required{{end}}>
|
||||
{{end}}
|
||||
{{if .SmallText}}
|
||||
<small class="form-text text-muted{{if .IsHidden}} d-none{{end}}">{{safe .SmallText}}</small>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-9 col-sm-6">
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">Limit</div>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="limits" min="1" max="60" id="limits_per_hour_{{underscore $n.Method }}" value="{{$n.Limits}}" placeholder="7">
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">Per Minute</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-3 col-sm-2 mt-1">
|
||||
<span class="switch">
|
||||
<input type="checkbox" name="enabled-option" class="switch" id="switch-{{ $n.Method }}" {{if $n.Enabled.Bool}}checked{{end}}>
|
||||
<label for="switch-{{ $n.Method }}"></label>
|
||||
<input type="hidden" name="enabled" id="switch-{{ $n.Method }}-value" value="{{if $n.Enabled.Bool}}true{{else}}false{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="method" value="{{underscore $n.Method }}">
|
||||
|
||||
<div class="col-12 col-sm-4 mb-2 mb-sm-0 mt-2 mt-sm-0">
|
||||
<button type="submit" class="btn btn-primary btn-block text-capitalize"><i class="fa fa-check-circle"></i> Save</button>
|
||||
</div>
|
||||
|
||||
{{if $n.CanTest}}
|
||||
<div class="col-12 col-sm-12">
|
||||
<button class="test_notifier btn btn-secondary btn-block text-capitalize col-12 float-right"><i class="fa fa-vial"></i> Test Notifier</button>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-12 mt-2">
|
||||
<div class="alert alert-danger d-none" id="{{underscore $n.Method}}-error" role="alert">
|
||||
<i class="fa fa-exclamation-triangle"></i> {{$n.Method}} has an error!
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success d-none" id="{{underscore $n.Method}}-success" role="alert">
|
||||
<i class="fa fa-smile-beam"></i> The {{$n.Method}} notifier is working correctly!
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
|
||||
{{if $n.Author}}
|
||||
<span class="d-block small text-center mt-3 mb-5">
|
||||
<span class="text-capitalize">{{$n.Title}}</span> Notifier created by <a href="{{$n.AuthorUrl}}" target="_blank">{{$n.Author}}</a>
|
||||
</span>
|
||||
{{ end }}
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
{{end}}
|
|
@ -1,170 +0,0 @@
|
|||
{{define "form_service"}}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{$s := .}}
|
||||
<form class="ajax_form" action="api/services{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="services" method="POST">
|
||||
<h4 class="mb-5 text-muted">Basic Information</h4>
|
||||
<div class="form-group row">
|
||||
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="name" class="form-control" id="service_name" value="{{.Name}}" placeholder="Name" required spellcheck="false" autocorrect="off">
|
||||
<small class="form-text text-muted">Give your service a name you can recognize</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_type" class="col-sm-4 col-form-label">Service Type</label>
|
||||
<div class="col-sm-8">
|
||||
<select name="type" class="form-control" id="service_type" value="{{.Type}}" {{if ne .Type ""}}readonly{{end}}>
|
||||
<option value="http" {{if eq .Type "http"}}selected{{end}}>HTTP Service</option>
|
||||
<option value="tcp" {{if eq .Type "tcp"}}selected{{end}}>TCP Service</option>
|
||||
<option value="udp" {{if eq .Type "udp"}}selected{{end}}>UDP Service</option>
|
||||
<option value="icmp" {{if eq .Type "icmp"}}selected{{end}}>ICMP Ping</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Use HTTP if you are checking a website or use TCP if you are checking a server</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_url" class="col-sm-4 col-form-label">{{if (eq .Type "tcp") or (eq .Type "udp")}}Host/IP Address{{else}}Application Endpoint (URL){{end}}</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="domain" class="form-control" id="service_url" value="{{.Domain}}" placeholder="https://google.com" required autocapitalize="none" spellcheck="false">
|
||||
<small class="form-text text-muted">Statping will attempt to connect to this URL</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_type" class="col-sm-4 col-form-label">Group</label>
|
||||
<div class="col-sm-8">
|
||||
<select name="group_id" class="form-control" id="group_id">
|
||||
<option value="0" {{if eq $s.GroupId 0}}selected{{end}}>None</option>
|
||||
{{range Groups false}}
|
||||
<option value="{{.Id}}" {{if eq $s.GroupId .Id}}selected{{end}}>{{.Name}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<small class="form-text text-muted">Attach this service to a group</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-5 mb-5 text-muted">Request Details</h4>
|
||||
|
||||
<div class="form-group row{{if (eq .Type "tcp") or (eq .Type "udp")}} d-none{{end}}">
|
||||
<label for="service_check_type" class="col-sm-4 col-form-label">Service Check Type</label>
|
||||
<div class="col-sm-8">
|
||||
<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>
|
||||
<small class="form-text text-muted">A GET request will simply request the endpoint, you can also send data with POST.</small>
|
||||
</div>
|
||||
</div>
|
||||
<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="none" spellcheck="false" placeholder='{"data": { "method": "success", "id": 148923 } }'>{{.PostData.String}}</textarea>
|
||||
<small class="form-text text-muted">Insert a JSON string to send data to the endpoint.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row{{if (eq .Type "tcp") or (eq .Type "udp")}} d-none{{end}}">
|
||||
<label for="headers" class="col-sm-4 col-form-label">HTTP Headers</label>
|
||||
<div class="col-sm-8">
|
||||
<input name="headers" class="form-control" id="headers" autocapitalize="none" spellcheck="false" placeholder='Authorization=1010101,Content-Type=application/json' value="{{.Headers.String}}">
|
||||
<small class="form-text text-muted">Comma delimited list of HTTP Headers (KEY=VALUE,KEY=VALUE)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row{{if (eq .Type "tcp") or (eq .Type "udp")}} d-none{{end}}">
|
||||
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea name="expected" class="form-control" id="service_response" rows="3" autocapitalize="none" spellcheck="false" placeholder='(method)": "((\\"|[success])*)"'>{{.Expected.String}}</textarea>
|
||||
<small class="form-text text-muted">You can use plain text or insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row{{if (eq .Type "tcp") or (eq .Type "udp")}} d-none{{end}}">
|
||||
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="expected_status" class="form-control" value="{{if ne .ExpectedStatus 0}}{{.ExpectedStatus}}{{else}}200{{end}}" placeholder="200" id="service_response_code">
|
||||
<small class="form-text text-muted">A status code of 200 is success, or view all the <a target="_blank" href="https://www.restapitutorial.com/httpstatuscodes.html">HTTP Status Codes</a></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row{{if (ne .Type "tcp") and (ne .Type "udp") and (ne .Type "icmp")}} d-none{{end}}">
|
||||
<label for="port" class="col-sm-4 col-form-label">TCP Port</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="port" class="form-control" value="{{if ne .Port 0}}{{.Port}}{{end}}" id="service_port" placeholder="8080">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-5 mb-5 text-muted">Additional Options</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="check_interval" class="form-control" value="{{if ne .Interval 0}}{{.Interval}}{{else}}60{{end}}" min="1" id="service_interval" required>
|
||||
<small id="interval" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_timeout" class="col-sm-4 col-form-label">Timeout in Seconds</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="timeout" class="form-control" value="{{if ne .Timeout 0}}{{.Timeout}}{{else}}15{{end}}" placeholder="15" id="service_timeout" min="1">
|
||||
<small class="form-text text-muted">If the endpoint does not respond within this time it will be considered to be offline</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="post_data" class="col-sm-4 col-form-label">Permalink URL</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="permalink" class="form-control" value="{{.Permalink.String}}" id="permalink" autocapitalize="none" spellcheck="true" placeholder='awesome_service'>
|
||||
<small class="form-text text-muted">Use text for the service URL rather than the service number.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row d-none">
|
||||
<label for="order" class="col-sm-4 col-form-label">List Order</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="order" class="form-control" min="0" value="{{.Order}}" id="order">
|
||||
<small class="form-text text-muted">You can also drag and drop services to reorder on the Services tab.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="order" class="col-sm-4 col-form-label">Verify SSL</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input type="checkbox" name="verify_ssl-option" class="switch" id="switch-verify-ssl" {{if eq .Id 0}}checked{{end}}{{if .VerifySSL.Bool}}checked{{end}}>
|
||||
<label for="switch-verify-ssl">Verify SSL Certificate for this service</label>
|
||||
<input type="hidden" name="verify_ssl" id="switch-verify-ssl-value" value="{{if eq .Id 0}}true{{else}}{{if .VerifySSL.Bool}}true{{else}}false{{end}}{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="order" class="col-sm-4 col-form-label">Notifications</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input type="checkbox" name="allow_notifications-option" class="switch" id="switch-notifications" {{if eq .Id 0}}checked{{end}}{{if .AllowNotifications.Bool}}checked{{end}}>
|
||||
<label for="switch-notifications">Allow notifications to be sent for this service</label>
|
||||
<input type="hidden" name="allow_notifications" id="switch-notifications-value" value="{{if eq .Id 0}}true{{else}}{{if .AllowNotifications.Bool}}true{{else}}false{{end}}{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="order" class="col-sm-4 col-form-label">Visible</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span class="switch float-left">
|
||||
<input type="checkbox" name="public-option" class="switch" id="switch-public" {{if eq .Id 0}}checked{{else}}{{if .Public.Bool}}checked{{end}}{{end}}>
|
||||
<label for="switch-public">Show service details to the public</label>
|
||||
<input type="hidden" name="public" id="switch-public-value" value="{{if eq .Id 0}}true{{else}}{{if .Public.Bool}}true{{else}}false{{end}}{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="{{if ne .Id 0}}col-6{{else}}col-12{{end}}">
|
||||
<button type="submit" class="btn btn-success btn-block">{{if ne .Id 0}}Update Service{{else}}Create Service{{end}}</button>
|
||||
</div>
|
||||
{{if ne .Id 0}}
|
||||
<div class="col-6">
|
||||
<a href="service/{{ .Id }}/delete_failures" data-method="GET" data-redirect="/service/{{ .Id }}" class="btn btn-danger btn-block confirm-btn">Delete All Failures</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,45 +0,0 @@
|
|||
{{define "form_user"}}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form class="ajax_form" action="api/users{{if ne .Id 0}}/{{.Id}}{{end}}" data-redirect="users" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Username</label>
|
||||
<div class="col-6 col-md-4">
|
||||
<input type="text" name="username" class="form-control" value="{{.Username}}" id="username" placeholder="Username" required autocorrect="off" autocapitalize="none">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<span class="switch">
|
||||
<input type="checkbox" name="admin" class="switch" id="switch-normal"{{if .Admin.Bool}} checked{{end}}>
|
||||
<label for="switch-normal">Administrator</label>
|
||||
<input type="hidden" name="admin" id="switch-normal-value" value="{{if .Admin.Bool}}true{{else}}false{{end}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="email" class="col-sm-4 col-form-label">Email Address</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="email" name="email" class="form-control" id="email" value="{{.Email}}" placeholder="user@domain.com" required autocapitalize="none" spellcheck="false">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-4 col-form-label">Password</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="password" name="password" class="form-control" id="password" {{if ne .Id 0}}value=""{{end}} placeholder="Password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password_confirm" class="col-sm-4 col-form-label">Confirm Password</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="password" name="password_confirm" class="form-control" id="password_confirm" {{if ne .Id 0}}value=""{{end}} placeholder="Confirm Password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update User{{else}}Create User{{end}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger d-none" id="alerter" role="alert"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,17 +0,0 @@
|
|||
{{define "title"}}{{.Name}} Status{{end}}
|
||||
{{define "description"}}Group {{.Name}}{{end}}
|
||||
{{ define "content" }}
|
||||
|
||||
{{$isAdmin := Auth}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
|
||||
{{if IsUser}}
|
||||
{{template "nav"}}
|
||||
{{end}}
|
||||
|
||||
<div class="col-12 mb-4">
|
||||
{{template "form_group" .Group}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{end}}
|
|
@ -1,21 +0,0 @@
|
|||
{{ define "head"}}
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{block "title" .}} {{end}}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
|
||||
<meta name="description" content="{{block "description" .}}{{end}}">
|
||||
<base href="{{BasePath}}">
|
||||
{{if USE_CDN}}
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statping.com/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.statping.com/base.css">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css" integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
|
||||
{{ 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">
|
||||
<link rel="stylesheet" href="font/all.css">
|
||||
{{end}}
|
||||
{{block "extra_css" .}} {{end}}
|
||||
</head>
|
||||
{{end}}
|
|
@ -1,28 +0,0 @@
|
|||
{{define "title"}}Statping | Help{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
{{if IsUser}}
|
||||
{{template "nav"}}
|
||||
{{end}}
|
||||
<div class="col-12 bg-white p-4">
|
||||
<div class="col-12 col-md-8 offset-md-2 mb-4 mt-3">
|
||||
<img class="col-12 mt-5 mt-md-0" src="banner.png">
|
||||
</div>
|
||||
<h3>Index</h3>
|
||||
{{ safe . }}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "extra_css"}}
|
||||
<style>
|
||||
pre {
|
||||
background-color: white;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #a2a2a233;
|
||||
border-radius: 7px;
|
||||
}
|
||||
code {
|
||||
color: #d87e1a;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
|
@ -1,108 +0,0 @@
|
|||
{{define "title"}}{{CoreApp.Name}} Status{{end}}
|
||||
{{define "description"}}{{CoreApp.Name}} is currently monitoring {{len CoreApp.Services}} services with 0 of them offline. {{CoreApp.Name}} is using Statping to monitor applications.{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-2 sm-container">
|
||||
<h1 class="col-12 text-center mb-4 mt-sm-3 header-title">{{.Name}}</h1>
|
||||
|
||||
{{ if .Description }}
|
||||
<h5 class="col-12 text-center mb-5 header-desc">{{ .Description }}</h5>
|
||||
{{ end }}
|
||||
|
||||
{{ range Groups true }}
|
||||
{{if ne (len .Services) 0}}
|
||||
<div class="col-12 full-col-12">
|
||||
<h4 class="group_header">{{.Name}}</h4>
|
||||
<div class="list-group online_list mb-3">
|
||||
{{ range VisibleGroupServices . }}
|
||||
<a href="#" class="service_li list-group-item list-group-item-action {{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>
|
||||
{{ end }}
|
||||
{{end}}
|
||||
|
||||
{{ if .Messages }}
|
||||
<div class="col-12">
|
||||
{{range .Messages}}
|
||||
<div class="alert alert-primary" role="alert">
|
||||
<h3>{{.Title}}</h3>
|
||||
<span class="mb-3">{{safe .Description}}</span>
|
||||
<div class="d-block mt-2 mb-4">
|
||||
<span class="float-left small">Starts on {{ToString .StartOn}}</span>
|
||||
<span class="float-right small">Ends on {{ToString .EndOn}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="col-12 full-col-12">
|
||||
{{ if not .Services }}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="alert-heading">No Services to Monitor!</h4>
|
||||
<p>Your Statping Status Page is working correctly, but you don't have any services to monitor. Go to the <a href="dashboard">Dashboard</a> and add a website to begin really using your status page!</p>
|
||||
<hr>
|
||||
<p class="mb-0">If this is a bug, please make an issue in the Statping Github Repo. <a href="https://github.com/hunterlong/statping" class="btn btn-sm btn-outline-danger float-right">Statping Github Repo</a></p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{ range VisibleServices }}
|
||||
{{$avgTime := .AvgTime}}
|
||||
<div class="mb-4" id="service_id_{{.Id}}">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="col-12">
|
||||
<h4 class="mt-3"><a href="service/{{ServiceLink .}}"{{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">
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{$avgTime}}ms</span>
|
||||
Average Response
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{.OnlineDaysPercent 1}}%</span>
|
||||
Uptime last 24 Hours
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{.OnlineDaysPercent 7}}%</span>
|
||||
Uptime last 7 Days
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{ if $avgTime }}
|
||||
<div class="chart-container">
|
||||
<div id="service_{{ .Id }}"></div>
|
||||
</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">
|
||||
<a href="service/{{ServiceLink .}}" class="btn {{if .Online}}btn-success{{else}}btn-danger{{end}} btn-sm float-right dyn-dark btn-block">View Service</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "extra_scripts"}}
|
||||
<script src="charts.js"></script>
|
||||
{{end}}
|
|
@ -1,90 +0,0 @@
|
|||
{{define "title"}}Statping | {{.Integrator.Name}} Integration{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
{{if Auth}}
|
||||
{{template "nav"}}
|
||||
{{end}}
|
||||
{{$i := .Integrator}}
|
||||
<div class="col-12">
|
||||
<h3 class="mb-2 text-muted">{{$i.Name}} Integration</h3>
|
||||
<p>{{safe $i.Description}}</p>
|
||||
{{if .Error}}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{.Error}}
|
||||
</div>
|
||||
{{else}}
|
||||
<table id="integrator_table" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><input name="all" type="checkbox" checked></th></th>
|
||||
<th scope="col">Service Name</th>
|
||||
<th scope="col">Endpoint</th>
|
||||
<th scope="col">Port</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Interval</th>
|
||||
<th scope="col">Timeout</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="integrator_services">
|
||||
{{range .Services}}
|
||||
<tr id="{{underscore .Name}}">
|
||||
<th scope="row"><input name="add" type="checkbox" checked></th>
|
||||
<th><div class="input-group-sm"><input type="text" class="form-control" name="name" value="{{.Name}}"></div></th>
|
||||
<th><div style="width: 80pt;" class="input-group-sm"><input type="text" class="form-control" name="domain" value="{{.Domain}}"></div></th>
|
||||
<th><div style="width: 55pt;" class="input-group-sm"><input type="number" class="form-control" name="port" value="{{.Port}}"></div></th>
|
||||
<th><div style="width: 32pt;" class="input-group-sm"><input type="text" class="form-control" name="type" value="{{.Type}}"></div></th>
|
||||
<th><div style="width: 40pt;" class="input-group-sm"><input type="text" class="form-control" name="check_interval" value="{{.Interval}}"></div></th>
|
||||
<th><div style="width: 40pt;" class="input-group-sm"><input type="text" class="form-control" name="timeout" value="{{.Timeout}}"></div></th>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="imported_area"></div>
|
||||
|
||||
<button class="btn btn-block btn-primary add_integration_services mb-5" data-id="{{.Integrator.ShortName}}">Add Services</button>
|
||||
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "extra_scripts"}}
|
||||
<script>
|
||||
$('.add_integration_services').on('click', function(e) {
|
||||
var table = $(`#integrator_services`);
|
||||
|
||||
table.find('tr').each(function() {
|
||||
var t = $(this).find('input');
|
||||
var eachService = t.serializeArray();
|
||||
let newArr = {};
|
||||
|
||||
var add = false;
|
||||
|
||||
eachService.forEach(function(k, v) {
|
||||
if (k.value === "on" && k.name === "add") {
|
||||
add = true
|
||||
}
|
||||
if($.isNumeric(k.value)){
|
||||
k.value = parseInt(k.value)
|
||||
}
|
||||
if (add && k.name !== "add") {
|
||||
newArr[k.name] = k.value;
|
||||
}
|
||||
});
|
||||
|
||||
let sendData = JSON.stringify(newArr);
|
||||
$.ajax({
|
||||
url: "/api/services",
|
||||
type: "POST",
|
||||
data: sendData,
|
||||
success: function (data) {
|
||||
var box = `<div class="alert alert-success" role="alert">Service '${data.output.name}' Added <a href="service/${data.output.id}" class="badge badge-secondary mt-1 float-right">View</a></div>`;
|
||||
$("#imported_area").append(box);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
|
@ -1,34 +0,0 @@
|
|||
{{define "title"}}Statping Login{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
<div class="col-10 offset-1 col-md-8 offset-md-2 mt-md-2">
|
||||
<div class="col-12 col-md-8 offset-md-2 mb-4">
|
||||
<img class="col-12 mt-5 mt-md-0" src="banner.png">
|
||||
</div>
|
||||
{{ if .Error }}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
Incorrect login information submitted, try again.
|
||||
</div>
|
||||
{{ end }}
|
||||
<form action="dashboard" class="spin_form" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-2 col-form-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" name="password" class="form-control" id="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-block mb-3">Sign in</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,20 +0,0 @@
|
|||
{{define "title"}}Statping | Logs{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
{{if Auth}}
|
||||
{{template "nav"}}
|
||||
{{end}}
|
||||
<div class="col-12">
|
||||
<textarea id="live_logs" class="form-control" rows="40" readonly>{{range .}}{{.}}{{end}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "extra_css"}}
|
||||
<style>
|
||||
@media (max-width: 767px) {
|
||||
#live_logs {
|
||||
font-size: 6pt;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
|
@ -1,27 +0,0 @@
|
|||
{{define "title"}}Statping | {{.Title}}{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
{{template "nav"}}
|
||||
<div class="col-12">
|
||||
<h3>{{.Title}}</h3>
|
||||
{{template "form_message" .}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "extra_css"}}
|
||||
<link rel="stylesheet" href="css/flatpickr.min.css">
|
||||
{{end}}
|
||||
{{define "extra_scripts"}}
|
||||
<script src="js/flatpickr.js"></script>
|
||||
<script src="js/rangePlugin.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("#start_on").flatpickr({
|
||||
enableTime: true,
|
||||
dateFormat: "Z",
|
||||
minDate: "today",
|
||||
"plugins": [new rangePlugin({ input: "#end_on"})]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
|
@ -1,59 +0,0 @@
|
|||
{{define "title"}}Statping Messages{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
{{template "nav"}}
|
||||
{{if .}}
|
||||
<div class="col-12">
|
||||
<h1 class="text-black-50">Messages</h1>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col" class="d-none d-md-table-cell">Service</th>
|
||||
<th scope="col" class="d-none d-md-table-cell">Begins</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr id="message_{{.Id}}">
|
||||
<td>{{.Title}}</td>
|
||||
<td class="d-none d-md-table-cell">{{if .Service}}<a href="service/{{.Service.Id}}">{{.Service.Name}}</a>{{end}}</td>
|
||||
<td class="d-none d-md-table-cell">{{ToString .StartOn}}</td>
|
||||
<td class="text-right">
|
||||
{{if Auth}}<div class="btn-group">
|
||||
<a href="message/{{.Id}}" class="btn btn-outline-secondary"><i class="fas fa-exclamation-triangle"></i> Edit</a>
|
||||
<a href="api/messages/{{.Id}}" class="ajax_delete btn btn-danger" data-method="DELETE" data-obj="message_{{.Id}}" data-id="{{.Id}}"><i class="fas fa-times"></i></a>
|
||||
</div>{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if Auth}}
|
||||
<div class="col-12">
|
||||
<h1 class="text-black-50 mt-5">Create Message</h1>
|
||||
{{template "form_message" NewMessage}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "extra_css"}}
|
||||
<link rel="stylesheet" href="css/flatpickr.min.css">
|
||||
{{end}}
|
||||
{{define "extra_scripts"}}
|
||||
<script src="js/flatpickr.js"></script>
|
||||
<script src="js/rangePlugin.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("#start_on").flatpickr({
|
||||
enableTime: true,
|
||||
dateFormat: "Z",
|
||||
minDate: "today",
|
||||
"plugins": [new rangePlugin({ input: "#end_on"})]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
|
@ -1,40 +0,0 @@
|
|||
{{define "nav"}}
|
||||
{{$isAdmin := Auth}}
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<a class="navbar-brand" href="">Statping</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item{{ if eq URL "dashboard" }} active{{ end }}">
|
||||
<a class="nav-link" href="dashboard">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item{{ if eq URL "services" }} active{{ end }}">
|
||||
<a class="nav-link" href="services">Services</a>
|
||||
</li>
|
||||
<li class="nav-item{{ if eq URL "users" }} active{{ end }}">
|
||||
<a class="nav-link" href="users">Users</a>
|
||||
</li>
|
||||
<li class="nav-item{{ if eq URL "messages" }} active{{ end }}">
|
||||
<a class="nav-link" href="messages">Messages</a>
|
||||
</li>
|
||||
{{ if $isAdmin }}
|
||||
<li class="nav-item{{ if eq URL "settings" }} active{{ end }}">
|
||||
<a class="nav-link" href="settings">Settings</a>
|
||||
</li>
|
||||
<li class="nav-item{{ if eq URL "logs" }} active{{ end }}">
|
||||
<a class="nav-link" href="logs">Logs</a>
|
||||
</li>
|
||||
{{end}}
|
||||
<li class="nav-item{{ if eq URL "help" }} active{{ end }}">
|
||||
<a class="nav-link" href="help">Help</a>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="navbar-text">
|
||||
<a class="nav-link" href="logout">Logout</a>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
{{end}}
|
|
@ -1,14 +0,0 @@
|
|||
{{define "scripts"}}
|
||||
{{if USE_CDN}}
|
||||
<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://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||
<script src="https://assets.statping.com/main.js"></script>
|
||||
{{ else }}
|
||||
<script src="js/jquery-3.3.1.min.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<script src="js/apexcharts.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
{{end}}
|
||||
{{block "extra_scripts" .}} {{end}}
|
||||
{{end}}
|
|
@ -1,395 +0,0 @@
|
|||
{{define "title"}}{{.Service.Name}} Status{{end}}
|
||||
{{define "description"}}{{$s := .Service}}{{if $s.Online }}{{.Service.Name}} is currently online and responding within {{$s.AvgTime}} milliseconds with {{$s.TotalUptime}}% total uptime on {{$s.Domain}}.{{else}}{{.Service.Name}} is currently offline on {{$s.Domain}}. Notify the admin to let them know their service is offline.{{end}}{{end}}
|
||||
{{ define "content" }}
|
||||
{{$s := .Service}}
|
||||
{{$failures := $s.LimitedFailures 16}}
|
||||
{{$incidents := $s.Incidents}}
|
||||
{{$checkinFailures := $s.LimitedCheckinFailures 16}}
|
||||
{{$isAdmin := Auth}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
|
||||
{{if IsUser}}
|
||||
{{template "nav"}}
|
||||
{{end}}
|
||||
|
||||
<div class="col-12 mb-4">
|
||||
|
||||
{{if $s.Online }}
|
||||
<span class="mt-3 mb-3 text-white d-md-none btn bg-success d-block d-md-none">ONLINE</span>
|
||||
{{ else }}
|
||||
<span class="mt-3 mb-3 text-white d-md-none btn bg-danger d-block d-md-none">OFFLINE</span>
|
||||
{{end}}
|
||||
|
||||
<h4 class="mt-2"><a href="">{{CoreApp.Name}}</a> - {{ $s.Name }}
|
||||
{{if $s.Online }}
|
||||
<span class="badge bg-success float-right d-none d-md-block">ONLINE</span>
|
||||
{{ else }}
|
||||
<span class="badge bg-danger float-right d-none d-md-block">OFFLINE</span>
|
||||
{{end}}</h4>
|
||||
|
||||
<div class="row stats_area mt-5 mb-5">
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{$s.OnlineDaysPercent 1}}%</span>
|
||||
Online last 24 Hours
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{$s.AvgTime}}ms</span>
|
||||
Average Response
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<span class="lg_number">{{$s.TotalUptime}}%</span>
|
||||
Total Uptime
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if $s.ActiveMessages}}
|
||||
<div class="col-12 mb-5">
|
||||
{{range $s.ActiveMessages}}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<h3>{{.Title}}</h3>
|
||||
<span class="mb-3">{{safe .Description}}</span>
|
||||
<div class="d-block mt-2 mb-4">
|
||||
<span class="float-left small">Starts at {{.StartOn}}</span>
|
||||
<span class="float-right small">Ends on {{.EndOn}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="service-chart-container">
|
||||
<div id="service"></div>
|
||||
<div id="service-bar"></div>
|
||||
</div>
|
||||
|
||||
<div class="service-chart-heatmap">
|
||||
<div id="service_heatmap"></div>
|
||||
</div>
|
||||
|
||||
<form id="service_date_form" class="col-12 mt-2 mb-3">
|
||||
<input type="text" class="d-none" name="start" id="service_start" data-input>
|
||||
<span data-toggle title="toggle" id="start_date" class="text-muted small float-left pointer mt-2">{{.Start}} to {{.End}}</span>
|
||||
<button type="submit" class="btn btn-light btn-sm mt-2">Set Timeframe</button>
|
||||
<input type="text" class="d-none" name="end" id="service_end" data-input>
|
||||
|
||||
<div id="start_container"></div>
|
||||
<div id="end_container"></div>
|
||||
</form>
|
||||
|
||||
{{if not $s.Online}}
|
||||
<div class="col-12 small text-center mt-3 text-muted">{{$s.DowntimeText}}</div>
|
||||
{{end}}
|
||||
|
||||
{{if IsUser}}
|
||||
<nav class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs" role="serviceLists">
|
||||
{{if $isAdmin}}<a class="flex-sm-fill text-sm-center nav-link active" id="edit-tab" data-toggle="tab" href="#edit" role="tab" aria-controls="edit" aria-selected="false">Edit Service</a>{{end}}
|
||||
<a class="flex-sm-fill text-sm-center nav-link{{ if not $failures }} disabled{{end}}" id="failures-tab" data-toggle="tab" href="#failures" role="tab" aria-controls="failures" aria-selected="true">Failures</a>
|
||||
<a class="flex-sm-fill text-sm-center nav-link{{ if not $incidents }} disabled{{end}}" id="incidents-tab" data-toggle="tab" href="#incidents" role="tab" aria-controls="incidents" aria-selected="true">Incidents</a>
|
||||
{{if $isAdmin}}<a class="flex-sm-fill text-sm-center nav-link" id="checkins-tab" data-toggle="tab" href="#checkins" role="tab" aria-controls="checkins" aria-selected="false">Checkins</a>{{end}}
|
||||
<a class="flex-sm-fill text-sm-center nav-link{{if not $isAdmin}} active{{end}}" id="response-tab" data-toggle="tab" href="#response" role="tab" aria-controls="response" aria-selected="false">Response</a>
|
||||
</nav>
|
||||
<div class="tab-content" id="myTabContent">
|
||||
{{if $isAdmin}}
|
||||
<div class="tab-pane fade" id="failures" role="serviceLists" aria-labelledby="failures-tab">
|
||||
{{ if $failures }}
|
||||
<div class="list-group mt-3 mb-4">
|
||||
{{ range $failures }}
|
||||
<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>{{.Ago}}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{.Issue}}</p>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="tab-pane fade" id="incidents" role="serviceLists" aria-labelledby="incidents-tab">
|
||||
{{ if $incidents }}
|
||||
<div class="list-group mt-3 mb-4">
|
||||
{{ range $incidents }}
|
||||
<div class="list-group-item flex-column align-items-start">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">{{.Title}}</h5>
|
||||
<small>{{.CreatedAt}}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{.Description}}</p>
|
||||
|
||||
<ul class="list-group mt-3">
|
||||
{{ range .AllUpdates }}
|
||||
<li class="list-group-item">
|
||||
<p>
|
||||
<span class="badge badge-primary">{{.Type}}</span>
|
||||
<span class="float-right">
|
||||
{{.Message}}
|
||||
<p class="text-muted text-right small">{{.CreatedAt}}</p>
|
||||
</span>
|
||||
</p>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{if $isAdmin}}
|
||||
<div class="tab-pane fade" id="checkins" role="serviceLists" aria-labelledby="checkins-tab">
|
||||
{{if $s.AllCheckins}}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Checkin</th>
|
||||
<th scope="col">Report Period<br>Grace Period</th>
|
||||
<th scope="col">Last Seen</th>
|
||||
<th scope="col">Expected</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style="font-size: 10pt;">
|
||||
{{range $s.AllCheckins}}
|
||||
{{ $ch := . }}
|
||||
<tr id="checkin_{{$ch.Id}}" class="{{ if lt $ch.Expected 0}}bg-warning text-black{{else}}bg-light{{end}}">
|
||||
<td>{{$ch.Name}}<br><a href="{{$ch.Link}}" target="_blank">{{$ch.Link}}</a></td>
|
||||
<td>every {{Duration $ch.Period}}<br>after {{Duration $ch.Grace}}</td>
|
||||
<td>{{ if $ch.Last.CreatedAt.IsZero}}
|
||||
Never
|
||||
{{else}}
|
||||
{{Ago $ch.Last.CreatedAt}}
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
{{ if $ch.Last.CreatedAt.IsZero}}
|
||||
-
|
||||
{{else}}
|
||||
{{ if lt $ch.Expected 0}}{{Duration $ch.Expected}} ago{{else}}in {{Duration $ch.Expected}}{{end}}
|
||||
{{end}}
|
||||
</td>
|
||||
<td><a href="api/checkin/{{$ch.ApiKey}}" data-method="DELETE" data-obj="checkin_{{$ch.Id}}" data-id="{{$ch.Id}}" class="ajax_delete btn btn-sm btn-danger">Delete</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
{{if $isAdmin}}
|
||||
{{template "form_checkin" $s}}
|
||||
{{end}}
|
||||
{{ if $checkinFailures }}
|
||||
<div class="list-group mt-3 mb-4">
|
||||
{{ range $checkinFailures }}
|
||||
<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>{{.Ago}}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{.Issue}}</p>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="tab-pane fade{{if not $isAdmin}} show active{{end}}" id="response" role="serviceLists" aria-labelledby="response-tab">
|
||||
<div class="col-12 mt-4{{if ne $s.Type "http"}} d-none{{end}}">
|
||||
<h3>Last Response</h3>
|
||||
<textarea rows="8" class="form-control" readonly>{{ $s.LastResponse }}</textarea>
|
||||
<div class="form-group row mt-2">
|
||||
<label for="last_status_code" class="col-sm-3 col-form-label">HTTP Status Code</label>
|
||||
<div class="col-sm-2">
|
||||
<input type="text" id="last_status_code" class="form-control" value="{{ $s.LastStatusCode }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{if $isAdmin}}
|
||||
<div class="tab-pane fade show active" id="edit" role="serviceLists" aria-labelledby="edit-tab">
|
||||
{{template "form_service" $s}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{if $s.Public.Bool }}
|
||||
{{ if $failures }}
|
||||
<div class="list-group mt-3 mb-4">
|
||||
{{ range $failures }}
|
||||
<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>{{.Ago}}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{.Issue}}</p>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "extra_css"}}
|
||||
<link rel="stylesheet" href="css/flatpickr.min.css">
|
||||
{{end}}
|
||||
{{define "extra_scripts"}}
|
||||
{{$s := .Service}}
|
||||
<script src="js/flatpickr.js"></script>
|
||||
<script src="js/rangePlugin.js"></script>
|
||||
<script>
|
||||
|
||||
let options = {
|
||||
chart: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
type: "area",
|
||||
animations: {
|
||||
enabled: false,
|
||||
initialAnimation: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
colors: ["#48d338"],
|
||||
opacity: 1,
|
||||
type: 'solid'
|
||||
},
|
||||
stroke: {
|
||||
show: true,
|
||||
curve: 'smooth',
|
||||
lineCap: 'butt',
|
||||
colors: ["#3aa82d"],
|
||||
},
|
||||
series: [{name: "Latency", data: [{}]}],
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
x: {show: true, format: 'MMM dd hh:mm:ss tt'},
|
||||
},
|
||||
xaxis: {
|
||||
type: "datetime",
|
||||
tickAmount: 8,
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: (value) => {
|
||||
return (value).toFixed(0) + "ms"
|
||||
},
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
var heat_options = {
|
||||
chart: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
type: 'heatmap',
|
||||
toolbar: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
enableShades: true,
|
||||
shadeIntensity: 0.5,
|
||||
colors: ["#d53a3b"],
|
||||
series: [{data: [{}]}],
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: (value) => {
|
||||
return value
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
x: {
|
||||
show: false,
|
||||
},
|
||||
y: {
|
||||
formatter: function(val, opts) { return val+" Failures" },
|
||||
title: {
|
||||
formatter: (seriesName) => seriesName,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
async function zoomedEvent(chart, { xaxis, yaxis }) {
|
||||
let start = Math.round(xaxis.min / 1000),
|
||||
end = Math.round(xaxis.max / 1000);
|
||||
|
||||
let chartData = await ChartLatency({{$s.Id}}, start, end);
|
||||
if (!chartData) {
|
||||
chartData = await ChartLatency({{$s.Id}}, start, end, "minute");
|
||||
}
|
||||
|
||||
if(!chartData || !chartData.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
chart.updateSeries([{ name: "Latency", data: chartData }]);
|
||||
}
|
||||
|
||||
async function RenderHeatmap() {
|
||||
let heatChart = new ApexCharts(
|
||||
document.querySelector("#service_heatmap"),
|
||||
heat_options
|
||||
);
|
||||
let dataArr = [];
|
||||
let heatmapData = await ChartHeatmap({{$s.Id}});
|
||||
heatmapData.forEach(function(d) {
|
||||
var date = new Date(d.date);
|
||||
dataArr.push({name: date.toLocaleString('en-us', { month: 'long' }), data: d.data});
|
||||
});
|
||||
heatChart.render();
|
||||
heatChart.updateSeries(dataArr);
|
||||
}
|
||||
|
||||
async function RenderChartLatency() {
|
||||
options.chart.events = {
|
||||
zoomed: zoomedEvent,
|
||||
}
|
||||
options.fill.colors = {{if $s.Online}}["#48d338"]{{else}}["#dd3545"]{{end}};
|
||||
options.stroke.colors = {{if $s.Online}}["#3aa82d"]{{else}}["#c23342"]{{end}};
|
||||
|
||||
let chart = new ApexCharts(document.querySelector("#service"), options);
|
||||
await RenderChart(chart,{{$s.Id}},{{.StartUnix}},{{.EndUnix}},"hour");
|
||||
}
|
||||
|
||||
$(document).ready(async function() {
|
||||
|
||||
let startDate = $("#service_start").flatpickr({
|
||||
enableTime: false,
|
||||
static: true,
|
||||
altInput: true,
|
||||
altFormat: "U",
|
||||
maxDate: "today",
|
||||
dateFormat: "F j, Y",
|
||||
onChange: function(selectedDates, dateStr, instance) {
|
||||
var one = Math.round((new Date(selectedDates[0])).getTime() / 1000);
|
||||
var two = Math.round((new Date(selectedDates[1])).getTime() / 1000);
|
||||
$("#service_start").val(one);
|
||||
$("#service_end").val(two);
|
||||
$("#start_date").html(dateStr);
|
||||
},
|
||||
"plugins": [new rangePlugin({ input: "#service_end"})]
|
||||
});
|
||||
|
||||
$("#start_date").click(function(e) {
|
||||
startDate.open()
|
||||
});
|
||||
|
||||
await RenderChartLatency();
|
||||
await RenderHeatmap();
|
||||
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
|
@ -1,9 +0,0 @@
|
|||
{{define "title"}}Statping | Create Service{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container col-md-7 col-sm-12 mt-md-5 bg-light">
|
||||
{{template "nav"}}
|
||||
<div class="col-12">
|
||||
{{template "form_service" NewService}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue