From b38c6cb670cf5362a174086acecf9b6b4f7c56a2 Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Sat, 18 Aug 2018 17:37:00 -0700 Subject: [PATCH] core updates --- Dockerfile-dev => .dev/Dockerfile | 0 .dev/test/cypress.json | 5 +- .dev/test/cypress/integration/0_setup.js | 2 +- .dev/test/cypress/integration/assets.js | 2 +- .dev/test/cypress/integration/services.js | 74 ++++++++++++++-- .dev/test/cypress/integration/settings.js | 6 ++ .dev/test/cypress/integration/users.js | 20 +++++ Makefile | 11 ++- cmd/cli.go | 20 ++--- cmd/main_test.go | 31 ++++--- core/checker.go | 85 +++++++++++------- core/checkin.go | 2 +- core/core.go | 3 +- core/events.go | 17 ++-- core/failures.go | 13 ++- core/hits.go | 14 ++- core/services.go | 77 +++++++--------- core/services_test.go | 103 +++++++++++++++------- core/setup.go | 20 ++--- handlers/api.go | 2 +- handlers/handlers_test.go | 1 + handlers/prometheus.go | 9 +- handlers/routes.go | 4 + handlers/services.go | 26 ++++-- source/js/charts.js | 19 +--- source/tmpl/dashboard.html | 4 +- source/tmpl/index.html | 4 +- source/tmpl/service.html | 2 +- source/tmpl/services.html | 2 +- types/service.go | 27 +++++- 30 files changed, 385 insertions(+), 220 deletions(-) rename Dockerfile-dev => .dev/Dockerfile (100%) diff --git a/Dockerfile-dev b/.dev/Dockerfile similarity index 100% rename from Dockerfile-dev rename to .dev/Dockerfile diff --git a/.dev/test/cypress.json b/.dev/test/cypress.json index 617977d3..69629250 100644 --- a/.dev/test/cypress.json +++ b/.dev/test/cypress.json @@ -8,5 +8,8 @@ "DB_PASS": "password123", "GO_ENV": "production" }, - "chromeWebSecurity": false + "chromeWebSecurity": false, + "defaultCommandTimeout": 5000, + "requestTimeout": 5000, + "watchForFileChanges": false } diff --git a/.dev/test/cypress/integration/0_setup.js b/.dev/test/cypress/integration/0_setup.js index 0867d061..c8c9cbf5 100644 --- a/.dev/test/cypress/integration/0_setup.js +++ b/.dev/test/cypress/integration/0_setup.js @@ -37,7 +37,7 @@ context('Setup Process', () => { cy.get('input[name="email"]').clear().type('info@domain.com') cy.get('input[name="password"]').clear().type('admin') cy.scrollTo('bottom') - cy.get('#setup_button').click().wait(10000) + cy.get('#setup_button').click() cy.get('.header-title').should('contain', 'Demo Tester') cy.get('.header-desc').should('contain', 'This is a test from Crypress!') cy.scrollTo('bottom') diff --git a/.dev/test/cypress/integration/assets.js b/.dev/test/cypress/integration/assets.js index c6019e2e..7391a7f3 100644 --- a/.dev/test/cypress/integration/assets.js +++ b/.dev/test/cypress/integration/assets.js @@ -21,7 +21,7 @@ context('Asset Tests', () => { }}) }) - it('should check css file', () => { + it('should confirm sass variable in css', () => { cy.request('http://localhost:8080/css/base.css').its('body').should('contain', '.test-var') }) diff --git a/.dev/test/cypress/integration/services.js b/.dev/test/cypress/integration/services.js index a9ef5706..b3a34749 100644 --- a/.dev/test/cypress/integration/services.js +++ b/.dev/test/cypress/integration/services.js @@ -13,21 +13,85 @@ context('Service Tests', () => { cy.title().should('eq', 'Statup | Services') }) - it('should create services', () => { + it('should create HTTP GET service', () => { cy.visit('http://localhost:8080/services') - cy.get('input[name="name"]').type('Google.com') - cy.get('input[name="domain"]').type('https://google.com') - cy.get('input[name="interval"]').type('30') + cy.get('select[name="method"]').select('GET') + cy.get('input[name="name"]').clear().type('Google.com') + cy.get('select[name="check_type"]').select('http') + cy.get('input[name="domain"]').clear().type('https://google.com') + cy.get('input[name="expected_status"]').clear().type('200') + cy.get('input[name="interval"]').clear().type('25') + cy.get('input[name="timeout"]').clear().type('30') cy.get('form').submit() cy.title().should('eq', 'Statup | Services') cy.get('tr').should('have.length', 7) }) + it('should create HTTP POST service', () => { + cy.visit('http://localhost:8080/services') + cy.get('select[name="method"]').select('POST') + cy.get('input[name="name"]').clear().type('JSON Regex Test') + cy.get('select[name="check_type"]').select('http') + cy.get('input[name="domain"]').clear().type('https://jsonplaceholder.typicode.com/posts') + cy.get('textarea[name="post_data"]').clear().type(`(title)": "((\\"|[statup])*)"`) + cy.get('input[name="expected_status"]').clear().type('201') + cy.get('input[name="interval"]').clear().type('15') + cy.get('input[name="timeout"]').clear().type('45') + cy.get('form').submit() + cy.title().should('eq', 'Statup | Services') + cy.get('tr').should('have.length', 8) + }) + + it('should create TCP service', () => { + cy.visit('http://localhost:8080/services') + cy.get('select[name="check_type"]').select('tcp') + cy.get('input[name="name"]').clear().type('Google DNS') + cy.get('input[name="domain"]').clear().type('8.8.8.8') + cy.get('input[name="port"]').clear().type('53') + cy.get('input[name="interval"]').clear().type('25') + cy.get('input[name="timeout"]').clear().type('15') + cy.get('form').submit() + cy.title().should('eq', 'Statup | Services') + cy.get('tr').should('have.length', 9) + }) + + it('should view HTTP GET service', () => { + cy.visit('http://localhost:8080/service/6') + cy.title().should('eq', 'Statup | Google.com Service') + }) + + it('should view HTTP POST service', () => { + cy.visit('http://localhost:8080/service/7') + cy.title().should('eq', 'Statup | JSON Regex Test Service') + }) + + it('should view TCP service', () => { + cy.visit('http://localhost:8080/service/8') + cy.title().should('eq', 'Statup | Google DNS Service') + }) + + it('should update HTTP service', () => { + cy.visit('http://localhost:8080/service/6') + cy.title().should('eq', 'Statup | Google.com Service') + cy.get('#service_name').clear().type('Google Updated') + cy.get('#service_interval').clear().type('60') + cy.get(':nth-child(3) > form').submit() + cy.title().should('eq', 'Statup | Google Updated Service') + cy.get('#service_name').should('have.value', 'Google Updated') + }); + + it('should check the updated service', () => { + cy.visit('http://localhost:8080/service/6') + cy.title().should('eq', 'Statup | Google Updated Service') + cy.get('#service_name').should('have.value', 'Google Updated') + cy.get('#service_interval').should('have.value', '60') + }) + it('should delete a service', () => { cy.visit('http://localhost:8080/services') cy.get(':nth-child(5) > .text-right > .btn-group > .btn-danger').click() cy.title().should('eq', 'Statup | Services') - cy.get('tr').should('have.length', 6) + cy.get('tr').should('have.length', 8) }) diff --git a/.dev/test/cypress/integration/settings.js b/.dev/test/cypress/integration/settings.js index d3374526..84a4ffc6 100644 --- a/.dev/test/cypress/integration/settings.js +++ b/.dev/test/cypress/integration/settings.js @@ -21,5 +21,11 @@ context('Settings Forms', () => { cy.get('.footer').should('contain', 'This is a custom footer') }) + // it('should check index page for changes', () => { + // cy.visit('http://localhost:8080/') + // cy.title().should('eq', 'Project Updated Status') + // cy.get('.header-title').should('contain', 'Project Updated') + // cy.get('.header-desc').should('contain', 'This is an awesome page') + // }) }); \ No newline at end of file diff --git a/.dev/test/cypress/integration/users.js b/.dev/test/cypress/integration/users.js index 0245a829..3b33e195 100644 --- a/.dev/test/cypress/integration/users.js +++ b/.dev/test/cypress/integration/users.js @@ -23,6 +23,26 @@ context('User Testing', () => { cy.get('tr').should('have.length', 3) }) + it('should create a edit user', () => { + cy.visit('http://localhost:8080/user/2') + cy.get('input[name="password"]').type('password567') + cy.get('input[name="password_confirm"]').type('password567') + cy.get('form').submit() + cy.get('tr').should('have.length', 3) + }) + + // it('should logout and login with new password', () => { + // cy.visit('http://localhost:8080/logout') + // cy.title().should('eq', 'Statup | Users') + // cy.get('#user_2 > .btn-group > .btn-danger').click() + // cy.get('tr').should('have.length', 2) + // cy.visit('http://localhost:8080/login') + // cy.get('input[name="username"]').type('hunterlong') + // cy.get('input[name="password"]').type('password567') + // cy.get('form').submit() + // cy.title().should('eq', 'Project Updated Status') + // }) + it('should delete a user', () => { cy.visit('http://localhost:8080/users') cy.get('#user_2 > .btn-group > .btn-danger').click() diff --git a/Makefile b/Makefile index 3acfc891..a30b3ec8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=0.45 +VERSION=0.46 BINARY_NAME=statup GOPATH:=$(GOPATH) GOCMD=go @@ -69,10 +69,13 @@ docker: docker-run: docker docker run -t -p 8080:8080 hunterlong/statup:latest -docker-dev: - docker build -t hunterlong/statup:dev -f Dockerfile-dev . +docker-dev: clean + docker build -t hunterlong/statup:dev -f .dev/Dockerfile . -docker-run-dev: clean docker-dev +docker-push-dev: docker-dev + docker push hunterlong/statup:dev + +docker-run-dev: docker-dev docker run -t -p 8080:8080 hunterlong/statup:dev docker-test: docker-dev diff --git a/cmd/cli.go b/cmd/cli.go index c769aab6..191a3e4d 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -145,7 +145,7 @@ func RunOnce() { utils.Log(4, err) } for _, s := range core.CoreApp.Services { - out := core.ServiceCheck(s.ToService()) + out := core.ServiceCheck(s, true) fmt.Printf(" Service %v | URL: %v | Latency: %0.0fms | Online: %v\n", out.Name, out.Domain, (out.Latency * 1000), out.Online) } } @@ -191,33 +191,33 @@ func TestPlugin(plug types.PluginActions) { core.OnLoad(core.DbSession) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnSuccess(Service)'") - core.OnSuccess(core.SelectService(1).ToService()) + core.OnSuccess(core.SelectService(1)) 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).ToService(), fakeFailD) + core.OnFailure(core.SelectService(1), fakeFailD) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnSettingsSaved(Core)'") fmt.Println(BRAKER) core.OnSettingsSaved(core.CoreApp.ToCore()) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnNewService(Service)'") - core.OnNewService(core.SelectService(2).ToService()) + core.OnNewService(core.SelectService(2)) 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).ToService() + srv := core.SelectService(2) 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).ToService()) + core.OnDeletedService(core.SelectService(1)) fmt.Println("\n" + BRAKER) } @@ -255,18 +255,18 @@ func FakeSeed(plug types.PluginActions) { core.CoreApp.ApiSecret = "0x0x0x0x0" core.CoreApp.ApiKey = "abcdefg12345" - fakeSrv := &types.Service{ + fakeSrv := &core.Service{Service: &types.Service{ Name: "Test Plugin Service", Domain: "https://google.com", Method: "GET", - } + }} core.CreateService(fakeSrv) - fakeSrv2 := &types.Service{ + fakeSrv2 := &core.Service{Service: &types.Service{ Name: "Awesome Plugin Service", Domain: "https://netflix.com", Method: "GET", - } + }} core.CreateService(fakeSrv2) fakeUser := &types.User{ diff --git a/cmd/main_test.go b/cmd/main_test.go index 9d98a338..54d0544e 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -335,11 +335,11 @@ func RunOneService_Check(t *testing.T) { service := core.SelectService(1) assert.NotNil(t, service) t.Log(service) - assert.Equal(t, "Google", service.ToService().Name) + assert.Equal(t, "Google", service.Name) } func RunService_Create(t *testing.T) { - service := &types.Service{ + service := &core.Service{Service: &types.Service{ Name: "test service", Domain: "https://google.com", ExpectedStatus: 200, @@ -348,7 +348,7 @@ func RunService_Create(t *testing.T) { Type: "http", Method: "GET", Timeout: 30, - } + }} id, err := core.CreateService(service) assert.Nil(t, err) assert.Equal(t, int64(6), id) @@ -380,7 +380,7 @@ func RunService_GraphData(t *testing.T) { } func RunBadService_Create(t *testing.T) { - service := &types.Service{ + service := &core.Service{Service: &types.Service{ Name: "Bad Service", Domain: "https://9839f83h72gey2g29278hd2od2d.com", ExpectedStatus: 200, @@ -389,7 +389,7 @@ func RunBadService_Create(t *testing.T) { Type: "http", Method: "GET", Timeout: 30, - } + }} id, err := core.CreateService(service) assert.Nil(t, err) assert.Equal(t, int64(7), id) @@ -398,14 +398,19 @@ 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.ToService().Name) + assert.Equal(t, "JSON API Tester", service.Name) + assert.True(t, service.IsRunning()) } func RunDeleteService(t *testing.T) { service := core.SelectService(4) assert.NotNil(t, service) - assert.Equal(t, "JSON API Tester", service.ToService().Name) - err := core.DeleteService(service.ToService()) + assert.Equal(t, "JSON API Tester", service.Name) + assert.True(t, service.IsRunning()) + t.Log(service.Running) + err := core.DeleteService(service) + t.Log(service.Running) + assert.False(t, service.IsRunning()) assert.Nil(t, err) } @@ -415,11 +420,11 @@ func RunCreateService_Hits(t *testing.T) { assert.NotNil(t, services) for i := 0; i <= 10; i++ { for _, s := range services { - var service *types.Service - if s.ToService().Type == "http" { - service = core.ServiceHTTPCheck(s.ToService()) + var service *core.Service + if s.Type == "http" { + service = core.ServiceHTTPCheck(s, true) } else { - service = core.ServiceTCPCheck(s.ToService()) + service = core.ServiceTCPCheck(s, true) } assert.NotNil(t, service) } @@ -438,7 +443,7 @@ func RunService_Failures(t *testing.T) { t.SkipNow() service := core.SelectService(6) assert.NotNil(t, service) - assert.NotEmpty(t, service.ToService().Failures) + assert.NotEmpty(t, service.Failures) } func RunService_LimitedHits(t *testing.T) { diff --git a/core/checker.go b/core/checker.go index e1a35c8f..45a62da2 100644 --- a/core/checker.go +++ b/core/checker.go @@ -33,28 +33,29 @@ type FailureData types.FailureData func CheckServices() { CoreApp.Services, _ = SelectAllServices() utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services))) - for _, ser := range CoreApp.Services { - s := ser.ToService() + for _, s := range CoreApp.Services { //go obj.StartCheckins() - s.Start() - go CheckQueue(s) + go CheckQueue(s, true) } } -func CheckQueue(s *types.Service) { +func CheckQueue(s *Service, record bool) { +CheckLoop: for { select { - case <-s.StopRoutine: - return + case <-s.Running: + utils.Log(1, fmt.Sprintf("Stopping service: %v", s.Name)) + break CheckLoop default: - s = SelectService(s.Id).ToService() - ServiceCheck(s) + utils.Log(1, fmt.Sprintf("Checking service: %v", s.Name)) + ServiceCheck(s, record) + time.Sleep(time.Duration(s.Interval) * time.Second) + continue } - time.Sleep(time.Duration(s.Interval) * time.Second) } } -func DNSCheck(s *types.Service) (float64, error) { +func DNSCheck(s *Service) (float64, error) { t1 := time.Now() url, err := url.Parse(s.Domain) if err != nil { @@ -69,7 +70,7 @@ func DNSCheck(s *types.Service) (float64, error) { return subTime, err } -func ServiceTCPCheck(s *types.Service) *types.Service { +func ServiceTCPCheck(s *Service, record bool) *Service { t1 := time.Now() domain := fmt.Sprintf("%v", s.Domain) if s.Port != 0 { @@ -77,34 +78,42 @@ func ServiceTCPCheck(s *types.Service) *types.Service { } conn, err := net.DialTimeout("tcp", domain, time.Duration(s.Timeout)*time.Second) if err != nil { - RecordFailure(s, fmt.Sprintf("TCP Dial Error %v", err)) + if record { + RecordFailure(s, fmt.Sprintf("TCP Dial Error %v", err)) + } return s } if err := conn.Close(); err != nil { - RecordFailure(s, fmt.Sprintf("TCP Socket Close Error %v", err)) + if record { + RecordFailure(s, fmt.Sprintf("TCP Socket Close Error %v", err)) + } return s } t2 := time.Now() s.Latency = t2.Sub(t1).Seconds() s.LastResponse = "" - RecordSuccess(s) - return s -} - -func ServiceCheck(s *types.Service) *types.Service { - switch s.Type { - case "http": - ServiceHTTPCheck(s) - case "tcp": - ServiceTCPCheck(s) + if record { + RecordSuccess(s) } return s } -func ServiceHTTPCheck(s *types.Service) *types.Service { +func ServiceCheck(s *Service, record bool) *Service { + switch s.Type { + case "http": + ServiceHTTPCheck(s, record) + case "tcp": + ServiceTCPCheck(s, record) + } + return s +} + +func ServiceHTTPCheck(s *Service, record bool) *Service { dnsLookup, err := DNSCheck(s) if err != nil { - RecordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err)) + if record { + RecordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err)) + } return s } s.DnsLookup = dnsLookup @@ -121,7 +130,9 @@ func ServiceHTTPCheck(s *types.Service) *types.Service { response, err = client.Get(s.Domain) } if err != nil { - RecordFailure(s, fmt.Sprintf("HTTP Error %v", err)) + if record { + RecordFailure(s, fmt.Sprintf("HTTP Error %v", err)) + } return s } response.Header.Set("Connection", "close") @@ -129,7 +140,9 @@ func ServiceHTTPCheck(s *types.Service) *types.Service { t2 := time.Now() s.Latency = t2.Sub(t1).Seconds() if err != nil { - RecordFailure(s, fmt.Sprintf("HTTP Error %v", err)) + if record { + RecordFailure(s, fmt.Sprintf("HTTP Error %v", err)) + } return s } defer response.Body.Close() @@ -145,20 +158,26 @@ func ServiceHTTPCheck(s *types.Service) *types.Service { if !match { s.LastResponse = string(contents) s.LastStatusCode = response.StatusCode - RecordFailure(s, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected)) + if record { + 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 - RecordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus)) + if record { + 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 - RecordSuccess(s) + if record { + RecordSuccess(s) + } return s } @@ -166,7 +185,7 @@ type HitData struct { Latency float64 } -func RecordSuccess(s *types.Service) { +func RecordSuccess(s *Service) { s.Online = true s.LastOnline = time.Now() data := HitData{ @@ -177,7 +196,7 @@ func RecordSuccess(s *types.Service) { OnSuccess(s) } -func RecordFailure(s *types.Service, issue string) { +func RecordFailure(s *Service, issue string) { s.Online = false data := FailureData{ Issue: issue, diff --git a/core/checkin.go b/core/checkin.go index dbb584ff..80083e28 100644 --- a/core/checkin.go +++ b/core/checkin.go @@ -31,7 +31,7 @@ func (c *Checkin) String() string { func FindCheckin(api string) *types.Checkin { for _, ser := range CoreApp.Services { - for _, c := range ser.ToService().Checkins { + for _, c := range ser.Checkins { if c.Api == api { return c } diff --git a/core/core.go b/core/core.go index c09c8b23..38947286 100644 --- a/core/core.go +++ b/core/core.go @@ -113,8 +113,7 @@ func (c Core) MobileSASS() string { } func (c Core) AllOnline() bool { - for _, ser := range CoreApp.Services { - s := ser.ToService() + for _, s := range CoreApp.Services { if !s.Online { return false } diff --git a/core/events.go b/core/events.go index 27555ae4..bce6d6fe 100644 --- a/core/events.go +++ b/core/events.go @@ -17,7 +17,6 @@ package core import ( "github.com/fatih/structs" - "github.com/hunterlong/statup/notifiers" "github.com/hunterlong/statup/types" "upper.io/db.v3/lib/sqlbuilder" ) @@ -28,18 +27,20 @@ func OnLoad(db sqlbuilder.Database) { } } -func OnSuccess(s *types.Service) { +func OnSuccess(s *Service) { for _, p := range CoreApp.AllPlugins { p.OnSuccess(structs.Map(s)) } - notifiers.OnSuccess(s) + //notifiers.OnSuccess(s) + // TODO convert notifiers to correct type } -func OnFailure(s *types.Service, f FailureData) { +func OnFailure(s *Service, f FailureData) { for _, p := range CoreApp.AllPlugins { p.OnFailure(structs.Map(s)) } - notifiers.OnFailure(s) + //notifiers.OnFailure(s) + // TODO convert notifiers to correct type } func OnSettingsSaved(c *types.Core) { @@ -54,19 +55,19 @@ func OnNewUser(u *types.User) { } } -func OnNewService(s *types.Service) { +func OnNewService(s *Service) { for _, p := range CoreApp.AllPlugins { p.OnNewService(structs.Map(s)) } } -func OnDeletedService(s *types.Service) { +func OnDeletedService(s *Service) { for _, p := range CoreApp.AllPlugins { p.OnDeletedService(structs.Map(s)) } } -func OnUpdateService(s *types.Service) { +func OnUpdateService(s *Service) { for _, p := range CoreApp.AllPlugins { p.OnUpdatedService(structs.Map(s)) } diff --git a/core/failures.go b/core/failures.go index ba9e8df2..fbddf587 100644 --- a/core/failures.go +++ b/core/failures.go @@ -24,7 +24,7 @@ import ( "time" ) -func CreateServiceFailure(s *types.Service, data FailureData) (int64, error) { +func CreateServiceFailure(s *Service, data FailureData) (int64, error) { fail := &types.Failure{ Issue: data.Issue, Service: s.Id, @@ -52,7 +52,7 @@ func SelectAllFailures(s *types.Service) []*types.Failure { return fails } -func DeleteFailures(u *types.Service) { +func DeleteFailures(u *Service) { var fails []*Failure col := DbSession.Collection("failures") col.Find("service", u.Id).All(&fails) @@ -61,8 +61,7 @@ func DeleteFailures(u *types.Service) { } } -func (ser *Service) LimitedFailures() []*Failure { - s := ser.ToService() +func (s *Service) LimitedFailures() []*Failure { var fails []*types.Failure var failArr []*Failure col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id").Limit(10) @@ -102,15 +101,13 @@ func CountFailures() uint64 { return amount } -func (ser *Service) TotalFailures() (uint64, error) { - s := ser.ToService() +func (s *Service) TotalFailures() (uint64, error) { col := DbSession.Collection("failures").Find("service", s.Id) amount, err := col.Count() return amount, err } -func (ser *Service) TotalFailures24Hours() (uint64, error) { - s := ser.ToService() +func (s *Service) TotalFailures24Hours() (uint64, error) { col := DbSession.Collection("failures").Find("service", s.Id) amount, err := col.Count() return amount, err diff --git a/core/hits.go b/core/hits.go index 59c00d5a..07a937d6 100644 --- a/core/hits.go +++ b/core/hits.go @@ -28,7 +28,7 @@ func hitCol() db.Collection { return DbSession.Collection("hits") } -func CreateServiceHit(s *types.Service, d HitData) (int64, error) { +func CreateServiceHit(s *Service, d HitData) (int64, error) { h := Hit{ Service: s.Id, Latency: d.Latency, @@ -42,16 +42,14 @@ func CreateServiceHit(s *types.Service, d HitData) (int64, error) { return uuid.(int64), err } -func (ser *Service) Hits() ([]Hit, error) { - s := ser.ToService() +func (s *Service) Hits() ([]Hit, error) { var hits []Hit col := hitCol().Find("service", s.Id).OrderBy("-id") err := col.All(&hits) return hits, err } -func (ser *Service) LimitedHits() ([]*Hit, error) { - s := ser.ToService() +func (s *Service) LimitedHits() ([]*Hit, error) { var hits []*Hit col := hitCol().Find("service", s.Id).OrderBy("-id").Limit(1024) err := col.All(&hits) @@ -65,16 +63,14 @@ func reverseHits(input []*Hit) []*Hit { return append(reverseHits(input[1:]), input[0]) } -func (ser *Service) SelectHitsGroupBy(group string) ([]Hit, error) { - s := ser.ToService() +func (s *Service) SelectHitsGroupBy(group string) ([]Hit, error) { var hits []Hit col := hitCol().Find("service", s.Id) err := col.All(&hits) return hits, err } -func (ser *Service) TotalHits() (uint64, error) { - s := ser.ToService() +func (s *Service) TotalHits() (uint64, error) { col := hitCol().Find("service", s.Id) amount, err := col.Count() return amount, err diff --git a/core/services.go b/core/services.go index 3fc88c48..dc35f0e7 100644 --- a/core/services.go +++ b/core/services.go @@ -26,7 +26,7 @@ import ( ) type Service struct { - s *types.Service + *types.Service } type Failure struct { @@ -39,9 +39,8 @@ func serviceCol() db.Collection { func SelectService(id int64) *Service { for _, s := range CoreApp.Services { - ser := s.ToService() - if ser.Id == id { - return &Service{ser} + if s.Id == id { + return s } } return nil @@ -58,9 +57,10 @@ func SelectAllServices() ([]*Service, error) { } for _, s := range services { ser := NewService(s) + ser.Start() + ser.Checkins = SelectAllCheckins(s) + ser.Failures = SelectAllFailures(s) sers = append(sers, ser) - s.Checkins = SelectAllCheckins(s) - s.Failures = SelectAllFailures(s) } CoreApp.Services = sers return sers, err @@ -78,10 +78,9 @@ func (s *Service) AvgTime() float64 { return val } -func (ser *Service) Online24() float32 { - s := ser.ToService() - total, _ := ser.TotalHits() - failed, _ := ser.TotalFailures24Hours() +func (s *Service) Online24() float32 { + total, _ := s.TotalHits() + failed, _ := s.TotalFailures24Hours() if failed == 0 { s.Online24Hours = 100.00 return s.Online24Hours @@ -105,18 +104,13 @@ type DateScan struct { Value int64 `json:"y"` } -func (s *Service) ToService() *types.Service { - return s.s -} - func NewService(s *types.Service) *Service { return &Service{s} } -func (ser *Service) SmallText() string { - s := ser.ToService() - last := ser.LimitedFailures() - hits, _ := ser.LimitedHits() +func (s *Service) SmallText() string { + last := s.LimitedFailures() + hits, _ := s.LimitedHits() if !s.Online { if len(last) > 0 { lastFailure := MakeFailure(last[0].ToFailure()) @@ -147,8 +141,7 @@ func GroupDataBy(column string, id int64, tm time.Time, increment string) string return sql } -func (ser *Service) GraphData() string { - s := ser.ToService() +func (s *Service) GraphData() string { var d []*DateScan since := time.Now().Add(time.Hour*-24 + time.Minute*0 + time.Second*0) @@ -182,10 +175,9 @@ func (ser *Service) GraphData() string { return string(data) } -func (ser *Service) AvgUptime() string { - s := ser.ToService() - failed, _ := ser.TotalFailures() - total, _ := ser.TotalHits() +func (s *Service) AvgUptime() string { + failed, _ := s.TotalFailures() + total, _ := s.TotalHits() if failed == 0 { s.TotalUptime = "100" return s.TotalUptime @@ -206,11 +198,10 @@ func (ser *Service) AvgUptime() string { return s.TotalUptime } -func RemoveArray(u *types.Service) []*Service { +func RemoveArray(u *Service) []*Service { var srvcs []*Service for _, s := range CoreApp.Services { - ser := s.ToService() - if ser.Id != u.Id { + if s.Id != u.Id { srvcs = append(srvcs, s) } } @@ -218,24 +209,20 @@ func RemoveArray(u *types.Service) []*Service { return srvcs } -func DeleteService(u *types.Service) error { +func DeleteService(u *Service) error { res := serviceCol().Find("id", u.Id) err := res.Delete() if err != nil { utils.Log(3, fmt.Sprintf("Failed to delete service %v. %v", u.Name, err)) return err } - utils.Log(1, fmt.Sprintf("Stopping %v Monitoring...", u.Name)) - if u.StopRoutine != nil { - close(u.StopRoutine) - } - utils.Log(1, fmt.Sprintf("Stopped %v Monitoring Service", u.Name)) + u.Close() RemoveArray(u) OnDeletedService(u) return err } -func UpdateService(service *types.Service) *types.Service { +func UpdateService(service *Service) *Service { service.CreatedAt = time.Now() res := serviceCol().Find("id", service.Id) err := res.Update(service) @@ -243,23 +230,28 @@ func UpdateService(service *types.Service) *types.Service { utils.Log(3, fmt.Sprintf("Failed to update service %v. %v", service.Name, err)) return service } - CoreApp.Services, _ = SelectAllServices() + updateService(service) + CoreApp.Services, err = SelectAllServices() + if err != nil { + utils.Log(3, fmt.Sprintf("error selecting all services: %v", err)) + return service + } OnUpdateService(service) return service } -func updateService(u *types.Service) { +func updateService(new *Service) { var services []*Service for _, s := range CoreApp.Services { - if s.s.Id == u.Id { - s.s = u + if s.Id == new.Id { + s = new } services = append(services, s) } CoreApp.Services = services } -func CreateService(u *types.Service) (int64, error) { +func CreateService(u *Service) (int64, error) { u.CreatedAt = time.Now() uuid, err := serviceCol().Insert(u) if uuid == nil { @@ -267,16 +259,15 @@ func CreateService(u *types.Service) (int64, error) { return 0, err } u.Id = uuid.(int64) - u.StopRoutine = make(chan bool) - CoreApp.Services = append(CoreApp.Services, &Service{u}) + u.Start() + CoreApp.Services = append(CoreApp.Services, u) return uuid.(int64), err } func CountOnline() int { amount := 0 for _, s := range CoreApp.Services { - ser := s.ToService() - if ser.Online { + if s.Online { amount++ } } diff --git a/core/services_test.go b/core/services_test.go index 683b0aa7..c16e7c0b 100644 --- a/core/services_test.go +++ b/core/services_test.go @@ -19,6 +19,7 @@ import ( "github.com/hunterlong/statup/types" "github.com/stretchr/testify/assert" "testing" + "time" ) var ( @@ -33,35 +34,47 @@ func TestSelectAllServices(t *testing.T) { func TestSelectHTTPService(t *testing.T) { service := SelectService(1) - assert.Equal(t, "Google", service.ToService().Name) - assert.Equal(t, "http", service.ToService().Type) + assert.Equal(t, "Google", service.Name) + assert.Equal(t, "http", service.Type) } func TestSelectTCPService(t *testing.T) { service := SelectService(5) - assert.Equal(t, "Google DNS", service.ToService().Name) - assert.Equal(t, "tcp", service.ToService().Type) + assert.Equal(t, "Google DNS", service.Name) + assert.Equal(t, "tcp", service.Type) } func TestUpdateService(t *testing.T) { service := SelectService(1) - assert.Equal(t, "Google", service.ToService().Name) - srv := service.ToService() + assert.Equal(t, "Google", service.Name) + srv := service srv.Name = "Updated Google" newService := UpdateService(srv) assert.Equal(t, "Updated Google", newService.Name) } +func TestUpdateAllServices(t *testing.T) { + services, err := SelectAllServices() + assert.Nil(t, err) + for k, s := range services { + srv := s + srv.Name = "Changed " + srv.Name + srv.Interval = k + 3 + newService := UpdateService(srv) + assert.Contains(t, newService.Name, "Changed") + } +} + func TestServiceHTTPCheck(t *testing.T) { service := SelectService(1) - checked := ServiceCheck(service.ToService()) - assert.Equal(t, "Updated Google", checked.Name) + checked := ServiceCheck(service, true) + assert.Equal(t, "Changed Updated Google", checked.Name) assert.True(t, checked.Online) } func TestCheckHTTPService(t *testing.T) { - service := SelectService(1).ToService() - assert.Equal(t, "Updated Google", service.Name) + service := SelectService(1) + assert.Equal(t, "Changed Updated Google", service.Name) assert.True(t, service.Online) assert.Equal(t, 200, service.LastStatusCode) assert.NotZero(t, service.Latency) @@ -69,14 +82,14 @@ func TestCheckHTTPService(t *testing.T) { func TestServiceTCPCheck(t *testing.T) { service := SelectService(5) - checked := ServiceCheck(service.ToService()) - assert.Equal(t, "Google DNS", checked.Name) + checked := ServiceCheck(service, true) + assert.Equal(t, "Changed Google DNS", checked.Name) assert.True(t, checked.Online) } func TestCheckTCPService(t *testing.T) { - service := SelectService(5).ToService() - assert.Equal(t, "Google DNS", service.Name) + service := SelectService(5) + assert.Equal(t, "Changed Google DNS", service.Name) assert.True(t, service.Online) assert.NotZero(t, service.Latency) } @@ -133,68 +146,74 @@ func TestCountOnline(t *testing.T) { } func TestCreateService(t *testing.T) { - s := &types.Service{ - Name: "Interpol - All The Rage Back Home", - Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU", + s := &Service{Service: &types.Service{ + Name: "That'll do 🐢", + Domain: "https://www.youtube.com/watch?v=rjQtzV9IZ0Q", ExpectedStatus: 200, - Interval: 30, + Interval: 3, Type: "http", Method: "GET", Timeout: 20, - } + }} var err error newServiceId, err = CreateService(s) assert.Nil(t, err) assert.NotZero(t, newServiceId) - newService := SelectService(newServiceId).ToService() - assert.Equal(t, "Interpol - All The Rage Back Home", newService.Name) + newService := SelectService(newServiceId) + assert.Equal(t, "That'll do 🐢", newService.Name) +} + +func TestViewNewService(t *testing.T) { + newService := SelectService(newServiceId) + assert.Equal(t, "That'll do 🐢", newService.Name) + } func TestCreateFailingHTTPService(t *testing.T) { - s := &types.Service{ + s := &Service{Service: &types.Service{ Name: "Bad URL", Domain: "http://localhost/iamnothere", ExpectedStatus: 200, - Interval: 30, + Interval: 2, Type: "http", Method: "GET", Timeout: 5, - } + }} var err error newServiceId, err = CreateService(s) assert.Nil(t, err) assert.NotZero(t, newServiceId) - newService := SelectService(newServiceId).ToService() + newService := SelectService(newServiceId) assert.Equal(t, "Bad URL", newService.Name) } func TestServiceFailedCheck(t *testing.T) { service := SelectService(7) - checked := ServiceCheck(service.ToService()) + checked := ServiceCheck(service, true) assert.Equal(t, "Bad URL", checked.Name) assert.False(t, checked.Online) } func TestCreateFailingTCPService(t *testing.T) { - s := &types.Service{ + s := &Service{Service: &types.Service{ Name: "Bad TCP", Domain: "localhost", Port: 5050, Interval: 30, Type: "tcp", Timeout: 5, - } + }} var err error newServiceId, err = CreateService(s) assert.Nil(t, err) assert.NotZero(t, newServiceId) - newService := SelectService(newServiceId).ToService() + newService := SelectService(newServiceId) assert.Equal(t, "Bad TCP", newService.Name) } func TestServiceFailedTCPCheck(t *testing.T) { service := SelectService(8) - checked := ServiceCheck(service.ToService()) + checked := ServiceCheck(service, true) assert.Equal(t, "Bad TCP", checked.Name) assert.False(t, checked.Online) } @@ -206,13 +225,13 @@ func TestCreateServiceFailure(t *testing.T) { } service := SelectService(8) - id, err := CreateServiceFailure(service.ToService(), fail) + id, err := CreateServiceFailure(service, fail) assert.Nil(t, err) assert.NotZero(t, id) } func TestDeleteService(t *testing.T) { - service := SelectService(newServiceId).ToService() + service := SelectService(newServiceId) count, err := SelectAllServices() assert.Nil(t, err) @@ -225,3 +244,23 @@ func TestDeleteService(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 7, len(count)) } + +func TestServiceCloseRoutine(t *testing.T) { + s := new(types.Service) + s.Name = "example" + s.Domain = "https://google.com" + s.Type = "http" + s.Method = "GET" + s.ExpectedStatus = 200 + s.Interval = 1 + service := NewService(s) + service.Start() + assert.True(t, service.IsRunning()) + go CheckQueue(service, false) + time.Sleep(5 * time.Second) + assert.True(t, service.IsRunning()) + service.Close() + assert.False(t, service.IsRunning()) + service.Close() + assert.False(t, service.IsRunning()) +} diff --git a/core/setup.go b/core/setup.go index c54d5473..bfe5df81 100644 --- a/core/setup.go +++ b/core/setup.go @@ -35,7 +35,7 @@ type ErrorResponse struct { func LoadSampleData() error { utils.Log(1, "Inserting Sample Data...") - s1 := &types.Service{ + s1 := &Service{Service: &types.Service{ Name: "Google", Domain: "https://google.com", ExpectedStatus: 200, @@ -43,8 +43,8 @@ func LoadSampleData() error { Type: "http", Method: "GET", Timeout: 10, - } - s2 := &types.Service{ + }} + s2 := &Service{Service: &types.Service{ Name: "Statup Github", Domain: "https://github.com/hunterlong/statup", ExpectedStatus: 200, @@ -52,8 +52,8 @@ func LoadSampleData() error { Type: "http", Method: "GET", Timeout: 20, - } - s3 := &types.Service{ + }} + s3 := &Service{Service: &types.Service{ Name: "JSON Users Test", Domain: "https://jsonplaceholder.typicode.com/users", ExpectedStatus: 200, @@ -61,8 +61,8 @@ func LoadSampleData() error { Type: "http", Method: "GET", Timeout: 30, - } - s4 := &types.Service{ + }} + s4 := &Service{Service: &types.Service{ Name: "JSON API Tester", Domain: "https://jsonplaceholder.typicode.com/posts", ExpectedStatus: 201, @@ -72,15 +72,15 @@ func LoadSampleData() error { Method: "POST", PostData: `{ "title": "statup", "body": "bar", "userId": 19999 }`, Timeout: 30, - } - s5 := &types.Service{ + }} + s5 := &Service{Service: &types.Service{ Name: "Google DNS", Domain: "8.8.8.8", Interval: 20, Type: "tcp", Port: 53, Timeout: 120, - } + }} id, err := CreateService(s1) if err != nil { utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err)) diff --git a/handlers/api.go b/handlers/api.go index 2c6d6c77..402259fd 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -79,7 +79,7 @@ func ApiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) { var s *types.Service decoder := json.NewDecoder(r.Body) decoder.Decode(&s) - service := serv.ToService() + service := serv core.UpdateService(service) json.NewEncoder(w).Encode(s) } diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index 838e397d..01833c8b 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -140,6 +140,7 @@ func TestServiceChartHandler(t *testing.T) { Router().ServeHTTP(rr, req) body := rr.Body.String() assert.Equal(t, 200, rr.Code) + t.Log(body) assert.Contains(t, body, "var ctx_1") assert.Contains(t, body, "var ctx_2") } diff --git a/handlers/prometheus.go b/handlers/prometheus.go index ce858c70..e110d45b 100644 --- a/handlers/prometheus.go +++ b/handlers/prometheus.go @@ -44,7 +44,7 @@ func PrometheusHandler(w http.ResponseWriter, r *http.Request) { system += fmt.Sprintf("statup_total_services %v", len(core.CoreApp.Services)) metrics = append(metrics, system) for _, ser := range core.CoreApp.Services { - v := ser.ToService() + v := ser online := 1 if !v.Online { online = 0 @@ -61,6 +61,13 @@ func PrometheusHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(output)) } +func ResetDbHandler(w http.ResponseWriter, r *http.Request) { + utils.Log(1, fmt.Sprintf("Prometheus /metrics Request From IP: %v\n", r.RemoteAddr)) + core.DropDatabase() + core.CoreApp = nil + w.WriteHeader(http.StatusOK) +} + func isAuthorized(r *http.Request) bool { var token string tokens, ok := r.Header["Authorization"] diff --git a/handlers/routes.go b/handlers/routes.go index f0cc48eb..01154d8e 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -23,6 +23,7 @@ import ( "github.com/hunterlong/statup/source" "github.com/hunterlong/statup/utils" "net/http" + "os" "time" ) @@ -88,6 +89,9 @@ func Router() *mux.Router { r.Handle("/api/users", http.HandlerFunc(ApiAllUsersHandler)) r.Handle("/api/users/{id}", http.HandlerFunc(ApiUserHandler)) r.Handle("/metrics", http.HandlerFunc(PrometheusHandler)) + if os.Getenv("GO_ENV") == "test" { + r.Handle("/reset", http.HandlerFunc(ResetDbHandler)) + } r.NotFoundHandler = http.HandlerFunc(Error404Handler) return r } diff --git a/handlers/services.go b/handlers/services.go index 86891b37..82fde90f 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -25,6 +25,10 @@ import ( "strconv" ) +type Service struct { + *types.Service +} + func RenderServiceChartsHandler(w http.ResponseWriter, r *http.Request) { services := core.CoreApp.Services w.Header().Set("Content-Type", "text/javascript") @@ -57,7 +61,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { checkType := r.PostForm.Get("check_type") postData := r.PostForm.Get("post_data") - service := &types.Service{ + service := &core.Service{Service: &types.Service{ Name: name, Domain: domain, Method: method, @@ -68,13 +72,13 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { Port: port, PostData: postData, Timeout: timeout, - } + }} _, err := core.CreateService(service) if err != nil { utils.Log(3, fmt.Sprintf("Error starting %v check routine. %v", service.Name, err)) } - go core.CheckQueue(service) + go core.CheckQueue(service, true) core.OnNewService(service) ExecuteResponse(w, r, "services.html", core.CoreApp.Services) @@ -87,7 +91,11 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) { } vars := mux.Vars(r) serv := core.SelectService(utils.StringInt(vars["id"])) - service := serv.ToService() + if serv == nil { + w.WriteHeader(http.StatusNotFound) + return + } + service := serv core.DeleteService(service) ExecuteResponse(w, r, "services.html", core.CoreApp.Services) } @@ -109,7 +117,7 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { } vars := mux.Vars(r) serv := core.SelectService(utils.StringInt(vars["id"])) - service := serv.ToService() + service := serv r.ParseForm() name := r.PostForm.Get("name") domain := r.PostForm.Get("domain") @@ -121,7 +129,7 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { timeout, _ := strconv.Atoi(r.PostForm.Get("timeout")) checkType := r.PostForm.Get("check_type") postData := r.PostForm.Get("post_data") - serviceUpdate := &types.Service{ + serviceUpdate := &core.Service{Service: &types.Service{ Id: service.Id, Name: name, Domain: domain, @@ -133,7 +141,7 @@ func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { Port: port, PostData: postData, Timeout: timeout, - } + }} service = core.UpdateService(serviceUpdate) core.CoreApp.Services, _ = core.SelectAllServices() @@ -148,7 +156,7 @@ func ServicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) { } vars := mux.Vars(r) serv := core.SelectService(utils.StringInt(vars["id"])) - service := serv.ToService() + service := serv core.DeleteFailures(service) core.CoreApp.Services, _ = core.SelectAllServices() ExecuteResponse(w, r, "services.html", core.CoreApp.Services) @@ -162,7 +170,7 @@ func CheckinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) interval := utils.StringInt(r.PostForm.Get("interval")) serv := core.SelectService(utils.StringInt(vars["id"])) - service := serv.ToService() + service := serv checkin := &core.Checkin{ Service: service.Id, Interval: interval, diff --git a/source/js/charts.js b/source/js/charts.js index a7a0ab87..65794b60 100644 --- a/source/js/charts.js +++ b/source/js/charts.js @@ -1,21 +1,4 @@ -/* - * Statup - * Copyright (C) 2018. Hunter Long and the project contributors - * Written by Hunter Long and the project contributors - * - * https://github.com/hunterlong/statup - * - * The licenses for most software and other practical works are designed - * to take away your freedom to share and change the works. By contrast, - * the GNU General Public License is intended to guarantee your freedom to - * share and change all versions of a program--to make sure it remains free - * software for all its users. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -{{ 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} +{{ range . }}{{$s := .}}{{ 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_{{$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}}}}) diff --git a/source/tmpl/dashboard.html b/source/tmpl/dashboard.html index 25e270c1..9cbb362e 100644 --- a/source/tmpl/dashboard.html +++ b/source/tmpl/dashboard.html @@ -48,7 +48,7 @@
{{ range .Services }} - {{ $s := .ToService }} + {{ $s := . }}
{{$s.Name}}
@@ -60,7 +60,7 @@
{{ range .Services }} - {{ $s := .ToService }} + {{ $s := . }} {{ if .LimitedFailures }}

{{$s.Name}} Failures

diff --git a/source/tmpl/index.html b/source/tmpl/index.html index a12c7e10..55634e7a 100644 --- a/source/tmpl/index.html +++ b/source/tmpl/index.html @@ -28,7 +28,7 @@
{{end}} {{ range .Services }} - {{ $s := .ToService }} + {{ $s := . }}
diff --git a/source/tmpl/service.html b/source/tmpl/service.html index 1244265d..35c9ff05 100644 --- a/source/tmpl/service.html +++ b/source/tmpl/service.html @@ -1,4 +1,4 @@ -{{ $s := .ToService }} +{{ $s := . }} diff --git a/source/tmpl/services.html b/source/tmpl/services.html index 39815389..2d111b7f 100644 --- a/source/tmpl/services.html +++ b/source/tmpl/services.html @@ -33,7 +33,7 @@ {{range .}} - {{ $s := .ToService }} + {{ $s := . }} {{$s.Name}} {{if $s.Online}}ONLINE{{else}}OFFLINE{{end}} diff --git a/types/service.go b/types/service.go index 84ae2003..a09d6cf4 100644 --- a/types/service.go +++ b/types/service.go @@ -15,7 +15,9 @@ package types -import "time" +import ( + "time" +) type Service struct { Id int64 `db:"id,omitempty" json:"id"` @@ -39,7 +41,7 @@ type Service struct { OrderId int64 `json:"order_id"` Failures []*Failure `json:"failures"` Checkins []*Checkin `json:"checkins"` - StopRoutine chan bool `json:"-"` + Running chan bool `json:"-"` LastResponse string LastStatusCode int LastOnline time.Time @@ -47,9 +49,26 @@ type Service struct { } func (s *Service) Start() { - s.StopRoutine = make(chan bool) + if s.Running == nil { + s.Running = make(chan bool) + } } func (s *Service) Close() { - s.StopRoutine <- true + if s.IsRunning() { + close(s.Running) + } +} + +func (s *Service) IsRunning() bool { + if s.Running == nil { + return false + } + select { + case <-s.Running: + return false + default: + return true + } + return false }