template update - checkins/failure API

pull/116/head
Hunter Long 2018-12-06 11:03:55 -08:00
parent cdcfa2f481
commit 0a22788385
53 changed files with 179 additions and 141 deletions

View File

@ -168,7 +168,7 @@ func ExportIndexHTML() []byte {
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
handlers.ExecuteResponse(w, r, "index.html", nil, nil)
handlers.ExecuteResponse(w, r, "index.gohtml", nil, nil)
return w.Body.Bytes()
}

View File

@ -236,10 +236,10 @@ func recordSuccess(s *Service) {
notifier.OnSuccess(s.Service)
}
// recordFailure will create a new 'failure' record in the database for a offline service
// recordFailure will create a new 'Failure' record in the database for a offline service
func recordFailure(s *Service, issue string) {
s.Online = false
fail := &failure{&types.Failure{
fail := &Failure{&types.Failure{
Service: s.Id,
Issue: issue,
PingTime: s.PingTime,

View File

@ -20,6 +20,7 @@ import (
"github.com/ararog/timeago"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"sort"
"time"
)
@ -31,6 +32,11 @@ type CheckinHit struct {
*types.CheckinHit
}
// Select returns a *types.Checkin
func (c *Checkin) Select() *types.Checkin {
return c.Checkin
}
// Routine for checking if the last Checkin was within its interval
func (c *Checkin) Routine() {
if c.Last() == nil {
@ -42,13 +48,13 @@ CheckinLoop:
select {
case <-c.Running:
utils.Log(1, fmt.Sprintf("Stopping checkin routine: %v", c.Name))
c.Failing = false
break CheckinLoop
case <-time.After(reCheck):
utils.Log(1, fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())))
if c.Expected().Seconds() <= 0 {
issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Last().CreatedAt)
utils.Log(3, issue)
c.Service()
c.CreateFailure()
}
reCheck = c.Period()
@ -73,23 +79,36 @@ func ReturnCheckinHit(c *types.CheckinHit) *CheckinHit {
}
func (c *Checkin) Service() *Service {
service := SelectService(c.ServiceId)
return service
return SelectService(c.ServiceId)
}
func (c *Checkin) CreateFailure() (int64, error) {
service := c.Service()
fail := &types.Failure{
c.Failing = true
fail := &Failure{&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,
}
Checkin: c.Id,
PingTime: c.Expected().Seconds(),
}}
row := failuresDB().Create(&fail)
sort.Sort(types.FailSort(c.Failures))
c.Failures = append(c.Failures, fail)
if len(c.Failures) > limitedFailures {
c.Failures = c.Failures[1:]
}
return fail.Id, row.Error
}
// LimitedHits will return the last amount of successful hits from a checkin
func (c *Checkin) LimitedHits(amount int64) []*types.CheckinHit {
var hits []*types.CheckinHit
checkinHitsDB().Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits)
return hits
}
// AllCheckins returns all checkin in system
func AllCheckins() []*Checkin {
var checkins []*Checkin
@ -99,16 +118,14 @@ func AllCheckins() []*Checkin {
// 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
}
// 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
for _, s := range Services() {
for _, c := range s.Select().Checkins {
if c.Select().ApiKey == api {
return c.(*Checkin)
}
}
}
return nil
}
// Period will return the duration of the Checkin interval
@ -150,6 +167,18 @@ func (c *Checkin) AllHits() []*types.CheckinHit {
return checkins
}
// Hits returns all of the CheckinHits for a given Checkin
func (c *Checkin) LimitedFailures(amount int64) []types.FailureInterface {
var failures []*Failure
var failInterfaces []types.FailureInterface
col := failuresDB().Where("checkin = ?", c.Id).Where("method = 'checkin'").Limit(amount).Order("id desc")
col.Find(&failures)
for _, f := range failures {
failInterfaces = append(failInterfaces, f)
}
return failInterfaces
}
// Hits returns all of the CheckinHits for a given Checkin
func (c *Checkin) AllFailures() []*types.Failure {
var failures []*types.Failure
@ -169,12 +198,14 @@ func (c *Checkin) Delete() error {
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
}
service := SelectService(c.ServiceId)
service.Checkins = append(service.Checkins, c)
c.Start()
go c.Routine()
return c.Id, row.Error
}

View File

@ -120,8 +120,8 @@ func (h *Hit) AfterFind() (err error) {
return
}
// AfterFind for failure will set the timezone
func (f *failure) AfterFind() (err error) {
// AfterFind for Failure will set the timezone
func (f *Failure) AfterFind() (err error) {
f.CreatedAt = utils.Timezoner(f.CreatedAt, CoreApp.Timezone)
return
}
@ -163,8 +163,8 @@ func (h *Hit) BeforeCreate() (err error) {
return
}
// BeforeCreate for failure will set CreatedAt to UTC
func (f *failure) BeforeCreate() (err error) {
// BeforeCreate for Failure will set CreatedAt to UTC
func (f *Failure) BeforeCreate() (err error) {
if f.CreatedAt.IsZero() {
f.CreatedAt = time.Now().UTC()
}

View File

@ -25,7 +25,7 @@ import (
"time"
)
type failure struct {
type Failure struct {
*types.Failure
}
@ -33,9 +33,9 @@ const (
limitedFailures = 32
)
// CreateFailure will create a new failure record for a service
// CreateFailure will create a new Failure record for a service
func (s *Service) CreateFailure(fail types.FailureInterface) (int64, error) {
f := fail.(*failure)
f := fail.(*Failure)
f.Service = s.Id
row := failuresDB().Create(f)
if row.Error != nil {
@ -51,8 +51,8 @@ func (s *Service) CreateFailure(fail types.FailureInterface) (int64, error) {
}
// AllFailures will return all failures attached to a service
func (s *Service) AllFailures() []*failure {
var fails []*failure
func (s *Service) AllFailures() []*Failure {
var fails []*Failure
col := failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc")
err := col.Find(&fails)
if err.Error != nil {
@ -72,32 +72,32 @@ func (s *Service) DeleteFailures() {
}
// LimitedFailures will return the last amount of failures from a service
func (s *Service) LimitedFailures(amount int64) []*failure {
var failArr []*failure
func (s *Service) LimitedFailures(amount int64) []*Failure {
var failArr []*Failure
failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
return failArr
}
// LimitedFailures will return the last amount of failures from a service
func (s *Service) LimitedCheckinFailures(amount int64) []*failure {
var failArr []*failure
func (s *Service) LimitedCheckinFailures(amount int64) []*Failure {
var failArr []*Failure
failuresDB().Where("service = ?", s.Id).Where("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
return failArr
}
// Ago returns a human readable timestamp for a failure
func (f *failure) Ago() string {
// Ago returns a human readable timestamp for a Failure
func (f *Failure) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), f.CreatedAt)
return got
}
// Select returns a *types.Failure
func (f *failure) Select() *types.Failure {
func (f *Failure) Select() *types.Failure {
return f.Failure
}
// Delete will remove a failure record from the database
func (f *failure) Delete() error {
// Delete will remove a Failure record from the database
func (f *Failure) Delete() error {
db := failuresDB().Delete(f)
return db.Error
}
@ -146,8 +146,8 @@ func (s *Service) TotalFailuresSince(ago time.Time) (uint64, error) {
return count, err.Error
}
// ParseError returns a human readable error for a failure
func (f *failure) ParseError() string {
// ParseError returns a human readable error for a Failure
func (f *Failure) ParseError() string {
if f.Method == "checkin" {
return fmt.Sprintf("Checkin is Offline")
}

View File

@ -359,13 +359,13 @@ func InsertLargeSampleData() error {
func insertFailureRecords(since time.Time, amount int64) {
for i := int64(14); i <= 15; i++ {
service := SelectService(i)
utils.Log(1, fmt.Sprintf("Adding %v failure records to service %v", amount, service.Name))
utils.Log(1, fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
createdAt := since
for fi := int64(1); fi <= amount; fi++ {
createdAt = createdAt.Add(2 * time.Minute)
failure := &failure{&types.Failure{
failure := &Failure{&types.Failure{
Service: service.Id,
Issue: "testing right here",
CreatedAt: createdAt,

View File

@ -55,39 +55,22 @@ func SelectService(id int64) *Service {
return nil
}
// SelectServicer returns a types.ServiceInterface from in memory
func SelectServicer(id int64) types.ServiceInterface {
for _, s := range Services() {
if s.Select().Id == id {
return s
}
}
return nil
}
// CheckinProcess runs the checkin routine for each checkin attached to service
func (s *Service) CheckinProcess() {
checkins := s.Checkins()
checkins := s.AllCheckins()
for _, c := range checkins {
c.Start()
go c.Routine()
}
}
// Checkins will return a slice of Checkins for a Service
func (s *Service) Checkins() []*Checkin {
// AllCheckins will return a slice of AllCheckins for a Service
func (s *Service) AllCheckins() []*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(start bool) ([]*Service, error) {
var services []*Service
@ -106,9 +89,15 @@ func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
for _, f := range fails {
service.Failures = append(service.Failures, f)
}
checkins := service.AllCheckins()
for _, c := range checkins {
c.Failures = c.LimitedFailures(limitedFailures)
c.Hits = c.LimitedHits(limitedFailures)
service.Checkins = append(service.Checkins, c)
}
CoreApp.Services = append(CoreApp.Services, service)
}
sort.Sort(ServiceOrder(CoreApp.Services))
reorderServices()
return services, db.Error
}
@ -175,8 +164,8 @@ type DateScanObj struct {
Array []DateScan `json:"data"`
}
// lastFailure returns the last failure a service had
func (s *Service) lastFailure() *failure {
// lastFailure returns the last Failure a service had
func (s *Service) lastFailure() *Failure {
limited := s.LimitedFailures(1)
if len(limited) == 0 {
return nil
@ -196,7 +185,7 @@ func (s *Service) SmallText() string {
if len(last) == 0 {
return fmt.Sprintf("Online since %v", utils.Timezoner(s.CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
} else {
return fmt.Sprintf("Online, last failure was %v", utils.Timezoner(hits[0].CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
return fmt.Sprintf("Online, last Failure was %v", utils.Timezoner(hits[0].CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
}
}
if len(last) > 0 {

View File

@ -46,7 +46,7 @@ func TestCreateCheckin(t *testing.T) {
func TestSelectCheckin(t *testing.T) {
service := SelectService(1)
checkins := service.Checkins()
checkins := service.AllCheckins()
assert.NotNil(t, checkins)
assert.Equal(t, 1, len(checkins))
testCheckin = checkins[0]
@ -63,7 +63,7 @@ func TestUpdateCheckin(t *testing.T) {
assert.NotZero(t, id)
assert.NotEmpty(t, testCheckin.ApiKey)
service := SelectService(1)
checkin := service.Checkins()[0]
checkin := service.AllCheckins()[0]
assert.Equal(t, int64(60), checkin.Interval)
assert.Equal(t, int64(15), checkin.GracePeriod)
t.Log(testCheckin.Expected())
@ -72,7 +72,7 @@ func TestUpdateCheckin(t *testing.T) {
func TestCreateCheckinHits(t *testing.T) {
service := SelectService(1)
checkins := service.Checkins()
checkins := service.AllCheckins()
assert.Equal(t, 1, len(checkins))
created := time.Now().UTC().Add(-60 * time.Second)
hit := ReturnCheckinHit(&types.CheckinHit{
@ -88,7 +88,7 @@ func TestCreateCheckinHits(t *testing.T) {
func TestSelectCheckinMethods(t *testing.T) {
time.Sleep(5 * time.Second)
service := SelectService(1)
checkins := service.Checkins()
checkins := service.AllCheckins()
assert.NotNil(t, checkins)
lastHit := testCheckin.Last()
assert.Equal(t, float64(60), testCheckin.Period().Seconds())

View File

@ -256,7 +256,7 @@ func TestServiceFailedTCPCheck(t *testing.T) {
}
func TestCreateServiceFailure(t *testing.T) {
fail := &failure{&types.Failure{
fail := &Failure{&types.Failure{
Issue: "This is not an issue, but it would container HTTP response errors.",
Method: "http",
}}

View File

@ -21,6 +21,7 @@ import (
"github.com/gorilla/mux"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"net"
"net/http"
"time"
@ -34,7 +35,7 @@ func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
checkins := core.AllCheckins()
for _, c := range checkins {
c.Hits = c.AllHits()
c.Failures = c.AllFailures()
c.Failures = c.LimitedFailures(64)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(checkins)
@ -51,8 +52,8 @@ func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r)
return
}
checkin.Hits = checkin.AllHits()
checkin.Failures = checkin.AllFailures()
checkin.Hits = checkin.LimitedHits(32)
checkin.Failures = checkin.LimitedFailures(32)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(checkin)
}
@ -69,6 +70,11 @@ func checkinCreateHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r)
return
}
service := core.SelectService(checkin.ServiceId)
if service == nil {
sendErrorJson(fmt.Errorf("missing service_id field"), w, r)
return
}
_, err = checkin.Create()
if err != nil {
sendErrorJson(err, w, r)
@ -85,20 +91,25 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
return
}
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
checkinHit := core.ReturnCheckinHit(&types.CheckinHit{
hit := &types.CheckinHit{
Checkin: checkin.Id,
From: ip,
CreatedAt: time.Now().UTC(),
})
}
checkinHit := core.ReturnCheckinHit(hit)
if checkin.Last() == nil {
checkin.Start()
go checkin.Routine()
}
_, err := checkinHit.Create()
checkin.Hits = append(checkin.Hits, checkinHit.CheckinHit)
if err != nil {
sendErrorJson(err, w, r)
return
}
checkin.Failing = false
checkin.LastHit = utils.Timezoner(time.Now().UTC(), core.CoreApp.Timezone)
w.Header().Set("Content-Type", "application/json")
sendJsonAction(checkinHit, "update", w, r)
}

View File

@ -29,9 +29,9 @@ import (
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
if !IsAuthenticated(r) {
err := core.ErrorResponse{}
ExecuteResponse(w, r, "login.html", err, nil)
ExecuteResponse(w, r, "login.gohtml", err, nil)
} else {
ExecuteResponse(w, r, "dashboard.html", core.CoreApp, nil)
ExecuteResponse(w, r, "dashboard.gohtml", core.CoreApp, nil)
}
}
@ -51,7 +51,7 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
} else {
err := core.ErrorResponse{Error: "Incorrect login information submitted, try again."}
ExecuteResponse(w, r, "login.html", err, nil)
ExecuteResponse(w, r, "login.gohtml", err, nil)
}
}
@ -68,7 +68,7 @@ func helpHandler(w http.ResponseWriter, r *http.Request) {
return
}
help := source.HelpMarkdown()
ExecuteResponse(w, r, "help.html", help, nil)
ExecuteResponse(w, r, "help.gohtml", help, nil)
}
func logsHandler(w http.ResponseWriter, r *http.Request) {
@ -84,7 +84,7 @@ func logsHandler(w http.ResponseWriter, r *http.Request) {
logs = append(logs, utils.LastLines[i].FormatForHtml()+"\r\n")
}
utils.LockLines.Unlock()
ExecuteResponse(w, r, "logs.html", logs, nil)
ExecuteResponse(w, r, "logs.gohtml", logs, nil)
}
func logsLineHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -198,7 +198,7 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
return
}
templates := []string{"base.html", "head.html", "nav.html", "footer.html", "scripts.html", "form_service.html", "form_notifier.html", "form_user.html", "form_checkin.html", "form_message.html"}
templates := []string{"base.gohtml", "head.gohtml", "nav.gohtml", "footer.gohtml", "scripts.gohtml", "form_service.gohtml", "form_notifier.gohtml", "form_user.gohtml", "form_checkin.gohtml", "form_message.gohtml"}
javascripts := []string{"charts.js", "chart_index.js"}
@ -275,5 +275,5 @@ func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data
// error404Handler is a HTTP handler for 404 error pages
func error404Handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
ExecuteResponse(w, r, "error_404.html", nil, nil)
ExecuteResponse(w, r, "error_404.gohtml", nil, nil)
}

View File

@ -28,7 +28,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/setup", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "index.html", core.CoreApp, nil)
ExecuteResponse(w, r, "index.gohtml", core.CoreApp, nil)
}
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
@ -41,7 +41,7 @@ func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
}
func trayHandler(w http.ResponseWriter, r *http.Request) {
ExecuteResponse(w, r, "tray.html", core.CoreApp, nil)
ExecuteResponse(w, r, "tray.gohtml", core.CoreApp, nil)
}
// DesktopInit will run the Statping server on a specific IP and port using SQLite database

View File

@ -31,7 +31,7 @@ func messagesHandler(w http.ResponseWriter, r *http.Request) {
return
}
messages, _ := core.SelectMessages()
ExecuteResponse(w, r, "messages.html", messages, nil)
ExecuteResponse(w, r, "messages.gohtml", messages, nil)
}
func viewMessageHandler(w http.ResponseWriter, r *http.Request) {
@ -46,7 +46,7 @@ func viewMessageHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
return
}
ExecuteResponse(w, r, "message.html", message, nil)
ExecuteResponse(w, r, "message.gohtml", message, nil)
}
func apiAllMessagesHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -104,7 +104,7 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
fakeNotifer, notif, err := notifier.SelectNotifier(method)
if err != nil {
utils.Log(3, fmt.Sprintf("issue saving notifier %v: %v", method, err))
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
return
}

View File

@ -162,7 +162,7 @@ func TestApiUpdateServiceHandler(t *testing.T) {
}
func TestApiDeleteServiceHandler(t *testing.T) {
rr, err := httpRequestAPI(t, "DELETE", "/api/services/1", nil)
rr, err := httpRequestAPI(t, "DELETE", "/api/services/2", nil)
assert.Nil(t, err)
body := rr.Body.String()
var obj apiResponse
@ -270,7 +270,7 @@ func TestApiRenewHandler(t *testing.T) {
func TestApiCheckinHandler(t *testing.T) {
t.SkipNow()
service := core.SelectService(1)
checkin := service.Checkins()
checkin := service.AllCheckins()
rr, err := httpRequestAPI(t, "GET", "/api/checkin/"+checkin[0].ApiKey, nil)
assert.Nil(t, err)
body := rr.Body.String()

View File

@ -51,7 +51,7 @@ func servicesHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "services.html", core.CoreApp.Services, nil)
ExecuteResponse(w, r, "services.gohtml", core.CoreApp.Services, nil)
}
type serviceOrder struct {
@ -115,7 +115,7 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
Data string
}{serv, start.Format(utils.FlatpickrReadable), end.Format(utils.FlatpickrReadable), start.Unix(), end.Unix(), data.ToString()}
ExecuteResponse(w, r, "service.html", out, nil)
ExecuteResponse(w, r, "service.gohtml", out, nil)
}
func apiServiceHandler(w http.ResponseWriter, r *http.Request) {
@ -124,14 +124,13 @@ func apiServiceHandler(w http.ResponseWriter, r *http.Request) {
return
}
vars := mux.Vars(r)
servicer := core.SelectServicer(utils.ToInt(vars["id"]))
servicer := core.SelectService(utils.ToInt(vars["id"]))
if servicer == nil {
sendErrorJson(errors.New("service not found"), w, r)
return
}
service := servicer.Select()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(service)
json.NewEncoder(w).Encode(servicer)
}
func apiCreateServiceHandler(w http.ResponseWriter, r *http.Request) {
@ -161,8 +160,8 @@ func apiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) {
return
}
vars := mux.Vars(r)
service := core.SelectServicer(utils.ToInt(vars["id"]))
if service.Select() == nil {
service := core.SelectService(utils.ToInt(vars["id"]))
if service == nil {
sendErrorJson(errors.New("service not found"), w, r)
return
}
@ -256,5 +255,5 @@ func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.ToInt(vars["id"]))
service.DeleteFailures()
ExecuteResponse(w, r, "services.html", core.CoreApp.Services, "/services")
ExecuteResponse(w, r, "services.gohtml", core.CoreApp.Services, "/services")
}

View File

@ -32,7 +32,7 @@ func settingsHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
ExecuteResponse(w, r, "settings.html", core.CoreApp, nil)
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, nil)
}
func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
@ -73,7 +73,7 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
utils.Log(3, fmt.Sprintf("issue updating Core: %v", err.Error()))
}
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
}
func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
@ -90,7 +90,7 @@ func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
source.SaveAsset([]byte(mobile), utils.Directory, "scss/mobile.scss")
source.CompileSASS(utils.Directory)
resetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
}
func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
@ -111,7 +111,7 @@ func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
utils.Log(3, "Default 'base.css' was inserted because SASS did not work.")
}
resetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
}
func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
@ -121,7 +121,7 @@ func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
}
source.DeleteAllAssets(utils.Directory)
resetRouter()
ExecuteResponse(w, r, "settings.html", core.CoreApp, "/settings")
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
}
func parseId(r *http.Request) int64 {

View File

@ -34,7 +34,7 @@ func setupHandler(w http.ResponseWriter, r *http.Request) {
data, _ = core.LoadUsingEnv()
}
w.WriteHeader(http.StatusOK)
ExecuteResponse(w, r, "setup.html", data, nil)
ExecuteResponse(w, r, "setup.gohtml", data, nil)
}
func processSetupHandler(w http.ResponseWriter, r *http.Request) {
@ -126,5 +126,5 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
}
func setupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
ExecuteResponse(w, r, "setup.html", a, nil)
ExecuteResponse(w, r, "setup.gohtml", a, nil)
}

View File

@ -33,7 +33,7 @@ func usersHandler(w http.ResponseWriter, r *http.Request) {
return
}
users, _ := core.SelectAllUsers()
ExecuteResponse(w, r, "users.html", users, nil)
ExecuteResponse(w, r, "users.gohtml", users, nil)
}
func usersEditHandler(w http.ResponseWriter, r *http.Request) {
@ -44,7 +44,7 @@ func usersEditHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.Atoi(vars["id"])
user, _ := core.SelectUser(int64(id))
ExecuteResponse(w, r, "user.html", user, nil)
ExecuteResponse(w, r, "user.gohtml", user, nil)
}
func apiUserHandler(w http.ResponseWriter, r *http.Request) {

View File

@ -33,7 +33,7 @@ var mobile = &mobilePush{&notifier.Notification{
Method: "mobile",
Title: "Mobile Notifications",
Description: `Receive push notifications on your Android or iPhone devices using the Statping App. You can scan the Authentication QR Code found in Settings to get the mobile app setup in seconds.
<p align="center"><a href="https://play.google.com/store/apps/details?id=com.statup"><img src="https://img.cjx.io/google-play.svg"></a> <a href="https://testflight.apple.com/join/TuBIj25Q"><img src="https://img.cjx.io/app-store-badge.svg"></a></p>`,
<p align="center"><a href="https://play.google.com/store/apps/details?id=com.statping"><img src="https://img.cjx.io/google-play.svg"></a> <a href="https://testflight.apple.com/join/TuBIj25Q"><img src="https://img.cjx.io/app-store-badge.svg"></a></p>`,
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
Delay: time.Duration(5 * time.Second),

View File

@ -104,7 +104,7 @@ func TestSlackNotifier(t *testing.T) {
slacker.OnSuccess(TestService)
assert.Equal(t, 1, len(slacker.Queue))
go notifier.Queue(slacker)
time.Sleep(5 * time.Second)
time.Sleep(6 * time.Second)
assert.Equal(t, 0, len(slacker.Queue))
})

View File

@ -135,11 +135,11 @@ func CreateAllAssets(folder string) error {
MakePublicFolder(folder + "/assets/font")
MakePublicFolder(folder + "/assets/files")
utils.Log(1, "Inserting scss, css, and javascript files into assets folder")
CopyAllToPublic(ScssBox, "scss")
CopyAllToPublic(FontBox, "font")
CopyAllToPublic(CssBox, "css")
CopyAllToPublic(JsBox, "js")
CopyToPublic(FontBox, folder+"/assets/font", "all.css")
CopyAllToPublic(ScssBox, "scss/")
CopyAllToPublic(FontBox, "font/")
CopyAllToPublic(CssBox, "css/")
CopyAllToPublic(JsBox, "js/")
CopyToPublic(FontBox, folder+"/assets/font/", "all.css")
CopyToPublic(TmplBox, folder+"/assets", "robots.txt")
CopyToPublic(TmplBox, folder+"/assets", "banner.png")
CopyToPublic(TmplBox, folder+"/assets", "favicon.ico")

View File

@ -1,8 +1,8 @@
{
"info": {
"_postman_id": "d74ac4a3-8915-46e8-8ed2-5044ea4ce53b",
"name": "Statping",
"description": "Statping API Requests",
"name": "Statup",
"description": "Statup API Requests",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
@ -10,7 +10,7 @@
"name": "Main",
"item": [
{
"name": "Statping Details",
"name": "Statup Details",
"event": [
{
"listen": "test",
@ -164,7 +164,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n\t\"token\": {\n\t\t\"value\": \"ExponentPushToken[SBryVgIxjgaMKCP5MBtt2J]\"\n\t},\n\t\"user\": {\n\t\t\"username\": \"Brent\"\n\t}\n}"
"raw": ""
},
"url": {
"raw": "{{endpoint}}/api/services/1",
@ -250,7 +250,7 @@
"pm.test(\"Create Service\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.output.name).to.eql(\"New Service\");",
" pm.expect(jsonData.output.domain).to.eql(\"https://statping.com\");",
" pm.expect(jsonData.output.domain).to.eql(\"https://statup.io\");",
" pm.expect(jsonData.output.type).to.eql(\"http\");",
" pm.expect(jsonData.output.method).to.eql(\"GET\");",
" pm.expect(jsonData.output.expected_status).to.eql(200);",
@ -280,7 +280,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"New Service\",\n \"domain\": \"https://statping.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 30,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0\n}"
"raw": "{\n \"name\": \"New Service\",\n \"domain\": \"https://statup.io\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 30,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 30,\n \"order_id\": 0\n}"
},
"url": {
"raw": "{{endpoint}}/api/services",
@ -439,14 +439,14 @@
"header": [],
"body": {},
"url": {
"raw": "{{endpoint}}/api/services/1",
"raw": "{{endpoint}}/api/services/2",
"host": [
"{{endpoint}}"
],
"path": [
"api",
"services",
"1"
"2"
]
}
},

View File

@ -75,15 +75,15 @@
{{end}}
{{if Auth}}
{{$failures := $s.LimitedFailures 16}}
<nav class="nav nav-pills flex-column flex-sm-row mt-3" id="service_tabs" role="serviceLists">
<a class="flex-sm-fill text-sm-center nav-link active" id="edit-tab" data-toggle="tab" href="#edit" role="tab" aria-controls="edit" aria-selected="false">Edit Service</a>
<a class="flex-sm-fill text-sm-center nav-link" id="failures-tab" data-toggle="tab" href="#failures" role="tab" aria-controls="failures" aria-selected="true">Failures</a>
<a class="flex-sm-fill text-sm-center nav-link{{ if not $failures }}disabled{{end}}" id="failures-tab" data-toggle="tab" href="#failures" role="tab" aria-controls="failures" aria-selected="true">Failures</a>
<a class="flex-sm-fill text-sm-center nav-link" id="checkins-tab" data-toggle="tab" href="#checkins" role="tab" aria-controls="checkins" aria-selected="false">Checkins</a>
<a class="flex-sm-fill text-sm-center nav-link" id="response-tab" data-toggle="tab" href="#response" role="tab" aria-controls="response" aria-selected="false">Response</a>
</nav>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade" id="failures" role="serviceLists" aria-labelledby="failures-tab">
{{$failures := $s.LimitedFailures 16}}
{{ if $failures }}
<div class="list-group mt-3 mb-4">
{{ range $failures }}
@ -99,7 +99,7 @@
{{ end }}
</div>
<div class="tab-pane fade" id="checkins" role="serviceLists" aria-labelledby="checkins-tab">
{{if $s.LimitedCheckins}}
{{if $s.AllCheckins}}
<table class="table">
<thead>
<tr>
@ -111,7 +111,7 @@
</tr>
</thead>
<tbody style="font-size: 10pt;">
{{range $s.LimitedCheckins}}
{{range $s.AllCheckins}}
{{ $ch := . }}
<tr id="checkin_{{$ch.Id}}" 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>

View File

@ -21,23 +21,29 @@ 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" json:"id"`
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
Name string `gorm:"column:name" json:"name"`
Interval int64 `gorm:"column:check_interval" json:"interval"`
GracePeriod int64 `gorm:"column:grace_period" json:"grace"`
ApiKey string `gorm:"column:api_key" json:"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:"-"`
Hits []*CheckinHit `gorm:"-" json:"hits"`
Failures []*Failure `gorm:"-" json:"failures"`
Id int64 `gorm:"primary_key;column:id" json:"id"`
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
Name string `gorm:"column:name" json:"name"`
Interval int64 `gorm:"column:check_interval" json:"interval"`
GracePeriod int64 `gorm:"column:grace_period" json:"grace"`
ApiKey string `gorm:"column:api_key" json:"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:"-"`
Failing bool `gorm:"-" json:"failing"`
LastHit time.Time `gorm:"-" json:"last_hit"`
Hits []*CheckinHit `gorm:"-" json:"hits"`
Failures []FailureInterface `gorm:"-" json:"failures"`
}
type CheckinInterface interface {
Select() *Checkin
}
// CheckinHit is a successful response from a Checkin
type CheckinHit struct {
Id int64 `gorm:"primary_key;column:id" json:"id"`
Checkin int64 `gorm:"index;column:checkin" json:"checkin"`
Checkin int64 `gorm:"index;column:checkin" json:"-"`
From string `gorm:"column:from_location" json:"from"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
}

View File

@ -27,6 +27,7 @@ type Failure struct {
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:"-"`
Checkin int64 `gorm:"index;column:checkin" json:"-"`
PingTime float64 `gorm:"column:ping_time" json:"ping"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
}

View File

@ -46,8 +46,9 @@ type Service struct {
SleepDuration time.Duration `gorm:"-" json:"-"`
LastResponse string `gorm:"-" json:"-"`
LastStatusCode int `gorm:"-" json:"status_code"`
LastOnline time.Time `gorm:"-" json:"last_online"`
LastOnline time.Time `gorm:"-" json:"last_success"`
Failures []FailureInterface `gorm:"-" json:"failures,omitempty"`
Checkins []CheckinInterface `gorm:"-" json:"checkins,omitempty"`
}
type ServiceInterface interface {

View File

@ -1 +1 @@
0.79.97
0.79.98