pull/429/head
hunterlong 2020-02-18 20:07:22 -08:00
parent f82abe9b49
commit d2331fe14b
56 changed files with 671 additions and 9469 deletions

View File

@ -65,7 +65,7 @@ func catchCLI(args []string) error {
if err := runAssets(); err != nil { if err := runAssets(); err != nil {
return err return err
} }
if err := source.CompileSASS(dir); err != nil { if err := source.CompileSASS(); err != nil {
return err return err
} }
return errors.New("end") return errors.New("end")

View File

@ -93,7 +93,7 @@ func (c *Checkin) CreateFailure() (int64, error) {
Checkin: c.Id, Checkin: c.Id,
PingTime: c.Expected().Seconds(), PingTime: c.Expected().Seconds(),
}} }}
row := failuresDB().Create(&fail) row := Database(&Failure{}).Create(&fail)
sort.Sort(types.FailSort(c.Failures)) sort.Sort(types.FailSort(c.Failures))
c.Failures = append(c.Failures, fail) c.Failures = append(c.Failures, fail)
if len(c.Failures) > limitedFailures { if len(c.Failures) > limitedFailures {
@ -105,14 +105,14 @@ func (c *Checkin) CreateFailure() (int64, error) {
// LimitedHits will return the last amount of successful hits from a checkin // LimitedHits will return the last amount of successful hits from a checkin
func (c *Checkin) LimitedHits(amount int) []*types.CheckinHit { func (c *Checkin) LimitedHits(amount int) []*types.CheckinHit {
var hits []*types.CheckinHit var hits []*types.CheckinHit
checkinHitsDB().Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits) Database(&CheckinHit{}).Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits)
return hits return hits
} }
// AllCheckins returns all checkin in system // AllCheckins returns all checkin in system
func AllCheckins() []*Checkin { func AllCheckins() []*Checkin {
var checkins []*Checkin var checkins []*Checkin
checkinDB().Find(&checkins) Database(&types.Checkin{}).Find(&checkins)
return checkins return checkins
} }
@ -152,7 +152,7 @@ func (c *Checkin) Expected() time.Duration {
// Last returns the last checkinHit for a Checkin // Last returns the last checkinHit for a Checkin
func (c *Checkin) Last() *CheckinHit { func (c *Checkin) Last() *CheckinHit {
var hit CheckinHit var hit CheckinHit
checkinHitsDB().Where("checkin = ?", c.Id).Last(&hit) Database(c).Where("checkin = ?", c.Id).Last(&hit)
return &hit return &hit
} }
@ -163,7 +163,7 @@ func (c *Checkin) Link() string {
// AllHits returns all of the CheckinHits for a given Checkin // AllHits returns all of the CheckinHits for a given Checkin
func (c *Checkin) AllHits() []*types.CheckinHit { func (c *Checkin) AllHits() []*types.CheckinHit {
var checkins []*types.CheckinHit var checkins []*types.CheckinHit
checkinHitsDB().Where("checkin = ?", c.Id).Order("id DESC").Find(&checkins) Database(&types.CheckinHit{}).Where("checkin = ?", c.Id).Order("id DESC").Find(&checkins)
return checkins return checkins
} }
@ -171,7 +171,7 @@ func (c *Checkin) AllHits() []*types.CheckinHit {
func (c *Checkin) LimitedFailures(amount int) []types.FailureInterface { func (c *Checkin) LimitedFailures(amount int) []types.FailureInterface {
var failures []*Failure var failures []*Failure
var failInterfaces []types.FailureInterface var failInterfaces []types.FailureInterface
col := failuresDB().Where("checkin = ?", c.Id).Where("method = 'checkin'").Limit(amount).Order("id desc") col := Database(&types.Failure{}).Where("checkin = ?", c.Id).Where("method = 'checkin'").Limit(amount).Order("id desc")
col.Find(&failures) col.Find(&failures)
for _, f := range failures { for _, f := range failures {
failInterfaces = append(failInterfaces, f) failInterfaces = append(failInterfaces, f)
@ -182,7 +182,7 @@ func (c *Checkin) LimitedFailures(amount int) []types.FailureInterface {
// Hits returns all of the CheckinHits for a given Checkin // Hits returns all of the CheckinHits for a given Checkin
func (c *Checkin) AllFailures() []*types.Failure { func (c *Checkin) AllFailures() []*types.Failure {
var failures []*types.Failure var failures []*types.Failure
col := failuresDB().Where("checkin = ?", c.Id).Where("method = 'checkin'").Order("id desc") col := Database(&types.Failure{}).Where("checkin = ?", c.Id).Where("method = 'checkin'").Order("id desc")
col.Find(&failures) col.Find(&failures)
return failures return failures
} }
@ -194,7 +194,7 @@ func (c *Checkin) Delete() error {
service := c.Service() service := c.Service()
slice := service.Checkins slice := service.Checkins
service.Checkins = append(slice[:i], slice[i+1:]...) service.Checkins = append(slice[:i], slice[i+1:]...)
row := checkinDB().Delete(&c) row := Database(c).Delete(&c)
return row.Error() return row.Error()
} }
@ -211,7 +211,7 @@ func (c *Checkin) index() int {
// Create will create a new Checkin // Create will create a new Checkin
func (c *Checkin) Create() (int64, error) { func (c *Checkin) Create() (int64, error) {
c.ApiKey = utils.RandomString(7) c.ApiKey = utils.RandomString(7)
row := checkinDB().Create(&c) row := Database(c).Create(&c)
if row.Error() != nil { if row.Error() != nil {
log.Warnln(row.Error()) log.Warnln(row.Error())
return 0, row.Error() return 0, row.Error()
@ -225,7 +225,7 @@ func (c *Checkin) Create() (int64, error) {
// Update will update a Checkin // Update will update a Checkin
func (c *Checkin) Update() (int64, error) { func (c *Checkin) Update() (int64, error) {
row := checkinDB().Update(&c) row := Database(c).Update(&c)
if row.Error() != nil { if row.Error() != nil {
log.Warnln(row.Error()) log.Warnln(row.Error())
return 0, row.Error() return 0, row.Error()
@ -238,7 +238,7 @@ func (c *CheckinHit) Create() (int64, error) {
if c.CreatedAt.IsZero() { if c.CreatedAt.IsZero() {
c.CreatedAt = utils.Now() c.CreatedAt = utils.Now()
} }
row := checkinHitsDB().Create(&c) row := Database(c).Create(&c)
if row.Error() != nil { if row.Error() != nil {
log.Warnln(row.Error()) log.Warnln(row.Error())
return 0, row.Error() return 0, row.Error()

View File

@ -83,7 +83,7 @@ func InsertNotifierDB() error {
return errors.New("database connection has not been created") return errors.New("database connection has not been created")
} }
} }
notifier.SetDB(DbSession, CoreApp.Timezone) notifier.SetDB(DbSession)
return nil return nil
} }
@ -101,7 +101,7 @@ func InsertIntegratorDB() error {
// UpdateCore will update the CoreApp variable inside of the 'core' table in database // UpdateCore will update the CoreApp variable inside of the 'core' table in database
func UpdateCore(c *Core) (*Core, error) { func UpdateCore(c *Core) (*Core, error) {
db := coreDB().Update(&c) db := Database(&Core{}).Update(&c)
return c, db.Error() return c, db.Error()
} }
@ -116,7 +116,7 @@ func (c Core) CurrentTime() string {
// Messages will return the current local time // Messages will return the current local time
func (c Core) Messages() []*Message { func (c Core) Messages() []*Message {
var message []*Message var message []*Message
messagesDb().Where("service = ?", 0).Limit(10).Find(&message) Database(&Message{}).Where("service = ?", 0).Limit(10).Find(&message)
return message return message
} }
@ -130,7 +130,7 @@ func (c Core) SassVars() string {
if !source.UsingAssets(utils.Directory) { if !source.UsingAssets(utils.Directory) {
return "" return ""
} }
return source.OpenAsset(utils.Directory, "scss/variables.scss") return source.OpenAsset("scss/variables.scss")
} }
// BaseSASS is the base design , this opens the file /assets/scss/base.scss to be edited in Theme // BaseSASS is the base design , this opens the file /assets/scss/base.scss to be edited in Theme
@ -138,7 +138,7 @@ func (c Core) BaseSASS() string {
if !source.UsingAssets(utils.Directory) { if !source.UsingAssets(utils.Directory) {
return "" return ""
} }
return source.OpenAsset(utils.Directory, "scss/base.scss") return source.OpenAsset("scss/base.scss")
} }
// MobileSASS is the -webkit responsive custom css designs. This opens the // MobileSASS is the -webkit responsive custom css designs. This opens the
@ -147,7 +147,7 @@ func (c Core) MobileSASS() string {
if !source.UsingAssets(utils.Directory) { if !source.UsingAssets(utils.Directory) {
return "" return ""
} }
return source.OpenAsset(utils.Directory, "scss/mobile.scss") return source.OpenAsset("scss/mobile.scss")
} }
// AllOnline will be true if all services are online // AllOnline will be true if all services are online
@ -171,7 +171,7 @@ func SelectCore() (*Core, error) {
log.Errorf("core database has not been setup yet, does not have the 'core' table") log.Errorf("core database has not been setup yet, does not have the 'core' table")
return nil, errors.New("core database has not been setup yet.") return nil, errors.New("core database has not been setup yet.")
} }
db := coreDB().First(&CoreApp) db := Database(&Core{}).First(&CoreApp)
if db.Error() != nil { if db.Error() != nil {
return nil, db.Error() return nil, db.Error()
} }

View File

@ -47,68 +47,42 @@ func init() {
// DbConfig stores the config.yml file for the statup configuration // DbConfig stores the config.yml file for the statup configuration
type DbConfig types.DbConfig type DbConfig types.DbConfig
// failuresDB returns the 'failures' database column func Database(obj interface{}) types.Database {
func failuresDB() types.Database { switch obj.(type) {
return DbSession.Model(&types.Failure{}) case *types.Service, *Service, []*Service:
}
// hitsDB returns the 'hits' database column
func hitsDB() types.Database {
return DbSession.Model(&types.Hit{})
}
// servicesDB returns the 'services' database column
func servicesDB() types.Database {
return DbSession.Model(&types.Service{}) return DbSession.Model(&types.Service{})
} case *types.Hit, *Hit, []*Hit:
return DbSession.Model(&types.Hit{})
// coreDB returns the single column 'core' case *types.Failure, *Failure, []*Failure:
func coreDB() types.Database { return DbSession.Model(&types.Failure{})
case *types.Core, *Core:
return DbSession.Table("core").Model(&CoreApp) return DbSession.Table("core").Model(&CoreApp)
} case *types.Checkin, *Checkin, []*Checkin:
// usersDB returns the 'users' database column
func usersDB() types.Database {
return DbSession.Model(&types.User{})
}
// checkinDB returns the Checkin records for a service
func checkinDB() types.Database {
return DbSession.Model(&types.Checkin{}) return DbSession.Model(&types.Checkin{})
} case *types.CheckinHit, *CheckinHit, []*CheckinHit:
// checkinHitsDB returns the Checkin Hits records for a service
func checkinHitsDB() types.Database {
return DbSession.Model(&types.CheckinHit{}) return DbSession.Model(&types.CheckinHit{})
} case *types.User, *User, []*User:
return DbSession.Model(&types.User{})
// messagesDb returns the Checkin records for a service case *types.Group, *Group, []*Group:
func messagesDb() types.Database {
return DbSession.Model(&types.Message{})
}
// messagesDb returns the Checkin records for a service
func groupsDb() types.Database {
return DbSession.Model(&types.Group{}) return DbSession.Model(&types.Group{})
} case *types.Incident, *Incident, []*Incident:
// incidentsDB returns the 'incidents' database column
func incidentsDB() types.Database {
return DbSession.Model(&types.Incident{}) return DbSession.Model(&types.Incident{})
} case *types.IncidentUpdate, *IncidentUpdate, []*IncidentUpdate:
// incidentsUpdatesDB returns the 'incidents updates' database column
func incidentsUpdatesDB() types.Database {
return DbSession.Model(&types.IncidentUpdate{}) return DbSession.Model(&types.IncidentUpdate{})
case *types.Message, *Message, []*Message:
return DbSession.Model(&types.Message{})
default:
return DbSession
}
} }
// HitsBetween returns the gorm database query for a collection of service hits between a time range // 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) types.Database { func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) types.Database {
selector := Dbtimestamp(group, column) selector := Dbtimestamp(group, column)
if CoreApp.Config.DbConn == "postgres" { 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)) return Database(&Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
} else { } else {
return hitsDB().Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY)) return Database(&Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY))
} }
} }
@ -186,7 +160,7 @@ func (c *Core) InsertCore(db *types.DbConfig) (*Core, error) {
MigrationId: time.Now().Unix(), MigrationId: time.Now().Unix(),
Config: db, Config: db,
}} }}
query := coreDB().Create(&CoreApp) query := Database(CoreApp).Create(&CoreApp)
return CoreApp, query.Error() return CoreApp, query.Error()
} }
@ -345,7 +319,7 @@ func (c *Core) CreateCore() *Core {
Domain: c.Domain, Domain: c.Domain,
MigrationId: time.Now().Unix(), MigrationId: time.Now().Unix(),
} }
db := coreDB().Create(&newCore) db := Database(newCore).Create(&newCore)
if db.Error() == nil { if db.Error() == nil {
CoreApp = &Core{Core: newCore} CoreApp = &Core{Core: newCore}
} }
@ -409,7 +383,7 @@ func (c *Core) MigrateDatabase() error {
} }
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error() != nil { if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error() != nil {
tx.Rollback() tx.Rollback()
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error)) log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error()))
return tx.Error() return tx.Error()
} }
log.Infoln("Statping Database Migrated") log.Infoln("Statping Database Migrated")

View File

@ -37,7 +37,7 @@ const (
// CreateFailure will create a new Failure record for a service // CreateFailure will create a new Failure record for a service
func (s *Service) CreateFailure(f *types.Failure) (int64, error) { func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
f.Service = s.Id f.Service = s.Id
row := failuresDB().Create(f) row := Database(&types.Failure{}).Create(f)
if row.Error() != nil { if row.Error() != nil {
log.Errorln(row.Error()) log.Errorln(row.Error())
return 0, row.Error() return 0, row.Error()
@ -62,7 +62,7 @@ func (s *Service) AllFailures() []types.Failure {
} }
func (s *Service) FailuresDb(r *http.Request) types.Database { func (s *Service) FailuresDb(r *http.Request) types.Database {
return failuresDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc") return Database(&types.Failure{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
} }
// DeleteFailures will delete all failures for a service // DeleteFailures will delete all failures for a service
@ -77,14 +77,14 @@ func (s *Service) DeleteFailures() {
// LimitedFailures will return the last amount of failures from a service // LimitedFailures will return the last amount of failures from a service
func (s *Service) LimitedFailures(amount int) []*Failure { func (s *Service) LimitedFailures(amount int) []*Failure {
var failArr []*Failure var failArr []*Failure
failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr) Database(&types.Failure{}).Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
return failArr return failArr
} }
// LimitedFailures will return the last amount of failures from a service // LimitedFailures will return the last amount of failures from a service
func (s *Service) LimitedCheckinFailures(amount int) []*Failure { func (s *Service) LimitedCheckinFailures(amount int) []*Failure {
var failArr []*Failure var failArr []*Failure
failuresDB().Where("service = ?", s.Id).Where("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr) Database(&types.Failure{}).Where("service = ?", s.Id).Where("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
return failArr return failArr
} }
@ -101,7 +101,7 @@ func (f *Failure) Select() *types.Failure {
// Delete will remove a Failure record from the database // Delete will remove a Failure record from the database
func (f *Failure) Delete() error { func (f *Failure) Delete() error {
db := failuresDB().Delete(f) db := Database(&types.Failure{}).Delete(f)
return db.Error() return db.Error()
} }
@ -119,9 +119,9 @@ func (c *Core) Count24HFailures() uint64 {
// CountFailures returns the total count of failures for all services // CountFailures returns the total count of failures for all services
func CountFailures() uint64 { func CountFailures() uint64 {
var count uint64 var count uint64
err := failuresDB().Count(&count) err := Database(&types.Failure{}).Count(&count)
if err.Error() != nil { if err.Error() != nil {
log.Warnln(err.Error) log.Warnln(err.Error())
return 0 return 0
} }
return count return count
@ -132,7 +132,7 @@ func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) {
var count uint64 var count uint64
date := ago.UTC().Format("2006-01-02 00:00:00") date := ago.UTC().Format("2006-01-02 00:00:00")
dateend := ago.UTC().Format("2006-01-02") + " 23:59:59" 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'") rows := Database(&types.Failure{}).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'")
err := rows.Count(&count) err := rows.Count(&count)
return count, err.Error() return count, err.Error()
} }
@ -146,7 +146,7 @@ func (s *Service) TotalFailures24() (uint64, error) {
// TotalFailures returns the total amount of failures for a service // TotalFailures returns the total amount of failures for a service
func (s *Service) TotalFailures() (uint64, error) { func (s *Service) TotalFailures() (uint64, error) {
var count uint64 var count uint64
rows := failuresDB().Where("service = ?", s.Id) rows := Database(&types.Failure{}).Where("service = ?", s.Id)
err := rows.Count(&count) err := rows.Count(&count)
return count, err.Error() return count, err.Error()
} }
@ -161,7 +161,7 @@ func (s *Service) FailuresDaysAgo(days int) uint64 {
// TotalFailuresSince returns the total amount of failures for a service since a specific time/date // TotalFailuresSince returns the total amount of failures for a service since a specific time/date
func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) { func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
var count uint64 var count uint64
rows := failuresDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Not("method = 'checkin'") rows := Database(&types.Failure{}).Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Not("method = 'checkin'")
err := rows.Count(&count) err := rows.Count(&count)
return count, err.Error() return count, err.Error()
} }

View File

@ -16,21 +16,21 @@ func (g *Group) Delete() error {
s.GroupId = 0 s.GroupId = 0
s.Update(false) s.Update(false)
} }
err := groupsDb().Delete(g) err := Database(&Group{}).Delete(g)
return err.Error() return err.Error()
} }
// Create will create a group and insert it into the database // Create will create a group and insert it into the database
func (g *Group) Create() (int64, error) { func (g *Group) Create() (int64, error) {
g.CreatedAt = time.Now().UTC() g.CreatedAt = time.Now().UTC()
db := groupsDb().Create(g) db := Database(&Group{}).Create(g)
return g.Id, db.Error() return g.Id, db.Error()
} }
// Update will update a group // Update will update a group
func (g *Group) Update() (int64, error) { func (g *Group) Update() (int64, error) {
g.UpdatedAt = time.Now().UTC() g.UpdatedAt = time.Now().UTC()
db := groupsDb().Update(g) db := Database(&Group{}).Update(g)
return g.Id, db.Error() return g.Id, db.Error()
} }
@ -64,7 +64,7 @@ func (g *Group) VisibleServices(auth bool) []*Service {
func SelectGroups(includeAll bool, auth bool) []*Group { func SelectGroups(includeAll bool, auth bool) []*Group {
var groups []*Group var groups []*Group
var validGroups []*Group var validGroups []*Group
groupsDb().Find(&groups).Order("order_id desc") Database(&Group{}).Find(&groups).Order("order_id desc")
for _, g := range groups { for _, g := range groups {
if !g.Public.Bool { if !g.Public.Bool {
if auth { if auth {

View File

@ -27,7 +27,7 @@ type Hit struct {
// CreateHit will create a new 'hit' record in the database for a successful/online service // CreateHit will create a new 'hit' record in the database for a successful/online service
func (s *Service) CreateHit(h *types.Hit) (int64, error) { func (s *Service) CreateHit(h *types.Hit) (int64, error) {
db := hitsDB().Create(&h) db := Database(&types.Hit{}).Create(&h)
if db.Error() != nil { if db.Error() != nil {
log.Errorln(db.Error()) log.Errorln(db.Error())
return 0, db.Error() return 0, db.Error()
@ -38,7 +38,7 @@ func (s *Service) CreateHit(h *types.Hit) (int64, error) {
// CountHits returns a int64 for all hits for a service // CountHits returns a int64 for all hits for a service
func (s *Service) CountHits() (int64, error) { func (s *Service) CountHits() (int64, error) {
var hits int64 var hits int64
col := hitsDB().Where("service = ?", s.Id) col := Database(&types.Hit{}).Where("service = ?", s.Id)
err := col.Count(&hits) err := col.Count(&hits)
return hits, err.Error() return hits, err.Error()
} }
@ -46,19 +46,19 @@ func (s *Service) CountHits() (int64, error) {
// Hits returns all successful hits for a service // Hits returns all successful hits for a service
func (s *Service) HitsQuery(r *http.Request) ([]*types.Hit, error) { func (s *Service) HitsQuery(r *http.Request) ([]*types.Hit, error) {
var hits []*types.Hit var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc") col := Database(&types.Hit{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
err := col.Find(&hits) err := col.Find(&hits)
return hits, err.Error() return hits, err.Error()
} }
func (s *Service) HitsDb(r *http.Request) types.Database { func (s *Service) HitsDb(r *http.Request) types.Database {
return hitsDB().Where("service = ?", s.Id).QuerySearch(r).Order("id desc") return Database(&types.Hit{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
} }
// Hits returns all successful hits for a service // Hits returns all successful hits for a service
func (s *Service) Hits() ([]*types.Hit, error) { func (s *Service) Hits() ([]*types.Hit, error) {
var hits []*types.Hit var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id).Order("id desc") col := Database(&types.Hit{}).Where("service = ?", s.Id).Order("id desc")
err := col.Find(&hits) err := col.Find(&hits)
return hits, err.Error() return hits, err.Error()
} }
@ -66,7 +66,7 @@ func (s *Service) Hits() ([]*types.Hit, error) {
// LimitedHits returns the last 1024 successful/online 'hit' records for a service // LimitedHits returns the last 1024 successful/online 'hit' records for a service
func (s *Service) LimitedHits(amount int) ([]*types.Hit, error) { func (s *Service) LimitedHits(amount int) ([]*types.Hit, error) {
var hits []*types.Hit var hits []*types.Hit
col := hitsDB().Where("service = ?", s.Id).Order("id desc").Limit(amount) col := Database(&types.Hit{}).Where("service = ?", s.Id).Order("id desc").Limit(amount)
err := col.Find(&hits) err := col.Find(&hits)
return reverseHits(hits), err.Error() return reverseHits(hits), err.Error()
} }
@ -82,21 +82,21 @@ func reverseHits(input []*types.Hit) []*types.Hit {
// TotalHits returns the total amount of successful hits a service has // TotalHits returns the total amount of successful hits a service has
func (s *Service) TotalHits() (uint64, error) { func (s *Service) TotalHits() (uint64, error) {
var count uint64 var count uint64
col := hitsDB().Where("service = ?", s.Id).Count(&count) col := Database(&types.Hit{}).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 // TotalHitsSince returns the total amount of hits based on a specific time/date
func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) { func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) {
var count uint64 var count uint64
rows := hitsDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Count(&count) rows := Database(&types.Hit{}).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. // Sum returns the added value Latency for all of the services successful hits.
func (s *Service) Sum() float64 { func (s *Service) Sum() float64 {
var sum float64 var sum float64
rows, _ := hitsDB().Where("service = ?", s.Id).Select("sum(latency) as total").Rows() rows, _ := Database(&types.Hit{}).Where("service = ?", s.Id).Select("sum(latency) as total").Rows()
for rows.Next() { for rows.Next() {
rows.Scan(&sum) rows.Scan(&sum)
} }

View File

@ -21,17 +21,17 @@ func ReturnIncident(u *types.Incident) *Incident {
// SelectIncident returns the Incident based on the Incident's ID. // SelectIncident returns the Incident based on the Incident's ID.
func SelectIncident(id int64) (*Incident, error) { func SelectIncident(id int64) (*Incident, error) {
var incident Incident var incident Incident
err := incidentsDB().Where("id = ?", id).First(&incident) err := Database(incident).Where("id = ?", id).First(&incident)
return &incident, err.Error() return &incident, err.Error()
} }
// AllIncidents will return all incidents and updates recorded // AllIncidents will return all incidents and updates recorded
func AllIncidents() []*Incident { func AllIncidents() []*Incident {
var incidents []*Incident var incidents []*Incident
incidentsDB().Find(&incidents).Order("id desc") Database(incidents).Find(&incidents).Order("id desc")
for _, i := range incidents { for _, i := range incidents {
var updates []*types.IncidentUpdate var updates []*types.IncidentUpdate
incidentsUpdatesDB().Find(&updates).Order("id desc") Database(updates).Find(&updates).Order("id desc")
i.Updates = updates i.Updates = updates
} }
return incidents return incidents
@ -40,46 +40,46 @@ func AllIncidents() []*Incident {
// Incidents will return the all incidents for a service // Incidents will return the all incidents for a service
func (s *Service) Incidents() []*Incident { func (s *Service) Incidents() []*Incident {
var incidentArr []*Incident var incidentArr []*Incident
incidentsDB().Where("service = ?", s.Id).Order("id desc").Find(&incidentArr) Database(incidentArr).Where("service = ?", s.Id).Order("id desc").Find(&incidentArr)
return incidentArr return incidentArr
} }
// AllUpdates will return the all updates for an incident // AllUpdates will return the all updates for an incident
func (i *Incident) AllUpdates() []*IncidentUpdate { func (i *Incident) AllUpdates() []*IncidentUpdate {
var updatesArr []*IncidentUpdate var updatesArr []*IncidentUpdate
incidentsUpdatesDB().Where("incident = ?", i.Id).Order("id desc").Find(&updatesArr) Database(updatesArr).Where("incident = ?", i.Id).Order("id desc").Find(&updatesArr)
return updatesArr return updatesArr
} }
// Delete will remove a incident // Delete will remove a incident
func (i *Incident) Delete() error { func (i *Incident) Delete() error {
err := incidentsDB().Delete(i) err := Database(i).Delete(i)
return err.Error() return err.Error()
} }
// Create will create a incident and insert it into the database // Create will create a incident and insert it into the database
func (i *Incident) Create() (int64, error) { func (i *Incident) Create() (int64, error) {
i.CreatedAt = time.Now().UTC() i.CreatedAt = time.Now().UTC()
db := incidentsDB().Create(i) db := Database(i).Create(i)
return i.Id, db.Error() return i.Id, db.Error()
} }
// Update will update a incident // Update will update a incident
func (i *Incident) Update() (int64, error) { func (i *Incident) Update() (int64, error) {
i.UpdatedAt = time.Now().UTC() i.UpdatedAt = time.Now().UTC()
db := incidentsDB().Update(i) db := Database(i).Update(i)
return i.Id, db.Error() return i.Id, db.Error()
} }
// Delete will remove a incident update // Delete will remove a incident update
func (i *IncidentUpdate) Delete() error { func (i *IncidentUpdate) Delete() error {
err := incidentsUpdatesDB().Delete(i) err := Database(i).Delete(i)
return err.Error() return err.Error()
} }
// Create will create a incident update and insert it into the database // Create will create a incident update and insert it into the database
func (i *IncidentUpdate) Create() (int64, error) { func (i *IncidentUpdate) Create() (int64, error) {
i.CreatedAt = time.Now().UTC() i.CreatedAt = time.Now().UTC()
db := incidentsUpdatesDB().Create(i) db := Database(i).Create(i)
return i.Id, db.Error() return i.Id, db.Error()
} }

View File

@ -8,7 +8,7 @@ import (
) )
func TestCsvFileIntegration(t *testing.T) { func TestCsvFileIntegration(t *testing.T) {
data, err := ioutil.ReadFile("../../source/tmpl/bulk_import.csv") data, err := ioutil.ReadFile("testdata/bulk_import.csv")
require.Nil(t, err) require.Nil(t, err)
t.Run("Set Field Value", func(t *testing.T) { t.Run("Set Field Value", func(t *testing.T) {

View File

@ -8,6 +8,8 @@ import (
func TestDockerIntegration(t *testing.T) { func TestDockerIntegration(t *testing.T) {
t.SkipNow()
t.Run("Set Field Value", func(t *testing.T) { t.Run("Set Field Value", func(t *testing.T) {
formPost := map[string][]string{} formPost := map[string][]string{}
formPost["path"] = []string{"unix:///var/run/docker.sock"} formPost["path"] = []string{"unix:///var/run/docker.sock"}

View File

@ -1,15 +0,0 @@
package integrations
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestIntegrations(t *testing.T) {
t.Run("Collect Integrations", func(t *testing.T) {
amount := len(Integrations)
assert.Equal(t, 3, amount)
})
}

View File

@ -0,0 +1,11 @@
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 name domain expected expected_status interval type method post_data port timeout order allow_notifications public group_id headers permalink verify_ssl
2 Bulk Upload http://google.com 200 60s http get 60s 1 TRUE TRUE Authorization=example bulk_example FALSE
3 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
4 Google DNS 8.8.8.8 60s tcp 53 10s 3 TRUE TRUE google_dns_example FALSE
5 Google DNS UDP 8.8.8.8 60s udp 53 10s 4 TRUE TRUE google_dns_udp_example FALSE
6 Statping Demo Page https://demo.statping.com/health (\"online\": true) 200 30s http get 10s 5 TRUE TRUE demo_link FALSE
7 Statping MySQL Page https://mysql.statping.com/health (\"online\": true) 200 30s http get 10s 6 TRUE TRUE mysql_demo_link FALSE
8 Statping SQLite Page https://sqlite.statping.com/health (\"online\": true) 200 30s http get 10s 7 TRUE TRUE sqlite_demo_link FALSE
9 Token Balance https://status.tokenbalance.com/health (\"online\": true) 200 30s http get 10s 8 TRUE TRUE token_balance FALSE
10 CloudFlare DNS 1.1.1.1 60s tcp 53 10s 9 TRUE TRUE cloudflare_dns_example FALSE
11 Verisign DNS 64.6.64.4 60s tcp 53 10s 10 TRUE TRUE verisign_dns_example FALSE

View File

@ -8,15 +8,15 @@ import (
func TestTraefikIntegration(t *testing.T) { func TestTraefikIntegration(t *testing.T) {
t.Run("List Services from Traefik", func(t *testing.T) {
t.SkipNow() t.SkipNow()
t.Run("List Services from Traefik", func(t *testing.T) {
services, err := TraefikIntegrator.List() services, err := TraefikIntegrator.List()
require.Nil(t, err) require.Nil(t, err)
assert.NotEqual(t, 0, len(services)) assert.NotEqual(t, 0, len(services))
}) })
t.Run("Confirm Services from Traefik", func(t *testing.T) { t.Run("Confirm Services from Traefik", func(t *testing.T) {
t.SkipNow()
services, err := TraefikIntegrator.List() services, err := TraefikIntegrator.List()
require.Nil(t, err) require.Nil(t, err)
for _, s := range services { for _, s := range services {

View File

@ -28,7 +28,7 @@ type Message struct {
// SelectServiceMessages returns all messages for a service // SelectServiceMessages returns all messages for a service
func SelectServiceMessages(id int64) []*Message { func SelectServiceMessages(id int64) []*Message {
var message []*Message var message []*Message
messagesDb().Where("service = ?", id).Limit(10).Find(&message) Database(&Message{}).Where("service = ?", id).Limit(10).Find(&message)
return message return message
} }
@ -40,14 +40,14 @@ func ReturnMessage(m *types.Message) *Message {
// SelectMessages returns all messages // SelectMessages returns all messages
func SelectMessages() ([]*Message, error) { func SelectMessages() ([]*Message, error) {
var messages []*Message var messages []*Message
db := messagesDb().Find(&messages).Order("id desc") db := Database(&Message{}).Find(&messages).Order("id desc")
return messages, db.Error() return messages, db.Error()
} }
// SelectMessage returns a Message based on the ID passed // SelectMessage returns a Message based on the ID passed
func SelectMessage(id int64) (*Message, error) { func SelectMessage(id int64) (*Message, error) {
var message Message var message Message
db := messagesDb().Where("id = ?", id).Find(&message) db := Database(&Message{}).Where("id = ?", id).Find(&message)
return &message, db.Error() return &message, db.Error()
} }
@ -61,7 +61,7 @@ func (m *Message) Service() *Service {
// Create will create a Message and insert it into the database // Create will create a Message and insert it into the database
func (m *Message) Create() (int64, error) { func (m *Message) Create() (int64, error) {
m.CreatedAt = time.Now().UTC() m.CreatedAt = time.Now().UTC()
db := messagesDb().Create(m) db := Database(&Message{}).Create(m)
if db.Error() != nil { if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error())) log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error()))
return 0, db.Error() return 0, db.Error()
@ -71,13 +71,13 @@ func (m *Message) Create() (int64, error) {
// Delete will delete a Message from database // Delete will delete a Message from database
func (m *Message) Delete() error { func (m *Message) Delete() error {
db := messagesDb().Delete(m) db := Database(&Message{}).Delete(m)
return db.Error() return db.Error()
} }
// Update will update a Message in the database // Update will update a Message in the database
func (m *Message) Update() (*Message, error) { func (m *Message) Update() (*Message, error) {
db := messagesDb().Update(m) db := Database(&Message{}).Update(m)
if db.Error() != nil { if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error())) log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error()))
return nil, db.Error() return nil, db.Error()

View File

@ -93,8 +93,8 @@ type NotificationLog struct {
// AfterFind for Notification will set the timezone // AfterFind for Notification will set the timezone
func (n *Notification) AfterFind() (err error) { func (n *Notification) AfterFind() (err error) {
n.CreatedAt = utils.Timezoner(n.CreatedAt, timezone) n.CreatedAt = utils.Now()
n.UpdatedAt = utils.Timezoner(n.UpdatedAt, timezone) n.UpdatedAt = utils.Now()
return return
} }
@ -116,9 +116,8 @@ func modelDb(n *Notification) types.Database {
} }
// SetDB is called by core to inject the database for a notifier to use // SetDB is called by core to inject the database for a notifier to use
func SetDB(d types.Database, zone float32) { func SetDB(d types.Database) {
db = d db = d
timezone = zone
} }
// asNotification accepts a Notifier and returns a Notification struct // asNotification accepts a Notifier and returns a Notification struct
@ -247,8 +246,8 @@ func Init(n Notifier) (*Notification, error) {
var notify *Notification var notify *Notification
if err == nil { if err == nil {
notify, _ = SelectNotification(n) notify, _ = SelectNotification(n)
notify.CreatedAt = utils.Timezoner(notify.CreatedAt, timezone) notify.CreatedAt = time.Now().UTC()
notify.UpdatedAt = utils.Timezoner(notify.UpdatedAt, timezone) notify.UpdatedAt = time.Now().UTC()
if notify.Delay.Seconds() == 0 { if notify.Delay.Seconds() == 0 {
notify.Delay = time.Duration(1 * time.Second) notify.Delay = time.Duration(1 * time.Second)
} }
@ -350,7 +349,7 @@ func (n *Notification) SentLastMinute() int {
func (n *Notification) SentLast(since time.Time) int { func (n *Notification) SentLast(since time.Time) int {
sent := 0 sent := 0
for _, v := range n.Logs() { for _, v := range n.Logs() {
lastTime := time.Time(v.Time) lastTime := time.Time(v.Time).UTC()
if lastTime.After(since) { if lastTime.After(since) {
sent++ sent++
} }

View File

@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"github.com/hunterlong/statping/types" "github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite" _ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
@ -56,19 +55,20 @@ var core = &types.Core{
} }
func injectDatabase() { func injectDatabase() {
utils.DeleteFile(dir + "/notifier.db") sqlPath := dir + "/notifier.db"
db, _ = gorm.Open("sqlite3", dir+"/notifier.db") utils.DeleteFile(sqlPath)
db, _ = types.Openw("sqlite3", sqlPath)
db.CreateTable(&Notification{}) db.CreateTable(&Notification{})
} }
func TestIsBasicType(t *testing.T) { func TestIsBasicType(t *testing.T) {
assert.True(t, isType(example, new(Notifier))) assert.True(t, utils.IsType(example, new(Notifier)))
assert.True(t, isType(example, new(BasicEvents))) assert.True(t, utils.IsType(example, new(BasicEvents)))
assert.True(t, isType(example, new(ServiceEvents))) assert.True(t, utils.IsType(example, new(ServiceEvents)))
assert.True(t, isType(example, new(UserEvents))) assert.True(t, utils.IsType(example, new(UserEvents)))
assert.True(t, isType(example, new(CoreEvents))) assert.True(t, utils.IsType(example, new(CoreEvents)))
assert.True(t, isType(example, new(NotifierEvents))) assert.True(t, utils.IsType(example, new(NotifierEvents)))
assert.True(t, isType(example, new(Tester))) assert.True(t, utils.IsType(example, new(Tester)))
} }
func TestIsInDatabase(t *testing.T) { func TestIsInDatabase(t *testing.T) {

View File

@ -207,7 +207,7 @@ func insertSampleCheckins() error {
// InsertSampleHits will create a couple new hits for the sample services // InsertSampleHits will create a couple new hits for the sample services
func InsertSampleHits() error { func InsertSampleHits() error {
tx := hitsDB().Begin() tx := Database(&Hit{}).Begin()
sg := new(sync.WaitGroup) sg := new(sync.WaitGroup)
for i := int64(1); i <= 5; i++ { for i := int64(1); i <= 5; i++ {
sg.Add(1) sg.Add(1)
@ -250,7 +250,7 @@ func insertSampleCore() error {
CreatedAt: time.Now().UTC(), CreatedAt: time.Now().UTC(),
UseCdn: types.NewNullBool(false), UseCdn: types.NewNullBool(false),
} }
query := coreDB().Create(core) query := Database(&Core{}).Create(core)
return query.Error() return query.Error()
} }
@ -510,6 +510,7 @@ func TmpRecords(dbFile string) error {
var err error var err error
CoreApp = NewCore() CoreApp = NewCore()
CoreApp.Name = "Tester" CoreApp.Name = "Tester"
CoreApp.Setup = true
configs := &types.DbConfig{ configs := &types.DbConfig{
DbConn: "sqlite", DbConn: "sqlite",
Project: "Tester", Project: "Tester",

View File

@ -103,14 +103,14 @@ func (s *Service) CheckinProcess() {
// AllCheckins will return a slice of AllCheckins for a Service // AllCheckins will return a slice of AllCheckins for a Service
func (s *Service) AllCheckins() []*Checkin { func (s *Service) AllCheckins() []*Checkin {
var checkin []*Checkin var checkin []*Checkin
checkinDB().Where("service = ?", s.Id).Find(&checkin) Database(&Checkin{}).Where("service = ?", s.Id).Find(&checkin)
return checkin return checkin
} }
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup. // SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.
func (c *Core) SelectAllServices(start bool) ([]*Service, error) { func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
var services []*Service var services []*Service
db := servicesDB().Find(&services).Order("order_id desc") db := Database(&Service{}).Find(&services).Order("order_id desc")
if db.Error() != nil { if db.Error() != nil {
log.Errorln(fmt.Sprintf("service error: %v", db.Error())) log.Errorln(fmt.Sprintf("service error: %v", db.Error()))
return nil, db.Error() return nil, db.Error()
@ -354,9 +354,9 @@ func updateService(s *Service) {
// Delete will remove a service from the database, it will also end the service checking go routine // Delete will remove a service from the database, it will also end the service checking go routine
func (s *Service) Delete() error { func (s *Service) Delete() error {
i := s.index() i := s.index()
err := servicesDB().Delete(s) err := Database(&Service{}).Delete(s)
if err.Error() != nil { if err.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error)) log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error()))
return err.Error() return err.Error()
} }
s.Close() s.Close()
@ -369,7 +369,7 @@ func (s *Service) Delete() error {
// Update will update a service in the database, the service's checking routine can be restarted by passing true // 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 { func (s *Service) Update(restart bool) error {
err := servicesDB().Update(&s) err := Database(&Service{}).Update(&s)
if err.Error() != nil { if err.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err)) log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
return err.Error() return err.Error()
@ -396,7 +396,7 @@ func (s *Service) Update(restart bool) error {
// Create will create a service and insert it into the database // Create will create a service and insert it into the database
func (s *Service) Create(check bool) (int64, error) { func (s *Service) Create(check bool) (int64, error) {
s.CreatedAt = time.Now().UTC() s.CreatedAt = time.Now().UTC()
db := servicesDB().Create(s) db := Database(&Service{}).Create(s)
if db.Error() != nil { if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error())) log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error()))
return 0, db.Error() return 0, db.Error()

View File

@ -35,35 +35,35 @@ func ReturnUser(u *types.User) *User {
// CountUsers returns the amount of users // CountUsers returns the amount of users
func CountUsers() int64 { func CountUsers() int64 {
var amount int64 var amount int64
usersDB().Count(&amount) Database(&User{}).Count(&amount)
return amount return amount
} }
// SelectUser returns the User based on the User's ID. // SelectUser returns the User based on the User's ID.
func SelectUser(id int64) (*User, error) { func SelectUser(id int64) (*User, error) {
var user User var user User
err := usersDB().Where("id = ?", id).First(&user) err := Database(&User{}).Where("id = ?", id).First(&user)
return &user, err.Error() return &user, err.Error()
} }
// SelectUsername returns the User based on the User's username // SelectUsername returns the User based on the User's username
func SelectUsername(username string) (*User, error) { func SelectUsername(username string) (*User, error) {
var user User var user User
res := usersDB().Where("username = ?", username) res := Database(&User{}).Where("username = ?", username)
err := res.First(&user) err := res.First(&user)
return &user, err.Error() return &user, err.Error()
} }
// Delete will remove the User record from the database // Delete will remove the User record from the database
func (u *User) Delete() error { func (u *User) Delete() error {
return usersDB().Delete(u).Error() return Database(&User{}).Delete(u).Error()
} }
// Update will update the User's record in database // Update will update the User's record in database
func (u *User) Update() error { func (u *User) Update() error {
u.ApiKey = utils.NewSHA1Hash(5) u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10) u.ApiSecret = utils.NewSHA1Hash(10)
return usersDB().Update(u).Error() return Database(&User{}).Update(u).Error()
} }
// Create will insert a new User into the database // Create will insert a new User into the database
@ -72,7 +72,7 @@ func (u *User) Create() (int64, error) {
u.Password = utils.HashPassword(u.Password) u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA1Hash(5) u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10) u.ApiSecret = utils.NewSHA1Hash(10)
db := usersDB().Create(u) db := Database(&User{}).Create(u)
if db.Error() != nil { if db.Error() != nil {
return 0, db.Error() return 0, db.Error()
} }
@ -86,7 +86,7 @@ func (u *User) Create() (int64, error) {
// SelectAllUsers returns all users // SelectAllUsers returns all users
func SelectAllUsers() ([]*User, error) { func SelectAllUsers() ([]*User, error) {
var users []*User var users []*User
db := usersDB().Find(&users) db := Database(&User{}).Find(&users)
if db.Error() != nil { if db.Error() != nil {
log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error())) log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error()))
return nil, db.Error() return nil, db.Error()

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
const VueLoaderPlugin = require('vue-loader/lib/plugin'); const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlPlugin = require('html-webpack-plugin');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin'); const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const helpers = require('./helpers'); const helpers = require('./helpers');
const isDev = process.env.NODE_ENV === 'development'; const isDev = process.env.NODE_ENV === 'development';
@ -60,8 +59,7 @@ const webpackConfig = {
] ]
}, },
plugins: [ plugins: [
new VueLoaderPlugin(), new VueLoaderPlugin()
new HtmlPlugin({ template: 'public/index.html', chunksSortMode: 'dependency' })
] ]
}; };

View File

@ -2,6 +2,7 @@
const webpack = require('webpack'); const webpack = require('webpack');
const merge = require('webpack-merge'); const merge = require('webpack-merge');
const HtmlPlugin = require('html-webpack-plugin');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
const helpers = require('./helpers'); const helpers = require('./helpers');
const commonConfig = require('./webpack.config.common'); const commonConfig = require('./webpack.config.common');
@ -25,7 +26,11 @@ const webpackConfig = merge(commonConfig, {
plugins: [ plugins: [
new webpack.EnvironmentPlugin(environment), new webpack.EnvironmentPlugin(environment),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new FriendlyErrorsPlugin() new FriendlyErrorsPlugin(),
new HtmlPlugin({
template: 'public/index.html',
chunksSortMode: 'dependency'
})
], ],
devServer: { devServer: {
compress: true, compress: true,

View File

@ -2,6 +2,7 @@
const webpack = require('webpack'); const webpack = require('webpack');
const merge = require('webpack-merge'); const merge = require('webpack-merge');
const HtmlPlugin = require('html-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin'); const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
@ -48,7 +49,7 @@ const webpackConfig = merge(commonConfig, {
}, },
styles: { styles: {
test: /\.css$/, test: /\.css$/,
name: 'styles', name: 'style',
chunks: 'all', chunks: 'all',
enforce: true enforce: true
} }
@ -59,7 +60,7 @@ const webpackConfig = merge(commonConfig, {
new webpack.EnvironmentPlugin(environment), new webpack.EnvironmentPlugin(environment),
new MiniCSSExtractPlugin({ new MiniCSSExtractPlugin({
filename: 'css/[name].css', filename: 'css/[name].css',
chunkFilename: 'css/[name].[hash].css' chunkFilename: 'css/[name].css'
}), }),
new CompressionPlugin({ new CompressionPlugin({
filename: '[path].gz[query]', filename: '[path].gz[query]',
@ -68,7 +69,13 @@ const webpackConfig = merge(commonConfig, {
threshold: 10240, threshold: 10240,
minRatio: 0.8 minRatio: 0.8
}), }),
new webpack.HashedModuleIdsPlugin() new webpack.HashedModuleIdsPlugin(),
new HtmlPlugin({
template: 'public/base.gohtml',
filename: 'base.gohtml',
inject: false,
minify: false
})
] ]
}); });

View File

@ -4,12 +4,11 @@
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "rm -rf dist && cross-env NODE_ENV=production webpack", "build": "rm -rf dist && cross-env NODE_ENV=production webpack --mode production",
"dev": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8888 --progress", "dev": "cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --port 8888 --progress",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "~7.2",
"@fortawesome/fontawesome-free-solid": "^5.1.0-3", "@fortawesome/fontawesome-free-solid": "^5.1.0-3",
"@fortawesome/fontawesome-svg-core": "^1.2.26", "@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-brands-svg-icons": "^5.12.0", "@fortawesome/free-brands-svg-icons": "^5.12.0",
@ -37,6 +36,7 @@
"@babel/plugin-proposal-json-strings": "~7.2", "@babel/plugin-proposal-json-strings": "~7.2",
"@babel/plugin-syntax-dynamic-import": "~7.2", "@babel/plugin-syntax-dynamic-import": "~7.2",
"@babel/plugin-syntax-import-meta": "~7.2", "@babel/plugin-syntax-import-meta": "~7.2",
"@babel/polyfill": "~7.2",
"@babel/preset-env": "~7.8.3", "@babel/preset-env": "~7.8.3",
"@vue/babel-preset-app": "^4.1.2", "@vue/babel-preset-app": "^4.1.2",
"@vue/cli-plugin-babel": "^4.1.0", "@vue/cli-plugin-babel": "^4.1.0",
@ -57,7 +57,7 @@
"eslint-plugin-vue": "~5.1", "eslint-plugin-vue": "~5.1",
"file-loader": "^5.0.2", "file-loader": "^5.0.2",
"friendly-errors-webpack-plugin": "~1.7", "friendly-errors-webpack-plugin": "~1.7",
"html-webpack-plugin": "~3.2", "html-webpack-plugin": "^4.0.0-beta.11",
"mini-css-extract-plugin": "~0.5", "mini-css-extract-plugin": "~0.5",
"node-sass": "^4.13.1", "node-sass": "^4.13.1",
"optimize-css-assets-webpack-plugin": "~5.0", "optimize-css-assets-webpack-plugin": "~5.0",

View File

@ -0,0 +1,34 @@
{{ define "base" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{Name}}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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="{{Description}}">
<base href="{{BasePath}}">
{{if USE_CDN}}
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statping.com/favicon.ico">
{{else}}
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
<% _.each(htmlWebpackPlugin.tags.headTags, function(headTag) { %>
<%= headTag %> <% }) %>
{{end}}
</head>
<body>
<noscript>
<strong>We're sorry but Statping doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
{{if USE_CDN}}
{{else}}
<% _.each(htmlWebpackPlugin.tags.bodyTags, function(bodyTag) { %>
<%= bodyTag %> <% }) %>
{{end}}
</body>
</html>
{{end}}

View File

@ -0,0 +1,75 @@
<template>
<div class="list-group mt-3 mb-4">
<div v-for="(failure, index) in failures" :key="index" class="mb-2 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">{{failure.issue}}</h5>
<small>{{toLocal(failure.created_at)}}</small>
</div>
<p class="mb-1">{{failure.issue}}</p>
</div>
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only">Previous</span>
</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
<span class="sr-only">Next</span>
</a>
</li>
</ul>
</nav>
</div>
</template>
<script>
import ServiceChart from "./ServiceChart";
import Api from "../API";
export default {
name: 'ServiceFailures',
components: {ServiceChart},
props: {
service: {
type: Object,
required: true
},
},
data () {
return {
failures: [],
limit: 15,
offset: 0,
}
},
async mounted () {
this.failures = await Api.service_failures(this.service.id, this.now(), this.now(), this.limit, this.offset)
},
methods: {
smallText(s) {
if (s.online) {
return `Online, last checked ${this.ago(s.last_success)}`
} else {
return `Offline, last error: ${s.last_failure.issue} ${this.ago(s.last_failure.created_at)}`
}
},
ago(t1) {
const tm = this.parseTime(t1)
return this.duration(this.$moment().utc(), tm)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -0,0 +1,70 @@
<template>
<form @submit.prevent="login">
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">
<input @keyup="checkForm" 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 @keyup="checkForm" 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">
<div v-if="error" class="alert alert-danger" role="alert">
Incorrect username or password
</div>
<button @click.prevent="login" type="submit" class="btn btn-block mb-3 btn-primary" :disabled="disabled || loading">
{{loading ? "Loading" : "Sign in"}}
</button>
</div>
</div>
</form>
</template>
<script>
import Api from "../components/API";
export default {
name: 'FormLogin',
data () {
return {
username: "",
password: "",
auth: {},
loading: false,
error: false,
disabled: true
}
},
methods: {
checkForm() {
if (!this.username || !this.password) {
this.disabled = true
} else {
this.disabled = false
}
},
async login () {
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)
await this.$store.dispatch('loadAdmin')
this.$router.push('/dashboard')
}
this.loading = false
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -41,13 +41,6 @@
</div> </div>
</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 v-model="message.notify_method" type="text" name="notify_method" class="form-control" id="notify_method" value="" placeholder="email">
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label> <label for="notify_method" class="col-sm-4 col-form-label">Notify Users</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -58,7 +51,14 @@
</div> </div>
</div> </div>
<div class="form-group row"> <div v-if="message.notify" class="form-group row">
<label for="notify_method" class="col-sm-4 col-form-label">Notification Method</label>
<div class="col-sm-8">
<input v-model="message.notify_method" type="text" name="notify_method" class="form-control" id="notify_method" value="" placeholder="email">
</div>
</div>
<div v-if="message.notify" class="form-group row">
<label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label> <label for="notify_before" class="col-sm-4 col-form-label">Notify Before</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="form-inline"> <div class="form-inline">

View File

@ -11,7 +11,7 @@
<div class="col-6"> <div class="col-6">
<div class="form-group"> <div class="form-group">
<label>Database Connection</label> <label>Database Connection</label>
<select v-model="setup.db_connection" class="form-control"> <select @change="canSubmit" v-model="setup.db_connection" class="form-control">
<option value="sqlite">Sqlite</option> <option value="sqlite">Sqlite</option>
<option value="postgres">Postgres</option> <option value="postgres">Postgres</option>
<option value="mysql">MySQL</option> <option value="mysql">MySQL</option>
@ -19,23 +19,23 @@
</div> </div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_host"> <div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_host">
<label>Host</label> <label>Host</label>
<input v-model="setup.db_host" type="text" class="form-control" placeholder="localhost"> <input @keyup="canSubmit" v-model="setup.db_host" type="text" class="form-control" placeholder="localhost">
</div> </div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_port"> <div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_port">
<label>Database Port</label> <label>Database Port</label>
<input v-model="setup.db_port" type="text" class="form-control" placeholder="localhost"> <input @keyup="canSubmit" v-model="setup.db_port" type="text" class="form-control" placeholder="localhost">
</div> </div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_user"> <div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_user">
<label>Username</label> <label>Username</label>
<input v-model="setup.db_user" type="text" class="form-control" placeholder="root"> <input @keyup="canSubmit" v-model="setup.db_user" type="text" class="form-control" placeholder="root">
</div> </div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_password"> <div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_password">
<label for="db_password">Password</label> <label for="db_password">Password</label>
<input v-model="setup.db_password" type="password" class="form-control" placeholder="password123"> <input @keyup="canSubmit" v-model="setup.db_password" type="password" class="form-control" placeholder="password123">
</div> </div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_database"> <div v-if="setup.db_connection !== 'sqlite'" class="form-group" id="db_database">
<label for="db_database">Database</label> <label for="db_database">Database</label>
<input v-model="setup.db_database" type="text" class="form-control" placeholder="Database name"> <input @keyup="canSubmit" v-model="setup.db_database" type="text" class="form-control" placeholder="Database name">
</div> </div>
</div> </div>
@ -44,37 +44,37 @@
<div class="form-group"> <div class="form-group">
<label>Project Name</label> <label>Project Name</label>
<input v-model="setup.project" type="text" class="form-control" placeholder="Great Uptime" required> <input @keyup="canSubmit" v-model="setup.project" type="text" class="form-control" placeholder="Great Uptime" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Project Description</label> <label>Project Description</label>
<input v-model="setup.description" type="text" class="form-control" placeholder="Great Uptime"> <input @keyup="canSubmit" v-model="setup.description" type="text" class="form-control" placeholder="Great Uptime">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="domain_input">Domain URL</label> <label for="domain_input">Domain URL</label>
<input v-model="setup.domain" type="text" class="form-control" id="domain_input" required> <input @keyup="canSubmit" v-model="setup.domain" type="text" class="form-control" id="domain_input" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Admin Username</label> <label>Admin Username</label>
<input v-model="setup.username" type="text" class="form-control" placeholder="admin" required> <input @keyup="canSubmit" v-model="setup.username" type="text" class="form-control" placeholder="admin" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Admin Password</label> <label>Admin Password</label>
<input v-model="setup.password" type="password" class="form-control" placeholder="password" required> <input @keyup="canSubmit" v-model="setup.password" type="password" class="form-control" placeholder="password" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Confirm Admin Password</label> <label>Confirm Admin Password</label>
<input v-model="setup.confirm_password" type="password" class="form-control" placeholder="password" required> <input @keyup="canSubmit" v-model="setup.confirm_password" type="password" class="form-control" placeholder="password" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<span class="switch"> <span class="switch">
<input v-model="setup.sample_data" type="checkbox" class="switch" id="switch-normal"> <input @keyup="canSubmit" v-model="setup.sample_data" type="checkbox" class="switch" id="switch-normal">
<label for="switch-normal">Load Sample Data</label> <label for="switch-normal">Load Sample Data</label>
</span> </span>
</div> </div>
@ -85,7 +85,7 @@
{{error}} {{error}}
</div> </div>
<button @click.prevent="saveSetup" v-bind:disabled="canSubmit() && loading" type="submit" class="btn btn-primary btn-block" :class="{'btn-primary': !loading, 'btn-default': loading}"> <button @click.prevent="saveSetup" v-bind:disabled="disabled || loading" type="submit" class="btn btn-primary btn-block" :class="{'btn-primary': !loading, 'btn-default': loading}">
{{loading ? "Loading..." : "Save Settings"}} {{loading ? "Loading..." : "Save Settings"}}
</button> </button>
</div> </div>
@ -105,6 +105,7 @@
return { return {
error: null, error: null,
loading: false, loading: false,
disabled: true,
setup: { setup: {
db_connection: "sqlite", db_connection: "sqlite",
db_host: "", db_host: "",
@ -136,12 +137,23 @@
}, },
methods: { methods: {
canSubmit() { canSubmit() {
if (this.db_connection !== 'sqlite') { this.error = null
if (!this.db_host || !this.db_port || !this.db_user || !this.db_password || !this.db_database) { const s = this.setup
return false if (s.db_connection !== 'sqlite') {
if (!s.db_host || !s.db_port || !s.db_user || !s.db_password || !s.db_database) {
this.disabled = true
return
} }
} }
return !(!this.project || !this.description || !this.domain || !this.username || !this.password || !this.confirm_password); if (!s.project || !s.description || !s.domain || !s.username || !s.password || !s.confirm_password) {
this.disabled = true
return
}
if (s.password !== s.confirm_password) {
this.disabled = true
return
}
this.disabled = false
}, },
async saveSetup() { async saveSetup() {
this.loading = true this.loading = true

View File

@ -82,7 +82,6 @@
watch: { watch: {
in_user() { in_user() {
const u = this.in_user const u = this.in_user
delete u.password
this.user = u this.user = u
} }
}, },

View File

@ -19,12 +19,6 @@
authenticated: false authenticated: false
} }
}, },
async mounted() {
const core = await Api.core()
if (!core.logged_in) {
this.$router.push('/login')
}
},
} }
</script> </script>

View File

@ -5,30 +5,7 @@
<img class="col-12 mt-5 mt-md-0" src="/public/banner.png"> <img class="col-12 mt-5 mt-md-0" src="/public/banner.png">
</div> </div>
<form @submit.prevent="login"> <FormLogin/>
<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.prevent="login" type="submit" class="btn btn-block mb-3 btn-primary" :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>
</div> </div>
@ -36,34 +13,20 @@
<script> <script>
import Api from "../components/API"; import Api from "../components/API";
import FormLogin from '../forms/Login';
export default { export default {
name: 'Login', name: 'Login',
components: { components: {
FormLogin
}, },
data () { data () {
return { return {
username: "",
password: "",
auth: {},
loading: false,
error: false
} }
}, },
methods: { methods: {
async login (e) {
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)
await this.$store.dispatch('loadAdmin')
this.$router.push('/dashboard')
}
this.loading = false
}
} }
} }
</script> </script>

View File

@ -60,16 +60,7 @@
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane fade active show"> <div class="tab-pane fade active show">
<div class="list-group mt-3 mb-4"> <ServiceFailures :service="service"/>
<div v-for="(failure, index) in failures" :key="index" class="mb-2 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">{{failure.issue}}</h5>
<small>{{failure.created_at | moment("dddd, MMMM Do YYYY")}}</small>
</div>
<p class="mb-1">{{failure.issue}}</p>
</div>
</div>
</div> </div>
<div class="tab-pane fade" :class="{active: tab === 'incidents'}" id="incidents"> <div class="tab-pane fade" :class="{active: tab === 'incidents'}" id="incidents">
@ -109,6 +100,7 @@
<script> <script>
import Api from "../components/API" import Api from "../components/API"
import MessageBlock from '../components/Index/MessageBlock'; import MessageBlock from '../components/Index/MessageBlock';
import ServiceFailures from '../components/Service/ServiceFailures';
import Checkin from "../forms/Checkin"; import Checkin from "../forms/Checkin";
const axisOptions = { const axisOptions = {
@ -138,6 +130,7 @@
export default { export default {
name: 'Service', name: 'Service',
components: { components: {
ServiceFailures,
MessageBlock, MessageBlock,
Checkin Checkin
}, },
@ -253,7 +246,7 @@ export default {
await this.serviceFailures() await this.serviceFailures()
}, },
async serviceFailures() { async serviceFailures() {
this.failures = await Api.service_failures(this.service.id, 0, 99999999999) this.failures = await Api.service_failures(this.service.id, this.now() - 3600, this.now(), 15)
}, },
async chartHits() { async chartHits() {
this.data = await Api.service_hits(this.service.id, 0, 99999999999, "hour") this.data = await Api.service_hits(this.service.id, 0, 99999999999, "hour")

View File

@ -30,23 +30,6 @@
<CoreSettings/> <CoreSettings/>
<h2 class="mt-5">Bulk Import Services</h2>
You can import multiple services based on a CSV file with the format shown on the <a href="https://github.com/hunterlong/statping/wiki/Bulk-Import-Services" target="_blank">Bulk Import Wiki</a>.
<div class="card mt-2">
<div class="card-body">
<form action="settings/bulk_import" method="POST" enctype="multipart/form-data" class="form-inline">
<div class="form-group col-10">
<input type="file" name="file" class="form-control-file" accept=".csv">
</div>
<div class="form-group">
<button type="submit" class="btn btn-outline-success right">Import</button>
</div>
</form>
</div>
</div>
<h2 class="mt-5">Additional Settings</h2> <h2 class="mt-5">Additional Settings</h2>
<div v-if="core.domain !== ''" class="row"> <div v-if="core.domain !== ''" class="row">
<div class="col-12"> <div class="col-12">
@ -57,6 +40,9 @@
<a href="settings/export" class="btn btn-sm btn-secondary">Export Settings</a> <a href="settings/export" class="btn btn-sm btn-secondary">Export Settings</a>
</div> </div>
</div> </div>
<div v-else>
Insert a domain to view QR code for the mobile app.
</div>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -56,7 +56,10 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
http.Redirect(w, r, "/settings", http.StatusSeeOther) output := apiResponse{
Status: "success",
}
returnJson(output, w, r)
} }
func apiCoreHandler(w http.ResponseWriter, r *http.Request) { func apiCoreHandler(w http.ResponseWriter, r *http.Request) {
@ -99,9 +102,8 @@ type cacheJson struct {
} }
func apiCacheHandler(w http.ResponseWriter, r *http.Request) { func apiCacheHandler(w http.ResponseWriter, r *http.Request) {
cache := CacheStorage
var cacheList []cacheJson var cacheList []cacheJson
for k, v := range cache.List() { for k, v := range CacheStorage.List() {
cacheList = append(cacheList, cacheJson{ cacheList = append(cacheList, cacheJson{
URL: k, URL: k,
Expiration: time.Unix(0, v.Expiration).UTC(), Expiration: time.Unix(0, v.Expiration).UTC(),
@ -112,8 +114,8 @@ func apiCacheHandler(w http.ResponseWriter, r *http.Request) {
} }
func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) { func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) {
CacheStorage.StopRoutine()
CacheStorage = NewStorage() CacheStorage = NewStorage()
go CleanRoutine()
output := apiResponse{ output := apiResponse{
Status: "success", Status: "success",
} }

View File

@ -35,6 +35,7 @@ func TestResetDatabase(t *testing.T) {
err := core.TmpRecords("handlers.db") err := core.TmpRecords("handlers.db")
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, core.CoreApp) require.NotNil(t, core.CoreApp)
require.NotNil(t, core.CoreApp.Config)
} }
func TestFailedHTTPServer(t *testing.T) { func TestFailedHTTPServer(t *testing.T) {
@ -43,7 +44,6 @@ func TestFailedHTTPServer(t *testing.T) {
} }
func TestSetupRoutes(t *testing.T) { func TestSetupRoutes(t *testing.T) {
form := url.Values{} form := url.Values{}
form.Add("db_host", "") form.Add("db_host", "")
form.Add("db_user", "") form.Add("db_user", "")
@ -61,17 +61,17 @@ func TestSetupRoutes(t *testing.T) {
tests := []HTTPTest{ tests := []HTTPTest{
{ {
Name: "Statping Setup Check", Name: "Statping Check",
URL: "/setup", URL: "/api",
Method: "GET", Method: "GET",
ExpectedStatus: 303, ExpectedStatus: 200,
}, },
{ {
Name: "Statping Run Setup", Name: "Statping Run Setup",
URL: "/setup", URL: "/api/setup",
Method: "POST", Method: "POST",
Body: form.Encode(), Body: form.Encode(),
ExpectedStatus: 303, ExpectedStatus: 200,
HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"}, HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
ExpectedFiles: []string{dir + "/config.yml", dir + "/tmp/" + types.SqliteFilename}, ExpectedFiles: []string{dir + "/config.yml", dir + "/tmp/" + types.SqliteFilename},
}} }}
@ -94,19 +94,19 @@ func TestMainApiRoutes(t *testing.T) {
URL: "/api", URL: "/api",
Method: "GET", Method: "GET",
ExpectedStatus: 200, ExpectedStatus: 200,
ExpectedContains: []string{`"name":"Statping Sample Data","description":"This data is only used to testing"`}, ExpectedContains: []string{`"description":"This data is only used to testing"`},
}, },
{ {
Name: "Statping Renew API Keys", Name: "Statping Renew API Keys",
URL: "/api/renew", URL: "/api/renew",
Method: "POST", Method: "POST",
ExpectedStatus: 303, ExpectedStatus: 200,
}, },
{ {
Name: "Statping Clear Cache", Name: "Statping Clear Cache",
URL: "/api/clear_cache", URL: "/api/clear_cache",
Method: "POST", Method: "POST",
ExpectedStatus: 303, ExpectedStatus: 200,
}, },
{ {
Name: "404 Error Page", Name: "404 Error Page",
@ -129,14 +129,14 @@ func TestApiServiceRoutes(t *testing.T) {
Name: "Statping All Services", Name: "Statping All Services",
URL: "/api/services", URL: "/api/services",
Method: "GET", Method: "GET",
ExpectedContains: []string{`"name":"Google"`},
ExpectedStatus: 200, ExpectedStatus: 200,
ExpectedContains: []string{`"id":1,"name":"Google","domain":"https://google.com"`},
}, },
{ {
Name: "Statping Service 1", Name: "Statping Service 1",
URL: "/api/services/1", URL: "/api/services/1",
Method: "GET", Method: "GET",
ExpectedContains: []string{`"id":1,"name":"Google","domain":"https://google.com"`}, ExpectedContains: []string{`"name":"Google"`},
ExpectedStatus: 200, ExpectedStatus: 200,
}, },
{ {
@ -370,7 +370,7 @@ func TestMessagesApiRoutes(t *testing.T) {
URL: "/api/messages", URL: "/api/messages",
Method: "GET", Method: "GET",
ExpectedStatus: 200, ExpectedStatus: 200,
ExpectedContains: []string{`"id":1,"title":"Routine Downtime"`}, ExpectedContains: []string{`"title":"Routine Downtime"`},
}, { }, {
Name: "Statping Create Message", Name: "Statping Create Message",
URL: "/api/messages", URL: "/api/messages",
@ -394,7 +394,7 @@ func TestMessagesApiRoutes(t *testing.T) {
URL: "/api/messages/1", URL: "/api/messages/1",
Method: "GET", Method: "GET",
ExpectedStatus: 200, ExpectedStatus: 200,
ExpectedContains: []string{`"id":1,"title":"Routine Downtime"`}, ExpectedContains: []string{`"title":"Routine Downtime"`},
}, { }, {
Name: "Statping Update Message", Name: "Statping Update Message",
URL: "/api/messages/1", URL: "/api/messages/1",

View File

@ -1,46 +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/>.
package handlers
import (
"net/http"
"testing"
)
func BenchmarkHandleIndex(b *testing.B) {
//b.ReportAllocs()
//r := request(b, "/")
//for i := 0; i < b.N; i++ {
// rw := httptest.NewRecorder()
// indexHandler(rw, r)
//}
}
func BenchmarkServicesHandlerIndex(b *testing.B) {
//r := request(b, "/")
//for i := 0; i < b.N; i++ {
// rw := httptest.NewRecorder()
// servicesHandler(rw, r)
//}
}
func request(t testing.TB, url string) *http.Request {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
t.Fatal(err)
}
return req
}

View File

@ -15,6 +15,7 @@ type Cacher interface {
List() map[string]Item List() map[string]Item
Lock() Lock()
Unlock() Unlock()
StopRoutine()
} }
// Item is a cached reference // Item is a cached reference
@ -23,17 +24,23 @@ type Item struct {
Expiration int64 Expiration int64
} }
// CleanRoutine is a go routine to automatically remove expired caches that haven't been hit recently // cleanRoutine is a go routine to automatically remove expired caches that haven't been hit recently
func CleanRoutine() { func cleanRoutine(s *Storage) {
for CacheStorage != nil { duration := 5 * time.Second
CacheStorage.Lock()
for k, v := range CacheStorage.List() { CacheRoutine:
for {
select {
case <-s.running:
break CacheRoutine
case <-time.After(duration):
duration = 5 * time.Second
for k, v := range s.List() {
if v.Expired() { if v.Expired() {
CacheStorage.Delete(k) s.Delete(k)
}
} }
} }
CacheStorage.Unlock()
time.Sleep(5 * time.Second)
} }
} }
@ -49,14 +56,22 @@ func (item Item) Expired() bool {
type Storage struct { type Storage struct {
items map[string]Item items map[string]Item
mu *sync.RWMutex mu *sync.RWMutex
running chan bool
} }
//NewStorage creates a new in memory CacheStorage //NewStorage creates a new in memory CacheStorage
func NewStorage() *Storage { func NewStorage() *Storage {
return &Storage{ storage := &Storage{
items: make(map[string]Item), items: make(map[string]Item),
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
running: make(chan bool),
} }
go cleanRoutine(storage)
return storage
}
func (s Storage) StopRoutine() {
close(s.running)
} }
func (s Storage) Lock() { func (s Storage) Lock() {

View File

@ -98,19 +98,19 @@ func apiThemeSaveHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
if err := source.SaveAsset([]byte(themes.Base), utils.Directory+"/assets/scss/base.scss"); err != nil { if err := source.SaveAsset([]byte(themes.Base), "scss/base.scss"); err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
if err := source.SaveAsset([]byte(themes.Variables), utils.Directory+"/assets/scss/variables.scss"); err != nil { if err := source.SaveAsset([]byte(themes.Variables), "scss/variables.scss"); err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
if err := source.SaveAsset([]byte(themes.Mobile), utils.Directory+"/assets/scss/mobile.scss"); err != nil { if err := source.SaveAsset([]byte(themes.Mobile), "scss/mobile.scss"); err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
if err := source.CompileSASS(utils.Directory); err != nil { if err := source.CompileSASS(); err != nil {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
@ -126,8 +126,8 @@ func apiThemeCreateHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
if err := source.CompileSASS(dir); err != nil { if err := source.CompileSASS(); err != nil {
source.CopyToPublic(source.TmplBox, dir+"/assets/css", "base.css") source.CopyToPublic(source.TmplBox, "css", "base.css")
log.Errorln("Default 'base.css' was inserted because SASS did not work.") log.Errorln("Default 'base.css' was inserted because SASS did not work.")
} }
resetRouter() resetRouter()

View File

@ -1,146 +0,0 @@
package handlers
import (
"github.com/hunterlong/statping/utils"
"github.com/stretchr/testify/require"
"net/url"
"testing"
)
func TestGenericRoutes(t *testing.T) {
form := url.Values{}
form.Add("username", "admin")
form.Add("password", "password123")
form2 := url.Values{}
form2.Add("username", "admin")
form2.Add("password", "wrongpassword")
form3 := url.Values{}
form3.Add("variables", "$background-color: #fcfcfc;")
tests := []HTTPTest{
{
Name: "Statping Index",
URL: "/",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{
`<title>Statping Sample Data Status</title>`,
`<footer>`,
},
},
{
Name: "Statping Incorrect Login",
URL: "/dashboard",
Method: "POST",
Body: form.Encode(),
HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
ExpectedContains: []string{"Incorrect login information submitted, try again."},
ExpectedStatus: 200,
},
{
Name: "Dashboard Routes",
URL: "/dashboard",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Dashboard</title>"},
},
{
Name: "Services Routes",
URL: "/services",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Services</title>"},
},
{
Name: "Users Routes",
URL: "/users",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Users</title>"},
},
{
Name: "Messages Routes",
URL: "/messages",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping Messages</title>"},
},
{
Name: "Settings Routes",
URL: "/settings",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Settings</title>"},
},
{
Name: "Logs Routes",
URL: "/logs",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Logs</title>"},
},
{
Name: "Help Routes",
URL: "/help",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"<title>Statping | Help</title>"},
},
{
Name: "Logout",
URL: "/logout",
Method: "GET",
ExpectedStatus: 303,
},
{
Name: "Prometheus Metrics Routes",
URL: "/metrics",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{"statping_total_services 15"},
},
{
Name: "Last Log Line",
URL: "/logs/line",
Method: "GET",
ExpectedStatus: 200,
},
{
Name: "Export JSON file of all objcts",
URL: "/settings/export",
Method: "GET",
ExpectedStatus: 200,
},
{
Name: "Export Static Assets",
URL: "/settings/build",
Method: "GET",
ExpectedStatus: 303,
ExpectedFiles: []string{utils.Directory + "/assets/css/base.css"},
},
{
Name: "Save SCSS",
URL: "/settings/css",
Method: "POST",
Body: form3.Encode(),
ExpectedStatus: 303,
HttpHeaders: []string{"Content-Type=application/x-www-form-urlencoded"},
},
{
Name: "Delete Assets",
URL: "/settings/delete_assets",
Method: "GET",
ExpectedStatus: 303,
},
}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -1,31 +0,0 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestMessageRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Messages",
URL: "/messages",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping Messages</title>`},
},
{
Name: "Message 2",
URL: "/message/2",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping | Server Reboot</title>`},
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -30,7 +30,6 @@ var (
log = utils.Log.WithField("type", "handlers") log = utils.Log.WithField("type", "handlers")
) )
func staticAssets(src string) http.Handler { func staticAssets(src string) http.Handler {
return http.StripPrefix(basePath+src+"/", http.FileServer(http.Dir(utils.Directory+"/assets/"+src))) return http.StripPrefix(basePath+src+"/", http.FileServer(http.Dir(utils.Directory+"/assets/"+src)))
} }
@ -80,7 +79,6 @@ func Router() *mux.Router {
} }
api := r.NewRoute().Subrouter() api := r.NewRoute().Subrouter()
//api := mux.NewRouter().StrictSlash(true)
api.Use(apiMiddleware) api.Use(apiMiddleware)
// API Routes // API Routes

View File

@ -1,37 +0,0 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestServiceRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Services",
URL: "/services",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping | Services</title>`},
},
{
Name: "Services 2",
URL: "/service/2",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping Github Status</title>`},
}, {
Name: "chart.js index file",
URL: "/charts.js",
Method: "GET",
ExpectedStatus: 200,
},
}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -16,240 +16,10 @@
package handlers package handlers
import ( import (
"bytes"
"fmt"
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/core/integrations"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"io"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"strings"
"time"
) )
func settingsHandler(w http.ResponseWriter, r *http.Request) {
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, nil)
}
func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
var err error
form := parseForm(r)
app := core.CoreApp
name := form.Get("project")
if name != "" {
app.Name = name
}
description := form.Get("description")
if description != app.Description {
app.Description = description
}
style := form.Get("style")
if style != app.Style {
app.Style = style
}
footer := form.Get("footer")
if footer != app.Footer.String {
app.Footer = types.NewNullString(footer)
}
domain := form.Get("domain")
if domain != app.Domain {
app.Domain = domain
}
timezone := form.Get("timezone")
timeFloat, _ := strconv.ParseFloat(timezone, 10)
app.Timezone = float32(timeFloat)
app.UpdateNotify = types.NewNullBool(form.Get("update_notify") == "true")
app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on")
core.CoreApp, err = core.UpdateCore(app)
if err != nil {
log.Errorln(fmt.Sprintf("issue updating Core: %v", err.Error()))
}
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
}
func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
form := parseForm(r)
theme := form.Get("theme")
variables := form.Get("variables")
mobile := form.Get("mobile")
source.SaveAsset([]byte(theme), utils.Directory+"/assets/scss/base.scss")
source.SaveAsset([]byte(variables), utils.Directory+"/assets/scss/variables.scss")
source.SaveAsset([]byte(mobile), utils.Directory+"/assets/scss/mobile.scss")
source.CompileSASS(utils.Directory)
resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
}
func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
dir := utils.Directory
if err := source.CreateAllAssets(dir); err != nil {
log.Errorln(err)
sendErrorJson(err, w, r)
return
}
if err := source.CompileSASS(dir); err != nil {
source.CopyToPublic(source.TmplBox, dir+"/assets/css", "base.css")
log.Errorln("Default 'base.css' was inserted because SASS did not work.")
}
resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
}
func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
if err := source.DeleteAllAssets(utils.Directory); err != nil {
log.Errorln(fmt.Errorf("error deleting all assets %v", err))
}
resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "settings")
}
func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
var fileData bytes.Buffer
file, _, err := r.FormFile("file")
if err != nil {
log.Errorln(fmt.Errorf("error bulk import services: %v", err))
w.Write([]byte(err.Error()))
return
}
defer file.Close()
io.Copy(&fileData, file)
data := fileData.String()
for i, line := range strings.Split(strings.TrimSuffix(data, "\n"), "\n")[1:] {
col := strings.Split(line, ",")
newService, err := commaToService(col)
if err != nil {
log.Errorln(fmt.Errorf("issue with row %v: %v", i, err))
continue
}
service := core.ReturnService(newService)
_, err = service.Create(true)
if err != nil {
log.Errorln(fmt.Errorf("cannot create service %v: %v", col[0], err))
continue
}
log.Infoln(fmt.Sprintf("Created new service %v", service.Name))
}
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
}
type integratorOut struct {
Integrator *types.Integration `json:"integrator"`
Services []*types.Service `json:"services"`
Error error
}
func integratorHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
integratorName := vars["name"]
r.ParseForm()
integrator, err := integrations.Find(integratorName)
if err != nil {
log.Errorln(err)
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Error: err,
}, nil)
return
}
log.Info(r.PostForm)
for _, v := range integrator.Get().Fields {
log.Info(v.Name, v.Value)
}
integrations.SetFields(integrator, r.PostForm)
for _, v := range integrator.Get().Fields {
log.Info(v.Name, v.Value)
}
services, err := integrator.List()
if err != nil {
log.Errorln(err)
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Integrator: integrator.Get(),
Error: err,
}, nil)
return
}
ExecuteResponse(w, r, "integrator.gohtml", integratorOut{
Integrator: integrator.Get(),
Services: services,
}, nil)
}
// commaToService will convert a CSV comma delimited string slice to a Service type
// this function is used for the bulk import services feature
func commaToService(s []string) (*types.Service, error) {
if len(s) != 17 {
err := fmt.Errorf("does not have the expected amount of %v columns for a service", 16)
return nil, err
}
interval, err := time.ParseDuration(s[4])
if err != nil {
return nil, err
}
timeout, err := time.ParseDuration(s[9])
if err != nil {
return nil, err
}
allowNotifications, err := strconv.ParseBool(s[11])
if err != nil {
return nil, err
}
public, err := strconv.ParseBool(s[12])
if err != nil {
return nil, err
}
verifySsl, err := strconv.ParseBool(s[16])
if err != nil {
return nil, err
}
newService := &types.Service{
Name: s[0],
Domain: s[1],
Expected: types.NewNullString(s[2]),
ExpectedStatus: int(utils.ToInt(s[3])),
Interval: int(utils.ToInt(interval.Seconds())),
Type: s[5],
Method: s[6],
PostData: types.NewNullString(s[7]),
Port: int(utils.ToInt(s[8])),
Timeout: int(utils.ToInt(timeout.Seconds())),
AllowNotifications: types.NewNullBool(allowNotifications),
Public: types.NewNullBool(public),
GroupId: int(utils.ToInt(s[13])),
Headers: types.NewNullString(s[14]),
Permalink: types.NewNullString(s[15]),
VerifySSL: types.NewNullBool(verifySsl),
}
return newService, nil
}
func parseForm(r *http.Request) url.Values { func parseForm(r *http.Request) url.Values {
r.ParseForm() r.ParseForm()
return r.PostForm return r.PostForm

View File

@ -1,31 +0,0 @@
package handlers
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestUserRoutes(t *testing.T) {
tests := []HTTPTest{
{
Name: "Users",
URL: "/users",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping | Users</title>`},
},
{
Name: "User 2",
URL: "/user/2",
Method: "GET",
ExpectedStatus: 200,
ExpectedContains: []string{`<title>Statping | testadmin2</title>`},
}}
for _, v := range tests {
t.Run(v.Name, func(t *testing.T) {
_, t, err := RunHTTPTest(v, t)
require.Nil(t, err)
})
}
}

View File

@ -80,5 +80,5 @@ func injectDatabase() {
panic(err) panic(err)
} }
db.CreateTable(&notifier.Notification{}) db.CreateTable(&notifier.Notification{})
notifier.SetDB(db, float32(-8)) notifier.SetDB(&types.Db{db})
} }

View File

@ -23,7 +23,6 @@ import (
"github.com/GeertJohan/go.rice" "github.com/GeertJohan/go.rice"
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"github.com/russross/blackfriday/v2" "github.com/russross/blackfriday/v2"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
) )
@ -46,14 +45,14 @@ func HelpMarkdown() string {
} }
// CompileSASS will attempt to compile the SASS files into CSS // CompileSASS will attempt to compile the SASS files into CSS
func CompileSASS(folder string) error { func CompileSASS() error {
sassBin := os.Getenv("SASS") sassBin := os.Getenv("SASS")
if sassBin == "" { if sassBin == "" {
sassBin = "sass" sassBin = "sass"
} }
scssFile := fmt.Sprintf("%v/%v", folder, "assets/scss/base.scss") scssFile := fmt.Sprintf("%v/assets/%v", utils.Directory, "scss/base.scss")
baseFile := fmt.Sprintf("%v/%v", folder, "assets/css/base.css") baseFile := fmt.Sprintf("%v/assets/%v", utils.Directory, "css/base.css")
log.Infoln(fmt.Sprintf("Compiling SASS %v into %v", scssFile, baseFile)) log.Infoln(fmt.Sprintf("Compiling SASS %v into %v", scssFile, baseFile))
command := fmt.Sprintf("%v %v %v", sassBin, scssFile, baseFile) command := fmt.Sprintf("%v %v %v", sassBin, scssFile, baseFile)
@ -83,8 +82,10 @@ func UsingAssets(folder string) bool {
} else { } else {
if os.Getenv("USE_ASSETS") == "true" { if os.Getenv("USE_ASSETS") == "true" {
log.Infoln("Environment variable USE_ASSETS was found.") log.Infoln("Environment variable USE_ASSETS was found.")
CreateAllAssets(folder) if err := CreateAllAssets(folder); err != nil {
err := CompileSASS(folder) log.Warnln(err)
}
err := CompileSASS()
if err != nil { 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.") log.Warnln("Default 'base.css' was insert because SASS did not work.")
@ -97,24 +98,26 @@ func UsingAssets(folder string) bool {
} }
// SaveAsset will save an asset to the '/assets/' folder. // SaveAsset will save an asset to the '/assets/' folder.
func SaveAsset(data []byte, location string) error { func SaveAsset(data []byte, path string) error {
log.Infoln(fmt.Sprintf("Saving %v", location)) path = fmt.Sprintf("%s/assets/%s", utils.Directory, path)
err := utils.SaveFile(location, data) log.Infoln(fmt.Sprintf("Saving %v", path))
err := utils.SaveFile(path, data)
if err != nil { if err != nil {
log.Errorln(fmt.Sprintf("Failed to save %v, %v", location, err)) log.Errorln(fmt.Sprintf("Failed to save %v, %v", path, err))
return err return err
} }
return nil return nil
} }
// OpenAsset returns a file's contents as a string // OpenAsset returns a file's contents as a string
func OpenAsset(folder, file string) string { func OpenAsset(path string) string {
dat, err := ioutil.ReadFile(folder + "/assets/" + file) path = fmt.Sprintf("%s/assets/%s", utils.Directory, path)
data, err := utils.OpenFile(path)
if err != nil { if err != nil {
log.Errorln(fmt.Sprintf("Failed to open %v, %v", file, err)) log.Errorln(fmt.Sprintf("Failed to open %v, %v", path, err))
return "" return ""
} }
return string(dat) return data
} }
// CreateAllAssets will dump HTML, CSS, SCSS, and JS assets into the '/assets' directory // CreateAllAssets will dump HTML, CSS, SCSS, and JS assets into the '/assets' directory
@ -130,18 +133,19 @@ func CreateAllAssets(folder string) error {
MakePublicFolder(fp(folder, "assets", "files")) MakePublicFolder(fp(folder, "assets", "files"))
log.Infoln("Inserting scss, css, and javascript files into assets folder") log.Infoln("Inserting scss, css, and javascript files into assets folder")
if err := CopyAllToPublic(TmplBox, fp(folder, "assets")); err != nil { if err := CopyAllToPublic(TmplBox); err != nil {
log.Errorln(err) log.Errorln(err)
return err
} }
CopyToPublic(TmplBox, folder+"/assets", "robots.txt") CopyToPublic(TmplBox, "", "robots.txt")
CopyToPublic(TmplBox, folder+"/assets", "banner.png") CopyToPublic(TmplBox, "", "banner.png")
CopyToPublic(TmplBox, folder+"/assets", "favicon.ico") CopyToPublic(TmplBox, "", "favicon.ico")
CopyToPublic(TmplBox, folder+"/assets/files", "swagger.json") CopyToPublic(TmplBox, "files", "swagger.json")
CopyToPublic(TmplBox, folder+"/assets/files", "postman.json") CopyToPublic(TmplBox, "files", "postman.json")
CopyToPublic(TmplBox, folder+"/assets/files", "grafana.json") CopyToPublic(TmplBox, "files", "grafana.json")
log.Infoln("Compiling CSS from SCSS style...") log.Infoln("Compiling CSS from SCSS style...")
err := CompileSASS(utils.Directory) err := CompileSASS()
log.Infoln("Statping assets have been inserted") log.Infoln("Statping assets have been inserted")
return err return err
} }
@ -158,7 +162,7 @@ func DeleteAllAssets(folder string) error {
} }
// CopyAllToPublic will copy all the files in a rice box into a local folder // CopyAllToPublic will copy all the files in a rice box into a local folder
func CopyAllToPublic(box *rice.Box, folder string) error { func CopyAllToPublic(box *rice.Box) error {
exclude := map[string]bool{ exclude := map[string]bool{
"base.gohtml": true, "base.gohtml": true,
@ -183,24 +187,26 @@ func CopyAllToPublic(box *rice.Box, folder string) error {
if err != nil { if err != nil {
return err return err
} }
filePath := filepath.Join(folder, path) return SaveAsset(file, path)
return SaveAsset(file, filePath)
}) })
return err return err
} }
// CopyToPublic will create a file from a rice Box to the '/assets' directory // CopyToPublic will create a file from a rice Box to the '/assets' directory
func CopyToPublic(box *rice.Box, folder, file string) error { func CopyToPublic(box *rice.Box, path, file string) error {
assetFolder := fmt.Sprintf("%v/%v", folder, file) assetPath := fmt.Sprintf("%v/assets/%v/%v", utils.Directory, path, file)
log.Infoln(fmt.Sprintf("Copying %v to %v", file, assetFolder)) if path == "" {
assetPath = fmt.Sprintf("%v/assets/%v", utils.Directory, file)
}
log.Infoln(fmt.Sprintf("Copying %v to %v", file, assetPath))
base, err := box.String(file) base, err := box.String(file)
if err != nil { if err != nil {
log.Errorln(fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetFolder, err)) log.Errorln(fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetPath, err))
return err return err
} }
err = ioutil.WriteFile(assetFolder, []byte(base), 0744) err = utils.SaveFile(assetPath, []byte(base))
if err != nil { if err != nil {
log.Errorln(fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetFolder, err)) log.Errorln(fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetPath, err))
return err return err
} }
return nil return nil

View File

@ -18,6 +18,7 @@ package source
import ( import (
"github.com/hunterlong/statping/utils" "github.com/hunterlong/statping/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing" "testing"
) )
@ -42,39 +43,45 @@ func TestCreateAssets(t *testing.T) {
assert.FileExists(t, dir+"/assets/css/base.css") assert.FileExists(t, dir+"/assets/css/base.css")
assert.FileExists(t, dir+"/assets/scss/base.scss") assert.FileExists(t, dir+"/assets/scss/base.scss")
} }
func TestCopyAllToPublic(t *testing.T) {
err := CopyAllToPublic(TmplBox)
require.Nil(t, err)
}
func TestCompileSASS(t *testing.T) { func TestCompileSASS(t *testing.T) {
CompileSASS(dir) err := CompileSASS()
require.Nil(t, err)
assert.True(t, UsingAssets(dir)) assert.True(t, UsingAssets(dir))
} }
func TestSaveAsset(t *testing.T) { func TestSaveAsset(t *testing.T) {
data := []byte("BODY { color: black; }") data := []byte("BODY { color: black; }")
asset := SaveAsset(data, dir, "scss/theme.scss") err := SaveAsset(data, "scss/theme.scss")
assert.Nil(t, asset) assert.Nil(t, err)
assert.FileExists(t, dir+"/assets/scss/theme.scss") assert.FileExists(t, dir+"/assets/scss/theme.scss")
} }
func TestOpenAsset(t *testing.T) { func TestOpenAsset(t *testing.T) {
asset := OpenAsset(dir, "scss/theme.scss") asset := OpenAsset("scss/theme.scss")
assert.NotEmpty(t, asset) assert.NotEmpty(t, asset)
} }
func TestDeleteAssets(t *testing.T) { func TestDeleteAssets(t *testing.T) {
assert.True(t, UsingAssets(dir))
assert.Nil(t, DeleteAllAssets(dir)) assert.Nil(t, DeleteAllAssets(dir))
assert.False(t, UsingAssets(dir)) assert.False(t, UsingAssets(dir))
} }
func TestCopyToPluginFailed(t *testing.T) { func TestCopyToPluginFailed(t *testing.T) {
assert.Nil(t, DeleteAllAssets(dir)) //assert.Nil(t, DeleteAllAssets(dir))
assert.False(t, UsingAssets(dir)) assert.False(t, UsingAssets(dir))
} }
func ExampleSaveAsset() { func ExampleSaveAsset() {
data := []byte("alert('helloooo')") data := []byte("alert('helloooo')")
SaveAsset(data, "js", "test.js") SaveAsset(data, "js/test.js")
} }
func ExampleOpenAsset() { func ExampleOpenAsset() {
OpenAsset("js", "main.js") OpenAsset("js/main.js")
} }

View File

@ -19,7 +19,7 @@ import (
"time" "time"
) )
// SqliteFilename is the name of the SQLlite database file // SqliteFilename is the name of the SQLlite Db file
const SqliteFilename = "statping.db" const SqliteFilename = "statping.db"
// AllNotifiers contains all the Notifiers loaded // AllNotifiers contains all the Notifiers loaded

View File

@ -20,7 +20,7 @@ import (
) )
// Failure is a failed attempt to check a service. Any a service does not meet the expected requirements, // Failure is a failed attempt to check a service. Any a service does not meet the expected requirements,
// a new Failure will be inserted into database. // a new Failure will be inserted into Db.
type Failure struct { type Failure struct {
Id int64 `gorm:"primary_key;column:id" json:"id"` Id int64 `gorm:"primary_key;column:id" json:"id"`
Issue string `gorm:"column:issue" json:"issue"` Issue string `gorm:"column:issue" json:"issue"`

View File

@ -116,18 +116,18 @@ type Failurer interface {
Fails() ([]*Failure, error) Fails() ([]*Failure, error)
} }
func (it *database) Failures(id int64) Database { func (it *Db) Failures(id int64) Database {
return it.Model(&Failure{}).Where("service = ?", id).Not("method = 'checkin'").Order("id desc") return it.Model(&Failure{}).Where("service = ?", id).Not("method = 'checkin'").Order("id desc")
} }
func (it *database) Fails() ([]*Failure, error) { func (it *Db) Fails() ([]*Failure, error) {
var fails []*Failure var fails []*Failure
err := it.Find(&fails) err := it.Find(&fails)
return fails, err.Error() return fails, err.Error()
} }
type database struct { type Db struct {
w *gorm.DB Database *gorm.DB
} }
// Openw is a drop-in replacement for Open() // Openw is a drop-in replacement for Open()
@ -138,316 +138,316 @@ func Openw(dialect string, args ...interface{}) (db Database, err error) {
// Wrap wraps gorm.DB in an interface // Wrap wraps gorm.DB in an interface
func Wrap(db *gorm.DB) Database { func Wrap(db *gorm.DB) Database {
return &database{db} return &Db{db}
} }
func (it *database) Close() error { func (it *Db) Close() error {
return it.w.Close() return it.Database.Close()
} }
func (it *database) DB() *sql.DB { func (it *Db) DB() *sql.DB {
return it.w.DB() return it.Database.DB()
} }
func (it *database) New() Database { func (it *Db) New() Database {
return Wrap(it.w.New()) return Wrap(it.Database.New())
} }
func (it *database) NewScope(value interface{}) *gorm.Scope { func (it *Db) NewScope(value interface{}) *gorm.Scope {
return it.w.NewScope(value) return it.Database.NewScope(value)
} }
func (it *database) CommonDB() gorm.SQLCommon { func (it *Db) CommonDB() gorm.SQLCommon {
return it.w.CommonDB() return it.Database.CommonDB()
} }
func (it *database) Callback() *gorm.Callback { func (it *Db) Callback() *gorm.Callback {
return it.w.Callback() return it.Database.Callback()
} }
func (it *database) SetLogger(log gorm.Logger) { func (it *Db) SetLogger(log gorm.Logger) {
it.w.SetLogger(log) it.Database.SetLogger(log)
} }
func (it *database) LogMode(enable bool) Database { func (it *Db) LogMode(enable bool) Database {
return Wrap(it.w.LogMode(enable)) return Wrap(it.Database.LogMode(enable))
} }
func (it *database) SingularTable(enable bool) { func (it *Db) SingularTable(enable bool) {
it.w.SingularTable(enable) it.Database.SingularTable(enable)
} }
func (it *database) Where(query interface{}, args ...interface{}) Database { func (it *Db) Where(query interface{}, args ...interface{}) Database {
return Wrap(it.w.Where(query, args...)) return Wrap(it.Database.Where(query, args...))
} }
func (it *database) Or(query interface{}, args ...interface{}) Database { func (it *Db) Or(query interface{}, args ...interface{}) Database {
return Wrap(it.w.Or(query, args...)) return Wrap(it.Database.Or(query, args...))
} }
func (it *database) Not(query interface{}, args ...interface{}) Database { func (it *Db) Not(query interface{}, args ...interface{}) Database {
return Wrap(it.w.Not(query, args...)) return Wrap(it.Database.Not(query, args...))
} }
func (it *database) Limit(value int) Database { func (it *Db) Limit(value int) Database {
return Wrap(it.w.Limit(value)) return Wrap(it.Database.Limit(value))
} }
func (it *database) Offset(value int) Database { func (it *Db) Offset(value int) Database {
return Wrap(it.w.Offset(value)) return Wrap(it.Database.Offset(value))
} }
func (it *database) Order(value string, reorder ...bool) Database { func (it *Db) Order(value string, reorder ...bool) Database {
return Wrap(it.w.Order(value, reorder...)) return Wrap(it.Database.Order(value, reorder...))
} }
func (it *database) Select(query interface{}, args ...interface{}) Database { func (it *Db) Select(query interface{}, args ...interface{}) Database {
return Wrap(it.w.Select(query, args...)) return Wrap(it.Database.Select(query, args...))
} }
func (it *database) Omit(columns ...string) Database { func (it *Db) Omit(columns ...string) Database {
return Wrap(it.w.Omit(columns...)) return Wrap(it.Database.Omit(columns...))
} }
func (it *database) Group(query string) Database { func (it *Db) Group(query string) Database {
return Wrap(it.w.Group(query)) return Wrap(it.Database.Group(query))
} }
func (it *database) Having(query string, values ...interface{}) Database { func (it *Db) Having(query string, values ...interface{}) Database {
return Wrap(it.w.Having(query, values...)) return Wrap(it.Database.Having(query, values...))
} }
func (it *database) Joins(query string, args ...interface{}) Database { func (it *Db) Joins(query string, args ...interface{}) Database {
return Wrap(it.w.Joins(query, args...)) return Wrap(it.Database.Joins(query, args...))
} }
func (it *database) Scopes(funcs ...func(*gorm.DB) *gorm.DB) Database { func (it *Db) Scopes(funcs ...func(*gorm.DB) *gorm.DB) Database {
return Wrap(it.w.Scopes(funcs...)) return Wrap(it.Database.Scopes(funcs...))
} }
func (it *database) Unscoped() Database { func (it *Db) Unscoped() Database {
return Wrap(it.w.Unscoped()) return Wrap(it.Database.Unscoped())
} }
func (it *database) Attrs(attrs ...interface{}) Database { func (it *Db) Attrs(attrs ...interface{}) Database {
return Wrap(it.w.Attrs(attrs...)) return Wrap(it.Database.Attrs(attrs...))
} }
func (it *database) Assign(attrs ...interface{}) Database { func (it *Db) Assign(attrs ...interface{}) Database {
return Wrap(it.w.Assign(attrs...)) return Wrap(it.Database.Assign(attrs...))
} }
func (it *database) First(out interface{}, where ...interface{}) Database { func (it *Db) First(out interface{}, where ...interface{}) Database {
return Wrap(it.w.First(out, where...)) return Wrap(it.Database.First(out, where...))
} }
func (it *database) Last(out interface{}, where ...interface{}) Database { func (it *Db) Last(out interface{}, where ...interface{}) Database {
return Wrap(it.w.Last(out, where...)) return Wrap(it.Database.Last(out, where...))
} }
func (it *database) Find(out interface{}, where ...interface{}) Database { func (it *Db) Find(out interface{}, where ...interface{}) Database {
return Wrap(it.w.Find(out, where...)) return Wrap(it.Database.Find(out, where...))
} }
func (it *database) Scan(dest interface{}) Database { func (it *Db) Scan(dest interface{}) Database {
return Wrap(it.w.Scan(dest)) return Wrap(it.Database.Scan(dest))
} }
func (it *database) Row() *sql.Row { func (it *Db) Row() *sql.Row {
return it.w.Row() return it.Database.Row()
} }
func (it *database) Rows() (*sql.Rows, error) { func (it *Db) Rows() (*sql.Rows, error) {
return it.w.Rows() return it.Database.Rows()
} }
func (it *database) ScanRows(rows *sql.Rows, result interface{}) error { func (it *Db) ScanRows(rows *sql.Rows, result interface{}) error {
return it.w.ScanRows(rows, result) return it.Database.ScanRows(rows, result)
} }
func (it *database) Pluck(column string, value interface{}) Database { func (it *Db) Pluck(column string, value interface{}) Database {
return Wrap(it.w.Pluck(column, value)) return Wrap(it.Database.Pluck(column, value))
} }
func (it *database) Count(value interface{}) Database { func (it *Db) Count(value interface{}) Database {
return Wrap(it.w.Count(value)) return Wrap(it.Database.Count(value))
} }
func (it *database) Related(value interface{}, foreignKeys ...string) Database { func (it *Db) Related(value interface{}, foreignKeys ...string) Database {
return Wrap(it.w.Related(value, foreignKeys...)) return Wrap(it.Database.Related(value, foreignKeys...))
} }
func (it *database) FirstOrInit(out interface{}, where ...interface{}) Database { func (it *Db) FirstOrInit(out interface{}, where ...interface{}) Database {
return Wrap(it.w.FirstOrInit(out, where...)) return Wrap(it.Database.FirstOrInit(out, where...))
} }
func (it *database) FirstOrCreate(out interface{}, where ...interface{}) Database { func (it *Db) FirstOrCreate(out interface{}, where ...interface{}) Database {
return Wrap(it.w.FirstOrCreate(out, where...)) return Wrap(it.Database.FirstOrCreate(out, where...))
} }
func (it *database) Update(attrs ...interface{}) Database { func (it *Db) Update(attrs ...interface{}) Database {
return Wrap(it.w.Update(attrs...)) return Wrap(it.Database.Update(attrs...))
} }
func (it *database) Updates(values interface{}, ignoreProtectedAttrs ...bool) Database { func (it *Db) Updates(values interface{}, ignoreProtectedAttrs ...bool) Database {
return Wrap(it.w.Updates(values, ignoreProtectedAttrs...)) return Wrap(it.Database.Updates(values, ignoreProtectedAttrs...))
} }
func (it *database) UpdateColumn(attrs ...interface{}) Database { func (it *Db) UpdateColumn(attrs ...interface{}) Database {
return Wrap(it.w.UpdateColumn(attrs...)) return Wrap(it.Database.UpdateColumn(attrs...))
} }
func (it *database) UpdateColumns(values interface{}) Database { func (it *Db) UpdateColumns(values interface{}) Database {
return Wrap(it.w.UpdateColumns(values)) return Wrap(it.Database.UpdateColumns(values))
} }
func (it *database) Save(value interface{}) Database { func (it *Db) Save(value interface{}) Database {
return Wrap(it.w.Save(value)) return Wrap(it.Database.Save(value))
} }
func (it *database) Create(value interface{}) Database { func (it *Db) Create(value interface{}) Database {
return Wrap(it.w.Create(value)) return Wrap(it.Database.Create(value))
} }
func (it *database) Delete(value interface{}, where ...interface{}) Database { func (it *Db) Delete(value interface{}, where ...interface{}) Database {
return Wrap(it.w.Delete(value, where...)) return Wrap(it.Database.Delete(value, where...))
} }
func (it *database) Raw(sql string, values ...interface{}) Database { func (it *Db) Raw(sql string, values ...interface{}) Database {
return Wrap(it.w.Raw(sql, values...)) return Wrap(it.Database.Raw(sql, values...))
} }
func (it *database) Exec(sql string, values ...interface{}) Database { func (it *Db) Exec(sql string, values ...interface{}) Database {
return Wrap(it.w.Exec(sql, values...)) return Wrap(it.Database.Exec(sql, values...))
} }
func (it *database) Model(value interface{}) Database { func (it *Db) Model(value interface{}) Database {
return Wrap(it.w.Model(value)) return Wrap(it.Database.Model(value))
} }
func (it *database) Table(name string) Database { func (it *Db) Table(name string) Database {
return Wrap(it.w.Table(name)) return Wrap(it.Database.Table(name))
} }
func (it *database) Debug() Database { func (it *Db) Debug() Database {
return Wrap(it.w.Debug()) return Wrap(it.Database.Debug())
} }
func (it *database) Begin() Database { func (it *Db) Begin() Database {
return Wrap(it.w.Begin()) return Wrap(it.Database.Begin())
} }
func (it *database) Commit() Database { func (it *Db) Commit() Database {
return Wrap(it.w.Commit()) return Wrap(it.Database.Commit())
} }
func (it *database) Rollback() Database { func (it *Db) Rollback() Database {
return Wrap(it.w.Rollback()) return Wrap(it.Database.Rollback())
} }
func (it *database) NewRecord(value interface{}) bool { func (it *Db) NewRecord(value interface{}) bool {
return it.w.NewRecord(value) return it.Database.NewRecord(value)
} }
func (it *database) RecordNotFound() bool { func (it *Db) RecordNotFound() bool {
return it.w.RecordNotFound() return it.Database.RecordNotFound()
} }
func (it *database) CreateTable(values ...interface{}) Database { func (it *Db) CreateTable(values ...interface{}) Database {
return Wrap(it.w.CreateTable(values...)) return Wrap(it.Database.CreateTable(values...))
} }
func (it *database) DropTable(values ...interface{}) Database { func (it *Db) DropTable(values ...interface{}) Database {
return Wrap(it.w.DropTable(values...)) return Wrap(it.Database.DropTable(values...))
} }
func (it *database) DropTableIfExists(values ...interface{}) Database { func (it *Db) DropTableIfExists(values ...interface{}) Database {
return Wrap(it.w.DropTableIfExists(values...)) return Wrap(it.Database.DropTableIfExists(values...))
} }
func (it *database) HasTable(value interface{}) bool { func (it *Db) HasTable(value interface{}) bool {
return it.w.HasTable(value) return it.Database.HasTable(value)
} }
func (it *database) AutoMigrate(values ...interface{}) Database { func (it *Db) AutoMigrate(values ...interface{}) Database {
return Wrap(it.w.AutoMigrate(values...)) return Wrap(it.Database.AutoMigrate(values...))
} }
func (it *database) ModifyColumn(column string, typ string) Database { func (it *Db) ModifyColumn(column string, typ string) Database {
return Wrap(it.w.ModifyColumn(column, typ)) return Wrap(it.Database.ModifyColumn(column, typ))
} }
func (it *database) DropColumn(column string) Database { func (it *Db) DropColumn(column string) Database {
return Wrap(it.w.DropColumn(column)) return Wrap(it.Database.DropColumn(column))
} }
func (it *database) AddIndex(indexName string, columns ...string) Database { func (it *Db) AddIndex(indexName string, columns ...string) Database {
return Wrap(it.w.AddIndex(indexName, columns...)) return Wrap(it.Database.AddIndex(indexName, columns...))
} }
func (it *database) AddUniqueIndex(indexName string, columns ...string) Database { func (it *Db) AddUniqueIndex(indexName string, columns ...string) Database {
return Wrap(it.w.AddUniqueIndex(indexName, columns...)) return Wrap(it.Database.AddUniqueIndex(indexName, columns...))
} }
func (it *database) RemoveIndex(indexName string) Database { func (it *Db) RemoveIndex(indexName string) Database {
return Wrap(it.w.RemoveIndex(indexName)) return Wrap(it.Database.RemoveIndex(indexName))
} }
func (it *database) Association(column string) *gorm.Association { func (it *Db) Association(column string) *gorm.Association {
return it.w.Association(column) return it.Database.Association(column)
} }
func (it *database) Preload(column string, conditions ...interface{}) Database { func (it *Db) Preload(column string, conditions ...interface{}) Database {
return Wrap(it.w.Preload(column, conditions...)) return Wrap(it.Database.Preload(column, conditions...))
} }
func (it *database) Set(name string, value interface{}) Database { func (it *Db) Set(name string, value interface{}) Database {
return Wrap(it.w.Set(name, value)) return Wrap(it.Database.Set(name, value))
} }
func (it *database) InstantSet(name string, value interface{}) Database { func (it *Db) InstantSet(name string, value interface{}) Database {
return Wrap(it.w.InstantSet(name, value)) return Wrap(it.Database.InstantSet(name, value))
} }
func (it *database) Get(name string) (interface{}, bool) { func (it *Db) Get(name string) (interface{}, bool) {
return it.w.Get(name) return it.Database.Get(name)
} }
func (it *database) SetJoinTableHandler(source interface{}, column string, handler gorm.JoinTableHandlerInterface) { func (it *Db) SetJoinTableHandler(source interface{}, column string, handler gorm.JoinTableHandlerInterface) {
it.w.SetJoinTableHandler(source, column, handler) it.Database.SetJoinTableHandler(source, column, handler)
} }
func (it *database) AddForeignKey(field string, dest string, onDelete string, onUpdate string) Database { func (it *Db) AddForeignKey(field string, dest string, onDelete string, onUpdate string) Database {
return Wrap(it.w.AddForeignKey(field, dest, onDelete, onUpdate)) return Wrap(it.Database.AddForeignKey(field, dest, onDelete, onUpdate))
} }
func (it *database) AddError(err error) error { func (it *Db) AddError(err error) error {
return it.w.AddError(err) return it.Database.AddError(err)
} }
func (it *database) GetErrors() (errors []error) { func (it *Db) GetErrors() (errors []error) {
return it.w.GetErrors() return it.Database.GetErrors()
} }
func (it *database) RowsAffected() int64 { func (it *Db) RowsAffected() int64 {
return it.w.RowsAffected return it.Database.RowsAffected
} }
func (it *database) Error() error { func (it *Db) Error() error {
return it.w.Error return it.Database.Error
} }
func (it *database) Hits() ([]*Hit, error) { func (it *Db) Hits() ([]*Hit, error) {
var hits []*Hit var hits []*Hit
err := it.Find(&hits) err := it.Find(&hits)
return hits, err.Error() return hits, err.Error()
} }
func (it *database) Since(ago time.Time) Database { func (it *Db) Since(ago time.Time) Database {
return it.Where("created_at > ?", ago.UTC().Format(TIME)) return it.Where("created_at > ?", ago.UTC().Format(TIME))
} }
func (it *database) Between(t1 time.Time, t2 time.Time) Database { func (it *Db) Between(t1 time.Time, t2 time.Time) Database {
return it.Where("created_at BETWEEN ? AND ?", t1.UTC().Format(TIME), t2.UTC().Format(TIME)) return it.Where("created_at BETWEEN ? AND ?", t1.UTC().Format(TIME), t2.UTC().Format(TIME))
} }
@ -457,8 +457,8 @@ type DateScan struct {
Value int64 `json:"y"` Value int64 `json:"y"`
} }
func (it *database) ToChart() ([]*DateScan, error) { func (it *Db) ToChart() ([]*DateScan, error) {
rows, err := it.w.Rows() rows, err := it.Database.Rows()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -481,11 +481,11 @@ func (it *database) ToChart() ([]*DateScan, error) {
return data, err return data, err
} }
func (it *database) QuerySearch(r *http.Request) Database { func (it *Db) QuerySearch(r *http.Request) Database {
if r == nil { if r == nil {
return it return it
} }
db := it.w db := it.Database
start := defaultField(r, "start") start := defaultField(r, "start")
end := defaultField(r, "end") end := defaultField(r, "end")
limit := defaultField(r, "limit") limit := defaultField(r, "limit")

View File

@ -61,6 +61,16 @@ type Service struct {
LastOnline time.Time `gorm:"-" json:"last_success"` LastOnline time.Time `gorm:"-" json:"last_success"`
Failures []FailureInterface `gorm:"-" json:"failures,omitempty" scope:"user,admin"` Failures []FailureInterface `gorm:"-" json:"failures,omitempty" scope:"user,admin"`
Checkins []CheckinInterface `gorm:"-" json:"checkins,omitempty" scope:"user,admin"` Checkins []CheckinInterface `gorm:"-" json:"checkins,omitempty" scope:"user,admin"`
Stats Stater `gorm:"-" json:"stats,omitempty"`
}
type Stater interface {
Fetch() *Stats
}
type Stats struct {
Failures uint64 `gorm:"-" json:"failures,omitempty"`
Hits uint64 `gorm:"-" json:"hits,omitempty"`
} }
// BeforeCreate for Service will set CreatedAt to UTC // BeforeCreate for Service will set CreatedAt to UTC

View File

@ -27,7 +27,7 @@ const (
) )
var ( var (
NOW = func() time.Time { return time.Now() }() NOW = func() time.Time { return time.Now().UTC() }()
//HOUR_1_AGO = time.Now().Add(-1 * time.Hour) //HOUR_1_AGO = time.Now().Add(-1 * time.Hour)
//HOUR_24_AGO = time.Now().Add(-24 * time.Hour) //HOUR_24_AGO = time.Now().Add(-24 * time.Hour)
//HOUR_72_AGO = time.Now().Add(-72 * time.Hour) //HOUR_72_AGO = time.Now().Add(-72 * time.Hour)

View File

@ -36,13 +36,13 @@ func (h *Hit) BeforeCreate() (err error) {
return return
} }
// DbConfig struct is used for the database connection and creates the 'config.yml' file // DbConfig struct is used for the Db connection and creates the 'config.yml' file
type DbConfig struct { type DbConfig struct {
DbConn string `yaml:"connection" json:"connection"` DbConn string `yaml:"connection" json:"connection"`
DbHost string `yaml:"host" json:"-"` DbHost string `yaml:"host" json:"-"`
DbUser string `yaml:"user" json:"-"` DbUser string `yaml:"user" json:"-"`
DbPass string `yaml:"password" json:"-"` DbPass string `yaml:"password" json:"-"`
DbData string `yaml:"database" json:"-"` DbData string `yaml:"Db" json:"-"`
DbPort int64 `yaml:"port" json:"-"` DbPort int64 `yaml:"port" json:"-"`
ApiKey string `yaml:"api_key" json:"-"` ApiKey string `yaml:"api_key" json:"-"`
ApiSecret string `yaml:"api_secret" json:"-"` ApiSecret string `yaml:"api_secret" json:"-"`

View File

@ -33,7 +33,7 @@ type User struct {
UserInterface `gorm:"-" json:"-"` UserInterface `gorm:"-" json:"-"`
} }
// UserInterface interfaces the database functions // UserInterface interfaces the Db functions
type UserInterface interface { type UserInterface interface {
Create() (int64, error) Create() (int64, error)
Update() error Update() error

View File

@ -335,7 +335,7 @@ func DurationReadable(d time.Duration) string {
// SaveFile will create a new file with data inside it // SaveFile will create a new file with data inside it
// SaveFile("newfile.json", []byte('{"data": "success"}') // SaveFile("newfile.json", []byte('{"data": "success"}')
func SaveFile(filename string, data []byte) error { func SaveFile(filename string, data []byte) error {
err := ioutil.WriteFile(filename, data, 0655) err := ioutil.WriteFile(filename, data, os.ModePerm)
return err return err
} }