diff --git a/core/checker.go b/core/checker.go index 604be8b7..41be5cb6 100644 --- a/core/checker.go +++ b/core/checker.go @@ -19,6 +19,7 @@ import ( "bytes" "fmt" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "github.com/tatsushid/go-fastping" @@ -32,30 +33,31 @@ import ( // checkServices will start the checking go routine for each service func checkServices() { - log.Infoln(fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services))) - for _, ser := range CoreApp.Services { - //go obj.StartCheckins() - go ser.CheckQueue(true) + log.Infoln(fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.services))) + for _, ser := range CoreApp.services { + //go CheckinRoutine() + go ServiceCheckQueue(ser, true) } } // Check will run checkHttp for HTTP services and checkTcp for TCP services // if record param is set to true, it will add a record into the database. -func (s *Service) Check(record bool) { - switch s.Type { +func CheckService(srv database.Servicer, record bool) { + switch srv.Model().Type { case "http": - s.CheckHttp(record) + CheckHttp(srv, record) case "tcp", "udp": - s.CheckTcp(record) + CheckTcp(srv, record) case "icmp": - s.CheckIcmp(record) + CheckIcmp(srv, record) } } // CheckQueue is the main go routine for checking a service -func (s *Service) CheckQueue(record bool) { +func ServiceCheckQueue(srv database.Servicer, record bool) { + s := srv.Model() s.Checkpoint = time.Now() - s.SleepDuration = time.Duration((time.Duration(s.Id) * 100) * time.Millisecond) + s.SleepDuration = (time.Duration(s.Id) * 100) * time.Millisecond CheckLoop: for { select { @@ -63,11 +65,11 @@ CheckLoop: log.Infoln(fmt.Sprintf("Stopping service: %v", s.Name)) break CheckLoop case <-time.After(s.SleepDuration): - s.Check(record) - s.Checkpoint = s.Checkpoint.Add(s.duration()) + CheckService(srv, record) + s.Checkpoint = s.Checkpoint.Add(srv.Interval()) sleep := s.Checkpoint.Sub(time.Now()) if !s.Online { - s.SleepDuration = s.duration() + s.SleepDuration = srv.Interval() } else { s.SleepDuration = sleep } @@ -77,17 +79,18 @@ CheckLoop: } // duration returns the amount of duration for a service to check its status -func (s *Service) duration() time.Duration { +func duration(s database.Servicer) time.Duration { var amount time.Duration - if s.Interval >= 10000 { - amount = time.Duration(s.Interval) * time.Microsecond + if s.Interval() >= 10000 { + amount = s.Interval() * time.Microsecond } else { - amount = time.Duration(s.Interval) * time.Second + amount = s.Interval() * time.Second } return amount } -func (s *Service) parseHost() string { +func parseHost(srv database.Servicer) string { + s := srv.Model() if s.Type == "tcp" || s.Type == "udp" { return s.Domain } else { @@ -100,10 +103,11 @@ func (s *Service) parseHost() string { } // dnsCheck will check the domain name and return a float64 for the amount of time the DNS check took -func (s *Service) dnsCheck() (float64, error) { +func dnsCheck(srv database.Servicer) (float64, error) { + s := srv.Model() var err error t1 := time.Now() - host := s.parseHost() + host := parseHost(srv) if s.Type == "tcp" { _, err = net.LookupHost(host) } else { @@ -122,7 +126,8 @@ func isIPv6(address string) bool { } // checkIcmp will send a ICMP ping packet to the service -func (s *Service) CheckIcmp(record bool) *Service { +func CheckIcmp(srv database.Servicer, record bool) *types.Service { + s := srv.Model() p := fastping.NewPinger() resolveIP := "ip4:icmp" if isIPv6(s.Domain) { @@ -130,7 +135,7 @@ func (s *Service) CheckIcmp(record bool) *Service { } ra, err := net.ResolveIPAddr(resolveIP, s.Domain) if err != nil { - recordFailure(s, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err)) + recordFailure(srv, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err)) return s } p.AddIPAddr(ra) @@ -140,7 +145,7 @@ func (s *Service) CheckIcmp(record bool) *Service { } err = p.Run() if err != nil { - recordFailure(s, fmt.Sprintf("Issue running ICMP to service %v, %v", s.Domain, err)) + recordFailure(srv, fmt.Sprintf("Issue running ICMP to service %v, %v", s.Domain, err)) return s } s.LastResponse = "" @@ -148,11 +153,12 @@ func (s *Service) CheckIcmp(record bool) *Service { } // checkTcp will check a TCP service -func (s *Service) CheckTcp(record bool) *Service { - dnsLookup, err := s.dnsCheck() +func CheckTcp(srv database.Servicer, record bool) *types.Service { + s := srv.Model() + dnsLookup, err := dnsCheck(srv) if err != nil { if record { - recordFailure(s, fmt.Sprintf("Could not get IP address for TCP service %v, %v", s.Domain, err)) + recordFailure(srv, fmt.Sprintf("Could not get IP address for TCP service %v, %v", s.Domain, err)) } return s } @@ -168,13 +174,13 @@ func (s *Service) CheckTcp(record bool) *Service { conn, err := net.DialTimeout(s.Type, domain, time.Duration(s.Timeout)*time.Second) if err != nil { if record { - recordFailure(s, fmt.Sprintf("Dial Error %v", err)) + recordFailure(srv, fmt.Sprintf("Dial Error %v", err)) } return s } if err := conn.Close(); err != nil { if record { - recordFailure(s, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err)) + recordFailure(srv, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err)) } return s } @@ -188,11 +194,12 @@ func (s *Service) CheckTcp(record bool) *Service { } // checkHttp will check a HTTP service -func (s *Service) CheckHttp(record bool) *Service { - dnsLookup, err := s.dnsCheck() +func CheckHttp(srv database.Servicer, record bool) *types.Service { + s := srv.Model() + dnsLookup, err := dnsCheck(srv) if err != nil { if record { - recordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err)) + recordFailure(srv, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err)) } return s } @@ -217,7 +224,7 @@ func (s *Service) CheckHttp(record bool) *Service { } if err != nil { if record { - recordFailure(s, fmt.Sprintf("HTTP Error %v", err)) + recordFailure(srv, fmt.Sprintf("HTTP Error %v", err)) } return s } @@ -233,14 +240,14 @@ func (s *Service) CheckHttp(record bool) *Service { } if !match { if record { - recordFailure(s, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected)) + recordFailure(srv, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected)) } return s } } if s.ExpectedStatus != res.StatusCode { if record { - recordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, s.ExpectedStatus)) + recordFailure(srv, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, s.ExpectedStatus)) } return s } @@ -251,7 +258,7 @@ func (s *Service) CheckHttp(record bool) *Service { } // recordSuccess will create a new 'hit' record in the database for a successful/online service -func recordSuccess(s *Service) { +func recordSuccess(s *types.Service) { s.LastOnline = time.Now().UTC() hit := &types.Hit{ Service: s.Id, @@ -259,15 +266,16 @@ func recordSuccess(s *Service) { PingTime: s.PingTime, CreatedAt: time.Now().UTC(), } - s.CreateHit(hit) - log.WithFields(utils.ToFields(hit, s.Select())).Infoln(fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000)) - notifier.OnSuccess(s.Service) + database.Create(hit) + log.WithFields(utils.ToFields(hit, s)).Infoln(fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000)) + notifier.OnSuccess(s) s.Online = true s.SuccessNotified = true } // recordFailure will create a new 'Failure' record in the database for a offline service -func recordFailure(s *Service, issue string) { +func recordFailure(srv database.Servicer, issue string) { + s := srv.Model() fail := &types.Failure{ Service: s.Id, Issue: issue, @@ -275,11 +283,11 @@ func recordFailure(s *Service, issue string) { CreatedAt: time.Now().UTC(), ErrorCode: s.LastStatusCode, } - log.WithFields(utils.ToFields(fail, s.Select())). + log.WithFields(utils.ToFields(fail, s)). Warnln(fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000)) - s.CreateFailure(fail) + database.Create(fail) s.Online = false s.SuccessNotified = false - s.DownText = s.DowntimeText() - notifier.OnFailure(s.Service, fail) + s.DownText = srv.DowntimeText() + notifier.OnFailure(s, fail) } diff --git a/core/checkin.go b/core/checkin.go index 0ba060a8..fe388fa7 100644 --- a/core/checkin.go +++ b/core/checkin.go @@ -18,13 +18,14 @@ package core import ( "fmt" "github.com/ararog/timeago" + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "time" ) type Checkin struct { - *types.Checkin + *database.CheckinObj } type CheckinHit struct { @@ -37,8 +38,9 @@ func (c *Checkin) Select() *types.Checkin { } // Routine for checking if the last Checkin was within its interval -func (c *Checkin) Routine() { - if c.Last() == nil { +func CheckinRoutine(checkin database.Checkiner) { + c := checkin.Object() + if c.Hits().Last() == nil { return } reCheck := c.Period() @@ -52,9 +54,10 @@ CheckinLoop: case <-time.After(reCheck): log.Infoln(fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period()))) if c.Expected().Seconds() <= 0 { - issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Last().CreatedAt) + issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Hits().Last().CreatedAt) log.Errorln(issue) - c.CreateFailure() + + CreateCheckinFailure(c) } reCheck = c.Period() } @@ -67,98 +70,44 @@ func (c *Checkin) String() string { return c.ApiKey } -// ReturnCheckin converts *types.Checking to *core.Checkin -func ReturnCheckin(c *types.Checkin) *Checkin { - return &Checkin{Checkin: c} -} - -// ReturnCheckinHit converts *types.checkinHit to *core.checkinHit -func ReturnCheckinHit(c *types.CheckinHit) *CheckinHit { - return &CheckinHit{CheckinHit: c} -} - -func (c *Checkin) Service() *Service { - return SelectService(c.ServiceId) -} - -func (c *Checkin) CreateFailure() (int64, error) { +func CreateCheckinFailure(checkin database.Checkiner) (int64, error) { + c := checkin.Object() service := c.Service() c.Failing = true - fail := &Failure{&types.Failure{ + fail := &types.Failure{ Issue: fmt.Sprintf("Checkin %v was not reported %v ago, it expects a request every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())), Method: "checkin", MethodId: c.Id, Service: service.Id, Checkin: c.Id, PingTime: c.Expected().Seconds(), - }} - row := Database(&Failure{}).Create(&fail) - //sort.Sort(types.FailSort(c.Failures)) - //c.Failures = append(c.Failures, fail) - if len(c.Failures) > limitedFailures { - c.Failures = c.Failures[1:] } - return fail.Id, row.Error() -} - -// LimitedHits will return the last amount of successful hits from a checkin -func (c *Checkin) LimitedHits(amount int) []*types.CheckinHit { - var hits []*types.CheckinHit - Database(&CheckinHit{}).Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits) - return hits + _, err := database.Create(fail) + if err != nil { + return 0, err + } + //sort.Sort(types.FailSort(c.Failures())) + return fail.Id, err } // AllCheckins returns all checkin in system -func AllCheckins() []*Checkin { - var checkins []*Checkin - Database(&types.Checkin{}).Find(&checkins) +func AllCheckins() []*database.CheckinObj { + checkins := database.AllCheckins() return checkins } // SelectCheckin will find a Checkin based on the API supplied func SelectCheckin(api string) *Checkin { for _, s := range Services() { - for _, c := range s.Select().Checkins { - if c.Select().ApiKey == api { - return c.(*Checkin) + for _, c := range s.AllCheckins() { + if c.ApiKey == api { + return &Checkin{c} } } } return nil } -// Period will return the duration of the Checkin interval -func (c *Checkin) Period() time.Duration { - duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.Interval)) - return duration -} - -// Grace will return the duration of the Checkin Grace Period (after service hasn't responded, wait a bit for a response) -func (c *Checkin) Grace() time.Duration { - duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.GracePeriod)) - return duration -} - -// Expected returns the duration of when the serviec should receive a Checkin -func (c *Checkin) Expected() time.Duration { - last := c.Last().CreatedAt - now := utils.Now() - lastDir := now.Sub(last) - sub := time.Duration(c.Period() - lastDir) - return sub -} - -// Last returns the last checkinHit for a Checkin -func (c *Checkin) Last() *CheckinHit { - var hit CheckinHit - Database(c).Where("checkin = ?", c.Id).Last(&hit) - return &hit -} - -func (c *Checkin) Link() string { - return fmt.Sprintf("%v/checkin/%v", CoreApp.Domain, c.ApiKey) -} - // AllHits returns all of the CheckinHits for a given Checkin func (c *Checkin) AllHits() []*types.CheckinHit { var checkins []*types.CheckinHit @@ -204,7 +153,7 @@ func (c *Checkin) Delete() error { // index returns a checkin index int for updating the *checkin.Service slice func (c *Checkin) index() int { for k, checkin := range c.Service().Checkins { - if c.Id == checkin.Select().Id { + if c.Id == checkin.Model().Id { return k } } @@ -214,16 +163,16 @@ func (c *Checkin) index() int { // Create will create a new Checkin func (c *Checkin) Create() (int64, error) { c.ApiKey = utils.RandomString(7) - row := Database(c).Create(&c) - if row.Error() != nil { - log.Warnln(row.Error()) - return 0, row.Error() + _, err := database.Create(c) + if err != nil { + log.Warnln(err) + return 0, err } service := SelectService(c.ServiceId) service.Checkins = append(service.Checkins, c) c.Start() - go c.Routine() - return c.Id, row.Error() + go CheckinRoutine(c) + return c.Id, err } // Update will update a Checkin diff --git a/core/configs.go b/core/configs.go index 974d7fc9..5cdb20e8 100644 --- a/core/configs.go +++ b/core/configs.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "github.com/go-yaml/yaml" + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "io/ioutil" @@ -71,7 +72,9 @@ func LoadUsingEnv() (*types.DbConfig, error) { log.Errorln(err) return nil, err } - CoreApp.SaveConfig(Configs) + if _, err := CoreApp.SaveConfig(Configs); err != nil { + return nil, err + } exists := DbSession.HasTable("core") if !exists { log.Infoln(fmt.Sprintf("Core database does not exist, creating now!")) @@ -91,15 +94,20 @@ func LoadUsingEnv() (*types.DbConfig, error) { password = "admin" } - admin := ReturnUser(&types.User{ + admin := &types.User{ Username: username, Password: password, Email: "info@admin.com", Admin: types.NewNullBool(true), - }) - _, err := admin.Create() + } + if _, err := database.Create(admin); err != nil { + return nil, err + } + + if err := SampleData(); err != nil { + return nil, err + } - SampleData() return Configs, err } return Configs, nil diff --git a/core/core.go b/core/core.go index 00445db4..b36d3020 100644 --- a/core/core.go +++ b/core/core.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/hunterlong/statping/core/integrations" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/notifiers" "github.com/hunterlong/statping/source" "github.com/hunterlong/statping/types" @@ -34,6 +35,7 @@ type PluginRepos types.PluginRepos type Core struct { *types.Core + services []database.Servicer } var ( @@ -48,7 +50,7 @@ func init() { // NewCore return a new *core.Core struct func NewCore() *Core { - CoreApp = &Core{&types.Core{ + CoreApp = &Core{Core: &types.Core{ Started: time.Now().UTC(), }, } @@ -65,7 +67,7 @@ func InitApp() { SelectCore() InsertNotifierDB() InsertIntegratorDB() - CoreApp.SelectAllServices(true) + SelectAllServices(true) checkServices() AttachNotifiers() AddIntegrations() @@ -150,16 +152,6 @@ func (c Core) MobileSASS() string { return source.OpenAsset("scss/mobile.scss") } -// AllOnline will be true if all services are online -func (c Core) AllOnline() bool { - for _, s := range CoreApp.Services { - if !s.Select().Online { - return false - } - } - return true -} - // SelectCore will return the CoreApp global variable and the settings/configs for Statping func SelectCore() (*Core, error) { if DbSession == nil { @@ -222,9 +214,9 @@ func AddIntegrations() error { } // ServiceOrder will reorder the services based on 'order_id' (Order) -type ServiceOrder []types.ServiceInterface +type ServiceOrder []database.Servicer // Sort interface for resroting the Services in order func (c ServiceOrder) Len() int { return len(c) } func (c ServiceOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] } -func (c ServiceOrder) Less(i, j int) bool { return c[i].(*Service).Order < c[j].(*Service).Order } +func (c ServiceOrder) Less(i, j int) bool { return c[i].Model().Order < c[j].Model().Order } diff --git a/core/database.go b/core/database.go index d28050da..f642104e 100644 --- a/core/database.go +++ b/core/database.go @@ -77,26 +77,6 @@ func Database(obj interface{}) database.Database { } } -// 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) database.Database { - selector := Dbtimestamp(group, column) - if CoreApp.Config.DbConn == "postgres" { - 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 { - 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)) - } -} - -// FailuresBetween returns the gorm database query for a collection of service hits between a time range -func (s *Service) FailuresBetween(t1, t2 time.Time, group string, column string) database.Database { - selector := Dbtimestamp(group, column) - if CoreApp.Config.DbConn == "postgres" { - return Database(&Failure{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME)) - } else { - return Database(&Failure{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY)) - } -} - // CloseDB will close the database connection if available func CloseDB() { if DbSession != nil { diff --git a/core/export.go b/core/export.go index 3339ce81..6ea61416 100644 --- a/core/export.go +++ b/core/export.go @@ -16,20 +16,19 @@ package core import ( - "encoding/json" "github.com/hunterlong/statping/types" ) // ExportChartsJs renders the charts for the index page type ExportData struct { - Core *types.Core `json:"core"` - Services []types.ServiceInterface `json:"services"` - Messages []*Message `json:"messages"` - Checkins []*Checkin `json:"checkins"` - Users []*User `json:"users"` - Groups []*Group `json:"groups"` - Notifiers []types.AllNotifiers `json:"notifiers"` + Core *types.Core `json:"core"` + Services []*types.Service `json:"services"` + Messages []*types.Message `json:"messages"` + Checkins []*types.Checkin `json:"checkins"` + Users []*types.User `json:"users"` + Groups []*types.Group `json:"groups"` + Notifiers []types.AllNotifiers `json:"notifiers"` } // ExportSettings will export a JSON file containing all of the settings below: @@ -40,21 +39,21 @@ type ExportData struct { // - Services // - Groups // - Messages -func ExportSettings() ([]byte, error) { - users, err := SelectAllUsers() - messages, err := SelectMessages() - if err != nil { - return nil, err - } - data := ExportData{ - Core: CoreApp.Core, - Notifiers: CoreApp.Notifications, - Checkins: AllCheckins(), - Users: users, - Services: CoreApp.Services, - Groups: SelectGroups(true, true), - Messages: messages, - } - export, err := json.Marshal(data) - return export, err -} +//func ExportSettings() ([]byte, error) { +// users, err := SelectAllUsers() +// messages, err := SelectMessages() +// if err != nil { +// return nil, err +// } +// data := ExportData{ +// Core: CoreApp.Core, +// Notifiers: CoreApp.Notifications, +// Checkins: database.AllCheckins(), +// Users: database.AllUsers(), +// Services: CoreApp.Services, +// Groups: SelectGroups(true, true), +// Messages: messages, +// } +// export, err := json.Marshal(data) +// return export, err +//} diff --git a/core/failures.go b/core/failures.go index 1ebc4dd9..35fbac83 100644 --- a/core/failures.go +++ b/core/failures.go @@ -16,192 +16,18 @@ package core import ( - "fmt" - "github.com/ararog/timeago" "github.com/hunterlong/statping/types" - "sort" - "strings" - "time" ) -type Failure struct { - *types.Failure -} +type Failure struct{} const ( limitedFailures = 32 limitedHits = 32 ) -// CreateFailure will create a new Failure record for a service -func (s *Service) CreateFailure(f *types.Failure) (int64, error) { - f.Service = s.Id - row := Database(&types.Failure{}).Create(f) - if row.Error() != nil { - log.Errorln(row.Error()) - return 0, row.Error() - } - sort.Sort(types.FailSort(s.Failures)) - //s.Failures = append(s.Failures, f) - if len(s.Failures) > limitedFailures { - s.Failures = s.Failures[1:] - } - return f.Id, row.Error() -} - -// AllFailures will return all failures attached to a service -func (s *Service) AllFailures() []types.Failure { - var fails []types.Failure - err := Database(&types.Failure{}).Find(&fails) - if err.Error() != nil { - log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err)) - return nil - } - return fails -} - -// DeleteFailures will delete all failures for a service -func (s *Service) DeleteFailures() { - err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id) - if err.Error() != nil { - log.Errorln(fmt.Sprintf("failed to delete all failures: %v", err)) - } - s.Failures = nil -} - -// LimitedFailures will return the last amount of failures from a service -func (s *Service) LimitedCheckinFailures(amount int) []*Failure { - var failArr []*Failure - Database(&types.Failure{}).Where("service = ?", s.Id).Where("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr) - return failArr -} - -// Ago returns a human readable timestamp for a Failure -func (f *Failure) Ago() string { - got, _ := timeago.TimeAgoWithTime(time.Now().UTC(), f.CreatedAt) - return got -} - -// Select returns a *types.Failure -func (f *Failure) Select() *types.Failure { - return f.Failure -} - // Delete will remove a Failure record from the database func (f *Failure) Delete() error { db := Database(&types.Failure{}).Delete(f) return db.Error() } - -// Count24HFailures returns the amount of failures for a service within the last 24 hours -func (c *Core) Count24HFailures() uint64 { - var count uint64 - for _, s := range CoreApp.Services { - service := s.(*Service) - fails, _ := service.TotalFailures24() - count += fails - } - return count -} - -// CountFailures returns the total count of failures for all services -func CountFailures() uint64 { - var count uint64 - err := Database(&types.Failure{}).Count(&count) - if err.Error() != nil { - log.Warnln(err.Error()) - return 0 - } - return count -} - -// TotalFailuresOnDate returns the total amount of failures for a service on a specific time/date -func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) { - var count uint64 - date := ago.UTC().Format("2006-01-02 00:00:00") - dateend := ago.UTC().Format("2006-01-02") + " 23:59:59" - rows := Database(&types.Failure{}).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'") - err := rows.Count(&count) - return count, err.Error() -} - -// TotalFailures24 returns the amount of failures for a service within the last 24 hours -func (s *Service) TotalFailures24() (uint64, error) { - ago := time.Now().UTC().Add(-24 * time.Hour) - return s.TotalFailuresSince(ago) -} - -// TotalFailures returns the total amount of failures for a service -func (s *Service) TotalFailures() (uint64, error) { - var count uint64 - rows := Database(&types.Failure{}).Where("service = ?", s.Id) - err := rows.Count(&count) - return count, err.Error() -} - -// FailuresDaysAgo returns the amount of failures since days ago -func (s *Service) FailuresDaysAgo(days int) uint64 { - ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour) - count, _ := s.TotalFailuresSince(ago) - return count -} - -// TotalFailuresSince returns the total amount of failures for a service since a specific time/date -func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) { - var count uint64 - 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) - return count, err.Error() -} - -// ParseError returns a human readable error for a Failure -func (f *Failure) ParseError() string { - if f.Method == "checkin" { - return fmt.Sprintf("Checkin is Offline") - } - err := strings.Contains(f.Issue, "connection reset by peer") - if err { - return fmt.Sprintf("Connection Reset") - } - err = strings.Contains(f.Issue, "operation timed out") - if err { - return fmt.Sprintf("HTTP Request Timed Out") - } - err = strings.Contains(f.Issue, "x509: certificate is valid") - if err { - return fmt.Sprintf("SSL Certificate invalid") - } - err = strings.Contains(f.Issue, "Client.Timeout exceeded while awaiting headers") - if err { - return fmt.Sprintf("Connection Timed Out") - } - err = strings.Contains(f.Issue, "no such host") - if err { - return fmt.Sprintf("Domain is offline or not found") - } - err = strings.Contains(f.Issue, "HTTP Status Code") - if err { - return fmt.Sprintf("Incorrect HTTP Status Code") - } - err = strings.Contains(f.Issue, "connection refused") - if err { - return fmt.Sprintf("Connection Failed") - } - err = strings.Contains(f.Issue, "can't assign requested address") - if err { - return fmt.Sprintf("Unable to Request Address") - } - err = strings.Contains(f.Issue, "no route to host") - if err { - return fmt.Sprintf("Domain is offline or not found") - } - err = strings.Contains(f.Issue, "i/o timeout") - if err { - return fmt.Sprintf("Connection Timed Out") - } - err = strings.Contains(f.Issue, "Client.Timeout exceeded while reading body") - if err { - return fmt.Sprintf("Timed Out on Response Body") - } - return f.Issue -} diff --git a/core/groups.go b/core/groups.go index 1741ad49..a7fdf91d 100644 --- a/core/groups.go +++ b/core/groups.go @@ -1,72 +1,23 @@ package core import ( + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" "sort" - "time" ) type Group struct { - *types.Group -} - -// Delete will remove a group -func (g *Group) Delete() error { - for _, s := range g.Services() { - s.GroupId = 0 - s.Update(false) - } - err := Database(&Group{}).Delete(g) - return err.Error() -} - -// Create will create a group and insert it into the database -func (g *Group) Create() (int64, error) { - g.CreatedAt = time.Now().UTC() - db := Database(&Group{}).Create(g) - return g.Id, db.Error() -} - -// Update will update a group -func (g *Group) Update() (int64, error) { - g.UpdatedAt = time.Now().UTC() - db := Database(&Group{}).Update(g) - return g.Id, db.Error() -} - -// Services returns all services belonging to a group -func (g *Group) Services() []*Service { - var services []*Service - for _, s := range Services() { - if s.Select().GroupId == int(g.Id) { - services = append(services, s.(*Service)) - } - } - return services -} - -// VisibleServices returns all services based on authentication -func (g *Group) VisibleServices(auth bool) []*Service { - var services []*Service - for _, g := range g.Services() { - if !g.Public.Bool { - if auth { - services = append(services, g) - } - } else { - services = append(services, g) - } - } - return services + database.Grouper } // SelectGroups returns all groups -func SelectGroups(includeAll bool, auth bool) []*Group { - var groups []*Group - var validGroups []*Group - Database(&Group{}).Find(&groups).Order("order_id desc") +func SelectGroups(includeAll bool, auth bool) []database.Grouper { + var validGroups []database.Grouper + + groups := database.AllGroups() + for _, g := range groups { - if !g.Public.Bool { + if !g.Model().Public.Bool { if auth { validGroups = append(validGroups, g) } @@ -76,26 +27,26 @@ func SelectGroups(includeAll bool, auth bool) []*Group { } sort.Sort(GroupOrder(validGroups)) if includeAll { - emptyGroup := &Group{&types.Group{Id: 0, Public: types.NewNullBool(true)}} + emptyGroup := &Group{} validGroups = append(validGroups, emptyGroup) } return validGroups } // SelectGroup returns a *core.Group -func SelectGroup(id int64) *Group { +func SelectGroup(id int64) *types.Group { for _, g := range SelectGroups(true, true) { - if g.Id == id { - return g + if g.Model().Id == id { + return g.Model() } } return nil } // GroupOrder will reorder the groups based on 'order_id' (Order) -type GroupOrder []*Group +type GroupOrder []database.Grouper // Sort interface for resorting the Groups in order func (c GroupOrder) Len() int { return len(c) } func (c GroupOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] } -func (c GroupOrder) Less(i, j int) bool { return c[i].Order < c[j].Order } +func (c GroupOrder) Less(i, j int) bool { return c[i].Model().Order < c[j].Model().Order } diff --git a/core/hits.go b/core/hits.go index 573c1496..3bbe4e83 100644 --- a/core/hits.go +++ b/core/hits.go @@ -16,90 +16,9 @@ package core import ( - "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" - "net/http" - "time" ) type Hit struct { *types.Hit } - -// CreateHit will create a new 'hit' record in the database for a successful/online service -func (s *Service) CreateHit(h *types.Hit) (int64, error) { - db := Database(&types.Hit{}).Create(&h) - if db.Error() != nil { - log.Errorln(db.Error()) - return 0, db.Error() - } - return h.Id, db.Error() -} - -// CountHits returns a int64 for all hits for a service -func (s *Service) CountHits() (int64, error) { - var hits int64 - col := Database(&types.Hit{}).Where("service = ?", s.Id) - err := col.Count(&hits) - return hits, err.Error() -} - -// Hits returns all successful hits for a service -func (s *Service) HitsQuery(r *http.Request) ([]*types.Hit, error) { - var hits []*types.Hit - col := Database(&types.Hit{}).Where("service = ?", s.Id).Requests(r) - err := col.Find(&hits) - return hits, err.Error() -} - -func (s *Service) HitsDb(r *http.Request) database.Database { - return Database(&types.Hit{}).Where("service = ?", s.Id).Requests(r).Order("id desc") -} - -// Hits returns all successful hits for a service -func (s *Service) Hits() ([]*types.Hit, error) { - var hits []*types.Hit - col := Database(&types.Hit{}).Where("service = ?", s.Id).Order("id desc") - err := col.Find(&hits) - return hits, err.Error() -} - -// LimitedHits returns the last 1024 successful/online 'hit' records for a service -func (s *Service) LimitedHits(amount int) ([]*types.Hit, error) { - var hits []*types.Hit - col := Database(&types.Hit{}).Where("service = ?", s.Id).Order("id desc").Limit(amount) - err := col.Find(&hits) - return reverseHits(hits), err.Error() -} - -// reverseHits will reverse the service's hit slice -func reverseHits(input []*types.Hit) []*types.Hit { - if len(input) == 0 { - return input - } - return append(reverseHits(input[1:]), input[0]) -} - -// TotalHits returns the total amount of successful hits a service has -func (s *Service) TotalHits() (uint64, error) { - var count uint64 - col := Database(&types.Hit{}).Where("service = ?", s.Id).Count(&count) - return count, col.Error() -} - -// TotalHitsSince returns the total amount of hits based on a specific time/date -func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) { - var count uint64 - rows := 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() -} - -// Sum returns the added value Latency for all of the services successful hits. -func (s *Service) Sum() float64 { - var sum float64 - rows, _ := Database(&types.Hit{}).Where("service = ?", s.Id).Select("sum(latency) as total").Rows() - for rows.Next() { - rows.Scan(&sum) - } - return sum -} diff --git a/core/messages.go b/core/messages.go index d9cc0c9f..2c95cd19 100644 --- a/core/messages.go +++ b/core/messages.go @@ -51,13 +51,6 @@ func SelectMessage(id int64) (*Message, error) { return &message, db.Error() } -func (m *Message) Service() *Service { - if m.ServiceId == 0 { - return nil - } - return SelectService(m.ServiceId) -} - // Create will create a Message and insert it into the database func (m *Message) Create() (int64, error) { m.CreatedAt = time.Now().UTC() diff --git a/core/sample.go b/core/sample.go index d4107584..23445086 100644 --- a/core/sample.go +++ b/core/sample.go @@ -18,6 +18,7 @@ package core import ( "fmt" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "sync" @@ -35,7 +36,7 @@ func InsertSampleData() error { insertSampleGroups() createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC() - s1 := ReturnService(&types.Service{ + s1 := &types.Service{ Name: "Google", Domain: "https://google.com", ExpectedStatus: 200, @@ -48,8 +49,8 @@ func InsertSampleData() error { Permalink: types.NewNullString("google"), VerifySSL: types.NewNullBool(true), CreatedAt: createdOn, - }) - s2 := ReturnService(&types.Service{ + } + s2 := &types.Service{ Name: "Statping Github", Domain: "https://github.com/hunterlong/statping", ExpectedStatus: 200, @@ -61,8 +62,8 @@ func InsertSampleData() error { Permalink: types.NewNullString("statping_github"), VerifySSL: types.NewNullBool(true), CreatedAt: createdOn, - }) - s3 := ReturnService(&types.Service{ + } + s3 := &types.Service{ Name: "JSON Users Test", Domain: "https://jsonplaceholder.typicode.com/users", ExpectedStatus: 200, @@ -75,8 +76,8 @@ func InsertSampleData() error { VerifySSL: types.NewNullBool(true), GroupId: 2, CreatedAt: createdOn, - }) - s4 := ReturnService(&types.Service{ + } + s4 := &types.Service{ Name: "JSON API Tester", Domain: "https://jsonplaceholder.typicode.com/posts", ExpectedStatus: 201, @@ -91,8 +92,8 @@ func InsertSampleData() error { VerifySSL: types.NewNullBool(true), GroupId: 2, CreatedAt: createdOn, - }) - s5 := ReturnService(&types.Service{ + } + s5 := &types.Service{ Name: "Google DNS", Domain: "8.8.8.8", Interval: 20, @@ -103,13 +104,13 @@ func InsertSampleData() error { Public: types.NewNullBool(true), GroupId: 1, CreatedAt: createdOn, - }) + } - s1.Create(false) - s2.Create(false) - s3.Create(false) - s4.Create(false) - s5.Create(false) + database.Create(s1) + database.Create(s2) + database.Create(s3) + database.Create(s4) + database.Create(s5) insertMessages() @@ -121,85 +122,111 @@ func InsertSampleData() error { } func insertSampleIncidents() error { - incident1 := &Incident{&types.Incident{ + incident1 := &types.Incident{ Title: "Github Downtime", Description: "This is an example of a incident for a service.", ServiceId: 2, - }} - _, err := incident1.Create() + } + if _, err := database.Create(incident1); err != nil { + return err + } - incidentUpdate1 := &IncidentUpdate{&types.IncidentUpdate{ + incidentUpdate1 := &types.IncidentUpdate{ IncidentId: incident1.Id, Message: "Github's page for Statping seems to be sending a 501 error.", Type: "Investigating", - }} - _, err = incidentUpdate1.Create() + } + if _, err := database.Create(incidentUpdate1); err != nil { + return err + } - incidentUpdate2 := &IncidentUpdate{&types.IncidentUpdate{ + incidentUpdate2 := &types.IncidentUpdate{ IncidentId: incident1.Id, Message: "Problem is continuing and we are looking at the issues.", Type: "Update", - }} - _, err = incidentUpdate2.Create() + } + if _, err := database.Create(incidentUpdate2); err != nil { + return err + } - incidentUpdate3 := &IncidentUpdate{&types.IncidentUpdate{ + incidentUpdate3 := &types.IncidentUpdate{ IncidentId: incident1.Id, Message: "Github is now back online and everything is working.", Type: "Resolved", - }} - _, err = incidentUpdate3.Create() + } + if _, err := database.Create(incidentUpdate3); err != nil { + return err + } - return err + return nil } func insertSampleGroups() error { - group1 := &Group{&types.Group{ + group1 := &types.Group{ Name: "Main Services", Public: types.NewNullBool(true), Order: 2, - }} - _, err := group1.Create() - group2 := &Group{&types.Group{ + } + if _, err := database.Create(group1); err != nil { + return err + } + + group2 := &types.Group{ Name: "Linked Services", Public: types.NewNullBool(false), Order: 1, - }} - _, err = group2.Create() - group3 := &Group{&types.Group{ + } + if _, err := database.Create(group2); err != nil { + return err + } + + group3 := &types.Group{ Name: "Empty Group", Public: types.NewNullBool(false), Order: 3, - }} - _, err = group3.Create() - return err + } + if _, err := database.Create(group3); err != nil { + return err + } + return nil } // insertSampleCheckins will create 2 checkins with 60 successful hits per Checkin func insertSampleCheckins() error { s1 := SelectService(1) - checkin1 := ReturnCheckin(&types.Checkin{ + checkin1 := &types.Checkin{ ServiceId: s1.Id, Interval: 300, GracePeriod: 300, - }) - checkin1.Update() + } + + if _, err := database.Create(checkin1); err != nil { + return err + } s2 := SelectService(1) - checkin2 := ReturnCheckin(&types.Checkin{ + checkin2 := &types.Checkin{ ServiceId: s2.Id, Interval: 900, GracePeriod: 300, - }) - checkin2.Update() + } + + if _, err := database.Create(checkin2); err != nil { + return err + } checkTime := time.Now().UTC().Add(-24 * time.Hour) for i := 0; i <= 60; i++ { - checkHit := ReturnCheckinHit(&types.CheckinHit{ + checkHit := &types.CheckinHit{ Checkin: checkin1.Id, From: "192.168.0.1", CreatedAt: checkTime.UTC(), - }) - checkHit.Create() + } + + if _, err := database.Create(checkHit); err != nil { + return err + } + checkTime = checkTime.Add(10 * time.Minute) } return nil @@ -250,50 +277,61 @@ func insertSampleCore() error { CreatedAt: time.Now().UTC(), UseCdn: types.NewNullBool(false), } - query := Database(&Core{}).Create(core) - return query.Error() + + _, err := database.Create(core) + + return err } // insertSampleUsers will create 2 admin users for a seed database func insertSampleUsers() error { - u2 := ReturnUser(&types.User{ + u2 := &types.User{ Username: "testadmin", Password: "password123", Email: "info@betatude.com", Admin: types.NewNullBool(true), - }) + } - u3 := ReturnUser(&types.User{ + if _, err := database.Create(u2); err != nil { + return err + } + + u3 := &types.User{ Username: "testadmin2", Password: "password123", Email: "info@adminhere.com", Admin: types.NewNullBool(true), - }) + } - _, err := u2.Create() - _, err = u3.Create() - return err + if _, err := database.Create(u3); err != nil { + return err + } + + return nil } func insertMessages() error { - m1 := ReturnMessage(&types.Message{ + m1 := &types.Message{ Title: "Routine Downtime", Description: "This is an example a upcoming message for a service!", ServiceId: 1, StartOn: time.Now().UTC().Add(15 * time.Minute), EndOn: time.Now().UTC().Add(2 * time.Hour), - }) - if _, err := m1.Create(); err != nil { + } + + if _, err := database.Create(m1); err != nil { return err } - m2 := ReturnMessage(&types.Message{ + + m2 := &types.Message{ Title: "Server Reboot", Description: "This is another example a upcoming message for a service!", ServiceId: 3, StartOn: time.Now().UTC().Add(15 * time.Minute), EndOn: time.Now().UTC().Add(2 * time.Hour), - }) - if _, err := m2.Create(); err != nil { + } + + if _, err := database.Create(m2); err != nil { return err } return nil @@ -317,7 +355,7 @@ func InsertLargeSampleData() error { return err } createdOn := time.Now().UTC().Add((-24 * 90) * time.Hour) - s6 := ReturnService(&types.Service{ + s6 := &types.Service{ Name: "JSON Lint", Domain: "https://jsonlint.com", ExpectedStatus: 200, @@ -327,9 +365,13 @@ func InsertLargeSampleData() error { Timeout: 10, Order: 6, CreatedAt: createdOn, - }) + } - s7 := ReturnService(&types.Service{ + if _, err := database.Create(s6); err != nil { + return err + } + + s7 := &types.Service{ Name: "Demo Page", Domain: "https://demo.statping.com", ExpectedStatus: 200, @@ -339,9 +381,13 @@ func InsertLargeSampleData() error { Timeout: 15, Order: 7, CreatedAt: createdOn, - }) + } - s8 := ReturnService(&types.Service{ + if _, err := database.Create(s7); err != nil { + return err + } + + s8 := &types.Service{ Name: "Golang", Domain: "https://golang.org", ExpectedStatus: 200, @@ -350,9 +396,13 @@ func InsertLargeSampleData() error { Method: "GET", Timeout: 10, Order: 8, - }) + } - s9 := ReturnService(&types.Service{ + if _, err := database.Create(s8); err != nil { + return err + } + + s9 := &types.Service{ Name: "Santa Monica", Domain: "https://www.santamonica.com", ExpectedStatus: 200, @@ -362,9 +412,13 @@ func InsertLargeSampleData() error { Timeout: 10, Order: 9, CreatedAt: createdOn, - }) + } - s10 := ReturnService(&types.Service{ + if _, err := database.Create(s9); err != nil { + return err + } + + s10 := &types.Service{ Name: "Oeschs Die Dritten", Domain: "https://www.oeschs-die-dritten.ch/en/", ExpectedStatus: 200, @@ -374,9 +428,13 @@ func InsertLargeSampleData() error { Timeout: 10, Order: 10, CreatedAt: createdOn, - }) + } - s11 := ReturnService(&types.Service{ + if _, err := database.Create(s10); err != nil { + return err + } + + s11 := &types.Service{ Name: "XS Project - Bochka, Bass, Kolbaser", Domain: "https://www.youtube.com/watch?v=VLW1ieY4Izw", ExpectedStatus: 200, @@ -386,9 +444,13 @@ func InsertLargeSampleData() error { Timeout: 20, Order: 11, CreatedAt: createdOn, - }) + } - s12 := ReturnService(&types.Service{ + if _, err := database.Create(s11); err != nil { + return err + } + + s12 := &types.Service{ Name: "Github", Domain: "https://github.com/hunterlong", ExpectedStatus: 200, @@ -398,9 +460,13 @@ func InsertLargeSampleData() error { Timeout: 20, Order: 12, CreatedAt: createdOn, - }) + } - s13 := ReturnService(&types.Service{ + if _, err := database.Create(s12); err != nil { + return err + } + + s13 := &types.Service{ Name: "Failing URL", Domain: "http://thisdomainisfakeanditsgoingtofail.com", ExpectedStatus: 200, @@ -410,9 +476,13 @@ func InsertLargeSampleData() error { Timeout: 10, Order: 13, CreatedAt: createdOn, - }) + } - s14 := ReturnService(&types.Service{ + if _, err := database.Create(s13); err != nil { + return err + } + + s14 := &types.Service{ Name: "Oesch's die Dritten - Die Jodelsprache", Domain: "https://www.youtube.com/watch?v=k3GTxRt4iao", ExpectedStatus: 200, @@ -422,9 +492,13 @@ func InsertLargeSampleData() error { Timeout: 12, Order: 14, CreatedAt: createdOn, - }) + } - s15 := ReturnService(&types.Service{ + if _, err := database.Create(s14); err != nil { + return err + } + + s15 := &types.Service{ Name: "Gorm", Domain: "http://gorm.io/", ExpectedStatus: 200, @@ -434,18 +508,11 @@ func InsertLargeSampleData() error { Timeout: 12, Order: 15, CreatedAt: createdOn, - }) + } - s6.Create(false) - s7.Create(false) - s8.Create(false) - s9.Create(false) - s10.Create(false) - s11.Create(false) - s12.Create(false) - s13.Create(false) - s14.Create(false) - s15.Create(false) + if _, err := database.Create(s15); err != nil { + return err + } var dayAgo = time.Now().UTC().Add((-24 * 90) * time.Hour) @@ -472,7 +539,7 @@ func insertFailureRecords(since time.Time, amount int) { CreatedAt: createdAt, } - service.CreateFailure(failure) + database.Create(failure) } } } @@ -492,7 +559,7 @@ func insertHitRecords(since time.Time, amount int) { CreatedAt: createdAt.UTC(), Latency: latency, } - service.CreateHit(hit) + database.Create(hit) } } @@ -554,7 +621,7 @@ func TmpRecords(dbFile string) error { return err } log.Infoln("loading all services") - if _, err := CoreApp.SelectAllServices(false); err != nil { + if _, err := SelectAllServices(false); err != nil { return err } if err := AttachNotifiers(); err != nil { @@ -597,7 +664,7 @@ func TmpRecords(dbFile string) error { return err } log.Infoln("loading all services") - if _, err := CoreApp.SelectAllServices(false); err != nil { + if _, err := SelectAllServices(false); err != nil { return err } log.Infoln("copying sql database file to: " + tmpSqlFile) diff --git a/core/services.go b/core/services.go index 8e443aee..52ba8fa0 100644 --- a/core/services.go +++ b/core/services.go @@ -20,9 +20,7 @@ import ( "github.com/hunterlong/statping/core/notifier" "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" - "github.com/hunterlong/statping/utils" "sort" - "strconv" "time" ) @@ -30,224 +28,63 @@ type Service struct { *types.Service } -// Select will return the *types.Service struct for Service -func (s *Service) Select() *types.Service { - return s.Service -} +type Servicer interface{} -// ReturnService will convert *types.Service to *core.Service -func ReturnService(s *types.Service) *Service { - return &Service{s} -} - -func Services() []types.ServiceInterface { - return CoreApp.Services +func Services() []database.Servicer { + return CoreApp.services } // SelectService returns a *core.Service from in memory -func SelectService(id int64) *Service { +func SelectService(id int64) *types.Service { for _, s := range Services() { - if s.Select().Id == id { - service := s.(*Service) - service = service.UpdateStats() - return service - } - } - return nil -} - -func (s *Service) GetFailures(count int) []*Failure { - var fails []*Failure - db := Database(&types.Failure{}).Where("service = ?", s.Id) - db.Limit(count).Find(&fails) - return fails -} - -func (s *Service) UpdateStats() *Service { - s.Online24Hours = s.OnlineDaysPercent(1) - s.Online7Days = s.OnlineDaysPercent(7) - s.AvgResponse = s.AvgTime() - s.FailuresLast24Hours = s.FailuresDaysAgo(1) - s.LastFailure = s.lastFailure() - return s -} - -func SelectServices(auth bool) []*Service { - var validServices []*Service - for _, sr := range CoreApp.Services { - s := sr.(*Service) - if !s.Public.Bool { - if auth { - validServices = append(validServices, s) - } - } else { - validServices = append(validServices, s) - } - } - return validServices -} - -// SelectServiceLink returns a *core.Service from the service permalink -func SelectServiceLink(permalink string) *Service { - for _, s := range Services() { - if s.Select().Permalink.String == permalink { - return s.(*Service) + if s.Model().Id == id { + fmt.Println("service: ", s.Model()) + return s.Model() } } return nil } // CheckinProcess runs the checkin routine for each checkin attached to service -func (s *Service) CheckinProcess() { - checkins := s.AllCheckins() - for _, c := range checkins { +func CheckinProcess(s database.Servicer) { + for _, c := range s.AllCheckins() { c.Start() - go c.Routine() + go CheckinRoutine(c) } } -// AllCheckins will return a slice of AllCheckins for a Service -func (s *Service) AllCheckins() []*Checkin { - var checkin []*Checkin - Database(&Checkin{}).Where("service = ?", s.Id).Find(&checkin) - return checkin -} - // 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) { - var services []*Service - db := Database(&Service{}).Find(&services).Order("order_id desc") - if db.Error() != nil { - log.Errorln(fmt.Sprintf("service error: %v", db.Error())) - return nil, db.Error() +func SelectAllServices(start bool) ([]*database.ServiceObj, error) { + srvs := database.Services() + for _, s := range srvs { + fmt.Println("services: ", s.Id, s.Name) } - CoreApp.Services = nil - for _, service := range services { + + for _, s := range srvs { if start { + service := s.Model() service.Start() - service.CheckinProcess() + CheckinProcess(s) } - fails := service.GetFailures(limitedFailures) - for _, f := range fails { - service.Failures = append(service.Failures, f) - } - checkins := service.AllCheckins() - for _, c := range checkins { - c.Failures = c.GetFailures(limitedFailures) - c.Hits = c.LimitedHits(limitedHits) - service.Checkins = append(service.Checkins, c) + //fails := service.Service (limitedFailures) + //for _, f := range fails { + // service.Failures = append(service.Failures, f) + //} + for _, c := range s.AllCheckins() { + s.Checkins = append(s.Checkins, c) } // collect initial service stats - service = service.UpdateStats() - CoreApp.Services = append(CoreApp.Services, service) + s.Service.Stats = s.UpdateStats() + CoreApp.services = append(CoreApp.services, s) } reorderServices() - return services, db.Error() + return srvs, nil } // reorderServices will sort the services based on 'order_id' func reorderServices() { - sort.Sort(ServiceOrder(CoreApp.Services)) -} - -// AvgTime will return the average amount of time for a service to response back successfully -func (s *Service) AvgTime() float64 { - total, _ := s.TotalHits() - if total == 0 { - return 0 - } - avg := s.Sum() / float64(total) * 100 - f, _ := strconv.ParseFloat(fmt.Sprintf("%0.0f", avg*10), 32) - return f -} - -// OnlineDaysPercent returns the service's uptime percent within last 24 hours -func (s *Service) OnlineDaysPercent(days int) float32 { - ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour) - return s.OnlineSince(ago) -} - -// OnlineSince accepts a time since parameter to return the percent of a service's uptime. -func (s *Service) OnlineSince(ago time.Time) float32 { - failed, _ := s.TotalFailuresSince(ago) - if failed == 0 { - s.Online24Hours = 100.00 - return s.Online24Hours - } - total, _ := s.TotalHitsSince(ago) - if total == 0 { - s.Online24Hours = 0 - return s.Online24Hours - } - avg := float64(failed) / float64(total) * 100 - avg = 100 - avg - if avg < 0 { - avg = 0 - } - amount, _ := strconv.ParseFloat(fmt.Sprintf("%0.2f", avg), 10) - s.Online24Hours = float32(amount) - return s.Online24Hours -} - -// lastFailure returns the last Failure a service had -func (s *Service) lastFailure() types.FailureInterface { - limited := s.GetFailures(1) - if len(limited) == 0 { - return nil - } - last := limited[len(limited)-1] - return last -} - -// DowntimeText will return the amount of downtime for a service based on the duration -// service.DowntimeText() -// // Service has been offline for 15 minutes -func (s *Service) DowntimeText() string { - return fmt.Sprintf("%v has been offline for %v", s.Name, utils.DurationReadable(s.Downtime())) -} - -// Dbtimestamp will return a SQL query for grouping by date -func Dbtimestamp(group string, column string) string { - seconds := 3600 - switch group { - case "minute": - seconds = 60 - case "hour": - seconds = 3600 - case "day": - seconds = 86400 - case "week": - seconds = 604800 - case "month": - seconds = 2592000 - case "year": - seconds = 31557600 - default: - seconds = 60 - } - switch CoreApp.Config.DbConn { - case "mysql": - return fmt.Sprintf("CONCAT(date_format(created_at, '%%Y-%%m-%%d %%H:00:00')) AS timeframe, AVG(%v) AS value", column) - case "postgres": - return fmt.Sprintf("date_trunc('%v', created_at) AS timeframe, AVG(%v) AS value", group, column) - default: - return fmt.Sprintf("datetime((strftime('%%s', created_at) / %v) * %v, 'unixepoch') AS timeframe, AVG(%v) as value", seconds, seconds, column) - } -} - -// Downtime returns the amount of time of a offline service -func (s *Service) Downtime() time.Duration { - hits, _ := s.Hits() - fail := s.lastFailure() - if fail == nil { - return time.Duration(0) - } - if len(hits) == 0 { - return time.Now().UTC().Sub(fail.Select().CreatedAt.UTC()) - } - since := fail.Select().CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC()) - return since + sort.Sort(ServiceOrder(CoreApp.services)) } // GraphData will return all hits or failures @@ -265,9 +102,9 @@ func GraphData(q *database.GroupQuery, dbType interface{}, by database.By) []*da } // index returns a services index int for updating the []*core.Services slice -func (s *Service) index() int { - for k, service := range CoreApp.Services { - if s.Id == service.(*Service).Id { +func index(s database.Servicer) int { + for k, service := range CoreApp.services { + if s.Model().Id == service.Model().Id { return k } } @@ -275,32 +112,34 @@ func (s *Service) index() int { } // updateService will update a service in the []*core.Services slice -func updateService(s *Service) { - CoreApp.Services[s.index()] = s +func updateService(s database.Servicer) { + CoreApp.services[index(s)] = s } // Delete will remove a service from the database, it will also end the service checking go routine -func (s *Service) Delete() error { - i := s.index() - err := Database(&Service{}).Delete(s) - if err.Error() != nil { - log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error())) - return err.Error() +func Delete(srv database.Servicer) error { + i := index(srv) + s := srv.Model() + err := database.Delete(s) + if err != nil { + log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err)) + return err } s.Close() - slice := CoreApp.Services - CoreApp.Services = append(slice[:i], slice[i+1:]...) + slice := CoreApp.services + CoreApp.services = append(slice[:i], slice[i+1:]...) reorderServices() - notifier.OnDeletedService(s.Service) - return err.Error() + notifier.OnDeletedService(s) + return err } // Update will update a service in the database, the service's checking routine can be restarted by passing true -func (s *Service) Update(restart bool) error { - err := Database(&Service{}).Update(&s) - if err.Error() != nil { +func Update(srv database.Servicer, restart bool) error { + s := srv.Model() + err := database.Update(s) + if err != nil { log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err)) - return err.Error() + return err } // clear the notification queue for a service if !s.AllowNotifications.Bool { @@ -313,55 +152,27 @@ func (s *Service) Update(restart bool) error { s.Close() s.Start() s.SleepDuration = time.Duration(s.Interval) * time.Second - go s.CheckQueue(true) + go ServiceCheckQueue(srv, true) } reorderServices() - updateService(s) - notifier.OnUpdatedService(s.Service) - return err.Error() + updateService(srv) + notifier.OnUpdatedService(s) + return err } // Create will create a service and insert it into the database -func (s *Service) Create(check bool) (int64, error) { +func Create(srv database.Servicer, check bool) (int64, error) { + s := srv.Model() s.CreatedAt = time.Now().UTC() - db := Database(&Service{}).Create(s) - if db.Error() != nil { - log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error())) - return 0, db.Error() + _, err := database.Create(s) + if err != nil { + log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, err)) + return 0, err } s.Start() - go s.CheckQueue(check) - CoreApp.Services = append(CoreApp.Services, s) + go ServiceCheckQueue(srv, check) + CoreApp.services = append(CoreApp.services, srv) reorderServices() - notifier.OnNewService(s.Service) + notifier.OnNewService(s) return s.Id, nil } - -// Messages returns all Messages for a Service -func (s *Service) Messages() []*Message { - messages := SelectServiceMessages(s.Id) - return messages -} - -// ActiveMessages returns all service messages that are available based on the current time -func (s *Service) ActiveMessages() []*Message { - var messages []*Message - msgs := SelectServiceMessages(s.Id) - for _, m := range msgs { - if m.StartOn.UTC().After(time.Now().UTC()) { - messages = append(messages, m) - } - } - return messages -} - -// CountOnline returns the amount of services online -func (c *Core) CountOnline() int { - amount := 0 - for _, s := range CoreApp.Services { - if s.Select().Online { - amount++ - } - } - return amount -} diff --git a/core/services_test.go b/core/services_test.go index 1e90abc3..eabd2988 100644 --- a/core/services_test.go +++ b/core/services_test.go @@ -139,38 +139,12 @@ func TestServiceAvgUptime(t *testing.T) { assert.NotEqual(t, "0", service4.AvgUptime(since)) } -func TestServiceHits(t *testing.T) { - service := SelectService(5) - hits, err := service.Hits() - assert.Nil(t, err) - assert.True(t, len(hits) > 1400) -} - -func TestServiceLimitedHits(t *testing.T) { - service := SelectService(5) - hits, err := service.LimitedHits(1024) - assert.Nil(t, err) - assert.Equal(t, int(1024), len(hits)) -} - -func TestServiceTotalHits(t *testing.T) { - service := SelectService(5) - hits, err := service.TotalHits() - assert.Nil(t, err) - assert.NotZero(t, hits) -} - func TestServiceSum(t *testing.T) { service := SelectService(5) sum := service.Sum() assert.NotZero(t, sum) } -func TestCountOnline(t *testing.T) { - amount := CoreApp.CountOnline() - assert.True(t, amount >= 2) -} - func TestCreateService(t *testing.T) { s := ReturnService(&types.Service{ Name: "That'll do 🐢", @@ -375,31 +349,3 @@ func TestSelectGroups(t *testing.T) { groups = SelectGroups(true, true) assert.Equal(t, int(5), len(groups)) } - -func TestService_TotalFailures(t *testing.T) { - service := SelectService(8) - failures, err := service.TotalFailures() - assert.Nil(t, err) - assert.Equal(t, uint64(1), failures) -} - -func TestService_TotalFailures24(t *testing.T) { - service := SelectService(8) - failures, err := service.TotalFailures24() - assert.Nil(t, err) - assert.Equal(t, uint64(1), failures) -} - -func TestService_TotalFailuresOnDate(t *testing.T) { - t.SkipNow() - ago := utils.Now().UTC() - service := SelectService(8) - failures, err := service.TotalFailuresOnDate(ago) - assert.Nil(t, err) - assert.Equal(t, uint64(1), failures) -} - -func TestCountFailures(t *testing.T) { - failures := CountFailures() - assert.NotEqual(t, uint64(0), failures) -} diff --git a/core/users.go b/core/users.go index 8850ef6f..a327454b 100644 --- a/core/users.go +++ b/core/users.go @@ -18,18 +18,17 @@ package core import ( "fmt" "github.com/hunterlong/statping/database" - "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "golang.org/x/crypto/bcrypt" "time" ) type User struct { - *types.User + *database.UserObj } // ReturnUser returns *core.User based off a *types.User -func ReturnUser(u *types.User) *User { +func uwrap(u *database.UserObj) *User { return &User{u} } @@ -42,29 +41,32 @@ func CountUsers() int64 { // SelectUser returns the User based on the User's ID. func SelectUser(id int64) (*User, error) { - var user User - err := Database(&User{}).Where("id = ?", id).First(&user) - return &user, err.Error() + user, err := database.User(id) + if err != nil { + return nil, err + } + return uwrap(user), err } // SelectUsername returns the User based on the User's username func SelectUsername(username string) (*User, error) { - var user User - res := Database(&User{}).Where("username = ?", username) - err := res.First(&user) - return &user, err.Error() + user, err := database.UserByUsername(username) + if err != nil { + return nil, err + } + return uwrap(user), err } // Delete will remove the User record from the database func (u *User) Delete() error { - return database.Delete(&u) + return database.Delete(u) } // Update will update the User's record in database func (u *User) Update() error { u.ApiKey = utils.NewSHA1Hash(5) u.ApiSecret = utils.NewSHA1Hash(10) - return database.Update(&u) + return database.Update(u) } // Create will insert a new User into the database @@ -74,7 +76,7 @@ func (u *User) Create() (int64, error) { u.ApiKey = utils.NewSHA1Hash(5) u.ApiSecret = utils.NewSHA1Hash(10) - user, err := database.Create(&u) + user, err := database.Create(u) if err != nil { return 0, err } @@ -88,12 +90,12 @@ func (u *User) Create() (int64, error) { // SelectAllUsers returns all users func SelectAllUsers() ([]*User, error) { var users []*User - db := Database(&User{}).Find(&users) - if db.Error() != nil { - log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error())) - return nil, db.Error() + err := database.AllUsers(&users) + if err != nil { + log.Errorln(fmt.Sprintf("Failed to load all users. %v", err)) + return nil, err } - return users, db.Error() + return users, err } // AuthUser will return the User and a boolean if authentication was correct. diff --git a/database/checkin_hits.go b/database/checkin_hits.go new file mode 100644 index 00000000..00fe2eec --- /dev/null +++ b/database/checkin_hits.go @@ -0,0 +1,8 @@ +package database + +import "github.com/hunterlong/statping/types" + +type CheckinHitObj struct { + hits []*types.CheckinHit + o *Object +} diff --git a/database/checkins.go b/database/checkins.go index c861d015..6de9e787 100644 --- a/database/checkins.go +++ b/database/checkins.go @@ -1,33 +1,119 @@ package database -import "github.com/hunterlong/statping/types" +import ( + "fmt" + "github.com/hunterlong/statping/types" + "time" +) type CheckinObj struct { *types.Checkin - failures + o *Object + + Checkiner } -func (o *Object) AsCheckin() HitsFailures { - return &CheckinObj{ - Checkin: o.model.(*types.Checkin), - } +type Checkiner interface { + Hits() *CheckinHitObj + Failures() *FailureObj + Model() *types.Checkin + Object() *CheckinObj } -func Checkin(id int64) (HitsFailures, error) { +func Checkin(id int64) (*CheckinObj, error) { var checkin types.Checkin - query := database.Model(&types.Checkin{}).Where("id = ?", id).Find(&checkin) - return &CheckinObj{Checkin: &checkin}, query.Error() + query := database.Checkins().Where("id = ?", id) + finder := query.Find(&checkin) + return &CheckinObj{Checkin: &checkin, o: wrapObject(id, &checkin, query)}, finder.Error() } -func (c *CheckinObj) Hits() *hits { - return &hits{ - database.Model(&types.Checkin{}).Where("checkin = ?", c.Id), +func CheckinByKey(api string) (*CheckinObj, error) { + var checkin types.Checkin + query := database.Checkins().Where("api = ?", api) + finder := query.Find(&checkin) + return &CheckinObj{Checkin: &checkin, o: wrapObject(checkin.Id, &checkin, query)}, finder.Error() +} + +func wrapCheckins(all []*types.Checkin, db Database) []*CheckinObj { + var arr []*CheckinObj + for _, v := range all { + arr = append(arr, &CheckinObj{Checkin: v, o: wrapObject(v.Id, v, db)}) + } + return arr +} + +func AllCheckins() []*CheckinObj { + var checkins []*types.Checkin + query := database.Checkins() + query.Find(&checkins) + return wrapCheckins(checkins, query) +} + +func (s *CheckinObj) Service() *ServiceObj { + var srv *types.Service + q := database.Checkins().Where("service = ?", s.ServiceId) + q.Find(&srv) + return &ServiceObj{ + Service: srv, + o: wrapObject(srv.Id, srv, q), } } -func (c *CheckinObj) Failures() *failures { - return &failures{ - database.Model(&types.Failure{}). - Where("method = 'checkin' AND service = ?", c.Id).Order("id desc"), - } +func (s *CheckinObj) Failures() *FailureObj { + q := database.Failures(). + Where("method = 'checkin' AND id = ?", s.Id). + Where("method = 'checkin'") + return &FailureObj{wrapObject(s.Id, nil, q)} +} + +func (s *CheckinObj) object() *Object { + return s.o +} + +func (c *CheckinObj) Model() *types.Checkin { + return c.Checkin +} + +func (c *CheckinObj) Object() *CheckinObj { + return c +} + +// Period will return the duration of the Checkin interval +func (c *CheckinObj) Period() time.Duration { + duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.Interval)) + return duration +} + +// Grace will return the duration of the Checkin Grace Period (after service hasn't responded, wait a bit for a response) +func (c *CheckinObj) Grace() time.Duration { + duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.GracePeriod)) + return duration +} + +// Expected returns the duration of when the serviec should receive a Checkin +func (c *CheckinObj) Expected() time.Duration { + last := c.Hits().Last() + now := time.Now().UTC() + lastDir := now.Sub(last.CreatedAt) + sub := time.Duration(c.Period() - lastDir) + return sub +} + +// Last returns the last checkinHit for a Checkin +func (c *CheckinObj) Hits() *CheckinHitObj { + var checkinHits []*types.CheckinHit + query := database.CheckinHits().Where("checkin = ?", c.Id) + query.Find(&checkinHits) + return &CheckinHitObj{checkinHits, wrapObject(c.Id, checkinHits, query)} +} + +// Last returns the last checkinHit for a Checkin +func (c *CheckinHitObj) Last() *types.CheckinHit { + var last types.CheckinHit + c.o.db.Last(&last) + return &last +} + +func (c *CheckinObj) Link() string { + return fmt.Sprintf("%v/checkin/%v", "DOMAINHERE", c.ApiKey) } diff --git a/database/crud.go b/database/crud.go index 62d71030..3dbd80fd 100644 --- a/database/crud.go +++ b/database/crud.go @@ -1,6 +1,8 @@ package database import ( + "fmt" + "github.com/hunterlong/statping/types" "reflect" ) @@ -10,19 +12,32 @@ type Object struct { db Database } -type HitsFailures interface { - Hits() *hits - Failures() *failures +type isObject interface { + object() *Object +} + +func wrapObject(id int64, model interface{}, db Database) *Object { + return &Object{ + Id: id, + model: model, + db: db, + } } func modelId(model interface{}) int64 { + fmt.Printf("%T\n", model) iface := reflect.ValueOf(model) field := iface.Elem().FieldByName("Id") return field.Int() } func toModel(model interface{}) Database { - return database.Model(&model) + switch model.(type) { + case *types.Core: + return database.Model(&types.Core{}).Table("core") + default: + return database.Model(&model) + } } func Create(data interface{}) (*Object, error) { diff --git a/database/database.go b/database/database.go index 789d62b6..7c98d8b9 100644 --- a/database/database.go +++ b/database/database.go @@ -104,7 +104,6 @@ type Database interface { Since(time.Time) Database Between(time.Time, time.Time) Database - ToChart() ([]*DateScan, error) SelectByTime(string) string MultipleSelects(args ...string) Database @@ -112,13 +111,73 @@ type Database interface { FormatTime(t time.Time) string ParseTime(t string) (time.Time, error) - Requests(*http.Request) Database + Requests(*http.Request, isObject) Database GroupQuery(query *GroupQuery, by By) GroupByer + + Objects } -func (it *Db) Requests(r *http.Request) Database { - g := ParseQueries(r, it) +type Objects interface { + Services() Database + Users() Database + Groups() Database + Incidents() Database + IncidentUpdates() Database + Hits() Database + Failures() Database + Checkins() Database + CheckinHits() Database + Messages() Database + Integrations() Database +} + +func (d *Db) Services() Database { + return d.Model(&types.Service{}) +} + +func (d *Db) Users() Database { + return d.Model(&types.User{}) +} + +func (d *Db) Groups() Database { + return d.Model(&types.Group{}) +} + +func (d *Db) Incidents() Database { + return d.Model(&types.Incident{}) +} + +func (d *Db) IncidentUpdates() Database { + return d.Model(&types.IncidentUpdate{}) +} + +func (d *Db) Hits() Database { + return d.Model(&types.Hit{}) +} + +func (d *Db) Integrations() Database { + return d.Model(&types.Integration{}) +} + +func (d *Db) Failures() Database { + return d.Model(&types.Failure{}) +} + +func (d *Db) Checkins() Database { + return d.Model(&types.Checkin{}) +} + +func (d *Db) CheckinHits() Database { + return d.Model(&types.CheckinHit{}) +} + +func (d *Db) Messages() Database { + return d.Model(&types.Message{}) +} + +func (it *Db) Requests(r *http.Request, o isObject) Database { + g := ParseQueries(r, o) return g.db } @@ -446,12 +505,6 @@ func (it *Db) Error() error { return it.Database.Error } -func (it *Db) Hits() ([]*types.Hit, error) { - var hits []*types.Hit - err := it.Find(&hits) - return hits, err.Error() -} - func (it *Db) Since(ago time.Time) Database { return it.Where("created_at > ?", it.FormatTime(ago)) } @@ -460,37 +513,7 @@ func (it *Db) Between(t1 time.Time, t2 time.Time) Database { return it.Where("created_at BETWEEN ? AND ?", it.FormatTime(t1), it.FormatTime(t2)) } -// DateScan struct is for creating the charts.js graph JSON array -type DateScan struct { - CreatedAt string `json:"x,omitempty"` - Value int64 `json:"y"` -} - type TimeValue struct { Timeframe string `json:"timeframe"` Amount float64 `json:"amount"` } - -func (it *Db) ToChart() ([]*DateScan, error) { - rows, err := it.Database.Rows() - if err != nil { - return nil, err - } - var data []*DateScan - for rows.Next() { - gd := new(DateScan) - var createdAt string - var value float64 - if err := rows.Scan(&createdAt, &value); err != nil { - return nil, err - } - createdTime, err := time.Parse(TIME, createdAt) - if err != nil { - return nil, err - } - gd.CreatedAt = createdTime.UTC().String() - gd.Value = int64(value * 1000) - data = append(data, gd) - } - return data, err -} diff --git a/database/failures.go b/database/failures.go index 2b8b3010..257da83c 100644 --- a/database/failures.go +++ b/database/failures.go @@ -2,31 +2,50 @@ package database import ( "github.com/hunterlong/statping/types" + "time" ) -type failures struct { - DB Database +type FailureObj struct { + o *Object } -func (f *failures) All() []*types.Failure { +type Failurer interface { + Model() []*types.Failure +} + +func (f *FailureObj) Model() []*types.Failure { + return f.All() +} + +func (f *FailureObj) All() []*types.Failure { var fails []*types.Failure - f.DB = f.DB.Find(&fails) + f.o.db.Find(&fails) return fails } -func (f *failures) Last(amount int) *types.Failure { +func (f *FailureObj) DeleteAll() error { + query := database.Exec(`DELETE FROM failures WHERE service = ?`, f.o.Id) + return query.Error() +} + +func (f *FailureObj) Last(amount int) *types.Failure { var fail types.Failure - f.DB = f.DB.Limit(amount).Find(&fail) + f.o.db.Limit(amount).Last(&fail) return &fail } -func (f *failures) Count() int { +func (f *FailureObj) Count() int { var amount int - f.DB = f.DB.Count(&amount) + f.o.db.Count(&amount) return amount } -func (f *failures) Find(data interface{}) error { - q := f.Find(&data) - return q +func (f *FailureObj) Since(t time.Time) []*types.Failure { + var fails []*types.Failure + f.o.db.Since(t).Find(&fails) + return fails +} + +func (f *FailureObj) object() *Object { + return f.o } diff --git a/database/group.go b/database/group.go index 72f877ee..a7bf8f17 100644 --- a/database/group.go +++ b/database/group.go @@ -4,23 +4,44 @@ import "github.com/hunterlong/statping/types" type GroupObj struct { *types.Group - db Database + o *Object + + Grouper } type Grouper interface { - Services() Database + Services() []*types.Service + Model() *types.Group } -func (o *Object) AsGroup() *types.Group { - return o.model.(*types.Group) +func AllGroups() []*GroupObj { + var groups []*types.Group + query := database.Groups() + query.Find(&groups) + return wrapGroups(groups, query) } -func (it *Db) GetGroup(id int64) (*GroupObj, error) { +func (g *Db) GetGroup(id int64) (*GroupObj, error) { var group types.Group - query := it.Model(&types.Group{}).Where("id = ?", id).Find(&group) - return &GroupObj{&group, it}, query.Error() + query := database.Groups().Where("id = ?", id) + finder := query.Find(&group) + return &GroupObj{Group: &group, o: wrapObject(id, &group, query)}, finder.Error() } -func (it *GroupObj) Services() Database { - return it.db.Model(&types.Service{}).Where("service = ?", it.Id) +func (g *GroupObj) Services() []*types.Service { + var services []*types.Service + database.Services().Where("group = ?", g.Id).Find(&services) + return services +} + +func (g *GroupObj) Model() *types.Group { + return g.Group +} + +func wrapGroups(all []*types.Group, db Database) []*GroupObj { + var arr []*GroupObj + for _, v := range all { + arr = append(arr, &GroupObj{Group: v, o: wrapObject(v.Id, v, db)}) + } + return arr } diff --git a/database/grouping.go b/database/grouping.go index a6df21c3..cf6f551b 100644 --- a/database/grouping.go +++ b/database/grouping.go @@ -3,7 +3,6 @@ package database import ( "fmt" "github.com/hunterlong/statping/types" - "github.com/hunterlong/statping/utils" "net/http" "net/url" "strconv" @@ -36,15 +35,16 @@ type GroupQuery struct { FillEmpty bool } +func (b GroupQuery) Find(data interface{}) error { + return b.db.Find(&data).Error() +} + func (b GroupQuery) Database() Database { return b.db } var ( - ByCount = By("COUNT(id) as amount") - BySum = func(column string) By { - return By(fmt.Sprintf("SUM(%s) as amount", column)) - } + ByCount = By("COUNT(id) as amount") ByAverage = func(column string) By { return By(fmt.Sprintf("AVG(%s) as amount", column)) } @@ -154,16 +154,21 @@ func (g *GroupBy) duration() time.Duration { } } -func ParseQueries(r *http.Request, db Database) *GroupQuery { +func toInt(v string) int64 { + val, _ := strconv.Atoi(v) + return int64(val) +} + +func ParseQueries(r *http.Request, o isObject) *GroupQuery { fields := parseGet(r) grouping := fields.Get("group") if grouping == "" { grouping = "hour" } - startField := utils.ToInt(fields.Get("start")) - endField := utils.ToInt(fields.Get("end")) - limit := utils.ToInt(fields.Get("limit")) - offset := utils.ToInt(fields.Get("offset")) + startField := toInt(fields.Get("start")) + endField := toInt(fields.Get("end")) + limit := toInt(fields.Get("limit")) + offset := toInt(fields.Get("offset")) fill, _ := strconv.ParseBool(fields.Get("fill")) orderBy := fields.Get("order") if limit == 0 { @@ -180,6 +185,8 @@ func ParseQueries(r *http.Request, db Database) *GroupQuery { FillEmpty: fill, } + db := o.object().db + if query.Limit != 0 { db = db.Limit(query.Limit) } diff --git a/database/hits.go b/database/hits.go index 50fdfcbe..408ca5f7 100644 --- a/database/hits.go +++ b/database/hits.go @@ -1,30 +1,38 @@ package database -import "github.com/hunterlong/statping/types" +import ( + "github.com/hunterlong/statping/types" + "time" +) -type hits struct { - DB Database +type HitObj struct { + o *Object } -func (h *hits) All() []*types.Hit { +func (h *HitObj) All() []*types.Hit { var fails []*types.Hit - h.DB = h.DB.Find(&fails) + h.o.db.Find(&fails) return fails } -func (h *hits) Last(amount int) *types.Hit { +func (h *HitObj) Last(amount int) *types.Hit { var hits types.Hit - h.DB = h.DB.Limit(amount).Find(&hits) + h.o.db.Limit(amount).Find(&hits) return &hits } -func (h *hits) Count() int { +func (h *HitObj) Since(t time.Time) []*types.Hit { + var hits []*types.Hit + h.o.db.Since(t).Find(&hits) + return hits +} + +func (h *HitObj) Count() int { var amount int - h.DB = h.DB.Count(&amount) + h.o.db.Count(&amount) return amount } -func (h *hits) Find(data interface{}) error { - q := h.Find(&data) - return q +func (h *HitObj) object() *Object { + return h.o } diff --git a/database/incident.go b/database/incident.go index 3447c7f3..80fc91fc 100644 --- a/database/incident.go +++ b/database/incident.go @@ -4,15 +4,28 @@ import "github.com/hunterlong/statping/types" type IncidentObj struct { *types.Incident - db Database -} - -func (o *IncidentObj) AsIncident() *types.Incident { - return o.Incident + o *Object } func Incident(id int64) (*IncidentObj, error) { var incident types.Incident - query := database.Model(&types.Incident{}).Where("id = ?", id).Find(&incident) - return &IncidentObj{Incident: &incident, db: query}, query.Error() + query := database.Incidents().Where("id = ?", id) + finder := query.Find(&incident) + return &IncidentObj{Incident: &incident, o: wrapObject(id, &incident, query)}, finder.Error() +} + +func AllIncidents() []*types.Incident { + var incidents []*types.Incident + database.Incidents().Find(&incidents) + return incidents +} + +func (i *IncidentObj) Updates() []*types.IncidentUpdate { + var incidents []*types.IncidentUpdate + database.IncidentUpdates().Where("incident = ?", i.Id).Find(&incidents) + return incidents +} + +func (i *IncidentObj) object() *Object { + return i.o } diff --git a/database/integration.go b/database/integration.go new file mode 100644 index 00000000..ba987624 --- /dev/null +++ b/database/integration.go @@ -0,0 +1,19 @@ +package database + +import "github.com/hunterlong/statping/types" + +type IntegrationObj struct { + *types.Integration + o *Object +} + +func Integration(id int64) (*IntegrationObj, error) { + var integration types.Integration + query := database.Model(&types.Integration{}).Where("id = ?", id) + finder := query.Find(&integration) + return &IntegrationObj{Integration: &integration, o: wrapObject(id, &integration, query)}, finder.Error() +} + +func (i *IntegrationObj) object() *Object { + return i.o +} diff --git a/database/message.go b/database/message.go new file mode 100644 index 00000000..a0537d70 --- /dev/null +++ b/database/message.go @@ -0,0 +1,19 @@ +package database + +import "github.com/hunterlong/statping/types" + +type MessageObj struct { + *types.Message + o *Object +} + +func Message(id int64) (*MessageObj, error) { + var message types.Message + query := database.Messages().Where("id = ?", id) + finder := query.Find(&message) + return &MessageObj{Message: &message, o: wrapObject(id, &message, query)}, finder.Error() +} + +func (m *MessageObj) object() *Object { + return m.o +} diff --git a/database/service.go b/database/service.go index 6b572ac0..0e984599 100644 --- a/database/service.go +++ b/database/service.go @@ -1,31 +1,217 @@ package database -import "github.com/hunterlong/statping/types" +import ( + "fmt" + "github.com/hunterlong/statping/types" + "strconv" + "strings" + "time" +) type ServiceObj struct { *types.Service - failures + o *Object + + Servicer } -func (o *Object) AsService() *types.Service { - return o.model.(*types.Service) +type Servicer interface { + Hits() *HitObj + Failures() *FailureObj + AllCheckins() []*CheckinObj + Model() *types.Service + Interval() time.Duration + DowntimeText() string + + Hittable } -func Service(id int64) (HitsFailures, error) { +type Hittable interface { + CreateHit(*types.Hit) (int64, error) +} + +func Service(id int64) (*ServiceObj, error) { var service types.Service - query := database.Model(&types.Service{}).Where("id = ?", id).Find(&service) - return &ServiceObj{Service: &service}, query.Error() + query := database.Services().Where("id = ?", id) + finer := query.Find(&service) + return &ServiceObj{Service: &service, o: wrapObject(id, &service, query)}, finer.Error() } -func (s *ServiceObj) Hits() *hits { - return &hits{ - database.Model(&types.Hit{}).Where("service = ?", s.Id), +func wrapServices(all []*types.Service, db Database) []*ServiceObj { + var arr []*ServiceObj + for _, v := range all { + arr = append(arr, &ServiceObj{Service: v, o: wrapObject(v.Id, v, db)}) } + return arr } -func (s *ServiceObj) Failures() *failures { - return &failures{ - database.Model(&types.Failure{}). - Where("method != 'checkin' AND service = ?", s.Id).Order("id desc"), - } +func Services() []*ServiceObj { + var services []*types.Service + db := database.Services().Order("order_id desc") + db.Find(&services) + return wrapServices(services, db) +} + +func (s *ServiceObj) AllCheckins() []*CheckinObj { + var checkins []*types.Checkin + query := database.Checkins().Where("service = ?", s.Id) + query.Find(&checkins) + return wrapCheckins(checkins, query) +} + +func (s *ServiceObj) DowntimeText() string { + last := s.Failures().Last(1) + return parseError(last) +} + +// ParseError returns a human readable error for a Failure +func parseError(f *types.Failure) string { + if f.Method == "checkin" { + return fmt.Sprintf("Checkin is Offline") + } + err := strings.Contains(f.Issue, "connection reset by peer") + if err { + return fmt.Sprintf("Connection Reset") + } + err = strings.Contains(f.Issue, "operation timed out") + if err { + return fmt.Sprintf("HTTP Request Timed Out") + } + err = strings.Contains(f.Issue, "x509: certificate is valid") + if err { + return fmt.Sprintf("SSL Certificate invalid") + } + err = strings.Contains(f.Issue, "Client.Timeout exceeded while awaiting headers") + if err { + return fmt.Sprintf("Connection Timed Out") + } + err = strings.Contains(f.Issue, "no such host") + if err { + return fmt.Sprintf("Domain is offline or not found") + } + err = strings.Contains(f.Issue, "HTTP Status Code") + if err { + return fmt.Sprintf("Incorrect HTTP Status Code") + } + err = strings.Contains(f.Issue, "connection refused") + if err { + return fmt.Sprintf("Connection Failed") + } + err = strings.Contains(f.Issue, "can't assign requested address") + if err { + return fmt.Sprintf("Unable to Request Address") + } + err = strings.Contains(f.Issue, "no route to host") + if err { + return fmt.Sprintf("Domain is offline or not found") + } + err = strings.Contains(f.Issue, "i/o timeout") + if err { + return fmt.Sprintf("Connection Timed Out") + } + err = strings.Contains(f.Issue, "Client.Timeout exceeded while reading body") + if err { + return fmt.Sprintf("Timed Out on Response Body") + } + return f.Issue +} + +func (s *ServiceObj) Interval() time.Duration { + return time.Duration(s.Service.Interval) * time.Second +} + +func (s *ServiceObj) Model() *types.Service { + return s.Service +} + +func (s *ServiceObj) Hits() *HitObj { + fmt.Println("hits") + query := database.Hits().Where("service = ?", s.Id) + return &HitObj{wrapObject(s.Id, nil, query)} +} + +func (s *ServiceObj) Failures() *FailureObj { + q := database.Failures().Where("method != 'checkin' AND service = ?", s.Id) + return &FailureObj{wrapObject(s.Id, nil, q)} +} + +func (s *ServiceObj) Group() *GroupObj { + var group types.Group + q := database.Groups().Where("id = ?", s.GroupId) + finder := q.Find(&group) + if finder.Error() != nil { + return nil + } + return &GroupObj{Group: &group, o: wrapObject(group.Id, &group, q)} +} + +func (s *ServiceObj) object() *Object { + return s.o +} + +func (s *ServiceObj) UpdateStats() *types.Stats { + s.Online24Hours = s.OnlineDaysPercent(1) + s.Online7Days = s.OnlineDaysPercent(7) + s.AvgResponse = s.AvgTime() + s.FailuresLast24Hours = len(s.Failures().Since(time.Now().Add(-time.Hour * 24))) + return s.Stats +} + +// AvgTime will return the average amount of time for a service to response back successfully +func (s *ServiceObj) AvgTime() float64 { + var sum float64 + database.Hits(). + Select("AVG(latency) as amount"). + Where("service = ?", s.Id).Pluck("amount", &sum).Debug() + + total := s.Hits().Count() + + if total == 0 { + return 0 + } + avg := sum / float64(total) * 100 + f, _ := strconv.ParseFloat(fmt.Sprintf("%0.0f", avg*10), 32) + return f +} + +// OnlineDaysPercent returns the service's uptime percent within last 24 hours +func (s *ServiceObj) OnlineDaysPercent(days int) float32 { + ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour) + return s.OnlineSince(ago) +} + +// OnlineSince accepts a time since parameter to return the percent of a service's uptime. +func (s *ServiceObj) OnlineSince(ago time.Time) float32 { + failed := s.Failures().Since(ago) + if len(failed) == 0 { + s.Online24Hours = 100.00 + return s.Online24Hours + } + total := s.Hits().Since(ago) + if len(total) == 0 { + s.Online24Hours = 0 + return s.Online24Hours + } + avg := float64(len(failed)) / float64(len(total)) * 100 + avg = 100 - avg + if avg < 0 { + avg = 0 + } + amount, _ := strconv.ParseFloat(fmt.Sprintf("%0.2f", avg), 10) + s.Online24Hours = float32(amount) + return s.Online24Hours +} + +// Downtime returns the amount of time of a offline service +func (s *ServiceObj) Downtime() time.Duration { + hits := s.Hits().Last(1) + fail := s.Failures().Last(1) + if fail == nil { + return time.Duration(0) + } + if hits == nil { + return time.Now().UTC().Sub(fail.CreatedAt.UTC()) + } + since := fail.CreatedAt.UTC().Sub(hits.CreatedAt.UTC()) + return since } diff --git a/database/user.go b/database/user.go index 833a00b4..596fdf55 100644 --- a/database/user.go +++ b/database/user.go @@ -4,16 +4,28 @@ import "github.com/hunterlong/statping/types" type UserObj struct { *types.User -} - -func (o *Object) AsUser() *UserObj { - return &UserObj{ - User: o.model.(*types.User), - } + o *Object } func User(id int64) (*UserObj, error) { var user types.User - query := database.Model(&types.User{}).Where("id = ?", id).Find(&user) - return &UserObj{User: &user}, query.Error() + query := database.Users().Where("id = ?", id) + finder := query.First(&user) + return &UserObj{User: &user, o: wrapObject(id, &user, query)}, finder.Error() +} + +func UserByUsername(username string) (*UserObj, error) { + var user types.User + query := database.Users().Where("username = ?", username) + finder := query.First(&user) + return &UserObj{User: &user, o: wrapObject(user.Id, &user, query)}, finder.Error() +} + +func AllUsers(input interface{}) error { + err := database.Users().Find(&input) + return err.Error() +} + +func (u *UserObj) object() *Object { + return u.o } diff --git a/handlers/api.go b/handlers/api.go index 0608fbaf..669b6ac3 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/hunterlong/statping/core" "github.com/hunterlong/statping/core/notifier" + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net/http" @@ -134,9 +135,9 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht var objName string var objId int64 switch v := obj.(type) { - case types.ServiceInterface: + case types.Servicer: objName = "service" - objId = v.Select().Id + objId = v.Model().Id case *notifier.Notification: objName = "notifier" objId = v.Id @@ -151,9 +152,9 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht case *types.Group: objName = "group" objId = v.Id - case *core.Group: + case database.Grouper: objName = "group" - objId = v.Id + objId = v.Model().Id case *core.Checkin: objName = "checkin" objId = v.Id diff --git a/handlers/checkin.go b/handlers/checkin.go index 9d872032..35babd2c 100644 --- a/handlers/checkin.go +++ b/handlers/checkin.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/gorilla/mux" "github.com/hunterlong/statping/core" + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net" @@ -27,23 +28,21 @@ import ( ) func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) { - checkins := core.AllCheckins() - for _, c := range checkins { - c.Hits = c.AllHits() - c.Failures = c.GetFailures(64) - } + checkins := database.AllCheckins() returnJson(checkins, w, r) } func apiCheckinHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - checkin := core.SelectCheckin(vars["api"]) - if checkin == nil { + checkin, err := database.CheckinByKey(vars["api"]) + if err != nil { sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r) return } - checkin.Hits = checkin.LimitedHits(32) - checkin.Failures = checkin.GetFailures(32) + out := checkin.Model() + + out.Hits = checkin.Hits() + out.Failures = checkin.Failures(32) returnJson(checkin, w, r) } diff --git a/handlers/function.go b/handlers/function.go index b27d80ba..04742c16 100644 --- a/handlers/function.go +++ b/handlers/function.go @@ -1,7 +1,7 @@ package handlers import ( - "bytes" + "encoding/json" "github.com/hunterlong/statping/core" "html/template" "net/http" @@ -12,37 +12,30 @@ var ( basePath = "/" ) -type HandlerFunc func(Responder, *Request) - -func (f HandlerFunc) ServeHTTP(w Responder, r *Request) { - f(w, r) +type CustomResponseWriter struct { + body []byte + statusCode int + header http.Header } -type Handler interface { - ServeHTTP(Responder, *Request) +func NewCustomResponseWriter() *CustomResponseWriter { + return &CustomResponseWriter{ + header: http.Header{}, + } } -type Responder struct { - Code int // the HTTP response code from WriteHeader - HeaderMap http.Header // the HTTP response headers - Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to - Flushed bool +func (w *CustomResponseWriter) Header() http.Header { + return w.header } -type Request struct { - *http.Request +func (w *CustomResponseWriter) Write(b []byte) (int, error) { + w.body = b + // implement it as per your requirement + return 0, nil } -func (r Responder) Header() http.Header { - return r.HeaderMap -} - -func (r Responder) Write(p []byte) (int, error) { - return r.Body.Write(p) -} - -func (r Responder) WriteHeader(statusCode int) { - r.Code = statusCode +func (w *CustomResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode } func parseForm(r *http.Request) url.Values { @@ -55,6 +48,20 @@ func parseGet(r *http.Request) url.Values { return r.Form } +func decodeRequest(r *http.Request, object interface{}) error { + decoder := json.NewDecoder(r.Body) + defer r.Body.Close() + return decoder.Decode(&object) +} + +type parsedObject struct { + Error Error +} + +func serviceFromID(r *http.Request, object interface{}) error { + return nil +} + var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap { return template.FuncMap{ "VERSION": func() string { diff --git a/handlers/incident.go b/handlers/incident.go index 73365602..91c6c201 100644 --- a/handlers/incident.go +++ b/handlers/incident.go @@ -4,13 +4,14 @@ import ( "encoding/json" "github.com/gorilla/mux" "github.com/hunterlong/statping/core" + "github.com/hunterlong/statping/database" "github.com/hunterlong/statping/types" "github.com/hunterlong/statping/utils" "net/http" ) func apiAllIncidentsHandler(w http.ResponseWriter, r *http.Request) { - incidents := core.AllIncidents() + incidents := database.AllIncidents() returnJson(incidents, w, r) } diff --git a/handlers/middleware.go b/handlers/middleware.go index 549d2ce6..8557ce18 100644 --- a/handlers/middleware.go +++ b/handlers/middleware.go @@ -130,8 +130,8 @@ func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect boo } // cached is a middleware function that accepts a duration and content type and will cache the response of the original request -func cached(duration, contentType string, handler func(w Responder, r *Request)) Responder { - return HandlerFunc(func(w Responder, r *Request) { +func cached(duration, contentType string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { content := CacheStorage.Get(r.RequestURI) w.Header().Set("Content-Type", contentType) w.Header().Set("Access-Control-Allow-Origin", "*") diff --git a/handlers/services.go b/handlers/services.go index 39e49b96..b70834a4 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -150,7 +150,7 @@ func apiServiceRunningHandler(w http.ResponseWriter, r *http.Request) { sendJsonAction(service, "running", w, r) } -func apiServiceDataHandler(w Responder, r *http.Request) { +func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) service, err := database.Service(utils.ToInt(vars["id"])) if err != nil { @@ -158,7 +158,7 @@ func apiServiceDataHandler(w Responder, r *http.Request) { return } - groupQuery := database.ParseQueries(r, service.Hits().DB) + groupQuery := database.ParseQueries(r, service.Hits()) obj := core.GraphData(groupQuery, &types.Hit{}, database.ByAverage("latency")) returnJson(obj, w, r) @@ -171,7 +171,7 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) { sendErrorJson(errors.New("service data not found"), w, r) return } - groupQuery := database.ParseQueries(r, service.Hits().DB) + groupQuery := database.ParseQueries(r, service.Hits()) obj := core.GraphData(groupQuery, &types.Failure{}, database.ByCount) returnJson(obj, w, r) @@ -184,7 +184,7 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) { sendErrorJson(errors.New("service data not found"), w, r) return } - groupQuery := database.ParseQueries(r, service.Hits().DB) + groupQuery := database.ParseQueries(r, service.Hits()) obj := core.GraphData(groupQuery, &types.Hit{}, database.ByAverage("ping_time")) returnJson(obj, w, r) @@ -274,7 +274,7 @@ func joinServices(srvs []types.ServiceInterface) []*types.Service { return services } -func servicesDeleteFailuresHandler(w Responder, r *http.Request) { +func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) service := core.SelectService(utils.ToInt(vars["id"])) if service == nil { @@ -293,12 +293,9 @@ func apiServiceFailuresHandler(r *http.Request) interface{} { return errors.New("service not found") } - service.Hits() - - service.Failures() - var fails []types.Failure - service.Failures().DB.Requests(r).Find(&fails) + database.ParseQueries(r, service.Failures()).Find(&fails) + return fails } @@ -310,7 +307,7 @@ func apiServiceHitsHandler(r *http.Request) interface{} { } var hits []types.Hit - service.Hits().Find(&hits) + database.ParseQueries(r, service.Hits()).Find(&hits) return hits } diff --git a/types/checkin.go b/types/checkin.go index 0c924362..320ee2c2 100644 --- a/types/checkin.go +++ b/types/checkin.go @@ -36,10 +36,6 @@ type Checkin struct { Failures []*Failure `gorm:"-" json:"failures"` } -type CheckinInterface interface { - Select() *Checkin -} - // BeforeCreate for Checkin will set CreatedAt to UTC func (c *Checkin) BeforeCreate() (err error) { if c.CreatedAt.IsZero() { diff --git a/types/core.go b/types/core.go index c4f6b89b..19c2d8cc 100644 --- a/types/core.go +++ b/types/core.go @@ -29,28 +29,31 @@ type AllNotifiers interface{} // will be saved into 1 row in the 'core' table. You can use the core.CoreApp // global variable to interact with the attributes to the application, such as services. type Core struct { - Name string `gorm:"not null;column:name" json:"name"` - Description string `gorm:"not null;column:description" json:"description,omitempty"` - ConfigFile string `gorm:"column:config" json:"-"` - ApiKey string `gorm:"column:api_key" json:"api_key" scope:"admin"` - ApiSecret string `gorm:"column:api_secret" json:"api_secret" scope:"admin"` - Style string `gorm:"not null;column:style" json:"style,omitempty"` - Footer NullString `gorm:"column:footer" json:"footer"` - Domain string `gorm:"not null;column:domain" json:"domain"` - Version string `gorm:"column:version" json:"version"` - Setup bool `gorm:"-" json:"setup"` - MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"` - UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"` - Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"` - LoggedIn bool `gorm:"-" json:"logged_in"` - CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` - Started time.Time `gorm:"-" json:"started_on"` - Services []ServiceInterface `gorm:"-" json:"-"` - Plugins []*Info `gorm:"-" json:"-"` - Repos []PluginJSON `gorm:"-" json:"-"` - AllPlugins []PluginActions `gorm:"-" json:"-"` - Notifications []AllNotifiers `gorm:"-" json:"-"` - Config *DbConfig `gorm:"-" json:"-"` - Integrations []Integrator `gorm:"-" json:"-"` + Name string `gorm:"not null;column:name" json:"name"` + Description string `gorm:"not null;column:description" json:"description,omitempty"` + ConfigFile string `gorm:"column:config" json:"-"` + ApiKey string `gorm:"column:api_key" json:"api_key" scope:"admin"` + ApiSecret string `gorm:"column:api_secret" json:"api_secret" scope:"admin"` + Style string `gorm:"not null;column:style" json:"style,omitempty"` + Footer NullString `gorm:"column:footer" json:"footer"` + Domain string `gorm:"not null;column:domain" json:"domain"` + Version string `gorm:"column:version" json:"version"` + Setup bool `gorm:"-" json:"setup"` + MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"` + UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"` + Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"` + LoggedIn bool `gorm:"-" json:"logged_in"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` + Started time.Time `gorm:"-" json:"started_on"` + Plugins []*Info `gorm:"-" json:"-"` + Repos []PluginJSON `gorm:"-" json:"-"` + AllPlugins []PluginActions `gorm:"-" json:"-"` + Notifications []AllNotifiers `gorm:"-" json:"-"` + Config *DbConfig `gorm:"-" json:"-"` + Integrations []Integrator `gorm:"-" json:"-"` +} + +type Servicer interface { + Model() *Service } diff --git a/types/failure.go b/types/failure.go index a0f8c0da..0e6ea788 100644 --- a/types/failure.go +++ b/types/failure.go @@ -33,10 +33,6 @@ type Failure struct { CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` } -type FailureInterface interface { - Select() *Failure -} - // BeforeCreate for Failure will set CreatedAt to UTC func (f *Failure) BeforeCreate() (err error) { if f.CreatedAt.IsZero() { @@ -45,7 +41,7 @@ func (f *Failure) BeforeCreate() (err error) { return } -type FailSort []FailureInterface +type FailSort []Failure func (s FailSort) Len() int { return len(s) @@ -54,5 +50,5 @@ func (s FailSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s FailSort) Less(i, j int) bool { - return s[i].Select().Id < s[j].Select().Id + return s[i].Id < s[j].Id } diff --git a/types/service.go b/types/service.go index 55f2b7cc..30a77d59 100644 --- a/types/service.go +++ b/types/service.go @@ -46,8 +46,7 @@ type Service struct { Online24Hours float32 `gorm:"-" json:"online_24_hours"` Online7Days float32 `gorm:"-" json:"online_7_days"` AvgResponse float64 `gorm:"-" json:"avg_response"` - FailuresLast24Hours uint64 `gorm:"-" json:"failures_24_hours"` - LastFailure FailureInterface `gorm:"-" json:"last_failure,omitempty"` + FailuresLast24Hours int `gorm:"-" json:"failures_24_hours"` Running chan bool `gorm:"-" json:"-"` Checkpoint time.Time `gorm:"-" json:"-"` SleepDuration time.Duration `gorm:"-" json:"-"` @@ -59,9 +58,13 @@ type Service struct { SuccessNotified bool `gorm:"-" json:"-"` // Is 'true' if the user has already be informed that the Services now again available LastStatusCode int `gorm:"-" json:"status_code"` LastOnline time.Time `gorm:"-" json:"last_success"` - Failures []FailureInterface `gorm:"-" json:"failures,omitempty" scope:"user,admin"` + Failures []Failure `gorm:"-" json:"failures,omitempty" scope:"user,admin"` Checkins []CheckinInterface `gorm:"-" json:"checkins,omitempty" scope:"user,admin"` - Stats Stater `gorm:"-" json:"stats,omitempty"` + Stats *Stats `gorm:"-" json:"stats,omitempty"` +} + +type CheckinInterface interface { + Model() *Checkin } type Stater interface { @@ -82,15 +85,6 @@ func (s *Service) BeforeCreate() (err error) { return } -type ServiceInterface interface { - Select() *Service - CheckQueue(bool) - Check(bool) - Create(bool) (int64, error) - Update(bool) error - Delete() error -} - // Start will create a channel for the service checking go routine func (s *Service) Start() { s.Running = make(chan bool)