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 | ||||
| GOPATH:=$(GOPATH) | ||||
| 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 | ||||
| 
 | ||||
| # 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: compile | ||||
|  |  | |||
							
								
								
									
										118
									
								
								core/checkin.go
								
								
								
								
							
							
						
						
									
										118
									
								
								core/checkin.go
								
								
								
								
							|  | @ -23,7 +23,7 @@ import ( | |||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type checkin struct { | ||||
| type Checkin struct { | ||||
| 	*types.Checkin | ||||
| } | ||||
| 
 | ||||
|  | @ -31,14 +31,40 @@ type checkinHit struct { | |||
| 	*types.CheckinHit | ||||
| } | ||||
| 
 | ||||
| // String will return a checkin API string
 | ||||
| func (c *checkin) String() string { | ||||
| // Routine for checking if the last Checkin was within its interval
 | ||||
| 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 | ||||
| } | ||||
| 
 | ||||
| // ReturnCheckin converts *types.Checking to *core.checkin
 | ||||
| func ReturnCheckin(c *types.Checkin) *checkin { | ||||
| 	return &checkin{Checkin: c} | ||||
| // ReturnCheckin converts *types.Checking to *core.Checkin
 | ||||
| func ReturnCheckin(c *types.Checkin) *Checkin { | ||||
| 	return &Checkin{Checkin: c} | ||||
| } | ||||
| 
 | ||||
| // ReturnCheckinHit converts *types.checkinHit to *core.checkinHit
 | ||||
|  | @ -46,27 +72,52 @@ func ReturnCheckinHit(c *types.CheckinHit) *checkinHit { | |||
| 	return &checkinHit{CheckinHit: c} | ||||
| } | ||||
| 
 | ||||
| // SelectCheckin will find a checkin based on the API supplied
 | ||||
| func SelectCheckin(api string) *checkin { | ||||
| 	var checkin checkin | ||||
| func (c *Checkin) Service() *Service { | ||||
| 	service := SelectService(c.ServiceId) | ||||
| 	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) | ||||
| 	return &checkin | ||||
| } | ||||
| 
 | ||||
| // Period will return the duration of the checkin interval
 | ||||
| func (c *checkin) Period() time.Duration { | ||||
| // SelectCheckin will find a Checkin based on the API supplied
 | ||||
| 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)) | ||||
| 	return duration | ||||
| } | ||||
| 
 | ||||
| // Grace will return the duration of the checkin Grace Period (after service hasn't responded, wait a bit for a response)
 | ||||
| func (c *checkin) Grace() time.Duration { | ||||
| // Grace will return the duration of the Checkin Grace Period (after service hasn't responded, wait a bit for a response)
 | ||||
| func (c *Checkin) Grace() time.Duration { | ||||
| 	duration, _ := time.ParseDuration(fmt.Sprintf("%vs", c.GracePeriod)) | ||||
| 	return duration | ||||
| } | ||||
| 
 | ||||
| // Expected returns the duration of when the serviec should receive a checkin
 | ||||
| func (c *checkin) Expected() time.Duration { | ||||
| // Expected returns the duration of when the serviec should receive a Checkin
 | ||||
| func (c *Checkin) Expected() time.Duration { | ||||
| 	last := c.Last().CreatedAt | ||||
| 	now := time.Now() | ||||
| 	lastDir := now.Sub(last) | ||||
|  | @ -74,24 +125,37 @@ func (c *checkin) Expected() time.Duration { | |||
| 	return sub | ||||
| } | ||||
| 
 | ||||
| // Last returns the last checkinHit for a checkin
 | ||||
| func (c *checkin) Last() checkinHit { | ||||
| // Last returns the last checkinHit for a Checkin
 | ||||
| func (c *Checkin) Last() *checkinHit { | ||||
| 	var hit checkinHit | ||||
| 	checkinHitsDB().Where("checkin = ?", c.Id).Last(&hit) | ||||
| 	return hit | ||||
| 	return &hit | ||||
| } | ||||
| 
 | ||||
| // Hits returns all of the CheckinHits for a given checkin
 | ||||
| func (c *checkin) Hits() []checkinHit { | ||||
| 	var checkins []checkinHit | ||||
| func (c *Checkin) Link() string { | ||||
| 	return fmt.Sprintf("%v/checkin/%v", CoreApp.Domain, c.ApiKey) | ||||
| } | ||||
| 
 | ||||
| // 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) | ||||
| 	return checkins | ||||
| } | ||||
| 
 | ||||
| // Create will create a new checkin
 | ||||
| func (c *checkin) Create() (int64, error) { | ||||
| // Create will create a new Checkin
 | ||||
| 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) | ||||
| 	row := checkinDB().Create(&c) | ||||
| 	c.Start() | ||||
| 	go c.Routine() | ||||
| 	if row.Error != nil { | ||||
| 		utils.Log(2, row.Error) | ||||
| 		return 0, row.Error | ||||
|  | @ -99,8 +163,8 @@ func (c *checkin) Create() (int64, error) { | |||
| 	return c.Id, row.Error | ||||
| } | ||||
| 
 | ||||
| // Update will update a checkin
 | ||||
| func (c *checkin) Update() (int64, error) { | ||||
| // Update will update a Checkin
 | ||||
| func (c *Checkin) Update() (int64, error) { | ||||
| 	row := checkinDB().Update(c) | ||||
| 	if row.Error != nil { | ||||
| 		utils.Log(2, row.Error) | ||||
|  | @ -128,8 +192,8 @@ func (c *checkinHit) Ago() string { | |||
| 	return got | ||||
| } | ||||
| 
 | ||||
| // RecheckCheckinFailure will check if a Service checkin has been reported yet
 | ||||
| func (c *checkin) RecheckCheckinFailure(guard chan struct{}) { | ||||
| // RecheckCheckinFailure will check if a Service Checkin has been reported yet
 | ||||
| func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) { | ||||
| 	between := time.Now().Sub(time.Now()).Seconds() | ||||
| 	if between > float64(c.Interval) { | ||||
| 		fmt.Println("rechecking every 15 seconds!") | ||||
|  |  | |||
|  | @ -63,12 +63,12 @@ func usersDB() *gorm.DB { | |||
| 	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 { | ||||
| 	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 { | ||||
| 	return DbSession.Model(&types.CheckinHit{}) | ||||
| } | ||||
|  | @ -115,8 +115,8 @@ func (u *user) AfterFind() (err error) { | |||
| 	return | ||||
| } | ||||
| 
 | ||||
| // AfterFind for checkin will set the timezone
 | ||||
| func (c *checkin) AfterFind() (err error) { | ||||
| // AfterFind for Checkin will set the timezone
 | ||||
| func (c *Checkin) AfterFind() (err error) { | ||||
| 	c.CreatedAt = utils.Timezoner(c.CreatedAt, CoreApp.Timezone) | ||||
| 	return | ||||
| } | ||||
|  | @ -159,8 +159,8 @@ func (s *Service) BeforeCreate() (err error) { | |||
| 	return | ||||
| } | ||||
| 
 | ||||
| // BeforeCreate for checkin will set CreatedAt to UTC
 | ||||
| func (c *checkin) BeforeCreate() (err error) { | ||||
| // BeforeCreate for Checkin will set CreatedAt to UTC
 | ||||
| func (c *Checkin) BeforeCreate() (err error) { | ||||
| 	if c.CreatedAt.IsZero() { | ||||
| 		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
 | ||||
| func (s *Service) AllFailures() []*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) | ||||
| 	if err.Error != nil { | ||||
| 		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
 | ||||
| func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) { | ||||
| 	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) | ||||
| 	return count, err.Error | ||||
| } | ||||
| 
 | ||||
| // ParseError returns a human readable error for a failure
 | ||||
| func (f *failure) ParseError() string { | ||||
| 	if f.Method == "checkin" { | ||||
| 		return fmt.Sprintf("Checkin is Offline") | ||||
| 	} | ||||
| 	err := strings.Contains(f.Issue, "connection reset by peer") | ||||
| 	if err { | ||||
| 		return fmt.Sprintf("Connection Reset") | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ type Hit struct { | |||
| 
 | ||||
| // CreateHit will create a new 'hit' record in the database for a successful/online service
 | ||||
| func (s *Service) CreateHit(h *types.Hit) (int64, error) { | ||||
| 	db := hitsDB().Create(h) | ||||
| 	db := hitsDB().Create(&h) | ||||
| 	if db.Error != nil { | ||||
| 		utils.Log(2, db.Error) | ||||
| 		return 0, db.Error | ||||
|  |  | |||
|  | @ -89,11 +89,11 @@ func InsertSampleData() error { | |||
| 	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 { | ||||
| 	s1 := SelectService(1) | ||||
| 	checkin1 := ReturnCheckin(&types.Checkin{ | ||||
| 		Service:     s1.Id, | ||||
| 		ServiceId:   s1.Id, | ||||
| 		Interval:    300, | ||||
| 		GracePeriod: 300, | ||||
| 	}) | ||||
|  | @ -101,7 +101,7 @@ func insertSampleCheckins() error { | |||
| 
 | ||||
| 	s2 := SelectService(1) | ||||
| 	checkin2 := ReturnCheckin(&types.Checkin{ | ||||
| 		Service:     s2.Id, | ||||
| 		ServiceId:   s2.Id, | ||||
| 		Interval:    900, | ||||
| 		GracePeriod: 300, | ||||
| 	}) | ||||
|  |  | |||
|  | @ -51,13 +51,29 @@ func SelectService(id int64) *Service { | |||
| 	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
 | ||||
| func (s *Service) Checkins() []*checkin { | ||||
| 	var checkin []*checkin | ||||
| func (s *Service) Checkins() []*Checkin { | ||||
| 	var checkin []*Checkin | ||||
| 	checkinDB().Where("service = ?", s.Id).Find(&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.
 | ||||
| func (c *Core) SelectAllServices() ([]*Service, error) { | ||||
| 	var services []*Service | ||||
|  | @ -69,7 +85,7 @@ func (c *Core) SelectAllServices() ([]*Service, error) { | |||
| 	CoreApp.Services = nil | ||||
| 	for _, service := range services { | ||||
| 		service.Start() | ||||
| 		service.Checkins() | ||||
| 		service.CheckinProcess() | ||||
| 		service.AllFailures() | ||||
| 		CoreApp.Services = append(CoreApp.Services, service) | ||||
| 	} | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ import ( | |||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	testCheckin *checkin | ||||
| 	testCheckin *Checkin | ||||
| ) | ||||
| 
 | ||||
| func TestCreateCheckin(t *testing.T) { | ||||
|  |  | |||
|  | @ -157,6 +157,9 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap | |||
| 		"NewUser": func() *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}/delete", http.HandlerFunc(servicesDeleteHandler)) | ||||
| 	r.Handle("/service/{id}/delete_failures", http.HandlerFunc(servicesDeleteFailuresHandler)).Methods("GET") | ||||
| 	r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateUpdateHandler)).Methods("POST") | ||||
| 	r.Handle("/checkin/{id}", http.HandlerFunc(checkinUpdateHandler)) | ||||
| 	r.Handle("/service/{id}/checkin", http.HandlerFunc(checkinCreateHandler)).Methods("POST") | ||||
| 	r.Handle("/checkin/{id}/delete", http.HandlerFunc(checkinDeleteHandler)).Methods("GET") | ||||
| 	r.Handle("/checkin/{id}", http.HandlerFunc(checkinHitHandler)) | ||||
| 
 | ||||
| 	// API SERVICE Routes
 | ||||
| 	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") | ||||
| } | ||||
| 
 | ||||
| func checkinCreateUpdateHandler(w http.ResponseWriter, r *http.Request) { | ||||
| func checkinDeleteHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	if !IsAuthenticated(r) { | ||||
| 		http.Redirect(w, r, "/", http.StatusSeeOther) | ||||
| 		return | ||||
| 	} | ||||
| 	vars := mux.Vars(r) | ||||
| 	service := core.SelectService(utils.StringInt(vars["id"])) | ||||
| 	api := r.PostForm.Get("api") | ||||
| 	checkin := core.SelectCheckin(api) | ||||
| 
 | ||||
| 	interval := utils.StringInt(r.PostForm.Get("interval")) | ||||
| 	grace := utils.StringInt(r.PostForm.Get("grace")) | ||||
| 	checkin.Interval = interval | ||||
| 	checkin.GracePeriod = grace | ||||
| 	checkin.Update() | ||||
| 	checkin := core.SelectCheckinId(utils.StringInt(vars["id"])) | ||||
| 	service := core.SelectService(checkin.ServiceId) | ||||
| 	fmt.Println(checkin, service) | ||||
| 	checkin.Delete() | ||||
| 	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) | ||||
| 	checkin := core.SelectCheckin(vars["id"]) | ||||
| 	ip, _, _ := net.SplitHostPort(r.RemoteAddr) | ||||
|  |  | |||
|  | @ -1,15 +1,19 @@ | |||
| {{define "form_checkin"}} | ||||
| <form action="/checkin/{{.Id}}" method="POST"> | ||||
| <form action="/service/{{.Id}}/checkin" method="POST"> | ||||
|     <div class="form-group row"> | ||||
|         <div class="col-md-4 col-sm-4"> | ||||
|             <label for="checkin_interval" class="col-form-label">Check Interval (in seconds)</label> | ||||
|             <input type="number" name="interval" class="form-control" id="checkin_interval" value="{{.Interval}}" placeholder="60"> | ||||
|         <div class="col-md-3"> | ||||
|             <label for="checkin_interval" class="col-form-label">Checkin Name</label> | ||||
|             <input type="text" name="name" class="form-control" id="checkin_name" placeholder="New Checkin"> | ||||
|         </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> | ||||
|             <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 class="col-md-4 col-sm-4"> | ||||
|         <div class="col-3"> | ||||
|             <label for="checkin_interval" class="col-form-label"></label> | ||||
|             <button type="submit" class="btn btn-success d-block">Save Checkin</button> | ||||
|         </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> | ||||
|         </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> | ||||
|         <div class="col-sm-8"> | ||||
|             <textarea name="expected" class="form-control" id="service_response" rows="3" autocapitalize="false" spellcheck="false">{{.Expected}}</textarea> | ||||
|         </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> | ||||
|         <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"> | ||||
|  |  | |||
|  | @ -95,6 +95,7 @@ | |||
| 
 | ||||
|     <div class="col-12 mt-4"> | ||||
|         <h3>Service Checkin</h3> | ||||
|         {{if $s.LimitedCheckins}} | ||||
|         <table class="table"> | ||||
|             <thead> | ||||
|                 <tr> | ||||
|  | @ -102,22 +103,35 @@ | |||
|                     <th scope="col">Report Period<br>Grace Period</th> | ||||
|                     <th scope="col">Last Seen</th> | ||||
|                     <th scope="col">Expected</th> | ||||
|                     <th scope="col"></th> | ||||
|                 </tr> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|             {{range $s.Checkins}} | ||||
|             {{range $s.LimitedCheckins}} | ||||
|             {{ $ch := . }} | ||||
|             <tr class="{{ if lt $ch.Expected 0}}bg-danger text-white{{else}}bg-light{{end}}"> | ||||
|                 <td>{{CoreApp.Domain}}/checkin/{{$ch.ApiKey}}</td> | ||||
|             <tr class="{{ if lt $ch.Expected 0}}bg-warning text-black{{else}}bg-light{{end}}"> | ||||
|                 <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>{{Ago $ch.Last.CreatedAt}}</td> | ||||
|                 <td>{{ if lt $ch.Expected 0}}{{Duration $ch.Expected}} ago{{else}}in {{Duration $ch.Expected}}{{end}}</td> | ||||
|                 <td>{{ if $ch.Last.CreatedAt.IsZero}} | ||||
|                         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> | ||||
|             {{template "form_checkin" $ch}} | ||||
|             {{end}} | ||||
|             </tbody> | ||||
|         </table> | ||||
| 
 | ||||
|         {{end}} | ||||
|         {{template "form_checkin" $s}} | ||||
|     </div> | ||||
| 
 | ||||
| {{end}} | ||||
|  | @ -219,19 +233,20 @@ | |||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     startPick.setDate(new Date({{.Start}}*1000)); | ||||
|     endPick.setDate(new Date({{.End}}*1000)); | ||||
| 
 | ||||
|     startPick.hide() | ||||
|     endPick.hide() | ||||
|     startPick.setDate(new Date({{.Start}}* 1000); | ||||
|     ) | ||||
|     endPick.setDate(new Date({{.End}}* 1000); | ||||
|     ) | ||||
|     startPick.hide(); | ||||
|     endPick.hide(); | ||||
| 
 | ||||
|     $("#start_date").click(function(e) { | ||||
|         startPick.show() | ||||
|     }) | ||||
|     }); | ||||
| 
 | ||||
|     $("#end_date").click(function(e) { | ||||
|         endPick.show() | ||||
|     }) | ||||
|     }); | ||||
| 
 | ||||
|     AjaxChart(chartdata,{{$s.Id}},{{.Start}},{{.End}},"hour"); | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,6 @@ | |||
|                         <h3>Settings</h3> | ||||
| 
 | ||||
|                         <form method="POST" action="/settings"> | ||||
| 
 | ||||
|                             <div class="form-group"> | ||||
|                                 <label for="project">Project Name</label> | ||||
|                                 <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
 | ||||
| type Checkin struct { | ||||
| 	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"` | ||||
| 	GracePeriod int64     `gorm:"column:grace_period"` | ||||
| 	ApiKey      string    `gorm:"column:api_key"` | ||||
| 	CreatedAt   time.Time `gorm:"column:created_at" json:"created_at"` | ||||
| 	UpdatedAt   time.Time `gorm:"column:updated_at" json:"updated_at"` | ||||
| 	Running     chan bool `gorm:"-" json:"-"` | ||||
| } | ||||
| 
 | ||||
| // CheckinHit is a successful response from a Checkin
 | ||||
|  | @ -37,3 +39,28 @@ type CheckinHit struct { | |||
| 	From      string    `gorm:"column:from_location"` | ||||
| 	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"` | ||||
| 	Issue            string    `gorm:"column:issue" json:"issue"` | ||||
| 	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:"-"` | ||||
| 	PingTime         float64   `gorm:"column:ping_time"` | ||||
| 	CreatedAt        time.Time `gorm:"column:created_at" json:"created_at"` | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import ( | |||
| 	"fmt" | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
| 	"math/rand" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // 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
 | ||||
| func RandomString(n int) string { | ||||
| 	b := make([]rune, n) | ||||
| 	rand.Seed(time.Now().UnixNano()) | ||||
| 	for i := range b { | ||||
| 		b[i] = characterRunes[rand.Intn(len(characterRunes))] | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Hunter Long
						Hunter Long