mirror of https://github.com/statping/statping
checkins - comments
parent
9acdd4637a
commit
8a08a9d14d
4
Makefile
4
Makefile
|
@ -1,4 +1,4 @@
|
||||||
VERSION=0.73
|
VERSION=0.74
|
||||||
BINARY_NAME=statup
|
BINARY_NAME=statup
|
||||||
GOPATH:=$(GOPATH)
|
GOPATH:=$(GOPATH)
|
||||||
GOCMD=go
|
GOCMD=go
|
||||||
|
@ -37,7 +37,7 @@ build-all: build-mac build-linux build-windows build-alpine compress
|
||||||
docker-build-all: docker-build-base docker-build-dev docker-build-latest
|
docker-build-all: docker-build-base docker-build-dev docker-build-latest
|
||||||
|
|
||||||
# push all docker tags built
|
# push all docker tags built
|
||||||
docker-publish-all: docker-push-base docker-push-dev
|
docker-publish-all: docker-push-base docker-push-dev docker-push-latest
|
||||||
|
|
||||||
# build Statup for local arch
|
# build Statup for local arch
|
||||||
build: compile
|
build: compile
|
||||||
|
|
118
core/checkin.go
118
core/checkin.go
|
@ -23,7 +23,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type checkin struct {
|
type Checkin struct {
|
||||||
*types.Checkin
|
*types.Checkin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,14 +31,40 @@ type checkinHit struct {
|
||||||
*types.CheckinHit
|
*types.CheckinHit
|
||||||
}
|
}
|
||||||
|
|
||||||
// String will return a checkin API string
|
// Routine for checking if the last Checkin was within its interval
|
||||||
func (c *checkin) String() string {
|
func (c *Checkin) Routine() {
|
||||||
|
if c.Last() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reCheck := c.Period()
|
||||||
|
CheckinLoop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.Running:
|
||||||
|
utils.Log(1, fmt.Sprintf("Stopping checkin routing: %v", c.Name))
|
||||||
|
break CheckinLoop
|
||||||
|
case <-time.After(reCheck):
|
||||||
|
utils.Log(1, fmt.Sprintf("checking %v is expected at %v, checking every %v", c.Name, c.Expected(), c.Period()))
|
||||||
|
if c.Expected().Seconds() <= 0 {
|
||||||
|
issue := fmt.Sprintf("checkin is failing, no request since %v", c.Last().CreatedAt)
|
||||||
|
utils.Log(3, issue)
|
||||||
|
c.Service()
|
||||||
|
c.CreateFailure()
|
||||||
|
}
|
||||||
|
reCheck = c.Period()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String will return a Checkin API 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(c *types.Checkin) *checkin {
|
func ReturnCheckin(c *types.Checkin) *Checkin {
|
||||||
return &checkin{Checkin: c}
|
return &Checkin{Checkin: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReturnCheckinHit converts *types.checkinHit to *core.checkinHit
|
// ReturnCheckinHit converts *types.checkinHit to *core.checkinHit
|
||||||
|
@ -46,27 +72,52 @@ func ReturnCheckinHit(c *types.CheckinHit) *checkinHit {
|
||||||
return &checkinHit{CheckinHit: c}
|
return &checkinHit{CheckinHit: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectCheckin will find a checkin based on the API supplied
|
func (c *Checkin) Service() *Service {
|
||||||
func SelectCheckin(api string) *checkin {
|
service := SelectService(c.ServiceId)
|
||||||
var checkin checkin
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checkin) CreateFailure() (int64, error) {
|
||||||
|
service := c.Service()
|
||||||
|
fail := &types.Failure{
|
||||||
|
Issue: fmt.Sprintf("Checkin %v was not reported %v ago, it expects a request every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())),
|
||||||
|
Method: "checkin",
|
||||||
|
MethodId: c.Id,
|
||||||
|
Service: service.Id,
|
||||||
|
PingTime: c.Expected().Seconds() * 0.001,
|
||||||
|
}
|
||||||
|
row := failuresDB().Create(&fail)
|
||||||
|
return fail.Id, row.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectCheckin will find a Checkin based on the API supplied
|
||||||
|
func SelectCheckin(api string) *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
|
// SelectCheckin will find a Checkin based on the API supplied
|
||||||
func (c *checkin) Period() time.Duration {
|
func SelectCheckinId(id int64) *Checkin {
|
||||||
|
var checkin Checkin
|
||||||
|
checkinDB().Where("id = ?", id).First(&checkin)
|
||||||
|
return &checkin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Period will return the duration of the Checkin interval
|
||||||
|
func (c *Checkin) Period() time.Duration {
|
||||||
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.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 (c *checkin) Grace() time.Duration {
|
func (c *Checkin) Grace() time.Duration {
|
||||||
duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.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 (c *checkin) Expected() time.Duration {
|
func (c *Checkin) Expected() time.Duration {
|
||||||
last := c.Last().CreatedAt
|
last := c.Last().CreatedAt
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
lastDir := now.Sub(last)
|
lastDir := now.Sub(last)
|
||||||
|
@ -74,24 +125,37 @@ func (c *checkin) Expected() time.Duration {
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last returns the last checkinHit for a checkin
|
// Last returns the last checkinHit for a Checkin
|
||||||
func (c *checkin) Last() checkinHit {
|
func (c *Checkin) Last() *checkinHit {
|
||||||
var hit checkinHit
|
var hit checkinHit
|
||||||
checkinHitsDB().Where("checkin = ?", c.Id).Last(&hit)
|
checkinHitsDB().Where("checkin = ?", c.Id).Last(&hit)
|
||||||
return hit
|
return &hit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hits returns all of the CheckinHits for a given checkin
|
func (c *Checkin) Link() string {
|
||||||
func (c *checkin) Hits() []checkinHit {
|
return fmt.Sprintf("%v/checkin/%v", CoreApp.Domain, c.ApiKey)
|
||||||
var checkins []checkinHit
|
}
|
||||||
|
|
||||||
|
// Hits returns all of the CheckinHits for a given Checkin
|
||||||
|
func (c *Checkin) Hits() []*checkinHit {
|
||||||
|
var checkins []*checkinHit
|
||||||
checkinHitsDB().Where("checkin = ?", c.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 (c *checkin) Create() (int64, error) {
|
func (c *Checkin) Delete() error {
|
||||||
|
c.Close()
|
||||||
|
row := checkinDB().Delete(&c)
|
||||||
|
return row.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create will create a new Checkin
|
||||||
|
func (c *Checkin) Create() (int64, error) {
|
||||||
c.ApiKey = utils.RandomString(7)
|
c.ApiKey = utils.RandomString(7)
|
||||||
row := checkinDB().Create(&c)
|
row := checkinDB().Create(&c)
|
||||||
|
c.Start()
|
||||||
|
go c.Routine()
|
||||||
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
|
||||||
|
@ -99,8 +163,8 @@ func (c *checkin) Create() (int64, error) {
|
||||||
return c.Id, row.Error
|
return c.Id, row.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update will update a checkin
|
// Update will update a Checkin
|
||||||
func (c *checkin) Update() (int64, error) {
|
func (c *Checkin) Update() (int64, error) {
|
||||||
row := checkinDB().Update(c)
|
row := checkinDB().Update(c)
|
||||||
if row.Error != nil {
|
if row.Error != nil {
|
||||||
utils.Log(2, row.Error)
|
utils.Log(2, row.Error)
|
||||||
|
@ -128,8 +192,8 @@ func (c *checkinHit) Ago() string {
|
||||||
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!")
|
||||||
|
|
|
@ -63,12 +63,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{})
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,8 @@ func (u *user) AfterFind() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterFind for checkin will set the timezone
|
// AfterFind for Checkin will set the timezone
|
||||||
func (c *checkin) AfterFind() (err error) {
|
func (c *Checkin) AfterFind() (err error) {
|
||||||
c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -159,8 +159,8 @@ func (s *Service) BeforeCreate() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeCreate for checkin will set CreatedAt to UTC
|
// BeforeCreate for Checkin will set CreatedAt to UTC
|
||||||
func (c *checkin) BeforeCreate() (err error) {
|
func (c *Checkin) BeforeCreate() (err error) {
|
||||||
if c.CreatedAt.IsZero() {
|
if c.CreatedAt.IsZero() {
|
||||||
c.CreatedAt = time.Now().UTC()
|
c.CreatedAt = time.Now().UTC()
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ 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).Not("method = 'checkin'").Order("id desc")
|
||||||
err := col.Find(&fails)
|
err := col.Find(&fails)
|
||||||
if err.Error != nil {
|
if err.Error != nil {
|
||||||
utils.Log(3, fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
utils.Log(3, fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
||||||
|
@ -123,13 +123,16 @@ func (s *Service) TotalFailures() (uint64, error) {
|
||||||
// TotalFailuresSince returns the total amount of failures for a service since a specific time/date
|
// TotalFailuresSince returns the total amount of failures for a service since a specific time/date
|
||||||
func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
|
func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
|
||||||
var count uint64
|
var count uint64
|
||||||
rows := failuresDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05"))
|
rows := failuresDB().Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Not("method = 'checkin'")
|
||||||
err := rows.Count(&count)
|
err := rows.Count(&count)
|
||||||
return count, err.Error
|
return count, err.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
if f.Method == "checkin" {
|
||||||
|
return fmt.Sprintf("Checkin is Offline")
|
||||||
|
}
|
||||||
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")
|
||||||
|
|
|
@ -27,7 +27,7 @@ type Hit struct {
|
||||||
|
|
||||||
// CreateHit will create a new 'hit' record in the database for a successful/online service
|
// CreateHit will create a new 'hit' record in the database for a successful/online service
|
||||||
func (s *Service) CreateHit(h *types.Hit) (int64, error) {
|
func (s *Service) CreateHit(h *types.Hit) (int64, error) {
|
||||||
db := hitsDB().Create(h)
|
db := hitsDB().Create(&h)
|
||||||
if db.Error != nil {
|
if db.Error != nil {
|
||||||
utils.Log(2, db.Error)
|
utils.Log(2, db.Error)
|
||||||
return 0, db.Error
|
return 0, db.Error
|
||||||
|
|
|
@ -89,11 +89,11 @@ func InsertSampleData() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertSampleCheckins will create 2 checkins with 60 successful hits per checkin
|
// insertSampleCheckins will create 2 checkins with 60 successful hits per Checkin
|
||||||
func insertSampleCheckins() error {
|
func insertSampleCheckins() error {
|
||||||
s1 := SelectService(1)
|
s1 := SelectService(1)
|
||||||
checkin1 := ReturnCheckin(&types.Checkin{
|
checkin1 := ReturnCheckin(&types.Checkin{
|
||||||
Service: s1.Id,
|
ServiceId: s1.Id,
|
||||||
Interval: 300,
|
Interval: 300,
|
||||||
GracePeriod: 300,
|
GracePeriod: 300,
|
||||||
})
|
})
|
||||||
|
@ -101,7 +101,7 @@ func insertSampleCheckins() error {
|
||||||
|
|
||||||
s2 := SelectService(1)
|
s2 := SelectService(1)
|
||||||
checkin2 := ReturnCheckin(&types.Checkin{
|
checkin2 := ReturnCheckin(&types.Checkin{
|
||||||
Service: s2.Id,
|
ServiceId: s2.Id,
|
||||||
Interval: 900,
|
Interval: 900,
|
||||||
GracePeriod: 300,
|
GracePeriod: 300,
|
||||||
})
|
})
|
||||||
|
|
|
@ -51,13 +51,29 @@ func SelectService(id int64) *Service {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckinProcess runs the checkin routine for each checkin attached to service
|
||||||
|
func (s *Service) CheckinProcess() {
|
||||||
|
checkins := s.Checkins()
|
||||||
|
for _, c := range checkins {
|
||||||
|
c.Start()
|
||||||
|
go c.Routine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LimitedCheckins will return a slice of Checkins for a Service
|
||||||
|
func (s *Service) LimitedCheckins() []*Checkin {
|
||||||
|
var checkin []*Checkin
|
||||||
|
checkinDB().Where("service = ?", s.Id).Limit(10).Find(&checkin)
|
||||||
|
return checkin
|
||||||
|
}
|
||||||
|
|
||||||
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.
|
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.
|
||||||
func (c *Core) SelectAllServices() ([]*Service, error) {
|
func (c *Core) SelectAllServices() ([]*Service, error) {
|
||||||
var services []*Service
|
var services []*Service
|
||||||
|
@ -69,7 +85,7 @@ func (c *Core) SelectAllServices() ([]*Service, error) {
|
||||||
CoreApp.Services = nil
|
CoreApp.Services = nil
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
service.Start()
|
service.Start()
|
||||||
service.Checkins()
|
service.CheckinProcess()
|
||||||
service.AllFailures()
|
service.AllFailures()
|
||||||
CoreApp.Services = append(CoreApp.Services, service)
|
CoreApp.Services = append(CoreApp.Services, service)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testCheckin *checkin
|
testCheckin *Checkin
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateCheckin(t *testing.T) {
|
func TestCreateCheckin(t *testing.T) {
|
||||||
|
|
|
@ -157,6 +157,9 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap
|
||||||
"NewUser": func() *types.User {
|
"NewUser": func() *types.User {
|
||||||
return new(types.User)
|
return new(types.User)
|
||||||
},
|
},
|
||||||
|
"NewCheckin": func() *types.Checkin {
|
||||||
|
return new(types.Checkin)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,8 +86,9 @@ func Router() *mux.Router {
|
||||||
r.Handle("/service/{id}/edit", http.HandlerFunc(servicesViewHandler))
|
r.Handle("/service/{id}/edit", http.HandlerFunc(servicesViewHandler))
|
||||||
r.Handle("/service/{id}/delete", http.HandlerFunc(servicesDeleteHandler))
|
r.Handle("/service/{id}/delete", http.HandlerFunc(servicesDeleteHandler))
|
||||||
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("GET")
|
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("GET")
|
||||||
r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateUpdateHandler)).Methods("POST")
|
r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateHandler)).Methods("POST")
|
||||||
r.Handle("/checkin/{id}", http.HandlerFunc(checkinUpdateHandler))
|
r.Handle("/checkin/{id}/delete", http.HandlerFunc(checkinDeleteHandler)).Methods("GET")
|
||||||
|
r.Handle("/checkin/{id}", http.HandlerFunc(checkinHitHandler))
|
||||||
|
|
||||||
// API SERVICE Routes
|
// API SERVICE Routes
|
||||||
r.Handle("/api/services", http.HandlerFunc(apiAllServicesHandler)).Methods("GET")
|
r.Handle("/api/services", http.HandlerFunc(apiAllServicesHandler)).Methods("GET")
|
||||||
|
|
|
@ -217,25 +217,42 @@ func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
executeResponse(w, r, "services.html", core.CoreApp.Services, "/services")
|
executeResponse(w, r, "services.html", core.CoreApp.Services, "/services")
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
func checkinDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !IsAuthenticated(r) {
|
if !IsAuthenticated(r) {
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
checkin := core.SelectCheckinId(utils.StringInt(vars["id"]))
|
||||||
api := r.PostForm.Get("api")
|
service := core.SelectService(checkin.ServiceId)
|
||||||
checkin := core.SelectCheckin(api)
|
fmt.Println(checkin, service)
|
||||||
|
checkin.Delete()
|
||||||
interval := utils.StringInt(r.PostForm.Get("interval"))
|
|
||||||
grace := utils.StringInt(r.PostForm.Get("grace"))
|
|
||||||
checkin.Interval = interval
|
|
||||||
checkin.GracePeriod = grace
|
|
||||||
checkin.Update()
|
|
||||||
executeResponse(w, r, "service.html", service, fmt.Sprintf("/service/%v", service.Id))
|
executeResponse(w, r, "service.html", service, fmt.Sprintf("/service/%v", service.Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkinUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
func checkinCreateHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !IsAuthenticated(r) {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
r.ParseForm()
|
||||||
|
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||||
|
fmt.Println(service.Name)
|
||||||
|
name := r.PostForm.Get("name")
|
||||||
|
interval := utils.StringInt(r.PostForm.Get("interval"))
|
||||||
|
grace := utils.StringInt(r.PostForm.Get("grace"))
|
||||||
|
checkin := core.ReturnCheckin(&types.Checkin{
|
||||||
|
Name: name,
|
||||||
|
ServiceId: service.Id,
|
||||||
|
Interval: interval,
|
||||||
|
GracePeriod: grace,
|
||||||
|
})
|
||||||
|
checkin.Create()
|
||||||
|
executeResponse(w, r, "service.html", service, fmt.Sprintf("/service/%v", service.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
checkin := core.SelectCheckin(vars["id"])
|
checkin := core.SelectCheckin(vars["id"])
|
||||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
{{define "form_checkin"}}
|
{{define "form_checkin"}}
|
||||||
<form action="/checkin/{{.Id}}" method="POST">
|
<form action="/service/{{.Id}}/checkin" method="POST">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-md-4 col-sm-4">
|
<div class="col-md-3">
|
||||||
<label for="checkin_interval" class="col-form-label">Check Interval (in seconds)</label>
|
<label for="checkin_interval" class="col-form-label">Checkin Name</label>
|
||||||
<input type="number" name="interval" class="form-control" id="checkin_interval" value="{{.Interval}}" placeholder="60">
|
<input type="text" name="name" class="form-control" id="checkin_name" placeholder="New Checkin">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 col-sm-4">
|
<div class="col-3">
|
||||||
|
<label for="checkin_interval" class="col-form-label">Interval</label>
|
||||||
|
<input type="number" name="interval" class="form-control" id="checkin_interval" placeholder="60">
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
<label for="grace_period" class="col-form-label">Grace Period</label>
|
<label for="grace_period" class="col-form-label">Grace Period</label>
|
||||||
<input type="number" name="grace" class="form-control" id="grace_period" value="{{.GracePeriod}}" placeholder="60">
|
<input type="number" name="grace" class="form-control" id="grace_period" placeholder="10">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 col-sm-4">
|
<div class="col-3">
|
||||||
<label for="checkin_interval" class="col-form-label"></label>
|
<label for="checkin_interval" class="col-form-label"></label>
|
||||||
<button type="submit" class="btn btn-success d-block">Save Checkin</button>
|
<button type="submit" class="btn btn-success d-block">Save Checkin</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,13 +41,13 @@
|
||||||
<small id="emailHelp" class="form-text text-muted">You can insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
|
<small id="emailHelp" class="form-text text-muted">You can insert <a target="_blank" href="https://regex101.com/r/I5bbj9/1">Regex</a> to validate the response</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row{{if eq .Type "tcp"}} d-none{{end}}">
|
<div class="form-group row{{if ne .Type "http"}} d-none{{end}}">
|
||||||
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
|
<label for="service_response" class="col-sm-4 col-form-label">Expected Response (Regex)</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<textarea name="expected" class="form-control" id="service_response" rows="3" autocapitalize="false" spellcheck="false">{{.Expected}}</textarea>
|
<textarea name="expected" class="form-control" id="service_response" rows="3" autocapitalize="false" spellcheck="false">{{.Expected}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row{{if eq .Type "tcp"}} d-none{{end}}">
|
<div class="form-group row{{if ne .Type "http"}} d-none{{end}}">
|
||||||
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
|
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="number" name="expected_status" class="form-control" value="{{if ne .ExpectedStatus 0}}{{.ExpectedStatus}}{{end}}" placeholder="200" id="service_response_code">
|
<input type="number" name="expected_status" class="form-control" value="{{if ne .ExpectedStatus 0}}{{.ExpectedStatus}}{{end}}" placeholder="200" id="service_response_code">
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
|
|
||||||
<div class="col-12 mt-4">
|
<div class="col-12 mt-4">
|
||||||
<h3>Service Checkin</h3>
|
<h3>Service Checkin</h3>
|
||||||
|
{{if $s.LimitedCheckins}}
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -102,22 +103,35 @@
|
||||||
<th scope="col">Report Period<br>Grace Period</th>
|
<th scope="col">Report Period<br>Grace Period</th>
|
||||||
<th scope="col">Last Seen</th>
|
<th scope="col">Last Seen</th>
|
||||||
<th scope="col">Expected</th>
|
<th scope="col">Expected</th>
|
||||||
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{range $s.Checkins}}
|
{{range $s.LimitedCheckins}}
|
||||||
{{ $ch := . }}
|
{{ $ch := . }}
|
||||||
<tr class="{{ if lt $ch.Expected 0}}bg-danger text-white{{else}}bg-light{{end}}">
|
<tr class="{{ if lt $ch.Expected 0}}bg-warning text-black{{else}}bg-light{{end}}">
|
||||||
<td>{{CoreApp.Domain}}/checkin/{{$ch.ApiKey}}</td>
|
<td>{{$ch.Name}}<br><a href="{{$ch.Link}}" target="_blank">{{$ch.Link}}</a></td>
|
||||||
<td>every {{Duration $ch.Period}}<br>after {{Duration $ch.Grace}}</td>
|
<td>every {{Duration $ch.Period}}<br>after {{Duration $ch.Grace}}</td>
|
||||||
<td>{{Ago $ch.Last.CreatedAt}}</td>
|
<td>{{ if $ch.Last.CreatedAt.IsZero}}
|
||||||
<td>{{ if lt $ch.Expected 0}}{{Duration $ch.Expected}} ago{{else}}in {{Duration $ch.Expected}}{{end}}</td>
|
Never
|
||||||
|
{{else}}
|
||||||
|
{{Ago $ch.Last.CreatedAt}}
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ if $ch.Last.CreatedAt.IsZero}}
|
||||||
|
-
|
||||||
|
{{else}}
|
||||||
|
{{ if lt $ch.Expected 0}}{{Duration $ch.Expected}} ago{{else}}in {{Duration $ch.Expected}}{{end}}
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
<td><a href="/checkin/{{$ch.Id}}/delete" class="btn btn-sm btn-danger">Delete</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{{template "form_checkin" $ch}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{{end}}
|
||||||
|
{{template "form_checkin" $s}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -219,19 +233,20 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
startPick.setDate(new Date({{.Start}}*1000));
|
startPick.setDate(new Date({{.Start}}* 1000);
|
||||||
endPick.setDate(new Date({{.End}}*1000));
|
)
|
||||||
|
endPick.setDate(new Date({{.End}}* 1000);
|
||||||
startPick.hide()
|
)
|
||||||
endPick.hide()
|
startPick.hide();
|
||||||
|
endPick.hide();
|
||||||
|
|
||||||
$("#start_date").click(function(e) {
|
$("#start_date").click(function(e) {
|
||||||
startPick.show()
|
startPick.show()
|
||||||
})
|
});
|
||||||
|
|
||||||
$("#end_date").click(function(e) {
|
$("#end_date").click(function(e) {
|
||||||
endPick.show()
|
endPick.show()
|
||||||
})
|
});
|
||||||
|
|
||||||
AjaxChart(chartdata,{{$s.Id}},{{.Start}},{{.End}},"hour");
|
AjaxChart(chartdata,{{$s.Id}},{{.Start}},{{.End}},"hour");
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
<h3>Settings</h3>
|
<h3>Settings</h3>
|
||||||
|
|
||||||
<form method="POST" action="/settings">
|
<form method="POST" action="/settings">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="project">Project Name</label>
|
<label for="project">Project Name</label>
|
||||||
<input type="text" name="project" class="form-control" value="{{ .Name }}" id="project" placeholder="Great Uptime">
|
<input type="text" name="project" class="form-control" value="{{ .Name }}" id="project" placeholder="Great Uptime">
|
||||||
|
|
|
@ -22,12 +22,14 @@ import (
|
||||||
// Checkin struct will allow an application to send a recurring HTTP GET to confirm a service is online
|
// Checkin struct will allow an application to send a recurring HTTP GET to confirm a service is online
|
||||||
type Checkin struct {
|
type Checkin struct {
|
||||||
Id int64 `gorm:"primary_key;column:id"`
|
Id int64 `gorm:"primary_key;column:id"`
|
||||||
Service int64 `gorm:"index;column:service"`
|
ServiceId int64 `gorm:"index;column:service"`
|
||||||
|
Name string `gorm:"column:name"`
|
||||||
Interval int64 `gorm:"column:check_interval"`
|
Interval int64 `gorm:"column:check_interval"`
|
||||||
GracePeriod int64 `gorm:"column:grace_period"`
|
GracePeriod int64 `gorm:"column:grace_period"`
|
||||||
ApiKey string `gorm:"column:api_key"`
|
ApiKey string `gorm:"column:api_key"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||||
|
Running chan bool `gorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckinHit is a successful response from a Checkin
|
// CheckinHit is a successful response from a Checkin
|
||||||
|
@ -37,3 +39,28 @@ type CheckinHit struct {
|
||||||
From string `gorm:"column:from_location"`
|
From string `gorm:"column:from_location"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start will create a channel for the checkin checking go routine
|
||||||
|
func (s *Checkin) Start() {
|
||||||
|
s.Running = make(chan bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close will stop the checkin routine
|
||||||
|
func (s *Checkin) Close() {
|
||||||
|
if s.IsRunning() {
|
||||||
|
close(s.Running)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRunning returns true if the checkin go routine is running
|
||||||
|
func (s *Checkin) IsRunning() bool {
|
||||||
|
if s.Running == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-s.Running:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ type Failure struct {
|
||||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||||
Issue string `gorm:"column:issue" json:"issue"`
|
Issue string `gorm:"column:issue" json:"issue"`
|
||||||
Method string `gorm:"column:method" json:"method,omitempty"`
|
Method string `gorm:"column:method" json:"method,omitempty"`
|
||||||
|
MethodId int64 `gorm:"column:method_id" json:"method_id,omitempty"`
|
||||||
Service int64 `gorm:"index;column:service" json:"-"`
|
Service int64 `gorm:"index;column:service" json:"-"`
|
||||||
PingTime float64 `gorm:"column:ping_time"`
|
PingTime float64 `gorm:"column:ping_time"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HashPassword returns the bcrypt hash of a password string
|
// HashPassword returns the bcrypt hash of a password string
|
||||||
|
@ -46,6 +47,7 @@ var characterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY
|
||||||
// RandomString generates a random string of n length
|
// RandomString generates a random string of n length
|
||||||
func RandomString(n int) string {
|
func RandomString(n int) string {
|
||||||
b := make([]rune, n)
|
b := make([]rune, n)
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
for i := range b {
|
for i := range b {
|
||||||
b[i] = characterRunes[rand.Intn(len(characterRunes))]
|
b[i] = characterRunes[rand.Intn(len(characterRunes))]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue