pull/78/head
Hunter Long 2018-10-06 21:48:33 -07:00
parent 55cb0808bd
commit 9061a97955
23 changed files with 162 additions and 154 deletions

2
.gitignore vendored
View File

@ -22,4 +22,4 @@ source/rice-box.go
sass sass
.DS_Store .DS_Store
source/css/base.css.map source/css/base.css.map
/dev/test/node_modules/ dev/test/node_modules

View File

@ -13,6 +13,7 @@ PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)
PUBLISH_BODY='{ "request": { "branch": "master", "config": { "env": { "VERSION": "$(VERSION)", "COMMIT": "$(TRAVIS_COMMIT)" } } } }' PUBLISH_BODY='{ "request": { "branch": "master", "config": { "env": { "VERSION": "$(VERSION)", "COMMIT": "$(TRAVIS_COMMIT)" } } } }'
TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statup v$(VERSION)", "config": { "os": [ "linux" ], "language": "go", "go": [ "1.10.x" ], "go_import_path": "github.com/hunterlong/statup", "install": true, "sudo": "required", "services": [ "docker" ], "env": { "VERSION": "$(VERSION)" }, "matrix": { "allow_failures": [ { "go": "master" } ], "fast_finish": true }, "before_deploy": [ "git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "make tag" ], "deploy": [ { "provider": "releases", "api_key": "$(GH_TOKEN)", "file": [ "build/statup-osx-x64.tar.gz", "build/statup-osx-x32.tar.gz", "build/statup-linux-x64.tar.gz", "build/statup-linux-x32.tar.gz", "build/statup-linux-arm64.tar.gz", "build/statup-linux-arm7.tar.gz", "build/statup-linux-alpine.tar.gz", "build/statup-windows-x64.zip" ], "skip_cleanup": true } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "travis_wait 30 docker pull karalabe/xgo-latest", "make release" ], "after_success": [], "after_deploy": [ "make publish-dev" ] } } }' TRAVIS_BUILD_CMD='{ "request": { "branch": "master", "message": "Compile master for Statup v$(VERSION)", "config": { "os": [ "linux" ], "language": "go", "go": [ "1.10.x" ], "go_import_path": "github.com/hunterlong/statup", "install": true, "sudo": "required", "services": [ "docker" ], "env": { "VERSION": "$(VERSION)" }, "matrix": { "allow_failures": [ { "go": "master" } ], "fast_finish": true }, "before_deploy": [ "git config --local user.name \"hunterlong\"", "git config --local user.email \"info@socialeck.com\"", "make tag" ], "deploy": [ { "provider": "releases", "api_key": "$(GH_TOKEN)", "file": [ "build/statup-osx-x64.tar.gz", "build/statup-osx-x32.tar.gz", "build/statup-linux-x64.tar.gz", "build/statup-linux-x32.tar.gz", "build/statup-linux-arm64.tar.gz", "build/statup-linux-arm7.tar.gz", "build/statup-linux-alpine.tar.gz", "build/statup-windows-x64.zip" ], "skip_cleanup": true } ], "notifications": { "email": false }, "before_script": ["gem install sass"], "script": [ "travis_wait 30 docker pull karalabe/xgo-latest", "make release" ], "after_success": [], "after_deploy": [ "make publish-dev" ] } } }'
TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statup TEST_DIR=$(GOPATH)/src/github.com/hunterlong/statup
PATH:=$(PATH)
# build and compile Statup and then test # build and compile Statup and then test
all: dev-deps compile install test-all docker-build-all all: dev-deps compile install test-all docker-build-all

View File

@ -92,7 +92,7 @@ func CatchCLI(args []string) error {
return err return err
} }
indexSource := core.ExportIndexHTML() indexSource := core.ExportIndexHTML()
err = core.SaveFile("./index.html", []byte(indexSource)) err = utils.SaveFile("./index.html", []byte(indexSource))
if err != nil { if err != nil {
utils.Log(4, err) utils.Log(4, err)
return err return err
@ -194,7 +194,7 @@ func HelpEcho() {
// core.OnSuccess(core.SelectService(1)) // core.OnSuccess(core.SelectService(1))
// fmt.Println("\n" + BRAKER) // fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnFailure(Service, FailureData)'") // fmt.Println(POINT + "Sending 'OnFailure(Service, FailureData)'")
// fakeFailD := &types.Failure{ // fakeFailD := &types.failure{
// Issue: "No issue, just testing this plugin. This would include HTTP failure information though", // 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), fakeFailD)
@ -206,7 +206,7 @@ func HelpEcho() {
// fmt.Println(POINT + "Sending 'OnNewService(Service)'") // fmt.Println(POINT + "Sending 'OnNewService(Service)'")
// core.OnNewService(core.SelectService(2)) // core.OnNewService(core.SelectService(2))
// fmt.Println("\n" + BRAKER) // fmt.Println("\n" + BRAKER)
// fmt.Println(POINT + "Sending 'OnNewUser(User)'") // fmt.Println(POINT + "Sending 'OnNewUser(user)'")
// user, _ := core.SelectUser(1) // user, _ := core.SelectUser(1)
// core.OnNewUser(user) // core.OnNewUser(user)
// fmt.Println("\n" + BRAKER) // fmt.Println("\n" + BRAKER)
@ -258,7 +258,7 @@ func HelpEcho() {
// }} // }}
// fakeSrv2.Create() // fakeSrv2.Create()
// //
// fakeUser := &types.User{ // fakeUser := &types.user{
// Id: 6334, // Id: 6334,
// Username: "Bulbasaur", // Username: "Bulbasaur",
// Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy", // Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy",
@ -268,7 +268,7 @@ func HelpEcho() {
// } // }
// fakeUser.Create() // fakeUser.Create()
// //
// fakeUser = &types.User{ // fakeUser = &types.user{
// Id: 6335, // Id: 6335,
// Username: "Billy", // Username: "Billy",
// Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy", // Password: "$2a$14$NzT/fLdE3f9iB1Eux2C84O6ZoPhI4NfY0Ke32qllCFo8pMTkUPZzy",
@ -288,12 +288,12 @@ func HelpEcho() {
// } // }
// fakeSrv2.CreateHit(dd) // fakeSrv2.CreateHit(dd)
// //
// fail := &types.Failure{ // fail := &types.failure{
// Issue: "This is not an issue, but it would container HTTP response errors.", // Issue: "This is not an issue, but it would container HTTP response errors.",
// } // }
// fakeSrv.CreateFailure(fail) // fakeSrv.CreateFailure(fail)
// //
// fail = &types.Failure{ // fail = &types.failure{
// Issue: "HTTP Status Code 521 did not match 200", // Issue: "HTTP Status Code 521 did not match 200",
// } // }
// fakeSrv.CreateFailure(fail) // fakeSrv.CreateFailure(fail)

View File

@ -28,10 +28,10 @@ import (
) )
var ( var (
VERSION string // VERSION stores the current version of Statup
VERSION string
// COMMIT stores the git commit hash for this version of Statup
COMMIT string COMMIT string
usingEnv bool
directory string
ipAddress string ipAddress string
port int port int
) )

View File

@ -121,12 +121,12 @@ func TestRunAll(t *testing.T) {
t.Run(dbt+" Create Users", func(t *testing.T) { t.Run(dbt+" Create Users", func(t *testing.T) {
RunUserCreate(t) RunUserCreate(t)
}) })
t.Run(dbt+" Update User", func(t *testing.T) { t.Run(dbt+" Update user", func(t *testing.T) {
RunUser_Update(t) runUserUpdate(t)
}) })
t.Run(dbt+" Create Non Unique Users", func(t *testing.T) { t.Run(dbt+" Create Non Unique Users", func(t *testing.T) {
t.SkipNow() t.SkipNow()
RunUser_NonUniqueCreate(t) runUserNonUniqueCreate(t)
}) })
t.Run(dbt+" Select Users", func(t *testing.T) { t.Run(dbt+" Select Users", func(t *testing.T) {
RunUserSelectAll(t) RunUserSelectAll(t)
@ -147,7 +147,7 @@ func TestRunAll(t *testing.T) {
RunServiceToJSON(t) RunServiceToJSON(t)
}) })
t.Run(dbt+" Avg Time", func(t *testing.T) { t.Run(dbt+" Avg Time", func(t *testing.T) {
RunService_AvgTime(t) runServiceAvgTime(t)
}) })
t.Run(dbt+" Online 24h", func(t *testing.T) { t.Run(dbt+" Online 24h", func(t *testing.T) {
RunServiceOnline24(t) RunServiceOnline24(t)
@ -173,7 +173,7 @@ func TestRunAll(t *testing.T) {
t.Run(dbt+" Delete Service", func(t *testing.T) { t.Run(dbt+" Delete Service", func(t *testing.T) {
RunDeleteService(t) RunDeleteService(t)
}) })
t.Run(dbt+" Delete User", func(t *testing.T) { t.Run(dbt+" Delete user", func(t *testing.T) {
RunUserDelete(t) RunUserDelete(t)
}) })
t.Run(dbt+" HTTP /", func(t *testing.T) { t.Run(dbt+" HTTP /", func(t *testing.T) {
@ -341,7 +341,7 @@ func RunUserCreate(t *testing.T) {
assert.Equal(t, int64(4), id) assert.Equal(t, int64(4), id)
} }
func RunUser_Update(t *testing.T) { func runUserUpdate(t *testing.T) {
user, err := core.SelectUser(1) user, err := core.SelectUser(1)
user.Email = "info@updatedemail.com" user.Email = "info@updatedemail.com"
assert.Nil(t, err) assert.Nil(t, err)
@ -352,7 +352,7 @@ func RunUser_Update(t *testing.T) {
assert.Equal(t, "info@updatedemail.com", updatedUser.Email) assert.Equal(t, "info@updatedemail.com", updatedUser.Email)
} }
func RunUser_NonUniqueCreate(t *testing.T) { func runUserNonUniqueCreate(t *testing.T) {
user := core.ReturnUser(&types.User{ user := core.ReturnUser(&types.User{
Username: "admin", Username: "admin",
Password: "admin", Password: "admin",
@ -410,7 +410,7 @@ func RunServiceToJSON(t *testing.T) {
assert.NotEmpty(t, jsoned) assert.NotEmpty(t, jsoned)
} }
func RunService_AvgTime(t *testing.T) { func runServiceAvgTime(t *testing.T) {
service := core.SelectService(1) service := core.SelectService(1)
assert.NotNil(t, service) assert.NotNil(t, service)
avg := service.AvgUptime24() avg := service.AvgUptime24()

View File

@ -176,7 +176,7 @@ func (s *Service) checkHttp(record bool) *Service {
return s return s
} }
response.Header.Set("Connection", "close") response.Header.Set("Connection", "close")
response.Header.Set("User-Agent", "StatupMonitor") response.Header.Set("user-Agent", "StatupMonitor")
t2 := time.Now() t2 := time.Now()
s.Latency = t2.Sub(t1).Seconds() s.Latency = t2.Sub(t1).Seconds()
if err != nil { if err != nil {

View File

@ -23,84 +23,84 @@ import (
"time" "time"
) )
type Checkin struct { type checkin struct {
*types.Checkin *types.Checkin
} }
type CheckinHit struct { type checkinHit struct {
*types.CheckinHit *types.CheckinHit
} }
// String will return a Checkin API string // String will return a checkin API string
func (c *Checkin) String() string { func (c *checkin) String() string {
return c.ApiKey return c.ApiKey
} }
// ReturnCheckin converts *types.Checking to *core.Checkin // ReturnCheckin converts *types.Checking to *core.checkin
func ReturnCheckin(s *types.Checkin) *Checkin { func ReturnCheckin(s *types.Checkin) *checkin {
return &Checkin{Checkin: s} return &checkin{Checkin: s}
} }
// ReturnCheckinHit converts *types.CheckinHit to *core.CheckinHit // ReturnCheckinHit converts *types.checkinHit to *core.checkinHit
func ReturnCheckinHit(h *types.CheckinHit) *CheckinHit { func ReturnCheckinHit(h *types.CheckinHit) *checkinHit {
return &CheckinHit{CheckinHit: h} return &checkinHit{CheckinHit: h}
} }
// SelectCheckin will find a Checkin based on the API supplied // SelectCheckin will find a checkin based on the API supplied
func SelectCheckin(api string) *Checkin { func SelectCheckin(api string) *checkin {
var checkin Checkin var checkin checkin
checkinDB().Where("api_key = ?", api).First(&checkin) checkinDB().Where("api_key = ?", api).First(&checkin)
return &checkin return &checkin
} }
// Period will return the duration of the Checkin interval // Period will return the duration of the checkin interval
func (u *Checkin) Period() time.Duration { func (c *checkin) Period() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", u.Interval)) duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.Interval))
return duration return duration
} }
// Grace will return the duration of the Checkin Grace Period (after service hasn't responded, wait a bit for a response) // Grace will return the duration of the checkin Grace Period (after service hasn't responded, wait a bit for a response)
func (u *Checkin) Grace() time.Duration { func (c *checkin) Grace() time.Duration {
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", u.GracePeriod)) duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.GracePeriod))
return duration return duration
} }
// Expected returns the duration of when the serviec should receive a checkin // Expected returns the duration of when the serviec should receive a checkin
func (u *Checkin) Expected() time.Duration { func (c *checkin) Expected() time.Duration {
last := u.Last().CreatedAt last := c.Last().CreatedAt
now := time.Now() now := time.Now()
lastDir := now.Sub(last) lastDir := now.Sub(last)
sub := time.Duration(u.Period() - lastDir) sub := time.Duration(c.Period() - lastDir)
return sub return sub
} }
// Last returns the last CheckinHit for a Checkin // Last returns the last checkinHit for a checkin
func (u *Checkin) Last() CheckinHit { func (c *checkin) Last() checkinHit {
var hit CheckinHit var hit checkinHit
checkinHitsDB().Where("checkin = ?", u.Id).Last(&hit) checkinHitsDB().Where("checkin = ?", c.Id).Last(&hit)
return hit return hit
} }
// Hits returns all of the CheckinHits for a given Checkin // Hits returns all of the CheckinHits for a given checkin
func (u *Checkin) Hits() []CheckinHit { func (c *checkin) Hits() []checkinHit {
var checkins []CheckinHit var checkins []checkinHit
checkinHitsDB().Where("checkin = ?", u.Id).Order("id DESC").Find(&checkins) checkinHitsDB().Where("checkin = ?", c.Id).Order("id DESC").Find(&checkins)
return checkins return checkins
} }
// Create will create a new Checkin // Create will create a new checkin
func (u *Checkin) Create() (int64, error) { func (c *checkin) Create() (int64, error) {
u.ApiKey = utils.RandomString(7) c.ApiKey = utils.RandomString(7)
row := checkinDB().Create(&u) row := checkinDB().Create(&c)
if row.Error != nil { if row.Error != nil {
utils.Log(2, row.Error) utils.Log(2, row.Error)
return 0, row.Error return 0, row.Error
} }
return u.Id, row.Error return c.Id, row.Error
} }
// Update will update a Checkin // Update will update a checkin
func (u *Checkin) Update() (int64, error) { func (u *checkin) Update() (int64, error) {
row := checkinDB().Update(&u) row := checkinDB().Update(&u)
if row.Error != nil { if row.Error != nil {
utils.Log(2, row.Error) utils.Log(2, row.Error)
@ -109,8 +109,8 @@ func (u *Checkin) Update() (int64, error) {
return u.Id, row.Error return u.Id, row.Error
} }
// Create will create a new successful CheckinHit // Create will create a new successful checkinHit
func (u *CheckinHit) Create() (int64, error) { func (u *checkinHit) Create() (int64, error) {
if u.CreatedAt.IsZero() { if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now() u.CreatedAt = time.Now()
} }
@ -122,14 +122,14 @@ func (u *CheckinHit) Create() (int64, error) {
return u.Id, row.Error return u.Id, row.Error
} }
// Ago returns the duration of time between now and the last successful CheckinHit // Ago returns the duration of time between now and the last successful checkinHit
func (f *CheckinHit) Ago() string { func (f *checkinHit) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt) got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt)
return got return got
} }
// RecheckCheckinFailure will check if a Service Checkin has been reported yet // RecheckCheckinFailure will check if a Service checkin has been reported yet
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) { func (c *checkin) RecheckCheckinFailure(guard chan struct{}) {
between := time.Now().Sub(time.Now()).Seconds() between := time.Now().Sub(time.Now()).Seconds()
if between > float64(c.Interval) { if between > float64(c.Interval) {
fmt.Println("rechecking every 15 seconds!") fmt.Println("rechecking every 15 seconds!")

View File

@ -61,12 +61,12 @@ func usersDB() *gorm.DB {
return DbSession.Model(&types.User{}) return DbSession.Model(&types.User{})
} }
// checkinDB returns the Checkin records for a service // checkinDB returns the checkin records for a service
func checkinDB() *gorm.DB { func checkinDB() *gorm.DB {
return DbSession.Model(&types.Checkin{}) return DbSession.Model(&types.Checkin{})
} }
// checkinHitsDB returns the 'hits' from the Checkin record // checkinHitsDB returns the 'hits' from the checkin record
func checkinHitsDB() *gorm.DB { func checkinHitsDB() *gorm.DB {
return DbSession.Model(&types.CheckinHit{}) return DbSession.Model(&types.CheckinHit{})
} }
@ -101,26 +101,26 @@ func (s *Hit) AfterFind() (err error) {
return return
} }
// AfterFind for Failure will set the timezone // AfterFind for failure will set the timezone
func (f *Failure) AfterFind() (err error) { func (f *failure) AfterFind() (err error) {
f.CreatedAt = utils.Timezoner(f.CreatedAt, CoreApp.Timezone) f.CreatedAt = utils.Timezoner(f.CreatedAt, CoreApp.Timezone)
return return
} }
// AfterFind for USer will set the timezone // AfterFind for USer will set the timezone
func (u *User) AfterFind() (err error) { func (u *user) AfterFind() (err error) {
u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone) u.CreatedAt = utils.Timezoner(u.CreatedAt, CoreApp.Timezone)
return return
} }
// AfterFind for Checkin will set the timezone // AfterFind for checkin will set the timezone
func (s *Checkin) AfterFind() (err error) { func (s *checkin) AfterFind() (err error) {
s.CreatedAt = utils.Timezoner(s.CreatedAt, CoreApp.Timezone) s.CreatedAt = utils.Timezoner(s.CreatedAt, CoreApp.Timezone)
return return
} }
// AfterFind for CheckinHit will set the timezone // AfterFind for checkinHit will set the timezone
func (s *CheckinHit) AfterFind() (err error) { func (s *checkinHit) AfterFind() (err error) {
s.CreatedAt = utils.Timezoner(s.CreatedAt, CoreApp.Timezone) s.CreatedAt = utils.Timezoner(s.CreatedAt, CoreApp.Timezone)
return return
} }
@ -133,16 +133,16 @@ func (u *Hit) BeforeCreate() (err error) {
return return
} }
// BeforeCreate for Failure will set CreatedAt to UTC // BeforeCreate for failure will set CreatedAt to UTC
func (u *Failure) BeforeCreate() (err error) { func (u *failure) BeforeCreate() (err error) {
if u.CreatedAt.IsZero() { if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC() u.CreatedAt = time.Now().UTC()
} }
return return
} }
// BeforeCreate for User will set CreatedAt to UTC // BeforeCreate for user will set CreatedAt to UTC
func (u *User) BeforeCreate() (err error) { func (u *user) BeforeCreate() (err error) {
if u.CreatedAt.IsZero() { if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC() u.CreatedAt = time.Now().UTC()
} }
@ -157,16 +157,16 @@ func (u *Service) BeforeCreate() (err error) {
return return
} }
// BeforeCreate for Checkin will set CreatedAt to UTC // BeforeCreate for checkin will set CreatedAt to UTC
func (u *Checkin) BeforeCreate() (err error) { func (u *checkin) BeforeCreate() (err error) {
if u.CreatedAt.IsZero() { if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC() u.CreatedAt = time.Now().UTC()
} }
return return
} }
// BeforeCreate for CheckinHit will set CreatedAt to UTC // BeforeCreate for checkinHit will set CreatedAt to UTC
func (u *CheckinHit) BeforeCreate() (err error) { func (u *checkinHit) BeforeCreate() (err error) {
if u.CreatedAt.IsZero() { if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now().UTC() u.CreatedAt = time.Now().UTC()
} }

View File

@ -21,13 +21,13 @@ import (
"github.com/hunterlong/statup/source" "github.com/hunterlong/statup/source"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
"html/template" "html/template"
"io/ioutil"
) )
func injectDatabase() { func injectDatabase() {
Configs.Connect(false, utils.Directory) Configs.Connect(false, utils.Directory)
} }
// ExportIndexHTML returns the HTML of the index page as a string
func ExportIndexHTML() string { func ExportIndexHTML() string {
source.Assets() source.Assets()
injectDatabase() injectDatabase()
@ -83,6 +83,7 @@ func ExportIndexHTML() string {
return result return result
} }
// ExportChartsJs renders the charts for the index page
func ExportChartsJs() string { func ExportChartsJs() string {
render, err := source.JsBox.String("charts.js") render, err := source.JsBox.String("charts.js")
if err != nil { if err != nil {
@ -102,8 +103,3 @@ func ExportChartsJs() string {
result := tpl.String() result := tpl.String()
return result return result
} }
func SaveFile(filename string, data []byte) error {
err := ioutil.WriteFile(filename, data, 0644)
return err
}

View File

@ -24,7 +24,7 @@ import (
"time" "time"
) )
type Failure struct { type failure struct {
*types.Failure *types.Failure
} }
@ -41,8 +41,8 @@ func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
} }
// AllFailures will return all failures attached to a service // AllFailures will return all failures attached to a service
func (s *Service) AllFailures() []*Failure { func (s *Service) AllFailures() []*failure {
var fails []*Failure var fails []*failure
col := failuresDB().Where("service = ?", s.Id).Order("id desc") col := failuresDB().Where("service = ?", s.Id).Order("id desc")
err := col.Find(&fails) err := col.Find(&fails)
if err.Error != nil { if err.Error != nil {
@ -56,30 +56,30 @@ func (s *Service) AllFailures() []*Failure {
} }
// DeleteFailures will delete all failures for a service // DeleteFailures will delete all failures for a service
func (u *Service) DeleteFailures() { func (s *Service) DeleteFailures() {
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, u.Id) err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
if err.Error != nil { if err.Error != nil {
utils.Log(3, fmt.Sprintf("failed to delete all failures: %v", err)) utils.Log(3, fmt.Sprintf("failed to delete all failures: %v", err))
} }
u.Failures = nil s.Failures = nil
} }
// LimitedFailures will return the last 10 failures from a service // LimitedFailures will return the last 10 failures from a service
func (s *Service) LimitedFailures() []*Failure { func (s *Service) LimitedFailures() []*failure {
var failArr []*Failure var failArr []*failure
col := failuresDB().Where("service = ?", s.Id).Order("id desc").Limit(10) col := failuresDB().Where("service = ?", s.Id).Order("id desc").Limit(10)
col.Find(&failArr) col.Find(&failArr)
return failArr return failArr
} }
// Ago returns a human readable timestamp for a failure // Ago returns a human readable timestamp for a failure
func (f *Failure) Ago() string { func (f *failure) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt) got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt)
return got return got
} }
// Delete will remove a failure record from the database // Delete will remove a failure record from the database
func (f *Failure) Delete() error { func (f *failure) Delete() error {
db := failuresDB().Delete(f) db := failuresDB().Delete(f)
return db.Error return db.Error
} }
@ -129,7 +129,7 @@ func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
} }
// ParseError returns a human readable error for a failure // ParseError returns a human readable error for a failure
func (f *Failure) ParseError() string { func (f *failure) ParseError() string {
err := strings.Contains(f.Issue, "connection reset by peer") err := strings.Contains(f.Issue, "connection reset by peer")
if err { if err {
return fmt.Sprintf("Connection Reset") return fmt.Sprintf("Connection Reset")

View File

@ -22,15 +22,15 @@ import (
) )
var ( var (
allowed_vars = []string{"host", "username", "password", "port", "api_key", "api_secret", "var1", "var2"} allowedVars = []string{"host", "username", "password", "port", "api_key", "api_secret", "var1", "var2"}
) )
func checkNotifierForm(n Notifier) error { func checkNotifierForm(n Notifier) error {
notifier := asNotification(n) notifier := asNotification(n)
for _, f := range notifier.Form { for _, f := range notifier.Form {
contains := contains(f.DbField, allowed_vars) contains := contains(f.DbField, allowedVars)
if !contains { if !contains {
return errors.New(fmt.Sprintf("the DbField '%v' is not allowed, allowed vars: %v", f.DbField, allowed_vars)) return errors.New(fmt.Sprintf("the DbField '%v' is not allowed, allowed vars: %v", f.DbField, allowedVars))
} }
} }
return nil return nil

View File

@ -110,7 +110,7 @@ func OnUpdatedCore(c *types.Core) {
} }
} }
// OnUpdatedCore is triggered when the CoreApp settings are saved - CoreEvents interface // OnStart is triggered when the Statup service has started
func OnStart(c *types.Core) { func OnStart(c *types.Core) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) {
@ -128,7 +128,7 @@ func OnNewNotifier(n *Notification) {
} }
} }
// NotifierEvents interface // OnUpdatedNotifier is triggered when a notifier has been updated
func OnUpdatedNotifier(n *Notification) { func OnUpdatedNotifier(n *Notification) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) {

View File

@ -28,8 +28,10 @@ import (
) )
var ( var (
AllCommunications []types.AllNotifiers // AllCommunications holds all the loaded notifiers // AllCommunications holds all the loaded notifiers
db *gorm.DB // db holds the Statup database connection AllCommunications []types.AllNotifiers
// db holds the Statup database connection
db *gorm.DB
) )
// Notification contains all the fields for a Statup Notifier. // Notification contains all the fields for a Statup Notifier.
@ -225,7 +227,7 @@ func SelectNotifier(method string) (*Notification, Notifier, error) {
for _, comm := range AllCommunications { for _, comm := range AllCommunications {
n, ok := comm.(Notifier) n, ok := comm.(Notifier)
if !ok { if !ok {
return nil, nil, errors.New(fmt.Sprintf("incorrect notification type: %v", reflect.TypeOf(n).String())) return nil, nil, fmt.Errorf("incorrect notification type: %v", reflect.TypeOf(n).String())
} }
notifier := n.Select() notifier := n.Select()
if notifier.Method == method { if notifier.Method == method {
@ -305,31 +307,31 @@ func install(n Notifier) error {
} }
// LastSent returns a time.Duration of the last sent notification for the notifier // LastSent returns a time.Duration of the last sent notification for the notifier
func (f *Notification) LastSent() time.Duration { func (n *Notification) LastSent() time.Duration {
if len(f.logs) == 0 { if len(n.logs) == 0 {
return time.Duration(0) return time.Duration(0)
} }
last := f.Logs()[0] last := n.Logs()[0]
since := time.Since(last.Timestamp) since := time.Since(last.Timestamp)
return since return since
} }
// SentLastHour returns the total amount of notifications sent in last 1 hour // SentLastHour returns the total amount of notifications sent in last 1 hour
func (f *Notification) SentLastHour() int { func (n *Notification) SentLastHour() int {
since := time.Now().Add(-1 * time.Hour) since := time.Now().Add(-1 * time.Hour)
return f.SentLast(since) return n.SentLast(since)
} }
// SentLastMinute returns the total amount of notifications sent in last 1 minute // SentLastMinute returns the total amount of notifications sent in last 1 minute
func (f *Notification) SentLastMinute() int { func (n *Notification) SentLastMinute() int {
since := time.Now().Add(-1 * time.Minute) since := time.Now().Add(-1 * time.Minute)
return f.SentLast(since) return n.SentLast(since)
} }
// SentLast accept a time.Time and returns the amount of sent notifications within your time to current // SentLast accept a time.Time and returns the amount of sent notifications within your time to current
func (f *Notification) SentLast(since time.Time) int { func (n *Notification) SentLast(since time.Time) int {
sent := 0 sent := 0
for _, v := range f.Logs() { for _, v := range n.Logs() {
lastTime := time.Time(v.Time) lastTime := time.Time(v.Time)
if lastTime.After(since) { if lastTime.After(since) {
sent++ sent++
@ -387,21 +389,21 @@ func inLimits(n interface{}) bool {
} }
// WithinLimits returns true if the notifier is within its sending limits // WithinLimits returns true if the notifier is within its sending limits
func (notify *Notification) WithinLimits() (bool, error) { func (n *Notification) WithinLimits() (bool, error) {
if notify.SentLastMinute() == 0 { if n.SentLastMinute() == 0 {
return true, nil return true, nil
} }
if notify.SentLastMinute() >= notify.Limits { if n.SentLastMinute() >= n.Limits {
return false, errors.New(fmt.Sprintf("notifier sent %v out of %v in last minute", notify.SentLastMinute(), notify.Limits)) return false, fmt.Errorf("notifier sent %v out of %v in last minute", n.SentLastMinute(), n.Limits)
} }
if notify.Delay.Seconds() == 0 { if n.Delay.Seconds() == 0 {
notify.Delay = time.Duration(500 * time.Millisecond) n.Delay = time.Duration(500 * time.Millisecond)
} }
if notify.LastSent().Seconds() == 0 { if n.LastSent().Seconds() == 0 {
return true, nil return true, nil
} }
if notify.Delay.Seconds() >= notify.LastSent().Seconds() { if n.Delay.Seconds() >= n.LastSent().Seconds() {
return false, errors.New(fmt.Sprintf("notifiers delay (%v) is greater than last message sent (%v)", notify.Delay.Seconds(), notify.LastSent().Seconds())) return false, fmt.Errorf("notifiers delay (%v) is greater than last message sent (%v)", n.Delay.Seconds(), n.LastSent().Seconds())
} }
return true, nil return true, nil
} }

View File

@ -175,7 +175,7 @@ func insertSampleUsers() {
u3.Create() u3.Create()
} }
// InsertSampleData will create the example/dummy services for a brand new Statup installation // InsertLargeSampleData will create the example/dummy services for testing the Statup server
func InsertLargeSampleData() error { func InsertLargeSampleData() error {
insertSampleCore() insertSampleCore()
InsertSampleData() InsertSampleData()

View File

@ -52,8 +52,8 @@ func SelectService(id int64) *Service {
} }
// Checkins will return a slice of Checkins for a Service // Checkins will return a slice of Checkins for a Service
func (s *Service) Checkins() []*Checkin { func (s *Service) Checkins() []*checkin {
var checkin []*Checkin var checkin []*checkin
checkinDB().Where("service = ?", s.Id).Find(&checkin) checkinDB().Where("service = ?", s.Id).Find(&checkin)
return checkin return checkin
} }
@ -141,7 +141,7 @@ type DateScanObj struct {
} }
// lastFailure returns the last failure a service had // lastFailure returns the last failure a service had
func (s *Service) lastFailure() *Failure { func (s *Service) lastFailure() *failure {
limited := s.LimitedFailures() limited := s.LimitedFailures()
if len(limited) == 0 { if len(limited) == 0 {
return nil return nil

View File

@ -23,37 +23,37 @@ import (
"time" "time"
) )
type User struct { type user struct {
*types.User *types.User
} }
// ReturnUser returns *core.User based off a *types.User // ReturnUser returns *core.user based off a *types.user
func ReturnUser(u *types.User) *User { func ReturnUser(u *types.User) *user {
return &User{u} return &user{u}
} }
// SelectUser returns the User based on the user's ID. // SelectUser returns the user based on the user's ID.
func SelectUser(id int64) (*User, error) { func SelectUser(id int64) (*user, error) {
var user User var user user
err := usersDB().First(&user, id) err := usersDB().First(&user, id)
return &user, err.Error return &user, err.Error
} }
// SelectUser returns the User based on the user's username // SelectUsername returns the user based on the user's username
func SelectUsername(username string) (*User, error) { func SelectUsername(username string) (*user, error) {
var user User var user user
res := usersDB().Where("username = ?", username) res := usersDB().Where("username = ?", username)
err := res.First(&user) err := res.First(&user)
return &user, err.Error return &user, err.Error
} }
// Delete will remove the user record from the database // Delete will remove the user record from the database
func (u *User) Delete() error { func (u *user) Delete() error {
return usersDB().Delete(u).Error return usersDB().Delete(u).Error
} }
// Update will update the user's record in database // Update will update the user's record in database
func (u *User) Update() error { func (u *user) Update() error {
u.Password = utils.HashPassword(u.Password) u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA1Hash(5) u.ApiKey = utils.NewSHA1Hash(5)
u.ApiSecret = utils.NewSHA1Hash(10) u.ApiSecret = utils.NewSHA1Hash(10)
@ -61,7 +61,7 @@ func (u *User) Update() error {
} }
// Create will insert a new user into the database // Create will insert a new user into the database
func (u *User) Create() (int64, error) { func (u *user) Create() (int64, error) {
u.CreatedAt = time.Now() u.CreatedAt = time.Now()
u.Password = utils.HashPassword(u.Password) u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA1Hash(5) u.ApiKey = utils.NewSHA1Hash(5)
@ -78,8 +78,8 @@ func (u *User) Create() (int64, error) {
} }
// SelectAllUsers returns all users // SelectAllUsers returns all users
func SelectAllUsers() ([]*User, error) { func SelectAllUsers() ([]*user, error) {
var users []*User var users []*user
db := usersDB().Find(&users) db := usersDB().Find(&users)
if db.Error != nil { if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to load all users. %v", db.Error)) utils.Log(3, fmt.Sprintf("Failed to load all users. %v", db.Error))
@ -87,9 +87,9 @@ func SelectAllUsers() ([]*User, error) {
return users, db.Error return users, db.Error
} }
// AuthUser will return the User and a boolean if authentication was correct. // AuthUser will return the user and a boolean if authentication was correct.
// AuthUser accepts username, and password as a string // AuthUser accepts username, and password as a string
func AuthUser(username, password string) (*User, bool) { func AuthUser(username, password string) (*user, bool) {
user, err := SelectUsername(username) user, err := SelectUsername(username)
if err != nil { if err != nil {
utils.Log(2, err) utils.Log(2, err)

View File

@ -244,7 +244,7 @@ func TestUsersEditHandler(t *testing.T) {
body := rr.Body.String() body := rr.Body.String()
assert.Equal(t, 200, rr.Code) assert.Equal(t, 200, rr.Code)
assert.Contains(t, body, "<title>Statup | admin</title>") assert.Contains(t, body, "<title>Statup | admin</title>")
assert.Contains(t, body, "<h3>User admin</h3>") assert.Contains(t, body, "<h3>user admin</h3>")
assert.Contains(t, body, "value=\"info@statup.io\"") assert.Contains(t, body, "value=\"info@statup.io\"")
assert.Contains(t, body, "value=\"##########\"") assert.Contains(t, body, "value=\"##########\"")
assert.Contains(t, body, "Statup made with ❤️") assert.Contains(t, body, "Statup made with ❤️")

View File

@ -22,10 +22,6 @@ import (
"net/http" "net/http"
) )
type index struct {
Core *core.Core
}
func indexHandler(w http.ResponseWriter, r *http.Request) { func indexHandler(w http.ResponseWriter, r *http.Request) {
if core.Configs == nil { if core.Configs == nil {
http.Redirect(w, r, "/setup", http.StatusSeeOther) http.Redirect(w, r, "/setup", http.StatusSeeOther)
@ -38,6 +34,7 @@ func trayHandler(w http.ResponseWriter, r *http.Request) {
executeResponse(w, r, "tray.html", core.CoreApp, nil) executeResponse(w, r, "tray.html", core.CoreApp, nil)
} }
// DesktopInit will run the Statup server on a specific IP and port using SQLite database
func DesktopInit(ip string, port int) { func DesktopInit(ip string, port int) {
var err error var err error
exists := utils.FileExists(utils.Directory + "/statup.db") exists := utils.FileExists(utils.Directory + "/statup.db")

View File

@ -44,7 +44,7 @@ func prometheusHandler(w http.ResponseWriter, r *http.Request) {
system += fmt.Sprintf("statup_total_services %v", len(core.CoreApp.Services)) system += fmt.Sprintf("statup_total_services %v", len(core.CoreApp.Services))
metrics = append(metrics, system) metrics = append(metrics, system)
for _, ser := range core.CoreApp.Services { for _, ser := range core.CoreApp.Services {
v := ser.(*core.Service) v := ser.Select()
online := 1 online := 1
if !v.Online { if !v.Online {
online = 0 online = 0

View File

@ -61,11 +61,16 @@ $('form').submit(function() {
$('select#service_type').on('change', function() { $('select#service_type').on('change', function() {
var selected = $('#service_type option:selected').val(); var selected = $('#service_type option:selected').val();
var typeLabel = $('#service_type_label');
if (selected === 'tcp' || selected === 'udp') { if (selected === 'tcp' || selected === 'udp') {
if (selected === 'tcp') {
typeLabel.html('TCP Port')
} else {
typeLabel.html('UDP Port')
}
$('#service_port').parent().parent().removeClass('d-none'); $('#service_port').parent().parent().removeClass('d-none');
$('#service_check_type').parent().parent().addClass('d-none'); $('#service_check_type').parent().parent().addClass('d-none');
$('#service_url').attr('placeholder', 'localhost'); $('#service_url').attr('placeholder', 'localhost');
$('#post_data').parent().parent().addClass('d-none'); $('#post_data').parent().parent().addClass('d-none');
$('#service_response').parent().parent().addClass('d-none'); $('#service_response').parent().parent().addClass('d-none');
$('#service_response_code').parent().parent().addClass('d-none'); $('#service_response_code').parent().parent().addClass('d-none');
@ -75,10 +80,8 @@ $('select#service_type').on('change', function() {
$('#service_response_code').parent().parent().removeClass('d-none'); $('#service_response_code').parent().parent().removeClass('d-none');
$('#service_check_type').parent().parent().removeClass('d-none'); $('#service_check_type').parent().parent().removeClass('d-none');
$('#service_url').attr('placeholder', 'https://google.com'); $('#service_url').attr('placeholder', 'https://google.com');
$('#service_port').parent().parent().addClass('d-none'); $('#service_port').parent().parent().addClass('d-none');
} }
}); });
function AjaxChart(chart, service, start=0, end=9999999999, group="hour") { function AjaxChart(chart, service, start=0, end=9999999999, group="hour") {

View File

@ -54,7 +54,7 @@
</div> </div>
</div> </div>
<div class="form-group row{{if eq .Type ""}} d-none{{else if eq .Type "http"}} d-none{{end}}"> <div class="form-group row{{if eq .Type ""}} d-none{{else if eq .Type "http"}} d-none{{end}}">
<label for="service_port" class="col-sm-4 col-form-label">TCP Port</label> <label id="service_type_label" for="service_port" class="col-sm-4 col-form-label">TCP Port</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="number" name="port" class="form-control" value="{{if ne .Port 0}}{{.Port}}{{end}}" id="service_port" placeholder="8080"> <input type="number" name="port" class="form-control" value="{{if ne .Port 0}}{{.Port}}{{end}}" id="service_port" placeholder="8080">
</div> </div>

View File

@ -20,6 +20,7 @@ import (
"time" "time"
) )
// FormatDuration converts a time.Duration into a string
func FormatDuration(d time.Duration) string { func FormatDuration(d time.Duration) string {
var out string var out string
if d.Hours() >= 24 { if d.Hours() >= 24 {

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"github.com/ararog/timeago" "github.com/ararog/timeago"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"regexp" "regexp"
@ -29,6 +30,7 @@ import (
) )
var ( var (
// Directory returns the current path or the STATUP_DIR environment variable
Directory string Directory string
) )
@ -207,3 +209,9 @@ func DurationReadable(d time.Duration) string {
} }
return d.String() return d.String()
} }
// SaveFile
func SaveFile(filename string, data []byte) error {
err := ioutil.WriteFile(filename, data, 0644)
return err
}