diff --git a/cli.go b/cli.go index cef480c8..c3fe9de6 100644 --- a/cli.go +++ b/cli.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/plugin" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "github.com/joho/godotenv" "io/ioutil" @@ -105,8 +106,9 @@ func RunOnce() { if err != nil { utils.Log(4, err) } - for _, s := range core.CoreApp.Services { - out := s.Check() + for _, ser := range core.CoreApp.Services { + s := ser.ToService() + out := core.ServiceCheck(s) fmt.Printf(" Service %v | URL: %v | Latency: %0.0fms | Online: %v\n", out.Name, out.Domain, (out.Latency * 1000), out.Online) } } @@ -148,33 +150,33 @@ func TestPlugin(plug plugin.PluginActions) { core.OnLoad(core.DbSession) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnSuccess(Service)'") - core.OnSuccess(core.SelectService(1)) + core.OnSuccess(core.SelectService(1).ToService()) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnFailure(Service, FailureData)'") fakeFailD := core.FailureData{ Issue: "No issue, just testing this plugin. This would include HTTP failure information though", } - core.OnFailure(core.SelectService(1), fakeFailD) + core.OnFailure(core.SelectService(1).ToService(), fakeFailD) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnSettingsSaved(Core)'") fmt.Println(BRAKER) core.OnSettingsSaved(core.CoreApp) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnNewService(Service)'") - core.OnNewService(core.SelectService(2)) + core.OnNewService(core.SelectService(2).ToService()) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnNewUser(User)'") user, _ := core.SelectUser(1) core.OnNewUser(user) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnUpdateService(Service)'") - srv := core.SelectService(2) + srv := core.SelectService(2).ToService() srv.Type = "http" srv.Domain = "https://yahoo.com" core.OnUpdateService(srv) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnDeletedService(Service)'") - core.OnDeletedService(core.SelectService(1)) + core.OnDeletedService(core.SelectService(1).ToService()) fmt.Println("\n" + BRAKER) } @@ -212,21 +214,21 @@ func FakeSeed(plug plugin.PluginActions) { core.CoreApp.ApiSecret = "0x0x0x0x0" core.CoreApp.ApiKey = "abcdefg12345" - fakeSrv := &core.Service{ + fakeSrv := &types.Service{ Name: "Test Plugin Service", Domain: "https://google.com", Method: "GET", } - fakeSrv.Create() + core.CreateService(fakeSrv) - fakeSrv2 := &core.Service{ + fakeSrv2 := &types.Service{ Name: "Awesome Plugin Service", Domain: "https://netflix.com", Method: "GET", } - fakeSrv2.Create() + core.CreateService(fakeSrv2) - fakeUser := &core.User{ + fakeUser := &types.User{ Id: 6334, Username: "Bulbasaur", Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy", @@ -234,35 +236,37 @@ func FakeSeed(plug plugin.PluginActions) { Admin: true, CreatedAt: time.Now(), } - fakeUser.Create() + core.CreateUser(fakeUser) - fakeUser = &core.User{ + fakeUser = &types.User{ Id: 6335, Username: "Billy", Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy", Email: "info@awesome.com", CreatedAt: time.Now(), } - fakeUser.Create() + core.CreateUser(fakeUser) for i := 0; i <= 50; i++ { dd := core.HitData{ Latency: rand.Float64(), } - fakeSrv.CreateHit(dd) + core.CreateServiceHit(fakeSrv, dd) + dd = core.HitData{ Latency: rand.Float64(), } - fakeSrv2.CreateHit(dd) + core.CreateServiceHit(fakeSrv2, dd) + fail := core.FailureData{ Issue: "This is not an issue, but it would container HTTP response errors.", } - fakeSrv.CreateFailure(fail) + core.CreateServiceFailure(fakeSrv, fail) fail = core.FailureData{ Issue: "HTTP Status Code 521 did not match 200", } - fakeSrv2.CreateFailure(fail) + core.CreateServiceFailure(fakeSrv, fail) } fmt.Println("Seeding example data is complete, running Plugin Tests") diff --git a/core/checker.go b/core/checker.go index 976516e7..e21b52b6 100644 --- a/core/checker.go +++ b/core/checker.go @@ -18,21 +18,22 @@ type FailureData types.FailureData func CheckServices() { CoreApp.Services, _ = SelectAllServices() utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services))) - for _, v := range CoreApp.Services { - obj := v + for _, ser := range CoreApp.Services { + s := ser.ToService() + obj := s //go obj.StartCheckins() - obj.stopRoutine = make(chan struct{}) - go obj.CheckQueue() + obj.StopRoutine = make(chan struct{}) + go CheckQueue(obj) } } -func (s *Service) CheckQueue() { +func CheckQueue(s *types.Service) { for { select { - case <-s.stopRoutine: + case <-s.StopRoutine: return default: - s.Check() + ServiceCheck(s) if s.Interval < 1 { s.Interval = 1 } @@ -43,7 +44,7 @@ func (s *Service) CheckQueue() { } } -func (s *Service) DNSCheck() (float64, error) { +func DNSCheck(s *types.Service) (float64, error) { t1 := time.Now() url, err := url.Parse(s.Domain) if err != nil { @@ -58,13 +59,13 @@ func (s *Service) DNSCheck() (float64, error) { return subTime, err } -func (s *Service) Check() *Service { - dnsLookup, err := s.DNSCheck() +func ServiceCheck(s *types.Service) *types.Service { + dnsLookup, err := DNSCheck(s) if err != nil { - s.Failure(fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err)) + RecordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err)) return s } - s.dnsLookup = dnsLookup + s.DnsLookup = dnsLookup t1 := time.Now() client := http.Client{ Timeout: 30 * time.Second, @@ -77,14 +78,14 @@ func (s *Service) Check() *Service { response, err = client.Get(s.Domain) } if err != nil { - s.Failure(fmt.Sprintf("HTTP Error %v", err)) + RecordFailure(s, fmt.Sprintf("HTTP Error %v", err)) return s } response.Header.Set("User-Agent", "StatupMonitor") t2 := time.Now() s.Latency = t2.Sub(t1).Seconds() if err != nil { - s.Failure(fmt.Sprintf("HTTP Error %v", err)) + RecordFailure(s, fmt.Sprintf("HTTP Error %v", err)) return s } defer response.Body.Close() @@ -100,20 +101,20 @@ func (s *Service) Check() *Service { if !match { s.LastResponse = string(contents) s.LastStatusCode = response.StatusCode - s.Failure(fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected)) + RecordFailure(s, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected)) return s } } if s.ExpectedStatus != response.StatusCode { s.LastResponse = string(contents) s.LastStatusCode = response.StatusCode - s.Failure(fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus)) + RecordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus)) return s } s.LastResponse = string(contents) s.LastStatusCode = response.StatusCode s.Online = true - s.Record(response) + RecordSuccess(s, response) return s } @@ -121,23 +122,23 @@ type HitData struct { Latency float64 } -func (s *Service) Record(response *http.Response) { +func RecordSuccess(s *types.Service, response *http.Response) { s.Online = true s.LastOnline = time.Now() data := HitData{ Latency: s.Latency, } - s.CreateHit(data) + CreateServiceHit(s, data) OnSuccess(s) } -func (s *Service) Failure(issue string) { +func RecordFailure(s *types.Service, issue string) { s.Online = false data := FailureData{ Issue: issue, } utils.Log(2, fmt.Sprintf("Service %v Failing: %v", s.Name, issue)) - s.CreateFailure(data) + CreateServiceFailure(s, data) //SendFailureEmail(s) OnFailure(s, data) } diff --git a/core/checkin.go b/core/checkin.go index 0aa6ef70..9dabb240 100644 --- a/core/checkin.go +++ b/core/checkin.go @@ -14,8 +14,9 @@ func (c *Checkin) String() string { return c.Api } -func FindCheckin(api string) *Checkin { - for _, s := range CoreApp.Services { +func FindCheckin(api string) *types.Checkin { + for _, ser := range CoreApp.Services { + s := ser.ToService() for _, c := range s.Checkins { if c.Api == api { return c @@ -25,8 +26,8 @@ func FindCheckin(api string) *Checkin { return nil } -func (s *Service) SelectAllCheckins() []*Checkin { - var checkins []*Checkin +func SelectAllCheckins(s *types.Service) []*types.Checkin { + var checkins []*types.Checkin col := DbSession.Collection("checkins").Find("service", s.Id).OrderBy("-id") col.All(&checkins) s.Checkins = checkins @@ -78,33 +79,33 @@ func (f *Checkin) Ago() string { return got } -func (c *Checkin) Run() { - if c.Interval == 0 { - return - } - fmt.Println("checking: ", c.Api) - between := time.Now().Sub(c.Last).Seconds() - if between > float64(c.Interval) { - guard := make(chan struct{}) - c.RecheckCheckinFailure(guard) - <-guard - } - time.Sleep(1 * time.Second) - c.Run() -} - -func (s *Service) StartCheckins() { - for _, c := range s.Checkins { - checkin := c - go checkin.Run() - } -} - -func CheckinProcess() { - for _, s := range CoreApp.Services { - for _, c := range s.Checkins { - checkin := c - go checkin.Run() - } - } -} +//func (c *Checkin) Run() { +// if c.Interval == 0 { +// return +// } +// fmt.Println("checking: ", c.Api) +// between := time.Now().Sub(c.Last).Seconds() +// if between > float64(c.Interval) { +// guard := make(chan struct{}) +// c.RecheckCheckinFailure(guard) +// <-guard +// } +// time.Sleep(1 * time.Second) +// c.Run() +//} +// +//func (s *Service) StartCheckins() { +// for _, c := range s.Checkins { +// checkin := c.(*Checkin) +// go checkin.Run() +// } +//} +// +//func CheckinProcess() { +// for _, s := range CoreApp.Services { +// for _, c := range s.Checkins { +// checkin := c +// go checkin.Run() +// } +// } +//} diff --git a/core/configs.go b/core/configs.go index 5229bac8..7908fdb6 100644 --- a/core/configs.go +++ b/core/configs.go @@ -14,7 +14,7 @@ import ( func LoadConfig() (*types.Config, error) { if os.Getenv("DB_CONN") != "" { utils.Log(1, "DB_CONN environment variable was found, sleeping for 30 seconds") - time.Sleep(30 * time.Second) + //time.Sleep(30 * time.Second) return LoadUsingEnv() } Configs = new(types.Config) @@ -102,13 +102,13 @@ func LoadUsingEnv() (*types.Config, error) { utils.Log(3, err) } - admin := &User{ + admin := &types.User{ Username: "admin", Password: "admin", Email: "info@admin.com", Admin: true, } - admin.Create() + CreateUser(admin) LoadSampleData() diff --git a/core/core.go b/core/core.go index 84bec322..1154147a 100644 --- a/core/core.go +++ b/core/core.go @@ -105,7 +105,8 @@ func (c Core) MobileSASS() string { } func (c Core) AllOnline() bool { - for _, s := range CoreApp.Services { + for _, ser := range CoreApp.Services { + s := ser.ToService() if !s.Online { return false } diff --git a/core/events.go b/core/events.go index 43c909af..0fc2d202 100644 --- a/core/events.go +++ b/core/events.go @@ -4,6 +4,7 @@ import ( "github.com/fatih/structs" "github.com/hunterlong/statup/notifiers" "github.com/hunterlong/statup/plugin" + "github.com/hunterlong/statup/types" "upper.io/db.v3/lib/sqlbuilder" ) @@ -13,18 +14,18 @@ func OnLoad(db sqlbuilder.Database) { } } -func OnSuccess(s *Service) { +func OnSuccess(s *types.Service) { for _, p := range CoreApp.AllPlugins { p.OnSuccess(structs.Map(s)) } - notifiers.OnSuccess(structs.Map(s)) + notifiers.OnSuccess(s) } -func OnFailure(s *Service, f FailureData) { +func OnFailure(s *types.Service, f FailureData) { for _, p := range CoreApp.AllPlugins { p.OnFailure(structs.Map(s)) } - notifiers.OnFailure(structs.Map(s)) + notifiers.OnFailure(s) } func OnSettingsSaved(c *Core) { @@ -33,25 +34,25 @@ func OnSettingsSaved(c *Core) { } } -func OnNewUser(u *User) { +func OnNewUser(u *types.User) { for _, p := range CoreApp.AllPlugins { p.OnNewUser(structs.Map(u)) } } -func OnNewService(s *Service) { +func OnNewService(s *types.Service) { for _, p := range CoreApp.AllPlugins { p.OnNewService(structs.Map(s)) } } -func OnDeletedService(s *Service) { +func OnDeletedService(s *types.Service) { for _, p := range CoreApp.AllPlugins { p.OnDeletedService(structs.Map(s)) } } -func OnUpdateService(s *Service) { +func OnUpdateService(s *types.Service) { for _, p := range CoreApp.AllPlugins { p.OnUpdatedService(structs.Map(s)) } diff --git a/core/failures.go b/core/failures.go index bb3fdb48..9f939f5d 100644 --- a/core/failures.go +++ b/core/failures.go @@ -3,13 +3,14 @@ package core import ( "fmt" "github.com/ararog/timeago" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "strings" "time" ) -func (s *Service) CreateFailure(data FailureData) (int64, error) { - fail := &Failure{ +func CreateServiceFailure(s *types.Service, data FailureData) (int64, error) { + fail := &types.Failure{ Issue: data.Issue, Service: s.Id, CreatedAt: time.Now(), @@ -26,8 +27,8 @@ func (s *Service) CreateFailure(data FailureData) (int64, error) { return uuid.(int64), err } -func (s *Service) SelectAllFailures() []*Failure { - var fails []*Failure +func SelectAllFailures(s *types.Service) []*types.Failure { + var fails []*types.Failure col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id") err := col.All(&fails) if err != nil { @@ -36,7 +37,7 @@ func (s *Service) SelectAllFailures() []*Failure { return fails } -func (u *Service) DeleteFailures() { +func DeleteFailures(u *types.Service) { var fails []*Failure col := DbSession.Collection("failures") col.Find("service", u.Id).All(&fails) @@ -45,26 +46,29 @@ func (u *Service) DeleteFailures() { } } -func (s *Service) LimitedFailures() []*Failure { - var fails []*Failure +func (ser *Service) LimitedFailures() []*types.Failure { + s := ser.ToService() + var fails []*types.Failure col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id").Limit(10) col.All(&fails) return fails } -func reverseFailures(input []*Failure) []*Failure { +func reverseFailures(input []*types.Failure) []*types.Failure { if len(input) == 0 { return input } return append(reverseFailures(input[1:]), input[0]) } -func (f *Failure) Ago() string { +func (fail *Failure) Ago() string { + f := fail.ToFailure() got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt) return got } -func (f *Failure) Delete() error { +func (fail *Failure) Delete() error { + f := fail.ToFailure() col := DbSession.Collection("failures").Find("id", f.Id) return col.Delete() } @@ -79,19 +83,31 @@ func CountFailures() uint64 { return amount } -func (s *Service) TotalFailures() (uint64, error) { +func (ser *Service) TotalFailures() (uint64, error) { + s := ser.ToService() col := DbSession.Collection("failures").Find("service", s.Id) amount, err := col.Count() return amount, err } -func (s *Service) TotalFailures24Hours() (uint64, error) { +func (ser *Service) TotalFailures24Hours() (uint64, error) { + s := ser.ToService() col := DbSession.Collection("failures").Find("service", s.Id) amount, err := col.Count() return amount, err } -func (f *Failure) ParseError() string { +func (f *Failure) ToFailure() *types.Failure { + return f.F.(*types.Failure) +} + +func MakeFailure(f *types.Failure) *Failure { + fail := &Failure{f} + return fail +} + +func (fail *Failure) ParseError() string { + f := fail.ToFailure() err := strings.Contains(f.Issue, "operation timed out") if err { return fmt.Sprintf("HTTP Request Timed Out") diff --git a/core/hits.go b/core/hits.go index c2d94cd1..38a9f092 100644 --- a/core/hits.go +++ b/core/hits.go @@ -13,7 +13,7 @@ func hitCol() db.Collection { return DbSession.Collection("hits") } -func (s *Service) CreateHit(d HitData) (int64, error) { +func CreateServiceHit(s *types.Service, d HitData) (int64, error) { h := Hit{ Service: s.Id, Latency: d.Latency, @@ -27,14 +27,16 @@ func (s *Service) CreateHit(d HitData) (int64, error) { return uuid.(int64), err } -func (s *Service) Hits() ([]Hit, error) { +func (ser *Service) Hits() ([]Hit, error) { + s := ser.ToService() var hits []Hit col := hitCol().Find("service", s.Id).OrderBy("-id") err := col.All(&hits) return hits, err } -func (s *Service) LimitedHits() ([]*Hit, error) { +func (ser *Service) LimitedHits() ([]*Hit, error) { + s := ser.ToService() var hits []*Hit col := hitCol().Find("service", s.Id).OrderBy("-id").Limit(1024) err := col.All(&hits) @@ -48,14 +50,16 @@ func reverseHits(input []*Hit) []*Hit { return append(reverseHits(input[1:]), input[0]) } -func (s *Service) SelectHitsGroupBy(group string) ([]Hit, error) { +func (ser *Service) SelectHitsGroupBy(group string) ([]Hit, error) { + s := ser.ToService() var hits []Hit col := hitCol().Find("service", s.Id) err := col.All(&hits) return hits, err } -func (s *Service) TotalHits() (uint64, error) { +func (ser *Service) TotalHits() (uint64, error) { + s := ser.ToService() col := hitCol().Find("service", s.Id) amount, err := col.Count() return amount, err diff --git a/core/services.go b/core/services.go index d763ea99..e08c01ec 100644 --- a/core/services.go +++ b/core/services.go @@ -10,33 +10,12 @@ import ( "upper.io/db.v3" ) -type Failure types.Failure - type Service struct { - Id int64 `db:"id,omitempty" json:"id"` - Name string `db:"name" json:"name"` - Domain string `db:"domain" json:"domain"` - Expected string `db:"expected" json:"expected"` - ExpectedStatus int `db:"expected_status" json:"expected_status"` - Interval int `db:"check_interval" json:"check_interval"` - Type string `db:"check_type" json:"type"` - Method string `db:"method" json:"method"` - PostData string `db:"post_data" json:"post_data"` - Port int `db:"port" json:"port"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - Online bool `json:"online"` - Latency float64 `json:"latency"` - Online24Hours float32 `json:"24_hours_online"` - AvgResponse string `json:"avg_response"` - TotalUptime string `json:"uptime"` - OrderId int64 `json:"order_id"` - Failures []*Failure `json:"failures"` - Checkins []*Checkin `json:"checkins"` - stopRoutine chan struct{} - LastResponse string - LastStatusCode int - LastOnline time.Time - dnsLookup float64 `json:"dns_lookup_time"` + S interface{} +} + +type Failure struct { + F interface{} } func serviceCol() db.Collection { @@ -44,28 +23,32 @@ func serviceCol() db.Collection { } func SelectService(id int64) *Service { - for _, s := range CoreApp.Services { + for _, ser := range CoreApp.Services { + s := ser.ToService() if s.Id == id { - return s + return ser } } return nil } func SelectAllServices() ([]*Service, error) { - var srvcs []*Service + var services []*types.Service + var sers []*Service col := serviceCol().Find() - err := col.All(&srvcs) + err := col.All(&services) if err != nil { utils.Log(3, err) return nil, err } - for _, s := range srvcs { - s.Checkins = s.SelectAllCheckins() - s.Failures = s.SelectAllFailures() + for _, s := range services { + ser := NewService(s) + sers = append(sers, ser) + s.Checkins = SelectAllCheckins(s) + s.Failures = SelectAllFailures(s) } - CoreApp.Services = srvcs - return srvcs, err + CoreApp.Services = sers + return sers, err } func (s *Service) AvgTime() float64 { @@ -80,9 +63,10 @@ func (s *Service) AvgTime() float64 { return val } -func (s *Service) Online24() float32 { - total, _ := s.TotalHits() - failed, _ := s.TotalFailures24Hours() +func (ser *Service) Online24() float32 { + s := ser.S.(*types.Service) + total, _ := ser.TotalHits() + failed, _ := ser.TotalFailures24Hours() if failed == 0 { s.Online24Hours = 100.00 return s.Online24Hours @@ -106,12 +90,23 @@ type DateScan struct { Value int64 `json:"y"` } -func (s *Service) SmallText() string { - last := s.LimitedFailures() - hits, _ := s.LimitedHits() +func (s *Service) ToService() *types.Service { + return s.S.(*types.Service) +} + +func NewService(s *types.Service) *Service { + ser := &Service{s} + return ser +} + +func (ser *Service) SmallText() string { + s := ser.ToService() + last := ser.LimitedFailures() + hits, _ := ser.LimitedHits() if !s.Online { if len(last) > 0 { - return fmt.Sprintf("%v on %v", last[0].ParseError(), last[0].CreatedAt.Format("Monday 3:04PM, Jan _2 2006")) + lastFailure := MakeFailure(last[0]) + return fmt.Sprintf("%v on %v", lastFailure.ParseError(), last[0].CreatedAt.Format("Monday 3:04PM, Jan _2 2006")) } else { return fmt.Sprintf("%v is currently offline", s.Name) } @@ -138,7 +133,8 @@ func GroupDataBy(column string, id int64, tm time.Time, increment string) string return sql } -func (s *Service) GraphData() string { +func (ser *Service) GraphData() string { + s := ser.ToService() var d []*DateScan since := time.Now().Add(time.Hour*-24 + time.Minute*0 + time.Second*0) @@ -172,9 +168,10 @@ func (s *Service) GraphData() string { return string(data) } -func (s *Service) AvgUptime() string { - failed, _ := s.TotalFailures() - total, _ := s.TotalHits() +func (ser *Service) AvgUptime() string { + s := ser.ToService() + failed, _ := ser.TotalFailures() + total, _ := ser.TotalHits() if failed == 0 { s.TotalUptime = "100" return s.TotalUptime @@ -195,18 +192,19 @@ func (s *Service) AvgUptime() string { return s.TotalUptime } -func (u *Service) RemoveArray() []*Service { +func RemoveArray(u *types.Service) []*Service { var srvcs []*Service - for _, s := range CoreApp.Services { + for _, ser := range CoreApp.Services { + s := ser.ToService() if s.Id != u.Id { - srvcs = append(srvcs, s) + srvcs = append(srvcs, ser) } } CoreApp.Services = srvcs return srvcs } -func (u *Service) Delete() error { +func DeleteService(u *types.Service) error { res := serviceCol().Find("id", u.Id) err := res.Delete() if err != nil { @@ -214,28 +212,27 @@ func (u *Service) Delete() error { return err } utils.Log(1, fmt.Sprintf("Stopping %v Monitoring...", u.Name)) - if u.stopRoutine != nil { - close(u.stopRoutine) + if u.StopRoutine != nil { + close(u.StopRoutine) } utils.Log(1, fmt.Sprintf("Stopped %v Monitoring Service", u.Name)) - u.RemoveArray() + RemoveArray(u) OnDeletedService(u) return err } -func (u *Service) Update(s *Service) *Service { - s.CreatedAt = time.Now() +func UpdateService(u *types.Service) *types.Service { + u.CreatedAt = time.Now() res := serviceCol().Find("id", u.Id) - err := res.Update(s) + err := res.Update(u) if err != nil { utils.Log(3, fmt.Sprintf("Failed to update service %v. %v", u.Name, err)) } - *u = *s OnUpdateService(u) return u } -func (u *Service) Create() (int64, error) { +func CreateService(u *types.Service) (int64, error) { u.CreatedAt = time.Now() uuid, err := serviceCol().Insert(u) if uuid == nil { @@ -243,15 +240,17 @@ func (u *Service) Create() (int64, error) { return 0, err } u.Id = uuid.(int64) - u.stopRoutine = make(chan struct{}) - CoreApp.Services = append(CoreApp.Services, u) + u.StopRoutine = make(chan struct{}) + nn := &Service{u} + CoreApp.Services = append(CoreApp.Services, nn) return uuid.(int64), err } func CountOnline() int { amount := 0 - for _, v := range CoreApp.Services { - if v.Online { + for _, ser := range CoreApp.Services { + s := ser.ToService() + if s.Online { amount++ } } diff --git a/core/setup.go b/core/setup.go index 841ca1f9..80df737a 100644 --- a/core/setup.go +++ b/core/setup.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "os" ) @@ -19,7 +20,7 @@ type ErrorResponse struct { func LoadSampleData() error { utils.Log(1, "Inserting Sample Data...") - s1 := &Service{ + s1 := &types.Service{ Name: "Google", Domain: "https://google.com", ExpectedStatus: 200, @@ -28,7 +29,7 @@ func LoadSampleData() error { Type: "http", Method: "GET", } - s2 := &Service{ + s2 := &types.Service{ Name: "Statup Github", Domain: "https://github.com/hunterlong/statup", ExpectedStatus: 200, @@ -37,7 +38,7 @@ func LoadSampleData() error { Type: "http", Method: "GET", } - s3 := &Service{ + s3 := &types.Service{ Name: "JSON Users Test", Domain: "https://jsonplaceholder.typicode.com/users", ExpectedStatus: 200, @@ -46,7 +47,7 @@ func LoadSampleData() error { Type: "http", Method: "GET", } - s4 := &Service{ + s4 := &types.Service{ Name: "JSON API Tester", Domain: "https://jsonplaceholder.typicode.com/posts", ExpectedStatus: 201, @@ -56,19 +57,19 @@ func LoadSampleData() error { Method: "POST", PostData: `{ "title": "statup", "body": "bar", "userId": 19999 }`, } - id, err := s1.Create() + id, err := CreateService(s1) if err != nil { utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err)) } - id, err = s2.Create() + id, err = CreateService(s2) if err != nil { utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err)) } - id, err = s3.Create() + id, err = CreateService(s3) if err != nil { utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err)) } - id, err = s4.Create() + id, err = CreateService(s4) if err != nil { utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err)) } diff --git a/core/users.go b/core/users.go index a341aa9b..e203b248 100644 --- a/core/users.go +++ b/core/users.go @@ -10,36 +10,36 @@ import ( type User types.User -func SelectUser(id int64) (*User, error) { - var user User +func SelectUser(id int64) (*types.User, error) { + var user *types.User col := DbSession.Collection("users") res := col.Find("id", id) err := res.One(&user) - return &user, err + return user, err } -func SelectUsername(username string) (*User, error) { - var user User +func SelectUsername(username string) (*types.User, error) { + var user *types.User col := DbSession.Collection("users") res := col.Find("username", username) err := res.One(&user) - return &user, err + return user, err } -func (u *User) Delete() error { +func DeleteUser(u *types.User) error { col := DbSession.Collection("users") user := col.Find("id", u.Id) return user.Delete() } -func (u *User) Update() error { +func UpdateUser(u *types.User) error { u.CreatedAt = time.Now() col := DbSession.Collection("users") user := col.Find("id", u.Id) return user.Update(u) } -func (u *User) Create() (int64, error) { +func CreateUser(u *types.User) (int64, error) { u.CreatedAt = time.Now() u.Password = utils.HashPassword(u.Password) u.ApiKey = utils.NewSHA1Hash(5) @@ -63,7 +63,7 @@ func SelectAllUsers() ([]User, error) { return users, err } -func AuthUser(username, password string) (*User, bool) { +func AuthUser(username, password string) (*types.User, bool) { var auth bool user, err := SelectUsername(username) if err != nil { diff --git a/handlers/api.go b/handlers/api.go index a96c86ce..1fbbc9a3 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/gorilla/mux" "github.com/hunterlong/statup/core" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "net/http" ) @@ -23,7 +24,7 @@ func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) { } vars := mux.Vars(r) checkin := core.FindCheckin(vars["api"]) - checkin.Receivehit() + //checkin.Receivehit() w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(checkin) } @@ -44,11 +45,12 @@ func ApiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) { return } vars := mux.Vars(r) - service := core.SelectService(utils.StringInt(vars["id"])) - var s core.Service + serv := core.SelectService(utils.StringInt(vars["id"])) + var s *types.Service decoder := json.NewDecoder(r.Body) decoder.Decode(&s) - service.Update(&s) + service := serv.ToService() + core.UpdateService(service) json.NewEncoder(w).Encode(s) } diff --git a/handlers/index.go b/handlers/index.go index c9b8a393..f5cf56c3 100644 --- a/handlers/index.go +++ b/handlers/index.go @@ -6,8 +6,7 @@ import ( ) type index struct { - Core core.Core - Services []*core.Service + Core *core.Core } func IndexHandler(w http.ResponseWriter, r *http.Request) { @@ -15,6 +14,5 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/setup", http.StatusSeeOther) return } - out := index{*core.CoreApp, core.CoreApp.Services} - ExecuteResponse(w, r, "index.html", out) + ExecuteResponse(w, r, "index.html", core.CoreApp) } diff --git a/handlers/prometheus.go b/handlers/prometheus.go index 96576a50..efdace07 100644 --- a/handlers/prometheus.go +++ b/handlers/prometheus.go @@ -28,7 +28,8 @@ func PrometheusHandler(w http.ResponseWriter, r *http.Request) { system := fmt.Sprintf("statup_total_failures %v\n", core.CountFailures()) system += fmt.Sprintf("statup_total_services %v", len(core.CoreApp.Services)) metrics = append(metrics, system) - for _, v := range core.CoreApp.Services { + for _, ser := range core.CoreApp.Services { + v := ser.ToService() online := 1 if !v.Online { online = 0 diff --git a/handlers/services.go b/handlers/services.go index 23e8b0b8..e4107f4f 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/gorilla/mux" "github.com/hunterlong/statup/core" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "net/http" "strconv" @@ -40,7 +41,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { checkType := r.PostForm.Get("check_type") postData := r.PostForm.Get("post_data") - service := &core.Service{ + service := &types.Service{ Name: name, Domain: domain, Method: method, @@ -51,12 +52,12 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { Port: port, PostData: postData, } - _, err := service.Create() + _, err := core.CreateService(service) if err != nil { utils.Log(3, fmt.Sprintf("Error starting %v check routine. %v", service.Name, err)) } - go service.CheckQueue() + go core.CheckQueue(service) core.OnNewService(service) http.Redirect(w, r, "/services", http.StatusSeeOther) @@ -68,21 +69,22 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) { return } vars := mux.Vars(r) - service := core.SelectService(utils.StringInt(vars["id"])) - service.Delete() + serv := core.SelectService(utils.StringInt(vars["id"])) + service := serv.ToService() + core.DeleteService(service) http.Redirect(w, r, "/services", http.StatusSeeOther) } func ServicesViewHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - service := core.SelectService(utils.StringInt(vars["id"])) - ExecuteResponse(w, r, "service.html", service) + serv := core.SelectService(utils.StringInt(vars["id"])) + ExecuteResponse(w, r, "service.html", serv) } func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - service := core.SelectService(utils.StringInt(vars["id"])) - + serv := core.SelectService(utils.StringInt(vars["id"])) + service := serv.ToService() var badge []byte if service.Online { badge = []byte(`` + service.Name + `` + service.Name + `onlineonline`) @@ -103,7 +105,8 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { return } vars := mux.Vars(r) - service := core.SelectService(utils.StringInt(vars["id"])) + serv := core.SelectService(utils.StringInt(vars["id"])) + service := serv.ToService() r.ParseForm() name := r.PostForm.Get("name") domain := r.PostForm.Get("domain") @@ -114,7 +117,7 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { port, _ := strconv.Atoi(r.PostForm.Get("port")) checkType := r.PostForm.Get("check_type") postData := r.PostForm.Get("post_data") - serviceUpdate := &core.Service{ + serviceUpdate := &types.Service{ Id: service.Id, Name: name, Domain: domain, @@ -126,7 +129,7 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { Port: port, PostData: postData, } - service = service.Update(serviceUpdate) + service = core.UpdateService(serviceUpdate) ExecuteResponse(w, r, "service.html", service) } @@ -136,9 +139,9 @@ func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) { return } vars := mux.Vars(r) - service := core.SelectService(utils.StringInt(vars["id"])) - - service.DeleteFailures() + serv := core.SelectService(utils.StringInt(vars["id"])) + service := serv.ToService() + core.DeleteFailures(service) core.CoreApp.Services, _ = core.SelectAllServices() http.Redirect(w, r, "/services", http.StatusSeeOther) } @@ -150,7 +153,8 @@ func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) { } vars := mux.Vars(r) interval := utils.StringInt(r.PostForm.Get("interval")) - service := core.SelectService(utils.StringInt(vars["id"])) + serv := core.SelectService(utils.StringInt(vars["id"])) + service := serv.ToService() checkin := &core.Checkin{ Service: service.Id, Interval: interval, diff --git a/handlers/setup.go b/handlers/setup.go index 975b8482..d6d1d18c 100644 --- a/handlers/setup.go +++ b/handlers/setup.go @@ -104,13 +104,13 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) { return } - admin := &core.User{ + admin := &types.User{ Username: config.Username, Password: config.Password, Email: config.Email, Admin: true, } - admin.Create() + core.CreateUser(admin) if sample == "on" { core.LoadSampleData() diff --git a/handlers/users.go b/handlers/users.go index 854d1bdf..61a8af26 100644 --- a/handlers/users.go +++ b/handlers/users.go @@ -64,7 +64,7 @@ func UpdateUserHandler(w http.ResponseWriter, r *http.Request) { if password != "##########" { user.Password = utils.HashPassword(password) } - user.Update() + core.UpdateUser(user) users, _ := core.SelectAllUsers() ExecuteResponse(w, r, "users.html", users) } @@ -80,13 +80,13 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) { email := r.PostForm.Get("email") admin := r.PostForm.Get("admin") - user := &core.User{ + user := &types.User{ Username: username, Password: password, Email: email, Admin: (admin == "on"), } - _, err := user.Create() + _, err := core.CreateUser(user) if err != nil { utils.Log(2, err) } @@ -108,6 +108,6 @@ func UsersDeleteHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/users", http.StatusSeeOther) return } - user.Delete() + core.DeleteUser(user) http.Redirect(w, r, "/users", http.StatusSeeOther) } diff --git a/main_test.go b/main_test.go index 40256ddc..519b6949 100644 --- a/main_test.go +++ b/main_test.go @@ -6,6 +6,7 @@ import ( "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/handlers" "github.com/hunterlong/statup/notifiers" + "github.com/hunterlong/statup/types" "github.com/rendon/testcli" "github.com/stretchr/testify/assert" "net/http" @@ -31,7 +32,7 @@ func RunInit(t *testing.T) { core.CoreApp = core.NewCore() } -var forceSequential chan bool = make(chan bool, 1) +var forceSequential = make(chan bool, 1) func TestRunAll(t *testing.T) { @@ -267,22 +268,22 @@ func RunUser_SelectAll(t *testing.T) { } func RunUser_Create(t *testing.T) { - user := &core.User{ + user := &types.User{ Username: "admin", Password: "admin", Email: "info@testuser.com", Admin: true, } - id, err := user.Create() + id, err := core.CreateUser(user) assert.Nil(t, err) assert.Equal(t, int64(1), id) - user2 := &core.User{ + user2 := &types.User{ Username: "superadmin", Password: "admin", Email: "info@adminer.com", Admin: true, } - id, err = user2.Create() + id, err = core.CreateUser(user2) assert.Nil(t, err) assert.Equal(t, int64(2), id) } @@ -291,7 +292,7 @@ func RunUser_Update(t *testing.T) { user, err := core.SelectUser(1) user.Email = "info@updatedemail.com" assert.Nil(t, err) - err = user.Update() + err = core.UpdateUser(user) assert.Nil(t, err) updatedUser, err := core.SelectUser(1) assert.Nil(t, err) @@ -299,12 +300,12 @@ func RunUser_Update(t *testing.T) { } func RunUser_NonUniqueCreate(t *testing.T) { - user := &core.User{ + user := &types.User{ Username: "admin", Password: "admin", Email: "info@testuser.com", } - _, err := user.Create() + _, err := core.CreateUser(user) assert.NotNil(t, err) } @@ -312,7 +313,7 @@ func RunUser_Delete(t *testing.T) { user, err := core.SelectUser(2) assert.Nil(t, err) assert.NotNil(t, user) - err = user.Delete() + err = core.DeleteUser(user) assert.Nil(t, err) } @@ -327,11 +328,11 @@ func RunOneService_Check(t *testing.T) { service := core.SelectService(1) assert.NotNil(t, service) t.Log(service) - assert.Equal(t, "Google", service.Name) + assert.Equal(t, "Google", service.ToService().Name) } func RunService_Create(t *testing.T) { - service := &core.Service{ + service := &types.Service{ Name: "test service", Domain: "https://google.com", ExpectedStatus: 200, @@ -340,7 +341,7 @@ func RunService_Create(t *testing.T) { Type: "http", Method: "GET", } - id, err := service.Create() + id, err := core.CreateService(service) assert.Nil(t, err) assert.Equal(t, int64(5), id) t.Log(service) @@ -371,7 +372,7 @@ func RunService_GraphData(t *testing.T) { } func RunBadService_Create(t *testing.T) { - service := &core.Service{ + service := &types.Service{ Name: "Bad Service", Domain: "https://9839f83h72gey2g29278hd2od2d.com", ExpectedStatus: 200, @@ -380,7 +381,7 @@ func RunBadService_Create(t *testing.T) { Type: "http", Method: "GET", } - id, err := service.Create() + id, err := core.CreateService(service) assert.Nil(t, err) assert.Equal(t, int64(6), id) } @@ -388,14 +389,14 @@ func RunBadService_Create(t *testing.T) { func RunBadService_Check(t *testing.T) { service := core.SelectService(4) assert.NotNil(t, service) - assert.Equal(t, "JSON API Tester", service.Name) + assert.Equal(t, "JSON API Tester", service.ToService().Name) } func RunDeleteService(t *testing.T) { service := core.SelectService(4) assert.NotNil(t, service) - assert.Equal(t, "JSON API Tester", service.Name) - err := service.Delete() + assert.Equal(t, "JSON API Tester", service.ToService().Name) + err := core.DeleteService(service.ToService()) assert.Nil(t, err) } @@ -405,7 +406,7 @@ func RunCreateService_Hits(t *testing.T) { assert.NotNil(t, services) for i := 0; i <= 10; i++ { for _, s := range services { - service := s.Check() + service := core.ServiceCheck(s.ToService()) assert.NotNil(t, service) } } @@ -423,7 +424,7 @@ func RunService_Failures(t *testing.T) { t.SkipNow() service := core.SelectService(6) assert.NotNil(t, service) - assert.NotEmpty(t, service.Failures) + assert.NotEmpty(t, service.ToService().Failures) } func RunService_LimitedHits(t *testing.T) { diff --git a/notifiers/email.go b/notifiers/email.go index dfd5b062..35e5fbd8 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -144,7 +144,7 @@ func (u *Email) Run() error { } // ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS -func (u *Email) OnFailure(data map[string]interface{}) error { +func (u *Email) OnFailure(s *types.Service) error { if u.Enabled { utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method)) // Do failing stuff here! @@ -153,7 +153,7 @@ func (u *Email) OnFailure(data map[string]interface{}) error { } // ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS -func (u *Email) OnSuccess(data map[string]interface{}) error { +func (u *Email) OnSuccess(s *types.Service) error { if u.Enabled { utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method)) // Do failing stuff here! diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go index 59294736..dfdaa242 100644 --- a/notifiers/notifiers.go +++ b/notifiers/notifiers.go @@ -2,6 +2,7 @@ package notifiers import ( "fmt" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "strings" "time" @@ -54,8 +55,8 @@ type Notifier interface { Init() error Install() error Run() error - OnFailure(map[string]interface{}) error - OnSuccess(map[string]interface{}) error + OnFailure(*types.Service) error + OnSuccess(*types.Service) error Select() *Notification Test() error } @@ -81,7 +82,6 @@ func SelectNotification(id int64) (*Notification, error) { func (n *Notification) Update() (*Notification, error) { n.CreatedAt = time.Now() err := Collections.Find("id", n.Id).Update(n) - return n, err } @@ -148,26 +148,36 @@ func (n *Notification) GetValue(dbField string) string { return "" } -func OnFailure(data map[string]interface{}) { +func OnFailure(s *types.Service) { for _, comm := range AllCommunications { n := comm.(Notifier) - n.OnFailure(data) + n.OnFailure(s) } } -func OnSuccess(data map[string]interface{}) { +func OnSuccess(s *types.Service) { for _, comm := range AllCommunications { n := comm.(Notifier) - n.OnSuccess(data) + n.OnSuccess(s) } } -func uniqueMessages(arr []string, v string) []string { - var newArray []string - for _, i := range arr { - if i != v { - newArray = append(newArray, v) +func uniqueStrings(elements []string) []string { + result := []string{} + + for i := 0; i < len(elements); i++ { + // Scan slice for a previous element of the same value. + exists := false + for v := 0; v < i; v++ { + if elements[v][:10] == elements[i][:10] { + exists = true + break + } + } + // If no previous element exists, append this one. + if !exists { + result = append(result, elements[i]) } } - return newArray + return result } diff --git a/notifiers/slack.go b/notifiers/slack.go index dec7606a..36328ca9 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -3,28 +3,37 @@ package notifiers import ( "bytes" "fmt" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "net/http" - "time" + "sync" "text/template" + "time" ) const ( - SLACK_ID = 2 - SLACK_METHOD = "slack" - SERVICE_TEMPLATE = `{ "attachments": [ { "fallback": "ReferenceError - UI is not defined: https://honeybadger.io/path/to/event/", "text": " - Your Statup service 'Google' has just received a Failure notification.", "fields": [ { "title": "Issue", "value": "Awesome Project", "short": true }, { "title": "Response", "value": "production", "short": true } ], "color": "#FF0000", "thumb_url": "http://example.com/path/to/thumb.png", "footer": "Statup", "footer_icon": "https://img.cjx.io/statuplogo32.png", "ts": 123456789 } ] }` - TEST_TEMPLATE = `{"text":"%{{.Message}}"}` + SLACK_ID = 2 + SLACK_METHOD = "slack" + FAILING_TEMPLATE = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "<{{.Service.Domain}}|{{.Service.Name}}> - Your Statup service '{{.Service.Name}}' has just received a Failure notification with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected", "value": "{{.Service.Expected}}", "short": true }, { "title": "Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ], "color": "#FF0000", "thumb_url": "https://statup.io", "footer": "Statup", "footer_icon": "https://img.cjx.io/statuplogo32.png", "ts": {{.Time}} } ] }` + SUCCESS_TEMPLATE = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is now back online", "text": "<{{.Service.Domain}}|{{.Service.Name}}> - Your Statup service '{{.Service.Name}}' has just received a Failure notification.", "fields": [ { "title": "Issue", "value": "Awesome Project", "short": true }, { "title": "Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ], "color": "#00FF00", "thumb_url": "https://statup.io", "footer": "Statup", "footer_icon": "https://img.cjx.io/statuplogo32.png", "ts": {{.Time}} } ] }` + TEST_TEMPLATE = `{"text":"{{.}}"}` ) var ( slacker *Slack slackMessages []string + messageLock *sync.Mutex ) type Slack struct { *Notification } +type slackMessage struct { + Service *types.Service + Time int64 +} + // DEFINE YOUR NOTIFICATION HERE. func init() { slacker = &Slack{&Notification{ @@ -40,6 +49,7 @@ func init() { }}}, } add(slacker) + messageLock = new(sync.Mutex) } // Select Obj @@ -49,9 +59,7 @@ func (u *Slack) Select() *Notification { // WHEN NOTIFIER LOADS func (u *Slack) Init() error { - err := u.Install() - if err == nil { notifier, _ := SelectNotification(u.Id) forms := u.Form @@ -66,21 +74,26 @@ func (u *Slack) Init() error { } func (u *Slack) Test() error { - SendSlack(TEST_TEMPLATE, nil) + msg := fmt.Sprintf("You're Statup Slack Notifier is working correctly!") + SendSlack(TEST_TEMPLATE, msg) return nil } // AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS func (u *Slack) Run() error { + messageLock.Lock() + slackMessages = uniqueStrings(slackMessages) for _, msg := range slackMessages { - utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook: %v", msg)) + utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook")) client := http.Client{Timeout: 15 * time.Second} _, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(msg))) if err != nil { utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err)) } - slackMessages = uniqueMessages(slackMessages, msg) + + fmt.Println(msg) } + messageLock.Unlock() time.Sleep(60 * time.Second) if u.Enabled { u.Run() @@ -89,37 +102,35 @@ func (u *Slack) Run() error { } // CUSTOM FUNCTION FO SENDING SLACK MESSAGES -func SendSlack(temp string, data ...interface{}) error { +func SendSlack(temp string, data interface{}) error { + messageLock.Lock() buf := new(bytes.Buffer) slackTemp, _ := template.New("slack").Parse(temp) slackTemp.Execute(buf, data) slackMessages = append(slackMessages, buf.String()) + messageLock.Unlock() return nil } // ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS -func (u *Slack) OnFailure(data map[string]interface{}) error { +func (u *Slack) OnFailure(s *types.Service) error { if u.Enabled { - utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method)) // Do failing stuff here! + + message := slackMessage{ + Service: s, + Time: time.Now().Unix(), + } + + SendSlack(FAILING_TEMPLATE, message) } return nil } // ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS -func (u *Slack) OnSuccess(data map[string]interface{}) error { +func (u *Slack) OnSuccess(s *types.Service) error { if u.Enabled { - utils.Log(1, fmt.Sprintf("Notification %v is receiving a successful notification. %v", u.Method, data)) - - //domain := data["Domain"] - //expected := data["Expected"] - //expectedStatus := data["ExpectedStatus"] - failures := data["Failures"] - response := data["LastResponse"] - - fullMessage := fmt.Sprintf(`{ "attachments": [ { "fallback": "Service is currently offline", "text": "Service is currently offline", "fields": [ { "title": "Issue", "value": "%v", "short": true }, { "title": "Response", "value": "%v", "short": true } ], "color": "#FF0000", "thumb_url": "http://example.com/path/to/thumb.png", "footer": "Statup", "footer_icon": "https://img.cjx.io/statuplogo32.png", "ts": %v } ] }`, failures, response, time.Now().Unix()) - slackMessages = append(slackMessages, fullMessage) - + SendSlack(SUCCESS_TEMPLATE, s) // Do checking or any successful things here } return nil diff --git a/servers/docker-compose.yml b/servers/docker-compose.yml index 97a5103b..79d65835 100644 --- a/servers/docker-compose.yml +++ b/servers/docker-compose.yml @@ -28,22 +28,22 @@ services: - internet - database depends_on: - - postgres + - postgres_statup volumes: - ./statup/app:/app environment: VIRTUAL_HOST: localhost VIRTUAL_PORT: 8080 DB_CONN: postgres - DB_HOST: postgres + DB_HOST: postgres_statup DB_USER: statup DB_PASS: password123 DB_DATABASE: statup NAME: EC2 Example DESCRIPTION: This is a Statup Docker Compose instance - postgres: - container_name: postgres + postgres_statup: + container_name: postgres_statup image: postgres restart: always networks: diff --git a/source/js/charts.js b/source/js/charts.js index 07ac03ff..1ca11b32 100644 --- a/source/js/charts.js +++ b/source/js/charts.js @@ -1,6 +1,6 @@ -{{ range . }}{{ if .AvgTime }}var ctx_{{.Id}}=document.getElementById("service_{{.Id}}").getContext('2d');var chartdata=new Chart(ctx_{{.Id}},{type:'line',data:{datasets:[{label:'Response Time (Milliseconds)',data:{{safe .GraphData}},backgroundColor:['rgba(47, 206, 30, 0.92)'],borderColor:['rgb(47, 171, 34)'],borderWidth:1}]},options:{maintainAspectRatio:!1,scaleShowValues:!0,layout:{padding:{left:0,right:0,top:0,bottom:-10}},hover:{animationDuration:0,},responsiveAnimationDuration:0,animation:{duration:3500,onComplete:function(){var chartInstance=this.chart,ctx=chartInstance.ctx;var controller=this.chart.controller;var xAxis=controller.scales['x-axis-0'];var yAxis=controller.scales['y-axis-0'];ctx.font=Chart.helpers.fontString(Chart.defaults.global.defaultFontSize,Chart.defaults.global.defaultFontStyle,Chart.defaults.global.defaultFontFamily);ctx.textAlign='center';ctx.textBaseline='bottom';var numTicks=xAxis.ticks.length;var yOffsetStart=xAxis.width/numTicks;var halfBarWidth=(xAxis.width/(numTicks*2));xAxis.ticks.forEach(function(value,index){var xOffset=20;var yOffset=(yOffsetStart*index)+halfBarWidth;ctx.fillStyle='#e2e2e2';ctx.fillText(value,yOffset,xOffset)});this.data.datasets.forEach(function(dataset,i){var meta=chartInstance.controller.getDatasetMeta(i);var hxH=0;var hyH=0;var hxL=0;var hyL=0;var highestNum=0;var lowestnum=999999999999;meta.data.forEach(function(bar,index){var data=dataset.data[index];if(lowestnum>data.y){lowestnum=data.y;hxL=bar._model.x;hyL=bar._model.y} +{{ range . }}{{$s := .ToService}}{{ if .AvgTime }}var ctx_{{$s.Id}}=document.getElementById("service_{{$s.Id}}").getContext('2d');var chartdata=new Chart(ctx_{{$s.Id}},{type:'line',data:{datasets:[{label:'Response Time (Milliseconds)',data:{{safe .GraphData}},backgroundColor:['rgba(47, 206, 30, 0.92)'],borderColor:['rgb(47, 171, 34)'],borderWidth:1}]},options:{maintainAspectRatio:!1,scaleShowValues:!0,layout:{padding:{left:0,right:0,top:0,bottom:-10}},hover:{animationDuration:0,},responsiveAnimationDuration:0,animation:{duration:3500,onComplete:function(){var chartInstance=this.chart,ctx=chartInstance.ctx;var controller=this.chart.controller;var xAxis=controller.scales['x-axis-0'];var yAxis=controller.scales['y-axis-0'];ctx.font=Chart.helpers.fontString(Chart.defaults.global.defaultFontSize,Chart.defaults.global.defaultFontStyle,Chart.defaults.global.defaultFontFamily);ctx.textAlign='center';ctx.textBaseline='bottom';var numTicks=xAxis.ticks.length;var yOffsetStart=xAxis.width/numTicks;var halfBarWidth=(xAxis.width/(numTicks*2));xAxis.ticks.forEach(function(value,index){var xOffset=20;var yOffset=(yOffsetStart*index)+halfBarWidth;ctx.fillStyle='#e2e2e2';ctx.fillText(value,yOffset,xOffset)});this.data.datasets.forEach(function(dataset,i){var meta=chartInstance.controller.getDatasetMeta(i);var hxH=0;var hyH=0;var hxL=0;var hyL=0;var highestNum=0;var lowestnum=999999999999;meta.data.forEach(function(bar,index){var data=dataset.data[index];if(lowestnum>data.y){lowestnum=data.y;hxL=bar._model.x;hyL=bar._model.y} if(data.y>highestNum){highestNum=data.y;hxH=bar._model.x;hyH=bar._model.y}});if(hxH>=820){hxH=820}else if(50>=hxH){hxH=50} if(hxL>=820){hxL=820}else if(70>=hxL){hxL=70} - ctx.fillStyle='#ffa7a2';ctx.fillText(highestNum+"ms",hxH-40,hyH+15);ctx.fillStyle='#45d642';ctx.fillText(lowestnum+"ms",hxL,hyL+10);console.log("done service_id_{{.Id}}")})}},legend:{display:!1},tooltips:{"enabled":!1},scales:{yAxes:[{display:!1,ticks:{fontSize:20,display:!1,beginAtZero:!1},gridLines:{display:!1}}],xAxes:[{type:'time',distribution:'series',autoSkip:!1,gridLines:{display:!1},ticks:{stepSize:1,min:0,fontColor:"white",fontSize:20,display:!1,}}]},elements:{point:{radius:0}}}}) + ctx.fillStyle='#ffa7a2';ctx.fillText(highestNum+"ms",hxH-40,hyH+15);ctx.fillStyle='#45d642';ctx.fillText(lowestnum+"ms",hxL,hyL+10);console.log("done service_id_{{$s.Id}}")})}},legend:{display:!1},tooltips:{"enabled":!1},scales:{yAxes:[{display:!1,ticks:{fontSize:20,display:!1,beginAtZero:!1},gridLines:{display:!1}}],xAxes:[{type:'time',distribution:'series',autoSkip:!1,gridLines:{display:!1},ticks:{stepSize:1,min:0,fontColor:"white",fontSize:20,display:!1,}}]},elements:{point:{radius:0}}}}) {{ end }} {{ end }} \ No newline at end of file diff --git a/source/tmpl/dashboard.html b/source/tmpl/dashboard.html index aff72068..aa301a95 100644 --- a/source/tmpl/dashboard.html +++ b/source/tmpl/dashboard.html @@ -48,10 +48,11 @@ {{ range .Services }} + {{ $s := .ToService }} - {{.Name}} - {{if .Online}} ONLINE {{else}} OFFLINE {{end}} + {{$s.Name}} + {{if $s.Online}} ONLINE {{else}} OFFLINE {{end}} {{.SmallText}} @@ -62,7 +63,7 @@ Latest Failures {{ range .Services }} - + {{ $s := .ToService }} {{ if .LimitedFailures }} {{ range .LimitedFailures }} diff --git a/source/tmpl/index.html b/source/tmpl/index.html index fb85c965..00b29d01 100644 --- a/source/tmpl/index.html +++ b/source/tmpl/index.html @@ -12,25 +12,27 @@ {{end}} - {{.Core.Name}} Status + {{.Name}} Status -{{.Core.Name}} +{{.Name}} - {{ if .Core.Description }} - {{ .Core.Description }} + {{ if .Description }} + {{ .Description }} {{ end }} {{ range .Services }} - - {{ .Name }} - {{if .Online}} + {{ $s := .ToService }} + + + {{ $s.Name }} + {{if $s.Online}} ONLINE {{ else }} OFFLINE @@ -51,12 +53,13 @@ {{end}} {{ range .Services }} - + {{ $s := .ToService }} + - {{ .Name }} - {{if .Online}} + {{ $s.Name }} + {{if $s.Online}} ONLINE {{ else }} OFFLINE @@ -84,13 +87,13 @@ {{ if .AvgTime }} - + {{ end }} - + {{.SmallText}} - View Service + View Service @@ -115,9 +118,9 @@ {{end}} - {{ if .Core.Style }} + {{ if .Style }} {{ end }} diff --git a/source/tmpl/service.html b/source/tmpl/service.html index 9e086755..47d63c71 100644 --- a/source/tmpl/service.html +++ b/source/tmpl/service.html @@ -1,4 +1,4 @@ - +{{ $s := .ToService }} @@ -13,7 +13,7 @@ {{end}} - Statup | {{.Name}} Service + Statup | {{$s.Name}} Service @@ -25,14 +25,14 @@ - {{if .Online }} + {{if $s.Online }} ONLINE {{ else }} OFFLINE {{end}} - {{ .Name }} - {{if .Online }} + {{ $s.Name }} + {{if $s.Online }} ONLINE {{ else }} OFFLINE @@ -81,65 +81,65 @@ Edit Service - + Service Name - + Service Check Type - - HTTP Service - TCP Service + + HTTP Service + TCP Service Application Endpoint (URL) - + Service Check Type - - GET - POST + + GET + POST Post Data (JSON) - {{.PostData}} + {{$s.PostData}} Expected Response (Regex) - {{.Expected}} + {{$s.Expected}} Expected Status Code - + TCP Port - + Check Interval (Seconds) - + @@ -147,7 +147,7 @@ Update Service - Delete All Failures + Delete All Failures @@ -157,12 +157,12 @@ Last Response - {{ .LastResponse }} + {{ $s.LastResponse }} Status Code - + @@ -171,12 +171,12 @@ Service Checkins -{{ range .Checkins }} +{{ range $s.Checkins }} Check #{{.Id}} Checked in {{.Ago}} {{ end }} - + Check Interval (in seconds) diff --git a/source/tmpl/services.html b/source/tmpl/services.html index 9694107d..c1d93ae7 100644 --- a/source/tmpl/services.html +++ b/source/tmpl/services.html @@ -33,13 +33,14 @@ {{range .}} + {{ $s := .ToService }} - {{.Name}} - {{if .Online}}ONLINE{{else}}OFFLINE{{end}} + {{$s.Name}} + {{if $s.Online}}ONLINE{{else}}OFFLINE{{end}} - View - Delete + View + Delete @@ -47,7 +48,6 @@ - Create Service diff --git a/types/interfaces.go b/types/interfaces.go new file mode 100644 index 00000000..d30bc752 --- /dev/null +++ b/types/interfaces.go @@ -0,0 +1,19 @@ +package types + +type ServiceInterface interface { + AvgTime() float64 + Online24() float32 + ToService() *Service + SmallText() string + GraphData() string + AvgUptime() string + LimitedFailures() []*Failure + TotalFailures() (uint64, error) + TotalFailures24Hours() (uint64, error) +} + +type FailureInterface interface { + ToFailure() *Failure + Ago() string + ParseError() string +} diff --git a/types/types.go b/types/types.go index d54bba6e..5c38ea9f 100644 --- a/types/types.go +++ b/types/types.go @@ -4,6 +4,33 @@ import ( "time" ) +type Service struct { + Id int64 `db:"id,omitempty" json:"id"` + Name string `db:"name" json:"name"` + Domain string `db:"domain" json:"domain"` + Expected string `db:"expected" json:"expected"` + ExpectedStatus int `db:"expected_status" json:"expected_status"` + Interval int `db:"check_interval" json:"check_interval"` + Type string `db:"check_type" json:"type"` + Method string `db:"method" json:"method"` + PostData string `db:"post_data" json:"post_data"` + Port int `db:"port" json:"port"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + Online bool `json:"online"` + Latency float64 `json:"latency"` + Online24Hours float32 `json:"24_hours_online"` + AvgResponse string `json:"avg_response"` + TotalUptime string `json:"uptime"` + OrderId int64 `json:"order_id"` + Failures []*Failure `json:"failures"` + Checkins []*Checkin `json:"checkins"` + StopRoutine chan struct{} + LastResponse string + LastStatusCode int + LastOnline time.Time + DnsLookup float64 `json:"dns_lookup_time"` +} + type User struct { Id int64 `db:"id,omitempty" json:"id"` Username string `db:"username" json:"username"`
{{.SmallText}}