mirror of https://github.com/statping/statping
vue
parent
7cf239125f
commit
7d097eb16e
|
@ -19,6 +19,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"github.com/tatsushid/go-fastping"
|
||||
|
@ -32,30 +33,31 @@ import (
|
|||
|
||||
// checkServices will start the checking go routine for each service
|
||||
func checkServices() {
|
||||
log.Infoln(fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
|
||||
for _, ser := range CoreApp.Services {
|
||||
//go obj.StartCheckins()
|
||||
go ser.CheckQueue(true)
|
||||
log.Infoln(fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.services)))
|
||||
for _, ser := range CoreApp.services {
|
||||
//go CheckinRoutine()
|
||||
go ServiceCheckQueue(ser, true)
|
||||
}
|
||||
}
|
||||
|
||||
// Check will run checkHttp for HTTP services and checkTcp for TCP services
|
||||
// if record param is set to true, it will add a record into the database.
|
||||
func (s *Service) Check(record bool) {
|
||||
switch s.Type {
|
||||
func CheckService(srv database.Servicer, record bool) {
|
||||
switch srv.Model().Type {
|
||||
case "http":
|
||||
s.CheckHttp(record)
|
||||
CheckHttp(srv, record)
|
||||
case "tcp", "udp":
|
||||
s.CheckTcp(record)
|
||||
CheckTcp(srv, record)
|
||||
case "icmp":
|
||||
s.CheckIcmp(record)
|
||||
CheckIcmp(srv, record)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckQueue is the main go routine for checking a service
|
||||
func (s *Service) CheckQueue(record bool) {
|
||||
func ServiceCheckQueue(srv database.Servicer, record bool) {
|
||||
s := srv.Model()
|
||||
s.Checkpoint = time.Now()
|
||||
s.SleepDuration = time.Duration((time.Duration(s.Id) * 100) * time.Millisecond)
|
||||
s.SleepDuration = (time.Duration(s.Id) * 100) * time.Millisecond
|
||||
CheckLoop:
|
||||
for {
|
||||
select {
|
||||
|
@ -63,11 +65,11 @@ CheckLoop:
|
|||
log.Infoln(fmt.Sprintf("Stopping service: %v", s.Name))
|
||||
break CheckLoop
|
||||
case <-time.After(s.SleepDuration):
|
||||
s.Check(record)
|
||||
s.Checkpoint = s.Checkpoint.Add(s.duration())
|
||||
CheckService(srv, record)
|
||||
s.Checkpoint = s.Checkpoint.Add(srv.Interval())
|
||||
sleep := s.Checkpoint.Sub(time.Now())
|
||||
if !s.Online {
|
||||
s.SleepDuration = s.duration()
|
||||
s.SleepDuration = srv.Interval()
|
||||
} else {
|
||||
s.SleepDuration = sleep
|
||||
}
|
||||
|
@ -77,17 +79,18 @@ CheckLoop:
|
|||
}
|
||||
|
||||
// duration returns the amount of duration for a service to check its status
|
||||
func (s *Service) duration() time.Duration {
|
||||
func duration(s database.Servicer) time.Duration {
|
||||
var amount time.Duration
|
||||
if s.Interval >= 10000 {
|
||||
amount = time.Duration(s.Interval) * time.Microsecond
|
||||
if s.Interval() >= 10000 {
|
||||
amount = s.Interval() * time.Microsecond
|
||||
} else {
|
||||
amount = time.Duration(s.Interval) * time.Second
|
||||
amount = s.Interval() * time.Second
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
func (s *Service) parseHost() string {
|
||||
func parseHost(srv database.Servicer) string {
|
||||
s := srv.Model()
|
||||
if s.Type == "tcp" || s.Type == "udp" {
|
||||
return s.Domain
|
||||
} else {
|
||||
|
@ -100,10 +103,11 @@ func (s *Service) parseHost() string {
|
|||
}
|
||||
|
||||
// dnsCheck will check the domain name and return a float64 for the amount of time the DNS check took
|
||||
func (s *Service) dnsCheck() (float64, error) {
|
||||
func dnsCheck(srv database.Servicer) (float64, error) {
|
||||
s := srv.Model()
|
||||
var err error
|
||||
t1 := time.Now()
|
||||
host := s.parseHost()
|
||||
host := parseHost(srv)
|
||||
if s.Type == "tcp" {
|
||||
_, err = net.LookupHost(host)
|
||||
} else {
|
||||
|
@ -122,7 +126,8 @@ func isIPv6(address string) bool {
|
|||
}
|
||||
|
||||
// checkIcmp will send a ICMP ping packet to the service
|
||||
func (s *Service) CheckIcmp(record bool) *Service {
|
||||
func CheckIcmp(srv database.Servicer, record bool) *types.Service {
|
||||
s := srv.Model()
|
||||
p := fastping.NewPinger()
|
||||
resolveIP := "ip4:icmp"
|
||||
if isIPv6(s.Domain) {
|
||||
|
@ -130,7 +135,7 @@ func (s *Service) CheckIcmp(record bool) *Service {
|
|||
}
|
||||
ra, err := net.ResolveIPAddr(resolveIP, s.Domain)
|
||||
if err != nil {
|
||||
recordFailure(s, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err))
|
||||
recordFailure(srv, fmt.Sprintf("Could not send ICMP to service %v, %v", s.Domain, err))
|
||||
return s
|
||||
}
|
||||
p.AddIPAddr(ra)
|
||||
|
@ -140,7 +145,7 @@ func (s *Service) CheckIcmp(record bool) *Service {
|
|||
}
|
||||
err = p.Run()
|
||||
if err != nil {
|
||||
recordFailure(s, fmt.Sprintf("Issue running ICMP to service %v, %v", s.Domain, err))
|
||||
recordFailure(srv, fmt.Sprintf("Issue running ICMP to service %v, %v", s.Domain, err))
|
||||
return s
|
||||
}
|
||||
s.LastResponse = ""
|
||||
|
@ -148,11 +153,12 @@ func (s *Service) CheckIcmp(record bool) *Service {
|
|||
}
|
||||
|
||||
// checkTcp will check a TCP service
|
||||
func (s *Service) CheckTcp(record bool) *Service {
|
||||
dnsLookup, err := s.dnsCheck()
|
||||
func CheckTcp(srv database.Servicer, record bool) *types.Service {
|
||||
s := srv.Model()
|
||||
dnsLookup, err := dnsCheck(srv)
|
||||
if err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("Could not get IP address for TCP service %v, %v", s.Domain, err))
|
||||
recordFailure(srv, fmt.Sprintf("Could not get IP address for TCP service %v, %v", s.Domain, err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -168,13 +174,13 @@ func (s *Service) CheckTcp(record bool) *Service {
|
|||
conn, err := net.DialTimeout(s.Type, domain, time.Duration(s.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("Dial Error %v", err))
|
||||
recordFailure(srv, fmt.Sprintf("Dial Error %v", err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
if err := conn.Close(); err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err))
|
||||
recordFailure(srv, fmt.Sprintf("%v Socket Close Error %v", strings.ToUpper(s.Type), err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -188,11 +194,12 @@ func (s *Service) CheckTcp(record bool) *Service {
|
|||
}
|
||||
|
||||
// checkHttp will check a HTTP service
|
||||
func (s *Service) CheckHttp(record bool) *Service {
|
||||
dnsLookup, err := s.dnsCheck()
|
||||
func CheckHttp(srv database.Servicer, record bool) *types.Service {
|
||||
s := srv.Model()
|
||||
dnsLookup, err := dnsCheck(srv)
|
||||
if err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err))
|
||||
recordFailure(srv, fmt.Sprintf("Could not get IP address for domain %v, %v", s.Domain, err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -217,7 +224,7 @@ func (s *Service) CheckHttp(record bool) *Service {
|
|||
}
|
||||
if err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("HTTP Error %v", err))
|
||||
recordFailure(srv, fmt.Sprintf("HTTP Error %v", err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -233,14 +240,14 @@ func (s *Service) CheckHttp(record bool) *Service {
|
|||
}
|
||||
if !match {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
|
||||
recordFailure(srv, fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
if s.ExpectedStatus != res.StatusCode {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, s.ExpectedStatus))
|
||||
recordFailure(srv, fmt.Sprintf("HTTP Status Code %v did not match %v", res.StatusCode, s.ExpectedStatus))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -251,7 +258,7 @@ func (s *Service) CheckHttp(record bool) *Service {
|
|||
}
|
||||
|
||||
// recordSuccess will create a new 'hit' record in the database for a successful/online service
|
||||
func recordSuccess(s *Service) {
|
||||
func recordSuccess(s *types.Service) {
|
||||
s.LastOnline = time.Now().UTC()
|
||||
hit := &types.Hit{
|
||||
Service: s.Id,
|
||||
|
@ -259,15 +266,16 @@ func recordSuccess(s *Service) {
|
|||
PingTime: s.PingTime,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
s.CreateHit(hit)
|
||||
log.WithFields(utils.ToFields(hit, s.Select())).Infoln(fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
|
||||
notifier.OnSuccess(s.Service)
|
||||
database.Create(hit)
|
||||
log.WithFields(utils.ToFields(hit, s)).Infoln(fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
|
||||
notifier.OnSuccess(s)
|
||||
s.Online = true
|
||||
s.SuccessNotified = true
|
||||
}
|
||||
|
||||
// recordFailure will create a new 'Failure' record in the database for a offline service
|
||||
func recordFailure(s *Service, issue string) {
|
||||
func recordFailure(srv database.Servicer, issue string) {
|
||||
s := srv.Model()
|
||||
fail := &types.Failure{
|
||||
Service: s.Id,
|
||||
Issue: issue,
|
||||
|
@ -275,11 +283,11 @@ func recordFailure(s *Service, issue string) {
|
|||
CreatedAt: time.Now().UTC(),
|
||||
ErrorCode: s.LastStatusCode,
|
||||
}
|
||||
log.WithFields(utils.ToFields(fail, s.Select())).
|
||||
log.WithFields(utils.ToFields(fail, s)).
|
||||
Warnln(fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
|
||||
s.CreateFailure(fail)
|
||||
database.Create(fail)
|
||||
s.Online = false
|
||||
s.SuccessNotified = false
|
||||
s.DownText = s.DowntimeText()
|
||||
notifier.OnFailure(s.Service, fail)
|
||||
s.DownText = srv.DowntimeText()
|
||||
notifier.OnFailure(s, fail)
|
||||
}
|
||||
|
|
109
core/checkin.go
109
core/checkin.go
|
@ -18,13 +18,14 @@ package core
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Checkin struct {
|
||||
*types.Checkin
|
||||
*database.CheckinObj
|
||||
}
|
||||
|
||||
type CheckinHit struct {
|
||||
|
@ -37,8 +38,9 @@ func (c *Checkin) Select() *types.Checkin {
|
|||
}
|
||||
|
||||
// Routine for checking if the last Checkin was within its interval
|
||||
func (c *Checkin) Routine() {
|
||||
if c.Last() == nil {
|
||||
func CheckinRoutine(checkin database.Checkiner) {
|
||||
c := checkin.Object()
|
||||
if c.Hits().Last() == nil {
|
||||
return
|
||||
}
|
||||
reCheck := c.Period()
|
||||
|
@ -52,9 +54,10 @@ CheckinLoop:
|
|||
case <-time.After(reCheck):
|
||||
log.Infoln(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)
|
||||
issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Hits().Last().CreatedAt)
|
||||
log.Errorln(issue)
|
||||
c.CreateFailure()
|
||||
|
||||
CreateCheckinFailure(c)
|
||||
}
|
||||
reCheck = c.Period()
|
||||
}
|
||||
|
@ -67,98 +70,44 @@ 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}
|
||||
}
|
||||
|
||||
// ReturnCheckinHit converts *types.checkinHit to *core.checkinHit
|
||||
func ReturnCheckinHit(c *types.CheckinHit) *CheckinHit {
|
||||
return &CheckinHit{CheckinHit: c}
|
||||
}
|
||||
|
||||
func (c *Checkin) Service() *Service {
|
||||
return SelectService(c.ServiceId)
|
||||
}
|
||||
|
||||
func (c *Checkin) CreateFailure() (int64, error) {
|
||||
func CreateCheckinFailure(checkin database.Checkiner) (int64, error) {
|
||||
c := checkin.Object()
|
||||
service := c.Service()
|
||||
c.Failing = true
|
||||
fail := &Failure{&types.Failure{
|
||||
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,
|
||||
Checkin: c.Id,
|
||||
PingTime: c.Expected().Seconds(),
|
||||
}}
|
||||
row := Database(&Failure{}).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 int) []*types.CheckinHit {
|
||||
var hits []*types.CheckinHit
|
||||
Database(&CheckinHit{}).Where("checkin = ?", c.Id).Order("id desc").Limit(amount).Find(&hits)
|
||||
return hits
|
||||
_, err := database.Create(fail)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
//sort.Sort(types.FailSort(c.Failures()))
|
||||
return fail.Id, err
|
||||
}
|
||||
|
||||
// AllCheckins returns all checkin in system
|
||||
func AllCheckins() []*Checkin {
|
||||
var checkins []*Checkin
|
||||
Database(&types.Checkin{}).Find(&checkins)
|
||||
func AllCheckins() []*database.CheckinObj {
|
||||
checkins := database.AllCheckins()
|
||||
return checkins
|
||||
}
|
||||
|
||||
// SelectCheckin will find a Checkin based on the API supplied
|
||||
func SelectCheckin(api string) *Checkin {
|
||||
for _, s := range Services() {
|
||||
for _, c := range s.Select().Checkins {
|
||||
if c.Select().ApiKey == api {
|
||||
return c.(*Checkin)
|
||||
for _, c := range s.AllCheckins() {
|
||||
if c.ApiKey == api {
|
||||
return &Checkin{c}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
last := c.Last().CreatedAt
|
||||
now := utils.Now()
|
||||
lastDir := now.Sub(last)
|
||||
sub := time.Duration(c.Period() - lastDir)
|
||||
return sub
|
||||
}
|
||||
|
||||
// Last returns the last checkinHit for a Checkin
|
||||
func (c *Checkin) Last() *CheckinHit {
|
||||
var hit CheckinHit
|
||||
Database(c).Where("checkin = ?", c.Id).Last(&hit)
|
||||
return &hit
|
||||
}
|
||||
|
||||
func (c *Checkin) Link() string {
|
||||
return fmt.Sprintf("%v/checkin/%v", CoreApp.Domain, c.ApiKey)
|
||||
}
|
||||
|
||||
// AllHits returns all of the CheckinHits for a given Checkin
|
||||
func (c *Checkin) AllHits() []*types.CheckinHit {
|
||||
var checkins []*types.CheckinHit
|
||||
|
@ -204,7 +153,7 @@ func (c *Checkin) Delete() error {
|
|||
// index returns a checkin index int for updating the *checkin.Service slice
|
||||
func (c *Checkin) index() int {
|
||||
for k, checkin := range c.Service().Checkins {
|
||||
if c.Id == checkin.Select().Id {
|
||||
if c.Id == checkin.Model().Id {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
@ -214,16 +163,16 @@ func (c *Checkin) index() int {
|
|||
// Create will create a new Checkin
|
||||
func (c *Checkin) Create() (int64, error) {
|
||||
c.ApiKey = utils.RandomString(7)
|
||||
row := Database(c).Create(&c)
|
||||
if row.Error() != nil {
|
||||
log.Warnln(row.Error())
|
||||
return 0, row.Error()
|
||||
_, err := database.Create(c)
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
return 0, err
|
||||
}
|
||||
service := SelectService(c.ServiceId)
|
||||
service.Checkins = append(service.Checkins, c)
|
||||
c.Start()
|
||||
go c.Routine()
|
||||
return c.Id, row.Error()
|
||||
go CheckinRoutine(c)
|
||||
return c.Id, err
|
||||
}
|
||||
|
||||
// Update will update a Checkin
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"io/ioutil"
|
||||
|
@ -71,7 +72,9 @@ func LoadUsingEnv() (*types.DbConfig, error) {
|
|||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
CoreApp.SaveConfig(Configs)
|
||||
if _, err := CoreApp.SaveConfig(Configs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exists := DbSession.HasTable("core")
|
||||
if !exists {
|
||||
log.Infoln(fmt.Sprintf("Core database does not exist, creating now!"))
|
||||
|
@ -91,15 +94,20 @@ func LoadUsingEnv() (*types.DbConfig, error) {
|
|||
password = "admin"
|
||||
}
|
||||
|
||||
admin := ReturnUser(&types.User{
|
||||
admin := &types.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: "info@admin.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
_, err := admin.Create()
|
||||
}
|
||||
if _, err := database.Create(admin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := SampleData(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
SampleData()
|
||||
return Configs, err
|
||||
}
|
||||
return Configs, nil
|
||||
|
|
20
core/core.go
20
core/core.go
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/hunterlong/statping/core/integrations"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/notifiers"
|
||||
"github.com/hunterlong/statping/source"
|
||||
"github.com/hunterlong/statping/types"
|
||||
|
@ -34,6 +35,7 @@ type PluginRepos types.PluginRepos
|
|||
|
||||
type Core struct {
|
||||
*types.Core
|
||||
services []database.Servicer
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -48,7 +50,7 @@ func init() {
|
|||
|
||||
// NewCore return a new *core.Core struct
|
||||
func NewCore() *Core {
|
||||
CoreApp = &Core{&types.Core{
|
||||
CoreApp = &Core{Core: &types.Core{
|
||||
Started: time.Now().UTC(),
|
||||
},
|
||||
}
|
||||
|
@ -65,7 +67,7 @@ func InitApp() {
|
|||
SelectCore()
|
||||
InsertNotifierDB()
|
||||
InsertIntegratorDB()
|
||||
CoreApp.SelectAllServices(true)
|
||||
SelectAllServices(true)
|
||||
checkServices()
|
||||
AttachNotifiers()
|
||||
AddIntegrations()
|
||||
|
@ -150,16 +152,6 @@ func (c Core) MobileSASS() string {
|
|||
return source.OpenAsset("scss/mobile.scss")
|
||||
}
|
||||
|
||||
// AllOnline will be true if all services are online
|
||||
func (c Core) AllOnline() bool {
|
||||
for _, s := range CoreApp.Services {
|
||||
if !s.Select().Online {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectCore will return the CoreApp global variable and the settings/configs for Statping
|
||||
func SelectCore() (*Core, error) {
|
||||
if DbSession == nil {
|
||||
|
@ -222,9 +214,9 @@ func AddIntegrations() error {
|
|||
}
|
||||
|
||||
// ServiceOrder will reorder the services based on 'order_id' (Order)
|
||||
type ServiceOrder []types.ServiceInterface
|
||||
type ServiceOrder []database.Servicer
|
||||
|
||||
// Sort interface for resroting the Services in order
|
||||
func (c ServiceOrder) Len() int { return len(c) }
|
||||
func (c ServiceOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ServiceOrder) Less(i, j int) bool { return c[i].(*Service).Order < c[j].(*Service).Order }
|
||||
func (c ServiceOrder) Less(i, j int) bool { return c[i].Model().Order < c[j].Model().Order }
|
||||
|
|
|
@ -77,26 +77,6 @@ func Database(obj interface{}) database.Database {
|
|||
}
|
||||
}
|
||||
|
||||
// HitsBetween returns the gorm database query for a collection of service hits between a time range
|
||||
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) database.Database {
|
||||
selector := Dbtimestamp(group, column)
|
||||
if CoreApp.Config.DbConn == "postgres" {
|
||||
return Database(&Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
|
||||
} else {
|
||||
return Database(&Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY))
|
||||
}
|
||||
}
|
||||
|
||||
// FailuresBetween returns the gorm database query for a collection of service hits between a time range
|
||||
func (s *Service) FailuresBetween(t1, t2 time.Time, group string, column string) database.Database {
|
||||
selector := Dbtimestamp(group, column)
|
||||
if CoreApp.Config.DbConn == "postgres" {
|
||||
return Database(&Failure{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
|
||||
} else {
|
||||
return Database(&Failure{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME_DAY), t2.UTC().Format(types.TIME_DAY))
|
||||
}
|
||||
}
|
||||
|
||||
// CloseDB will close the database connection if available
|
||||
func CloseDB() {
|
||||
if DbSession != nil {
|
||||
|
|
|
@ -16,20 +16,19 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/hunterlong/statping/types"
|
||||
)
|
||||
|
||||
// ExportChartsJs renders the charts for the index page
|
||||
|
||||
type ExportData struct {
|
||||
Core *types.Core `json:"core"`
|
||||
Services []types.ServiceInterface `json:"services"`
|
||||
Messages []*Message `json:"messages"`
|
||||
Checkins []*Checkin `json:"checkins"`
|
||||
Users []*User `json:"users"`
|
||||
Groups []*Group `json:"groups"`
|
||||
Notifiers []types.AllNotifiers `json:"notifiers"`
|
||||
Core *types.Core `json:"core"`
|
||||
Services []*types.Service `json:"services"`
|
||||
Messages []*types.Message `json:"messages"`
|
||||
Checkins []*types.Checkin `json:"checkins"`
|
||||
Users []*types.User `json:"users"`
|
||||
Groups []*types.Group `json:"groups"`
|
||||
Notifiers []types.AllNotifiers `json:"notifiers"`
|
||||
}
|
||||
|
||||
// ExportSettings will export a JSON file containing all of the settings below:
|
||||
|
@ -40,21 +39,21 @@ type ExportData struct {
|
|||
// - Services
|
||||
// - Groups
|
||||
// - Messages
|
||||
func ExportSettings() ([]byte, error) {
|
||||
users, err := SelectAllUsers()
|
||||
messages, err := SelectMessages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := ExportData{
|
||||
Core: CoreApp.Core,
|
||||
Notifiers: CoreApp.Notifications,
|
||||
Checkins: AllCheckins(),
|
||||
Users: users,
|
||||
Services: CoreApp.Services,
|
||||
Groups: SelectGroups(true, true),
|
||||
Messages: messages,
|
||||
}
|
||||
export, err := json.Marshal(data)
|
||||
return export, err
|
||||
}
|
||||
//func ExportSettings() ([]byte, error) {
|
||||
// users, err := SelectAllUsers()
|
||||
// messages, err := SelectMessages()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// data := ExportData{
|
||||
// Core: CoreApp.Core,
|
||||
// Notifiers: CoreApp.Notifications,
|
||||
// Checkins: database.AllCheckins(),
|
||||
// Users: database.AllUsers(),
|
||||
// Services: CoreApp.Services,
|
||||
// Groups: SelectGroups(true, true),
|
||||
// Messages: messages,
|
||||
// }
|
||||
// export, err := json.Marshal(data)
|
||||
// return export, err
|
||||
//}
|
||||
|
|
176
core/failures.go
176
core/failures.go
|
@ -16,192 +16,18 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Failure struct {
|
||||
*types.Failure
|
||||
}
|
||||
type Failure struct{}
|
||||
|
||||
const (
|
||||
limitedFailures = 32
|
||||
limitedHits = 32
|
||||
)
|
||||
|
||||
// CreateFailure will create a new Failure record for a service
|
||||
func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
|
||||
f.Service = s.Id
|
||||
row := Database(&types.Failure{}).Create(f)
|
||||
if row.Error() != nil {
|
||||
log.Errorln(row.Error())
|
||||
return 0, row.Error()
|
||||
}
|
||||
sort.Sort(types.FailSort(s.Failures))
|
||||
//s.Failures = append(s.Failures, f)
|
||||
if len(s.Failures) > limitedFailures {
|
||||
s.Failures = s.Failures[1:]
|
||||
}
|
||||
return f.Id, row.Error()
|
||||
}
|
||||
|
||||
// AllFailures will return all failures attached to a service
|
||||
func (s *Service) AllFailures() []types.Failure {
|
||||
var fails []types.Failure
|
||||
err := Database(&types.Failure{}).Find(&fails)
|
||||
if err.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
||||
return nil
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
// DeleteFailures will delete all failures for a service
|
||||
func (s *Service) DeleteFailures() {
|
||||
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
|
||||
if err.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("failed to delete all failures: %v", err))
|
||||
}
|
||||
s.Failures = nil
|
||||
}
|
||||
|
||||
// LimitedFailures will return the last amount of failures from a service
|
||||
func (s *Service) LimitedCheckinFailures(amount int) []*Failure {
|
||||
var failArr []*Failure
|
||||
Database(&types.Failure{}).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 {
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now().UTC(), f.CreatedAt)
|
||||
return got
|
||||
}
|
||||
|
||||
// Select returns a *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 {
|
||||
db := Database(&types.Failure{}).Delete(f)
|
||||
return db.Error()
|
||||
}
|
||||
|
||||
// Count24HFailures returns the amount of failures for a service within the last 24 hours
|
||||
func (c *Core) Count24HFailures() uint64 {
|
||||
var count uint64
|
||||
for _, s := range CoreApp.Services {
|
||||
service := s.(*Service)
|
||||
fails, _ := service.TotalFailures24()
|
||||
count += fails
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// CountFailures returns the total count of failures for all services
|
||||
func CountFailures() uint64 {
|
||||
var count uint64
|
||||
err := Database(&types.Failure{}).Count(&count)
|
||||
if err.Error() != nil {
|
||||
log.Warnln(err.Error())
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// TotalFailuresOnDate returns the total amount of failures for a service on a specific time/date
|
||||
func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) {
|
||||
var count uint64
|
||||
date := ago.UTC().Format("2006-01-02 00:00:00")
|
||||
dateend := ago.UTC().Format("2006-01-02") + " 23:59:59"
|
||||
rows := Database(&types.Failure{}).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'")
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error()
|
||||
}
|
||||
|
||||
// TotalFailures24 returns the amount of failures for a service within the last 24 hours
|
||||
func (s *Service) TotalFailures24() (uint64, error) {
|
||||
ago := time.Now().UTC().Add(-24 * time.Hour)
|
||||
return s.TotalFailuresSince(ago)
|
||||
}
|
||||
|
||||
// TotalFailures returns the total amount of failures for a service
|
||||
func (s *Service) TotalFailures() (uint64, error) {
|
||||
var count uint64
|
||||
rows := Database(&types.Failure{}).Where("service = ?", s.Id)
|
||||
err := rows.Count(&count)
|
||||
return count, err.Error()
|
||||
}
|
||||
|
||||
// FailuresDaysAgo returns the amount of failures since days ago
|
||||
func (s *Service) FailuresDaysAgo(days int) uint64 {
|
||||
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
count, _ := s.TotalFailuresSince(ago)
|
||||
return count
|
||||
}
|
||||
|
||||
// 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 := Database(&types.Failure{}).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")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "operation timed out")
|
||||
if err {
|
||||
return fmt.Sprintf("HTTP Request Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "x509: certificate is valid")
|
||||
if err {
|
||||
return fmt.Sprintf("SSL Certificate invalid")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "Client.Timeout exceeded while awaiting headers")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "no such host")
|
||||
if err {
|
||||
return fmt.Sprintf("Domain is offline or not found")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "HTTP Status Code")
|
||||
if err {
|
||||
return fmt.Sprintf("Incorrect HTTP Status Code")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "connection refused")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Failed")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "can't assign requested address")
|
||||
if err {
|
||||
return fmt.Sprintf("Unable to Request Address")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "no route to host")
|
||||
if err {
|
||||
return fmt.Sprintf("Domain is offline or not found")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "i/o timeout")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "Client.Timeout exceeded while reading body")
|
||||
if err {
|
||||
return fmt.Sprintf("Timed Out on Response Body")
|
||||
}
|
||||
return f.Issue
|
||||
}
|
||||
|
|
|
@ -1,72 +1,23 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
*types.Group
|
||||
}
|
||||
|
||||
// Delete will remove a group
|
||||
func (g *Group) Delete() error {
|
||||
for _, s := range g.Services() {
|
||||
s.GroupId = 0
|
||||
s.Update(false)
|
||||
}
|
||||
err := Database(&Group{}).Delete(g)
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// Create will create a group and insert it into the database
|
||||
func (g *Group) Create() (int64, error) {
|
||||
g.CreatedAt = time.Now().UTC()
|
||||
db := Database(&Group{}).Create(g)
|
||||
return g.Id, db.Error()
|
||||
}
|
||||
|
||||
// Update will update a group
|
||||
func (g *Group) Update() (int64, error) {
|
||||
g.UpdatedAt = time.Now().UTC()
|
||||
db := Database(&Group{}).Update(g)
|
||||
return g.Id, db.Error()
|
||||
}
|
||||
|
||||
// Services returns all services belonging to a group
|
||||
func (g *Group) Services() []*Service {
|
||||
var services []*Service
|
||||
for _, s := range Services() {
|
||||
if s.Select().GroupId == int(g.Id) {
|
||||
services = append(services, s.(*Service))
|
||||
}
|
||||
}
|
||||
return services
|
||||
}
|
||||
|
||||
// VisibleServices returns all services based on authentication
|
||||
func (g *Group) VisibleServices(auth bool) []*Service {
|
||||
var services []*Service
|
||||
for _, g := range g.Services() {
|
||||
if !g.Public.Bool {
|
||||
if auth {
|
||||
services = append(services, g)
|
||||
}
|
||||
} else {
|
||||
services = append(services, g)
|
||||
}
|
||||
}
|
||||
return services
|
||||
database.Grouper
|
||||
}
|
||||
|
||||
// SelectGroups returns all groups
|
||||
func SelectGroups(includeAll bool, auth bool) []*Group {
|
||||
var groups []*Group
|
||||
var validGroups []*Group
|
||||
Database(&Group{}).Find(&groups).Order("order_id desc")
|
||||
func SelectGroups(includeAll bool, auth bool) []database.Grouper {
|
||||
var validGroups []database.Grouper
|
||||
|
||||
groups := database.AllGroups()
|
||||
|
||||
for _, g := range groups {
|
||||
if !g.Public.Bool {
|
||||
if !g.Model().Public.Bool {
|
||||
if auth {
|
||||
validGroups = append(validGroups, g)
|
||||
}
|
||||
|
@ -76,26 +27,26 @@ func SelectGroups(includeAll bool, auth bool) []*Group {
|
|||
}
|
||||
sort.Sort(GroupOrder(validGroups))
|
||||
if includeAll {
|
||||
emptyGroup := &Group{&types.Group{Id: 0, Public: types.NewNullBool(true)}}
|
||||
emptyGroup := &Group{}
|
||||
validGroups = append(validGroups, emptyGroup)
|
||||
}
|
||||
return validGroups
|
||||
}
|
||||
|
||||
// SelectGroup returns a *core.Group
|
||||
func SelectGroup(id int64) *Group {
|
||||
func SelectGroup(id int64) *types.Group {
|
||||
for _, g := range SelectGroups(true, true) {
|
||||
if g.Id == id {
|
||||
return g
|
||||
if g.Model().Id == id {
|
||||
return g.Model()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GroupOrder will reorder the groups based on 'order_id' (Order)
|
||||
type GroupOrder []*Group
|
||||
type GroupOrder []database.Grouper
|
||||
|
||||
// Sort interface for resorting the Groups in order
|
||||
func (c GroupOrder) Len() int { return len(c) }
|
||||
func (c GroupOrder) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c GroupOrder) Less(i, j int) bool { return c[i].Order < c[j].Order }
|
||||
func (c GroupOrder) Less(i, j int) bool { return c[i].Model().Order < c[j].Model().Order }
|
||||
|
|
81
core/hits.go
81
core/hits.go
|
@ -16,90 +16,9 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Hit struct {
|
||||
*types.Hit
|
||||
}
|
||||
|
||||
// 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 := Database(&types.Hit{}).Create(&h)
|
||||
if db.Error() != nil {
|
||||
log.Errorln(db.Error())
|
||||
return 0, db.Error()
|
||||
}
|
||||
return h.Id, db.Error()
|
||||
}
|
||||
|
||||
// CountHits returns a int64 for all hits for a service
|
||||
func (s *Service) CountHits() (int64, error) {
|
||||
var hits int64
|
||||
col := Database(&types.Hit{}).Where("service = ?", s.Id)
|
||||
err := col.Count(&hits)
|
||||
return hits, err.Error()
|
||||
}
|
||||
|
||||
// Hits returns all successful hits for a service
|
||||
func (s *Service) HitsQuery(r *http.Request) ([]*types.Hit, error) {
|
||||
var hits []*types.Hit
|
||||
col := Database(&types.Hit{}).Where("service = ?", s.Id).Requests(r)
|
||||
err := col.Find(&hits)
|
||||
return hits, err.Error()
|
||||
}
|
||||
|
||||
func (s *Service) HitsDb(r *http.Request) database.Database {
|
||||
return Database(&types.Hit{}).Where("service = ?", s.Id).Requests(r).Order("id desc")
|
||||
}
|
||||
|
||||
// Hits returns all successful hits for a service
|
||||
func (s *Service) Hits() ([]*types.Hit, error) {
|
||||
var hits []*types.Hit
|
||||
col := Database(&types.Hit{}).Where("service = ?", s.Id).Order("id desc")
|
||||
err := col.Find(&hits)
|
||||
return hits, err.Error()
|
||||
}
|
||||
|
||||
// LimitedHits returns the last 1024 successful/online 'hit' records for a service
|
||||
func (s *Service) LimitedHits(amount int) ([]*types.Hit, error) {
|
||||
var hits []*types.Hit
|
||||
col := Database(&types.Hit{}).Where("service = ?", s.Id).Order("id desc").Limit(amount)
|
||||
err := col.Find(&hits)
|
||||
return reverseHits(hits), err.Error()
|
||||
}
|
||||
|
||||
// reverseHits will reverse the service's hit slice
|
||||
func reverseHits(input []*types.Hit) []*types.Hit {
|
||||
if len(input) == 0 {
|
||||
return input
|
||||
}
|
||||
return append(reverseHits(input[1:]), input[0])
|
||||
}
|
||||
|
||||
// TotalHits returns the total amount of successful hits a service has
|
||||
func (s *Service) TotalHits() (uint64, error) {
|
||||
var count uint64
|
||||
col := Database(&types.Hit{}).Where("service = ?", s.Id).Count(&count)
|
||||
return count, col.Error()
|
||||
}
|
||||
|
||||
// TotalHitsSince returns the total amount of hits based on a specific time/date
|
||||
func (s *Service) TotalHitsSince(ago time.Time) (uint64, error) {
|
||||
var count uint64
|
||||
rows := Database(&types.Hit{}).Where("service = ? AND created_at > ?", s.Id, ago.UTC().Format("2006-01-02 15:04:05")).Count(&count)
|
||||
return count, rows.Error()
|
||||
}
|
||||
|
||||
// Sum returns the added value Latency for all of the services successful hits.
|
||||
func (s *Service) Sum() float64 {
|
||||
var sum float64
|
||||
rows, _ := Database(&types.Hit{}).Where("service = ?", s.Id).Select("sum(latency) as total").Rows()
|
||||
for rows.Next() {
|
||||
rows.Scan(&sum)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
|
|
@ -51,13 +51,6 @@ func SelectMessage(id int64) (*Message, error) {
|
|||
return &message, db.Error()
|
||||
}
|
||||
|
||||
func (m *Message) Service() *Service {
|
||||
if m.ServiceId == 0 {
|
||||
return nil
|
||||
}
|
||||
return SelectService(m.ServiceId)
|
||||
}
|
||||
|
||||
// Create will create a Message and insert it into the database
|
||||
func (m *Message) Create() (int64, error) {
|
||||
m.CreatedAt = time.Now().UTC()
|
||||
|
|
259
core/sample.go
259
core/sample.go
|
@ -18,6 +18,7 @@ package core
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sync"
|
||||
|
@ -35,7 +36,7 @@ func InsertSampleData() error {
|
|||
|
||||
insertSampleGroups()
|
||||
createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC()
|
||||
s1 := ReturnService(&types.Service{
|
||||
s1 := &types.Service{
|
||||
Name: "Google",
|
||||
Domain: "https://google.com",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -48,8 +49,8 @@ func InsertSampleData() error {
|
|||
Permalink: types.NewNullString("google"),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s2 := ReturnService(&types.Service{
|
||||
}
|
||||
s2 := &types.Service{
|
||||
Name: "Statping Github",
|
||||
Domain: "https://github.com/hunterlong/statping",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -61,8 +62,8 @@ func InsertSampleData() error {
|
|||
Permalink: types.NewNullString("statping_github"),
|
||||
VerifySSL: types.NewNullBool(true),
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s3 := ReturnService(&types.Service{
|
||||
}
|
||||
s3 := &types.Service{
|
||||
Name: "JSON Users Test",
|
||||
Domain: "https://jsonplaceholder.typicode.com/users",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -75,8 +76,8 @@ func InsertSampleData() error {
|
|||
VerifySSL: types.NewNullBool(true),
|
||||
GroupId: 2,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s4 := ReturnService(&types.Service{
|
||||
}
|
||||
s4 := &types.Service{
|
||||
Name: "JSON API Tester",
|
||||
Domain: "https://jsonplaceholder.typicode.com/posts",
|
||||
ExpectedStatus: 201,
|
||||
|
@ -91,8 +92,8 @@ func InsertSampleData() error {
|
|||
VerifySSL: types.NewNullBool(true),
|
||||
GroupId: 2,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
s5 := ReturnService(&types.Service{
|
||||
}
|
||||
s5 := &types.Service{
|
||||
Name: "Google DNS",
|
||||
Domain: "8.8.8.8",
|
||||
Interval: 20,
|
||||
|
@ -103,13 +104,13 @@ func InsertSampleData() error {
|
|||
Public: types.NewNullBool(true),
|
||||
GroupId: 1,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s1.Create(false)
|
||||
s2.Create(false)
|
||||
s3.Create(false)
|
||||
s4.Create(false)
|
||||
s5.Create(false)
|
||||
database.Create(s1)
|
||||
database.Create(s2)
|
||||
database.Create(s3)
|
||||
database.Create(s4)
|
||||
database.Create(s5)
|
||||
|
||||
insertMessages()
|
||||
|
||||
|
@ -121,85 +122,111 @@ func InsertSampleData() error {
|
|||
}
|
||||
|
||||
func insertSampleIncidents() error {
|
||||
incident1 := &Incident{&types.Incident{
|
||||
incident1 := &types.Incident{
|
||||
Title: "Github Downtime",
|
||||
Description: "This is an example of a incident for a service.",
|
||||
ServiceId: 2,
|
||||
}}
|
||||
_, err := incident1.Create()
|
||||
}
|
||||
if _, err := database.Create(incident1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
incidentUpdate1 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
incidentUpdate1 := &types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Github's page for Statping seems to be sending a 501 error.",
|
||||
Type: "Investigating",
|
||||
}}
|
||||
_, err = incidentUpdate1.Create()
|
||||
}
|
||||
if _, err := database.Create(incidentUpdate1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
incidentUpdate2 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
incidentUpdate2 := &types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Problem is continuing and we are looking at the issues.",
|
||||
Type: "Update",
|
||||
}}
|
||||
_, err = incidentUpdate2.Create()
|
||||
}
|
||||
if _, err := database.Create(incidentUpdate2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
incidentUpdate3 := &IncidentUpdate{&types.IncidentUpdate{
|
||||
incidentUpdate3 := &types.IncidentUpdate{
|
||||
IncidentId: incident1.Id,
|
||||
Message: "Github is now back online and everything is working.",
|
||||
Type: "Resolved",
|
||||
}}
|
||||
_, err = incidentUpdate3.Create()
|
||||
}
|
||||
if _, err := database.Create(incidentUpdate3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertSampleGroups() error {
|
||||
group1 := &Group{&types.Group{
|
||||
group1 := &types.Group{
|
||||
Name: "Main Services",
|
||||
Public: types.NewNullBool(true),
|
||||
Order: 2,
|
||||
}}
|
||||
_, err := group1.Create()
|
||||
group2 := &Group{&types.Group{
|
||||
}
|
||||
if _, err := database.Create(group1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group2 := &types.Group{
|
||||
Name: "Linked Services",
|
||||
Public: types.NewNullBool(false),
|
||||
Order: 1,
|
||||
}}
|
||||
_, err = group2.Create()
|
||||
group3 := &Group{&types.Group{
|
||||
}
|
||||
if _, err := database.Create(group2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group3 := &types.Group{
|
||||
Name: "Empty Group",
|
||||
Public: types.NewNullBool(false),
|
||||
Order: 3,
|
||||
}}
|
||||
_, err = group3.Create()
|
||||
return err
|
||||
}
|
||||
if _, err := database.Create(group3); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// insertSampleCheckins will create 2 checkins with 60 successful hits per Checkin
|
||||
func insertSampleCheckins() error {
|
||||
s1 := SelectService(1)
|
||||
checkin1 := ReturnCheckin(&types.Checkin{
|
||||
checkin1 := &types.Checkin{
|
||||
ServiceId: s1.Id,
|
||||
Interval: 300,
|
||||
GracePeriod: 300,
|
||||
})
|
||||
checkin1.Update()
|
||||
}
|
||||
|
||||
if _, err := database.Create(checkin1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s2 := SelectService(1)
|
||||
checkin2 := ReturnCheckin(&types.Checkin{
|
||||
checkin2 := &types.Checkin{
|
||||
ServiceId: s2.Id,
|
||||
Interval: 900,
|
||||
GracePeriod: 300,
|
||||
})
|
||||
checkin2.Update()
|
||||
}
|
||||
|
||||
if _, err := database.Create(checkin2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkTime := time.Now().UTC().Add(-24 * time.Hour)
|
||||
for i := 0; i <= 60; i++ {
|
||||
checkHit := ReturnCheckinHit(&types.CheckinHit{
|
||||
checkHit := &types.CheckinHit{
|
||||
Checkin: checkin1.Id,
|
||||
From: "192.168.0.1",
|
||||
CreatedAt: checkTime.UTC(),
|
||||
})
|
||||
checkHit.Create()
|
||||
}
|
||||
|
||||
if _, err := database.Create(checkHit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkTime = checkTime.Add(10 * time.Minute)
|
||||
}
|
||||
return nil
|
||||
|
@ -250,50 +277,61 @@ func insertSampleCore() error {
|
|||
CreatedAt: time.Now().UTC(),
|
||||
UseCdn: types.NewNullBool(false),
|
||||
}
|
||||
query := Database(&Core{}).Create(core)
|
||||
return query.Error()
|
||||
|
||||
_, err := database.Create(core)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// insertSampleUsers will create 2 admin users for a seed database
|
||||
func insertSampleUsers() error {
|
||||
u2 := ReturnUser(&types.User{
|
||||
u2 := &types.User{
|
||||
Username: "testadmin",
|
||||
Password: "password123",
|
||||
Email: "info@betatude.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
}
|
||||
|
||||
u3 := ReturnUser(&types.User{
|
||||
if _, err := database.Create(u2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u3 := &types.User{
|
||||
Username: "testadmin2",
|
||||
Password: "password123",
|
||||
Email: "info@adminhere.com",
|
||||
Admin: types.NewNullBool(true),
|
||||
})
|
||||
}
|
||||
|
||||
_, err := u2.Create()
|
||||
_, err = u3.Create()
|
||||
return err
|
||||
if _, err := database.Create(u3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertMessages() error {
|
||||
m1 := ReturnMessage(&types.Message{
|
||||
m1 := &types.Message{
|
||||
Title: "Routine Downtime",
|
||||
Description: "This is an example a upcoming message for a service!",
|
||||
ServiceId: 1,
|
||||
StartOn: time.Now().UTC().Add(15 * time.Minute),
|
||||
EndOn: time.Now().UTC().Add(2 * time.Hour),
|
||||
})
|
||||
if _, err := m1.Create(); err != nil {
|
||||
}
|
||||
|
||||
if _, err := database.Create(m1); err != nil {
|
||||
return err
|
||||
}
|
||||
m2 := ReturnMessage(&types.Message{
|
||||
|
||||
m2 := &types.Message{
|
||||
Title: "Server Reboot",
|
||||
Description: "This is another example a upcoming message for a service!",
|
||||
ServiceId: 3,
|
||||
StartOn: time.Now().UTC().Add(15 * time.Minute),
|
||||
EndOn: time.Now().UTC().Add(2 * time.Hour),
|
||||
})
|
||||
if _, err := m2.Create(); err != nil {
|
||||
}
|
||||
|
||||
if _, err := database.Create(m2); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -317,7 +355,7 @@ func InsertLargeSampleData() error {
|
|||
return err
|
||||
}
|
||||
createdOn := time.Now().UTC().Add((-24 * 90) * time.Hour)
|
||||
s6 := ReturnService(&types.Service{
|
||||
s6 := &types.Service{
|
||||
Name: "JSON Lint",
|
||||
Domain: "https://jsonlint.com",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -327,9 +365,13 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 10,
|
||||
Order: 6,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s7 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s6); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s7 := &types.Service{
|
||||
Name: "Demo Page",
|
||||
Domain: "https://demo.statping.com",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -339,9 +381,13 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 15,
|
||||
Order: 7,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s8 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s7); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s8 := &types.Service{
|
||||
Name: "Golang",
|
||||
Domain: "https://golang.org",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -350,9 +396,13 @@ func InsertLargeSampleData() error {
|
|||
Method: "GET",
|
||||
Timeout: 10,
|
||||
Order: 8,
|
||||
})
|
||||
}
|
||||
|
||||
s9 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s8); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s9 := &types.Service{
|
||||
Name: "Santa Monica",
|
||||
Domain: "https://www.santamonica.com",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -362,9 +412,13 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 10,
|
||||
Order: 9,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s10 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s9); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s10 := &types.Service{
|
||||
Name: "Oeschs Die Dritten",
|
||||
Domain: "https://www.oeschs-die-dritten.ch/en/",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -374,9 +428,13 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 10,
|
||||
Order: 10,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s11 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s10); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s11 := &types.Service{
|
||||
Name: "XS Project - Bochka, Bass, Kolbaser",
|
||||
Domain: "https://www.youtube.com/watch?v=VLW1ieY4Izw",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -386,9 +444,13 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 20,
|
||||
Order: 11,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s12 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s11); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s12 := &types.Service{
|
||||
Name: "Github",
|
||||
Domain: "https://github.com/hunterlong",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -398,9 +460,13 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 20,
|
||||
Order: 12,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s13 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s12); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s13 := &types.Service{
|
||||
Name: "Failing URL",
|
||||
Domain: "http://thisdomainisfakeanditsgoingtofail.com",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -410,9 +476,13 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 10,
|
||||
Order: 13,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s14 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s13); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s14 := &types.Service{
|
||||
Name: "Oesch's die Dritten - Die Jodelsprache",
|
||||
Domain: "https://www.youtube.com/watch?v=k3GTxRt4iao",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -422,9 +492,13 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 12,
|
||||
Order: 14,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s15 := ReturnService(&types.Service{
|
||||
if _, err := database.Create(s14); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s15 := &types.Service{
|
||||
Name: "Gorm",
|
||||
Domain: "http://gorm.io/",
|
||||
ExpectedStatus: 200,
|
||||
|
@ -434,18 +508,11 @@ func InsertLargeSampleData() error {
|
|||
Timeout: 12,
|
||||
Order: 15,
|
||||
CreatedAt: createdOn,
|
||||
})
|
||||
}
|
||||
|
||||
s6.Create(false)
|
||||
s7.Create(false)
|
||||
s8.Create(false)
|
||||
s9.Create(false)
|
||||
s10.Create(false)
|
||||
s11.Create(false)
|
||||
s12.Create(false)
|
||||
s13.Create(false)
|
||||
s14.Create(false)
|
||||
s15.Create(false)
|
||||
if _, err := database.Create(s15); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dayAgo = time.Now().UTC().Add((-24 * 90) * time.Hour)
|
||||
|
||||
|
@ -472,7 +539,7 @@ func insertFailureRecords(since time.Time, amount int) {
|
|||
CreatedAt: createdAt,
|
||||
}
|
||||
|
||||
service.CreateFailure(failure)
|
||||
database.Create(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -492,7 +559,7 @@ func insertHitRecords(since time.Time, amount int) {
|
|||
CreatedAt: createdAt.UTC(),
|
||||
Latency: latency,
|
||||
}
|
||||
service.CreateHit(hit)
|
||||
database.Create(hit)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -554,7 +621,7 @@ func TmpRecords(dbFile string) error {
|
|||
return err
|
||||
}
|
||||
log.Infoln("loading all services")
|
||||
if _, err := CoreApp.SelectAllServices(false); err != nil {
|
||||
if _, err := SelectAllServices(false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := AttachNotifiers(); err != nil {
|
||||
|
@ -597,7 +664,7 @@ func TmpRecords(dbFile string) error {
|
|||
return err
|
||||
}
|
||||
log.Infoln("loading all services")
|
||||
if _, err := CoreApp.SelectAllServices(false); err != nil {
|
||||
if _, err := SelectAllServices(false); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("copying sql database file to: " + tmpSqlFile)
|
||||
|
|
313
core/services.go
313
core/services.go
|
@ -20,9 +20,7 @@ import (
|
|||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -30,224 +28,63 @@ type Service struct {
|
|||
*types.Service
|
||||
}
|
||||
|
||||
// Select will return the *types.Service struct for Service
|
||||
func (s *Service) Select() *types.Service {
|
||||
return s.Service
|
||||
}
|
||||
type Servicer interface{}
|
||||
|
||||
// ReturnService will convert *types.Service to *core.Service
|
||||
func ReturnService(s *types.Service) *Service {
|
||||
return &Service{s}
|
||||
}
|
||||
|
||||
func Services() []types.ServiceInterface {
|
||||
return CoreApp.Services
|
||||
func Services() []database.Servicer {
|
||||
return CoreApp.services
|
||||
}
|
||||
|
||||
// SelectService returns a *core.Service from in memory
|
||||
func SelectService(id int64) *Service {
|
||||
func SelectService(id int64) *types.Service {
|
||||
for _, s := range Services() {
|
||||
if s.Select().Id == id {
|
||||
service := s.(*Service)
|
||||
service = service.UpdateStats()
|
||||
return service
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetFailures(count int) []*Failure {
|
||||
var fails []*Failure
|
||||
db := Database(&types.Failure{}).Where("service = ?", s.Id)
|
||||
db.Limit(count).Find(&fails)
|
||||
return fails
|
||||
}
|
||||
|
||||
func (s *Service) UpdateStats() *Service {
|
||||
s.Online24Hours = s.OnlineDaysPercent(1)
|
||||
s.Online7Days = s.OnlineDaysPercent(7)
|
||||
s.AvgResponse = s.AvgTime()
|
||||
s.FailuresLast24Hours = s.FailuresDaysAgo(1)
|
||||
s.LastFailure = s.lastFailure()
|
||||
return s
|
||||
}
|
||||
|
||||
func SelectServices(auth bool) []*Service {
|
||||
var validServices []*Service
|
||||
for _, sr := range CoreApp.Services {
|
||||
s := sr.(*Service)
|
||||
if !s.Public.Bool {
|
||||
if auth {
|
||||
validServices = append(validServices, s)
|
||||
}
|
||||
} else {
|
||||
validServices = append(validServices, s)
|
||||
}
|
||||
}
|
||||
return validServices
|
||||
}
|
||||
|
||||
// SelectServiceLink returns a *core.Service from the service permalink
|
||||
func SelectServiceLink(permalink string) *Service {
|
||||
for _, s := range Services() {
|
||||
if s.Select().Permalink.String == permalink {
|
||||
return s.(*Service)
|
||||
if s.Model().Id == id {
|
||||
fmt.Println("service: ", s.Model())
|
||||
return s.Model()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckinProcess runs the checkin routine for each checkin attached to service
|
||||
func (s *Service) CheckinProcess() {
|
||||
checkins := s.AllCheckins()
|
||||
for _, c := range checkins {
|
||||
func CheckinProcess(s database.Servicer) {
|
||||
for _, c := range s.AllCheckins() {
|
||||
c.Start()
|
||||
go c.Routine()
|
||||
go CheckinRoutine(c)
|
||||
}
|
||||
}
|
||||
|
||||
// AllCheckins will return a slice of AllCheckins for a Service
|
||||
func (s *Service) AllCheckins() []*Checkin {
|
||||
var checkin []*Checkin
|
||||
Database(&Checkin{}).Where("service = ?", s.Id).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
|
||||
db := Database(&Service{}).Find(&services).Order("order_id desc")
|
||||
if db.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("service error: %v", db.Error()))
|
||||
return nil, db.Error()
|
||||
func SelectAllServices(start bool) ([]*database.ServiceObj, error) {
|
||||
srvs := database.Services()
|
||||
for _, s := range srvs {
|
||||
fmt.Println("services: ", s.Id, s.Name)
|
||||
}
|
||||
CoreApp.Services = nil
|
||||
for _, service := range services {
|
||||
|
||||
for _, s := range srvs {
|
||||
if start {
|
||||
service := s.Model()
|
||||
service.Start()
|
||||
service.CheckinProcess()
|
||||
CheckinProcess(s)
|
||||
}
|
||||
fails := service.GetFailures(limitedFailures)
|
||||
for _, f := range fails {
|
||||
service.Failures = append(service.Failures, f)
|
||||
}
|
||||
checkins := service.AllCheckins()
|
||||
for _, c := range checkins {
|
||||
c.Failures = c.GetFailures(limitedFailures)
|
||||
c.Hits = c.LimitedHits(limitedHits)
|
||||
service.Checkins = append(service.Checkins, c)
|
||||
//fails := service.Service (limitedFailures)
|
||||
//for _, f := range fails {
|
||||
// service.Failures = append(service.Failures, f)
|
||||
//}
|
||||
for _, c := range s.AllCheckins() {
|
||||
s.Checkins = append(s.Checkins, c)
|
||||
}
|
||||
// collect initial service stats
|
||||
service = service.UpdateStats()
|
||||
CoreApp.Services = append(CoreApp.Services, service)
|
||||
s.Service.Stats = s.UpdateStats()
|
||||
CoreApp.services = append(CoreApp.services, s)
|
||||
}
|
||||
reorderServices()
|
||||
return services, db.Error()
|
||||
return srvs, nil
|
||||
}
|
||||
|
||||
// reorderServices will sort the services based on 'order_id'
|
||||
func reorderServices() {
|
||||
sort.Sort(ServiceOrder(CoreApp.Services))
|
||||
}
|
||||
|
||||
// AvgTime will return the average amount of time for a service to response back successfully
|
||||
func (s *Service) AvgTime() float64 {
|
||||
total, _ := s.TotalHits()
|
||||
if total == 0 {
|
||||
return 0
|
||||
}
|
||||
avg := s.Sum() / float64(total) * 100
|
||||
f, _ := strconv.ParseFloat(fmt.Sprintf("%0.0f", avg*10), 32)
|
||||
return f
|
||||
}
|
||||
|
||||
// OnlineDaysPercent returns the service's uptime percent within last 24 hours
|
||||
func (s *Service) OnlineDaysPercent(days int) float32 {
|
||||
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
return s.OnlineSince(ago)
|
||||
}
|
||||
|
||||
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
|
||||
func (s *Service) OnlineSince(ago time.Time) float32 {
|
||||
failed, _ := s.TotalFailuresSince(ago)
|
||||
if failed == 0 {
|
||||
s.Online24Hours = 100.00
|
||||
return s.Online24Hours
|
||||
}
|
||||
total, _ := s.TotalHitsSince(ago)
|
||||
if total == 0 {
|
||||
s.Online24Hours = 0
|
||||
return s.Online24Hours
|
||||
}
|
||||
avg := float64(failed) / float64(total) * 100
|
||||
avg = 100 - avg
|
||||
if avg < 0 {
|
||||
avg = 0
|
||||
}
|
||||
amount, _ := strconv.ParseFloat(fmt.Sprintf("%0.2f", avg), 10)
|
||||
s.Online24Hours = float32(amount)
|
||||
return s.Online24Hours
|
||||
}
|
||||
|
||||
// lastFailure returns the last Failure a service had
|
||||
func (s *Service) lastFailure() types.FailureInterface {
|
||||
limited := s.GetFailures(1)
|
||||
if len(limited) == 0 {
|
||||
return nil
|
||||
}
|
||||
last := limited[len(limited)-1]
|
||||
return last
|
||||
}
|
||||
|
||||
// DowntimeText will return the amount of downtime for a service based on the duration
|
||||
// service.DowntimeText()
|
||||
// // Service has been offline for 15 minutes
|
||||
func (s *Service) DowntimeText() string {
|
||||
return fmt.Sprintf("%v has been offline for %v", s.Name, utils.DurationReadable(s.Downtime()))
|
||||
}
|
||||
|
||||
// Dbtimestamp will return a SQL query for grouping by date
|
||||
func Dbtimestamp(group string, column string) string {
|
||||
seconds := 3600
|
||||
switch group {
|
||||
case "minute":
|
||||
seconds = 60
|
||||
case "hour":
|
||||
seconds = 3600
|
||||
case "day":
|
||||
seconds = 86400
|
||||
case "week":
|
||||
seconds = 604800
|
||||
case "month":
|
||||
seconds = 2592000
|
||||
case "year":
|
||||
seconds = 31557600
|
||||
default:
|
||||
seconds = 60
|
||||
}
|
||||
switch CoreApp.Config.DbConn {
|
||||
case "mysql":
|
||||
return fmt.Sprintf("CONCAT(date_format(created_at, '%%Y-%%m-%%d %%H:00:00')) AS timeframe, AVG(%v) AS value", column)
|
||||
case "postgres":
|
||||
return fmt.Sprintf("date_trunc('%v', created_at) AS timeframe, AVG(%v) AS value", group, column)
|
||||
default:
|
||||
return fmt.Sprintf("datetime((strftime('%%s', created_at) / %v) * %v, 'unixepoch') AS timeframe, AVG(%v) as value", seconds, seconds, column)
|
||||
}
|
||||
}
|
||||
|
||||
// Downtime returns the amount of time of a offline service
|
||||
func (s *Service) Downtime() time.Duration {
|
||||
hits, _ := s.Hits()
|
||||
fail := s.lastFailure()
|
||||
if fail == nil {
|
||||
return time.Duration(0)
|
||||
}
|
||||
if len(hits) == 0 {
|
||||
return time.Now().UTC().Sub(fail.Select().CreatedAt.UTC())
|
||||
}
|
||||
since := fail.Select().CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC())
|
||||
return since
|
||||
sort.Sort(ServiceOrder(CoreApp.services))
|
||||
}
|
||||
|
||||
// GraphData will return all hits or failures
|
||||
|
@ -265,9 +102,9 @@ func GraphData(q *database.GroupQuery, dbType interface{}, by database.By) []*da
|
|||
}
|
||||
|
||||
// index returns a services index int for updating the []*core.Services slice
|
||||
func (s *Service) index() int {
|
||||
for k, service := range CoreApp.Services {
|
||||
if s.Id == service.(*Service).Id {
|
||||
func index(s database.Servicer) int {
|
||||
for k, service := range CoreApp.services {
|
||||
if s.Model().Id == service.Model().Id {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
@ -275,32 +112,34 @@ func (s *Service) index() int {
|
|||
}
|
||||
|
||||
// updateService will update a service in the []*core.Services slice
|
||||
func updateService(s *Service) {
|
||||
CoreApp.Services[s.index()] = s
|
||||
func updateService(s database.Servicer) {
|
||||
CoreApp.services[index(s)] = s
|
||||
}
|
||||
|
||||
// Delete will remove a service from the database, it will also end the service checking go routine
|
||||
func (s *Service) Delete() error {
|
||||
i := s.index()
|
||||
err := Database(&Service{}).Delete(s)
|
||||
if err.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error()))
|
||||
return err.Error()
|
||||
func Delete(srv database.Servicer) error {
|
||||
i := index(srv)
|
||||
s := srv.Model()
|
||||
err := database.Delete(s)
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err))
|
||||
return err
|
||||
}
|
||||
s.Close()
|
||||
slice := CoreApp.Services
|
||||
CoreApp.Services = append(slice[:i], slice[i+1:]...)
|
||||
slice := CoreApp.services
|
||||
CoreApp.services = append(slice[:i], slice[i+1:]...)
|
||||
reorderServices()
|
||||
notifier.OnDeletedService(s.Service)
|
||||
return err.Error()
|
||||
notifier.OnDeletedService(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update will update a service in the database, the service's checking routine can be restarted by passing true
|
||||
func (s *Service) Update(restart bool) error {
|
||||
err := Database(&Service{}).Update(&s)
|
||||
if err.Error() != nil {
|
||||
func Update(srv database.Servicer, restart bool) error {
|
||||
s := srv.Model()
|
||||
err := database.Update(s)
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
|
||||
return err.Error()
|
||||
return err
|
||||
}
|
||||
// clear the notification queue for a service
|
||||
if !s.AllowNotifications.Bool {
|
||||
|
@ -313,55 +152,27 @@ func (s *Service) Update(restart bool) error {
|
|||
s.Close()
|
||||
s.Start()
|
||||
s.SleepDuration = time.Duration(s.Interval) * time.Second
|
||||
go s.CheckQueue(true)
|
||||
go ServiceCheckQueue(srv, true)
|
||||
}
|
||||
reorderServices()
|
||||
updateService(s)
|
||||
notifier.OnUpdatedService(s.Service)
|
||||
return err.Error()
|
||||
updateService(srv)
|
||||
notifier.OnUpdatedService(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create will create a service and insert it into the database
|
||||
func (s *Service) Create(check bool) (int64, error) {
|
||||
func Create(srv database.Servicer, check bool) (int64, error) {
|
||||
s := srv.Model()
|
||||
s.CreatedAt = time.Now().UTC()
|
||||
db := Database(&Service{}).Create(s)
|
||||
if db.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error()))
|
||||
return 0, db.Error()
|
||||
_, err := database.Create(s)
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, err))
|
||||
return 0, err
|
||||
}
|
||||
s.Start()
|
||||
go s.CheckQueue(check)
|
||||
CoreApp.Services = append(CoreApp.Services, s)
|
||||
go ServiceCheckQueue(srv, check)
|
||||
CoreApp.services = append(CoreApp.services, srv)
|
||||
reorderServices()
|
||||
notifier.OnNewService(s.Service)
|
||||
notifier.OnNewService(s)
|
||||
return s.Id, nil
|
||||
}
|
||||
|
||||
// Messages returns all Messages for a Service
|
||||
func (s *Service) Messages() []*Message {
|
||||
messages := SelectServiceMessages(s.Id)
|
||||
return messages
|
||||
}
|
||||
|
||||
// ActiveMessages returns all service messages that are available based on the current time
|
||||
func (s *Service) ActiveMessages() []*Message {
|
||||
var messages []*Message
|
||||
msgs := SelectServiceMessages(s.Id)
|
||||
for _, m := range msgs {
|
||||
if m.StartOn.UTC().After(time.Now().UTC()) {
|
||||
messages = append(messages, m)
|
||||
}
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
// CountOnline returns the amount of services online
|
||||
func (c *Core) CountOnline() int {
|
||||
amount := 0
|
||||
for _, s := range CoreApp.Services {
|
||||
if s.Select().Online {
|
||||
amount++
|
||||
}
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
|
|
@ -139,38 +139,12 @@ func TestServiceAvgUptime(t *testing.T) {
|
|||
assert.NotEqual(t, "0", service4.AvgUptime(since))
|
||||
}
|
||||
|
||||
func TestServiceHits(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
hits, err := service.Hits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(hits) > 1400)
|
||||
}
|
||||
|
||||
func TestServiceLimitedHits(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
hits, err := service.LimitedHits(1024)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int(1024), len(hits))
|
||||
}
|
||||
|
||||
func TestServiceTotalHits(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
hits, err := service.TotalHits()
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, hits)
|
||||
}
|
||||
|
||||
func TestServiceSum(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
sum := service.Sum()
|
||||
assert.NotZero(t, sum)
|
||||
}
|
||||
|
||||
func TestCountOnline(t *testing.T) {
|
||||
amount := CoreApp.CountOnline()
|
||||
assert.True(t, amount >= 2)
|
||||
}
|
||||
|
||||
func TestCreateService(t *testing.T) {
|
||||
s := ReturnService(&types.Service{
|
||||
Name: "That'll do 🐢",
|
||||
|
@ -375,31 +349,3 @@ func TestSelectGroups(t *testing.T) {
|
|||
groups = SelectGroups(true, true)
|
||||
assert.Equal(t, int(5), len(groups))
|
||||
}
|
||||
|
||||
func TestService_TotalFailures(t *testing.T) {
|
||||
service := SelectService(8)
|
||||
failures, err := service.TotalFailures()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(1), failures)
|
||||
}
|
||||
|
||||
func TestService_TotalFailures24(t *testing.T) {
|
||||
service := SelectService(8)
|
||||
failures, err := service.TotalFailures24()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(1), failures)
|
||||
}
|
||||
|
||||
func TestService_TotalFailuresOnDate(t *testing.T) {
|
||||
t.SkipNow()
|
||||
ago := utils.Now().UTC()
|
||||
service := SelectService(8)
|
||||
failures, err := service.TotalFailuresOnDate(ago)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(1), failures)
|
||||
}
|
||||
|
||||
func TestCountFailures(t *testing.T) {
|
||||
failures := CountFailures()
|
||||
assert.NotEqual(t, uint64(0), failures)
|
||||
}
|
||||
|
|
|
@ -18,18 +18,17 @@ package core
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
*types.User
|
||||
*database.UserObj
|
||||
}
|
||||
|
||||
// ReturnUser returns *core.User based off a *types.User
|
||||
func ReturnUser(u *types.User) *User {
|
||||
func uwrap(u *database.UserObj) *User {
|
||||
return &User{u}
|
||||
}
|
||||
|
||||
|
@ -42,29 +41,32 @@ func CountUsers() int64 {
|
|||
|
||||
// SelectUser returns the User based on the User's ID.
|
||||
func SelectUser(id int64) (*User, error) {
|
||||
var user User
|
||||
err := Database(&User{}).Where("id = ?", id).First(&user)
|
||||
return &user, err.Error()
|
||||
user, err := database.User(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uwrap(user), err
|
||||
}
|
||||
|
||||
// SelectUsername returns the User based on the User's username
|
||||
func SelectUsername(username string) (*User, error) {
|
||||
var user User
|
||||
res := Database(&User{}).Where("username = ?", username)
|
||||
err := res.First(&user)
|
||||
return &user, err.Error()
|
||||
user, err := database.UserByUsername(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uwrap(user), err
|
||||
}
|
||||
|
||||
// Delete will remove the User record from the database
|
||||
func (u *User) Delete() error {
|
||||
return database.Delete(&u)
|
||||
return database.Delete(u)
|
||||
}
|
||||
|
||||
// Update will update the User's record in database
|
||||
func (u *User) Update() error {
|
||||
u.ApiKey = utils.NewSHA1Hash(5)
|
||||
u.ApiSecret = utils.NewSHA1Hash(10)
|
||||
return database.Update(&u)
|
||||
return database.Update(u)
|
||||
}
|
||||
|
||||
// Create will insert a new User into the database
|
||||
|
@ -74,7 +76,7 @@ func (u *User) Create() (int64, error) {
|
|||
u.ApiKey = utils.NewSHA1Hash(5)
|
||||
u.ApiSecret = utils.NewSHA1Hash(10)
|
||||
|
||||
user, err := database.Create(&u)
|
||||
user, err := database.Create(u)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -88,12 +90,12 @@ func (u *User) Create() (int64, error) {
|
|||
// SelectAllUsers returns all users
|
||||
func SelectAllUsers() ([]*User, error) {
|
||||
var users []*User
|
||||
db := Database(&User{}).Find(&users)
|
||||
if db.Error() != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error()))
|
||||
return nil, db.Error()
|
||||
err := database.AllUsers(&users)
|
||||
if err != nil {
|
||||
log.Errorln(fmt.Sprintf("Failed to load all users. %v", err))
|
||||
return nil, err
|
||||
}
|
||||
return users, db.Error()
|
||||
return users, err
|
||||
}
|
||||
|
||||
// AuthUser will return the User and a boolean if authentication was correct.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package database
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
|
||||
type CheckinHitObj struct {
|
||||
hits []*types.CheckinHit
|
||||
o *Object
|
||||
}
|
|
@ -1,33 +1,119 @@
|
|||
package database
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CheckinObj struct {
|
||||
*types.Checkin
|
||||
failures
|
||||
o *Object
|
||||
|
||||
Checkiner
|
||||
}
|
||||
|
||||
func (o *Object) AsCheckin() HitsFailures {
|
||||
return &CheckinObj{
|
||||
Checkin: o.model.(*types.Checkin),
|
||||
}
|
||||
type Checkiner interface {
|
||||
Hits() *CheckinHitObj
|
||||
Failures() *FailureObj
|
||||
Model() *types.Checkin
|
||||
Object() *CheckinObj
|
||||
}
|
||||
|
||||
func Checkin(id int64) (HitsFailures, error) {
|
||||
func Checkin(id int64) (*CheckinObj, error) {
|
||||
var checkin types.Checkin
|
||||
query := database.Model(&types.Checkin{}).Where("id = ?", id).Find(&checkin)
|
||||
return &CheckinObj{Checkin: &checkin}, query.Error()
|
||||
query := database.Checkins().Where("id = ?", id)
|
||||
finder := query.Find(&checkin)
|
||||
return &CheckinObj{Checkin: &checkin, o: wrapObject(id, &checkin, query)}, finder.Error()
|
||||
}
|
||||
|
||||
func (c *CheckinObj) Hits() *hits {
|
||||
return &hits{
|
||||
database.Model(&types.Checkin{}).Where("checkin = ?", c.Id),
|
||||
func CheckinByKey(api string) (*CheckinObj, error) {
|
||||
var checkin types.Checkin
|
||||
query := database.Checkins().Where("api = ?", api)
|
||||
finder := query.Find(&checkin)
|
||||
return &CheckinObj{Checkin: &checkin, o: wrapObject(checkin.Id, &checkin, query)}, finder.Error()
|
||||
}
|
||||
|
||||
func wrapCheckins(all []*types.Checkin, db Database) []*CheckinObj {
|
||||
var arr []*CheckinObj
|
||||
for _, v := range all {
|
||||
arr = append(arr, &CheckinObj{Checkin: v, o: wrapObject(v.Id, v, db)})
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
func AllCheckins() []*CheckinObj {
|
||||
var checkins []*types.Checkin
|
||||
query := database.Checkins()
|
||||
query.Find(&checkins)
|
||||
return wrapCheckins(checkins, query)
|
||||
}
|
||||
|
||||
func (s *CheckinObj) Service() *ServiceObj {
|
||||
var srv *types.Service
|
||||
q := database.Checkins().Where("service = ?", s.ServiceId)
|
||||
q.Find(&srv)
|
||||
return &ServiceObj{
|
||||
Service: srv,
|
||||
o: wrapObject(srv.Id, srv, q),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CheckinObj) Failures() *failures {
|
||||
return &failures{
|
||||
database.Model(&types.Failure{}).
|
||||
Where("method = 'checkin' AND service = ?", c.Id).Order("id desc"),
|
||||
}
|
||||
func (s *CheckinObj) Failures() *FailureObj {
|
||||
q := database.Failures().
|
||||
Where("method = 'checkin' AND id = ?", s.Id).
|
||||
Where("method = 'checkin'")
|
||||
return &FailureObj{wrapObject(s.Id, nil, q)}
|
||||
}
|
||||
|
||||
func (s *CheckinObj) object() *Object {
|
||||
return s.o
|
||||
}
|
||||
|
||||
func (c *CheckinObj) Model() *types.Checkin {
|
||||
return c.Checkin
|
||||
}
|
||||
|
||||
func (c *CheckinObj) Object() *CheckinObj {
|
||||
return c
|
||||
}
|
||||
|
||||
// Period will return the duration of the Checkin interval
|
||||
func (c *CheckinObj) 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 *CheckinObj) 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 *CheckinObj) Expected() time.Duration {
|
||||
last := c.Hits().Last()
|
||||
now := time.Now().UTC()
|
||||
lastDir := now.Sub(last.CreatedAt)
|
||||
sub := time.Duration(c.Period() - lastDir)
|
||||
return sub
|
||||
}
|
||||
|
||||
// Last returns the last checkinHit for a Checkin
|
||||
func (c *CheckinObj) Hits() *CheckinHitObj {
|
||||
var checkinHits []*types.CheckinHit
|
||||
query := database.CheckinHits().Where("checkin = ?", c.Id)
|
||||
query.Find(&checkinHits)
|
||||
return &CheckinHitObj{checkinHits, wrapObject(c.Id, checkinHits, query)}
|
||||
}
|
||||
|
||||
// Last returns the last checkinHit for a Checkin
|
||||
func (c *CheckinHitObj) Last() *types.CheckinHit {
|
||||
var last types.CheckinHit
|
||||
c.o.db.Last(&last)
|
||||
return &last
|
||||
}
|
||||
|
||||
func (c *CheckinObj) Link() string {
|
||||
return fmt.Sprintf("%v/checkin/%v", "DOMAINHERE", c.ApiKey)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
|
@ -10,19 +12,32 @@ type Object struct {
|
|||
db Database
|
||||
}
|
||||
|
||||
type HitsFailures interface {
|
||||
Hits() *hits
|
||||
Failures() *failures
|
||||
type isObject interface {
|
||||
object() *Object
|
||||
}
|
||||
|
||||
func wrapObject(id int64, model interface{}, db Database) *Object {
|
||||
return &Object{
|
||||
Id: id,
|
||||
model: model,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func modelId(model interface{}) int64 {
|
||||
fmt.Printf("%T\n", model)
|
||||
iface := reflect.ValueOf(model)
|
||||
field := iface.Elem().FieldByName("Id")
|
||||
return field.Int()
|
||||
}
|
||||
|
||||
func toModel(model interface{}) Database {
|
||||
return database.Model(&model)
|
||||
switch model.(type) {
|
||||
case *types.Core:
|
||||
return database.Model(&types.Core{}).Table("core")
|
||||
default:
|
||||
return database.Model(&model)
|
||||
}
|
||||
}
|
||||
|
||||
func Create(data interface{}) (*Object, error) {
|
||||
|
|
|
@ -104,7 +104,6 @@ type Database interface {
|
|||
|
||||
Since(time.Time) Database
|
||||
Between(time.Time, time.Time) Database
|
||||
ToChart() ([]*DateScan, error)
|
||||
|
||||
SelectByTime(string) string
|
||||
MultipleSelects(args ...string) Database
|
||||
|
@ -112,13 +111,73 @@ type Database interface {
|
|||
FormatTime(t time.Time) string
|
||||
ParseTime(t string) (time.Time, error)
|
||||
|
||||
Requests(*http.Request) Database
|
||||
Requests(*http.Request, isObject) Database
|
||||
|
||||
GroupQuery(query *GroupQuery, by By) GroupByer
|
||||
|
||||
Objects
|
||||
}
|
||||
|
||||
func (it *Db) Requests(r *http.Request) Database {
|
||||
g := ParseQueries(r, it)
|
||||
type Objects interface {
|
||||
Services() Database
|
||||
Users() Database
|
||||
Groups() Database
|
||||
Incidents() Database
|
||||
IncidentUpdates() Database
|
||||
Hits() Database
|
||||
Failures() Database
|
||||
Checkins() Database
|
||||
CheckinHits() Database
|
||||
Messages() Database
|
||||
Integrations() Database
|
||||
}
|
||||
|
||||
func (d *Db) Services() Database {
|
||||
return d.Model(&types.Service{})
|
||||
}
|
||||
|
||||
func (d *Db) Users() Database {
|
||||
return d.Model(&types.User{})
|
||||
}
|
||||
|
||||
func (d *Db) Groups() Database {
|
||||
return d.Model(&types.Group{})
|
||||
}
|
||||
|
||||
func (d *Db) Incidents() Database {
|
||||
return d.Model(&types.Incident{})
|
||||
}
|
||||
|
||||
func (d *Db) IncidentUpdates() Database {
|
||||
return d.Model(&types.IncidentUpdate{})
|
||||
}
|
||||
|
||||
func (d *Db) Hits() Database {
|
||||
return d.Model(&types.Hit{})
|
||||
}
|
||||
|
||||
func (d *Db) Integrations() Database {
|
||||
return d.Model(&types.Integration{})
|
||||
}
|
||||
|
||||
func (d *Db) Failures() Database {
|
||||
return d.Model(&types.Failure{})
|
||||
}
|
||||
|
||||
func (d *Db) Checkins() Database {
|
||||
return d.Model(&types.Checkin{})
|
||||
}
|
||||
|
||||
func (d *Db) CheckinHits() Database {
|
||||
return d.Model(&types.CheckinHit{})
|
||||
}
|
||||
|
||||
func (d *Db) Messages() Database {
|
||||
return d.Model(&types.Message{})
|
||||
}
|
||||
|
||||
func (it *Db) Requests(r *http.Request, o isObject) Database {
|
||||
g := ParseQueries(r, o)
|
||||
return g.db
|
||||
}
|
||||
|
||||
|
@ -446,12 +505,6 @@ func (it *Db) Error() error {
|
|||
return it.Database.Error
|
||||
}
|
||||
|
||||
func (it *Db) Hits() ([]*types.Hit, error) {
|
||||
var hits []*types.Hit
|
||||
err := it.Find(&hits)
|
||||
return hits, err.Error()
|
||||
}
|
||||
|
||||
func (it *Db) Since(ago time.Time) Database {
|
||||
return it.Where("created_at > ?", it.FormatTime(ago))
|
||||
}
|
||||
|
@ -460,37 +513,7 @@ func (it *Db) Between(t1 time.Time, t2 time.Time) Database {
|
|||
return it.Where("created_at BETWEEN ? AND ?", it.FormatTime(t1), it.FormatTime(t2))
|
||||
}
|
||||
|
||||
// DateScan struct is for creating the charts.js graph JSON array
|
||||
type DateScan struct {
|
||||
CreatedAt string `json:"x,omitempty"`
|
||||
Value int64 `json:"y"`
|
||||
}
|
||||
|
||||
type TimeValue struct {
|
||||
Timeframe string `json:"timeframe"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
func (it *Db) ToChart() ([]*DateScan, error) {
|
||||
rows, err := it.Database.Rows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []*DateScan
|
||||
for rows.Next() {
|
||||
gd := new(DateScan)
|
||||
var createdAt string
|
||||
var value float64
|
||||
if err := rows.Scan(&createdAt, &value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createdTime, err := time.Parse(TIME, createdAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gd.CreatedAt = createdTime.UTC().String()
|
||||
gd.Value = int64(value * 1000)
|
||||
data = append(data, gd)
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
|
|
@ -2,31 +2,50 @@ package database
|
|||
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type failures struct {
|
||||
DB Database
|
||||
type FailureObj struct {
|
||||
o *Object
|
||||
}
|
||||
|
||||
func (f *failures) All() []*types.Failure {
|
||||
type Failurer interface {
|
||||
Model() []*types.Failure
|
||||
}
|
||||
|
||||
func (f *FailureObj) Model() []*types.Failure {
|
||||
return f.All()
|
||||
}
|
||||
|
||||
func (f *FailureObj) All() []*types.Failure {
|
||||
var fails []*types.Failure
|
||||
f.DB = f.DB.Find(&fails)
|
||||
f.o.db.Find(&fails)
|
||||
return fails
|
||||
}
|
||||
|
||||
func (f *failures) Last(amount int) *types.Failure {
|
||||
func (f *FailureObj) DeleteAll() error {
|
||||
query := database.Exec(`DELETE FROM failures WHERE service = ?`, f.o.Id)
|
||||
return query.Error()
|
||||
}
|
||||
|
||||
func (f *FailureObj) Last(amount int) *types.Failure {
|
||||
var fail types.Failure
|
||||
f.DB = f.DB.Limit(amount).Find(&fail)
|
||||
f.o.db.Limit(amount).Last(&fail)
|
||||
return &fail
|
||||
}
|
||||
|
||||
func (f *failures) Count() int {
|
||||
func (f *FailureObj) Count() int {
|
||||
var amount int
|
||||
f.DB = f.DB.Count(&amount)
|
||||
f.o.db.Count(&amount)
|
||||
return amount
|
||||
}
|
||||
|
||||
func (f *failures) Find(data interface{}) error {
|
||||
q := f.Find(&data)
|
||||
return q
|
||||
func (f *FailureObj) Since(t time.Time) []*types.Failure {
|
||||
var fails []*types.Failure
|
||||
f.o.db.Since(t).Find(&fails)
|
||||
return fails
|
||||
}
|
||||
|
||||
func (f *FailureObj) object() *Object {
|
||||
return f.o
|
||||
}
|
||||
|
|
|
@ -4,23 +4,44 @@ import "github.com/hunterlong/statping/types"
|
|||
|
||||
type GroupObj struct {
|
||||
*types.Group
|
||||
db Database
|
||||
o *Object
|
||||
|
||||
Grouper
|
||||
}
|
||||
|
||||
type Grouper interface {
|
||||
Services() Database
|
||||
Services() []*types.Service
|
||||
Model() *types.Group
|
||||
}
|
||||
|
||||
func (o *Object) AsGroup() *types.Group {
|
||||
return o.model.(*types.Group)
|
||||
func AllGroups() []*GroupObj {
|
||||
var groups []*types.Group
|
||||
query := database.Groups()
|
||||
query.Find(&groups)
|
||||
return wrapGroups(groups, query)
|
||||
}
|
||||
|
||||
func (it *Db) GetGroup(id int64) (*GroupObj, error) {
|
||||
func (g *Db) GetGroup(id int64) (*GroupObj, error) {
|
||||
var group types.Group
|
||||
query := it.Model(&types.Group{}).Where("id = ?", id).Find(&group)
|
||||
return &GroupObj{&group, it}, query.Error()
|
||||
query := database.Groups().Where("id = ?", id)
|
||||
finder := query.Find(&group)
|
||||
return &GroupObj{Group: &group, o: wrapObject(id, &group, query)}, finder.Error()
|
||||
}
|
||||
|
||||
func (it *GroupObj) Services() Database {
|
||||
return it.db.Model(&types.Service{}).Where("service = ?", it.Id)
|
||||
func (g *GroupObj) Services() []*types.Service {
|
||||
var services []*types.Service
|
||||
database.Services().Where("group = ?", g.Id).Find(&services)
|
||||
return services
|
||||
}
|
||||
|
||||
func (g *GroupObj) Model() *types.Group {
|
||||
return g.Group
|
||||
}
|
||||
|
||||
func wrapGroups(all []*types.Group, db Database) []*GroupObj {
|
||||
var arr []*GroupObj
|
||||
for _, v := range all {
|
||||
arr = append(arr, &GroupObj{Group: v, o: wrapObject(v.Id, v, db)})
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package database
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
@ -36,15 +35,16 @@ type GroupQuery struct {
|
|||
FillEmpty bool
|
||||
}
|
||||
|
||||
func (b GroupQuery) Find(data interface{}) error {
|
||||
return b.db.Find(&data).Error()
|
||||
}
|
||||
|
||||
func (b GroupQuery) Database() Database {
|
||||
return b.db
|
||||
}
|
||||
|
||||
var (
|
||||
ByCount = By("COUNT(id) as amount")
|
||||
BySum = func(column string) By {
|
||||
return By(fmt.Sprintf("SUM(%s) as amount", column))
|
||||
}
|
||||
ByCount = By("COUNT(id) as amount")
|
||||
ByAverage = func(column string) By {
|
||||
return By(fmt.Sprintf("AVG(%s) as amount", column))
|
||||
}
|
||||
|
@ -154,16 +154,21 @@ func (g *GroupBy) duration() time.Duration {
|
|||
}
|
||||
}
|
||||
|
||||
func ParseQueries(r *http.Request, db Database) *GroupQuery {
|
||||
func toInt(v string) int64 {
|
||||
val, _ := strconv.Atoi(v)
|
||||
return int64(val)
|
||||
}
|
||||
|
||||
func ParseQueries(r *http.Request, o isObject) *GroupQuery {
|
||||
fields := parseGet(r)
|
||||
grouping := fields.Get("group")
|
||||
if grouping == "" {
|
||||
grouping = "hour"
|
||||
}
|
||||
startField := utils.ToInt(fields.Get("start"))
|
||||
endField := utils.ToInt(fields.Get("end"))
|
||||
limit := utils.ToInt(fields.Get("limit"))
|
||||
offset := utils.ToInt(fields.Get("offset"))
|
||||
startField := toInt(fields.Get("start"))
|
||||
endField := toInt(fields.Get("end"))
|
||||
limit := toInt(fields.Get("limit"))
|
||||
offset := toInt(fields.Get("offset"))
|
||||
fill, _ := strconv.ParseBool(fields.Get("fill"))
|
||||
orderBy := fields.Get("order")
|
||||
if limit == 0 {
|
||||
|
@ -180,6 +185,8 @@ func ParseQueries(r *http.Request, db Database) *GroupQuery {
|
|||
FillEmpty: fill,
|
||||
}
|
||||
|
||||
db := o.object().db
|
||||
|
||||
if query.Limit != 0 {
|
||||
db = db.Limit(query.Limit)
|
||||
}
|
||||
|
|
|
@ -1,30 +1,38 @@
|
|||
package database
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
import (
|
||||
"github.com/hunterlong/statping/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type hits struct {
|
||||
DB Database
|
||||
type HitObj struct {
|
||||
o *Object
|
||||
}
|
||||
|
||||
func (h *hits) All() []*types.Hit {
|
||||
func (h *HitObj) All() []*types.Hit {
|
||||
var fails []*types.Hit
|
||||
h.DB = h.DB.Find(&fails)
|
||||
h.o.db.Find(&fails)
|
||||
return fails
|
||||
}
|
||||
|
||||
func (h *hits) Last(amount int) *types.Hit {
|
||||
func (h *HitObj) Last(amount int) *types.Hit {
|
||||
var hits types.Hit
|
||||
h.DB = h.DB.Limit(amount).Find(&hits)
|
||||
h.o.db.Limit(amount).Find(&hits)
|
||||
return &hits
|
||||
}
|
||||
|
||||
func (h *hits) Count() int {
|
||||
func (h *HitObj) Since(t time.Time) []*types.Hit {
|
||||
var hits []*types.Hit
|
||||
h.o.db.Since(t).Find(&hits)
|
||||
return hits
|
||||
}
|
||||
|
||||
func (h *HitObj) Count() int {
|
||||
var amount int
|
||||
h.DB = h.DB.Count(&amount)
|
||||
h.o.db.Count(&amount)
|
||||
return amount
|
||||
}
|
||||
|
||||
func (h *hits) Find(data interface{}) error {
|
||||
q := h.Find(&data)
|
||||
return q
|
||||
func (h *HitObj) object() *Object {
|
||||
return h.o
|
||||
}
|
||||
|
|
|
@ -4,15 +4,28 @@ import "github.com/hunterlong/statping/types"
|
|||
|
||||
type IncidentObj struct {
|
||||
*types.Incident
|
||||
db Database
|
||||
}
|
||||
|
||||
func (o *IncidentObj) AsIncident() *types.Incident {
|
||||
return o.Incident
|
||||
o *Object
|
||||
}
|
||||
|
||||
func Incident(id int64) (*IncidentObj, error) {
|
||||
var incident types.Incident
|
||||
query := database.Model(&types.Incident{}).Where("id = ?", id).Find(&incident)
|
||||
return &IncidentObj{Incident: &incident, db: query}, query.Error()
|
||||
query := database.Incidents().Where("id = ?", id)
|
||||
finder := query.Find(&incident)
|
||||
return &IncidentObj{Incident: &incident, o: wrapObject(id, &incident, query)}, finder.Error()
|
||||
}
|
||||
|
||||
func AllIncidents() []*types.Incident {
|
||||
var incidents []*types.Incident
|
||||
database.Incidents().Find(&incidents)
|
||||
return incidents
|
||||
}
|
||||
|
||||
func (i *IncidentObj) Updates() []*types.IncidentUpdate {
|
||||
var incidents []*types.IncidentUpdate
|
||||
database.IncidentUpdates().Where("incident = ?", i.Id).Find(&incidents)
|
||||
return incidents
|
||||
}
|
||||
|
||||
func (i *IncidentObj) object() *Object {
|
||||
return i.o
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package database
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
|
||||
type IntegrationObj struct {
|
||||
*types.Integration
|
||||
o *Object
|
||||
}
|
||||
|
||||
func Integration(id int64) (*IntegrationObj, error) {
|
||||
var integration types.Integration
|
||||
query := database.Model(&types.Integration{}).Where("id = ?", id)
|
||||
finder := query.Find(&integration)
|
||||
return &IntegrationObj{Integration: &integration, o: wrapObject(id, &integration, query)}, finder.Error()
|
||||
}
|
||||
|
||||
func (i *IntegrationObj) object() *Object {
|
||||
return i.o
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package database
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
|
||||
type MessageObj struct {
|
||||
*types.Message
|
||||
o *Object
|
||||
}
|
||||
|
||||
func Message(id int64) (*MessageObj, error) {
|
||||
var message types.Message
|
||||
query := database.Messages().Where("id = ?", id)
|
||||
finder := query.Find(&message)
|
||||
return &MessageObj{Message: &message, o: wrapObject(id, &message, query)}, finder.Error()
|
||||
}
|
||||
|
||||
func (m *MessageObj) object() *Object {
|
||||
return m.o
|
||||
}
|
|
@ -1,31 +1,217 @@
|
|||
package database
|
||||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ServiceObj struct {
|
||||
*types.Service
|
||||
failures
|
||||
o *Object
|
||||
|
||||
Servicer
|
||||
}
|
||||
|
||||
func (o *Object) AsService() *types.Service {
|
||||
return o.model.(*types.Service)
|
||||
type Servicer interface {
|
||||
Hits() *HitObj
|
||||
Failures() *FailureObj
|
||||
AllCheckins() []*CheckinObj
|
||||
Model() *types.Service
|
||||
Interval() time.Duration
|
||||
DowntimeText() string
|
||||
|
||||
Hittable
|
||||
}
|
||||
|
||||
func Service(id int64) (HitsFailures, error) {
|
||||
type Hittable interface {
|
||||
CreateHit(*types.Hit) (int64, error)
|
||||
}
|
||||
|
||||
func Service(id int64) (*ServiceObj, error) {
|
||||
var service types.Service
|
||||
query := database.Model(&types.Service{}).Where("id = ?", id).Find(&service)
|
||||
return &ServiceObj{Service: &service}, query.Error()
|
||||
query := database.Services().Where("id = ?", id)
|
||||
finer := query.Find(&service)
|
||||
return &ServiceObj{Service: &service, o: wrapObject(id, &service, query)}, finer.Error()
|
||||
}
|
||||
|
||||
func (s *ServiceObj) Hits() *hits {
|
||||
return &hits{
|
||||
database.Model(&types.Hit{}).Where("service = ?", s.Id),
|
||||
func wrapServices(all []*types.Service, db Database) []*ServiceObj {
|
||||
var arr []*ServiceObj
|
||||
for _, v := range all {
|
||||
arr = append(arr, &ServiceObj{Service: v, o: wrapObject(v.Id, v, db)})
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
func (s *ServiceObj) Failures() *failures {
|
||||
return &failures{
|
||||
database.Model(&types.Failure{}).
|
||||
Where("method != 'checkin' AND service = ?", s.Id).Order("id desc"),
|
||||
}
|
||||
func Services() []*ServiceObj {
|
||||
var services []*types.Service
|
||||
db := database.Services().Order("order_id desc")
|
||||
db.Find(&services)
|
||||
return wrapServices(services, db)
|
||||
}
|
||||
|
||||
func (s *ServiceObj) AllCheckins() []*CheckinObj {
|
||||
var checkins []*types.Checkin
|
||||
query := database.Checkins().Where("service = ?", s.Id)
|
||||
query.Find(&checkins)
|
||||
return wrapCheckins(checkins, query)
|
||||
}
|
||||
|
||||
func (s *ServiceObj) DowntimeText() string {
|
||||
last := s.Failures().Last(1)
|
||||
return parseError(last)
|
||||
}
|
||||
|
||||
// ParseError returns a human readable error for a Failure
|
||||
func parseError(f *types.Failure) 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")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "operation timed out")
|
||||
if err {
|
||||
return fmt.Sprintf("HTTP Request Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "x509: certificate is valid")
|
||||
if err {
|
||||
return fmt.Sprintf("SSL Certificate invalid")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "Client.Timeout exceeded while awaiting headers")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "no such host")
|
||||
if err {
|
||||
return fmt.Sprintf("Domain is offline or not found")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "HTTP Status Code")
|
||||
if err {
|
||||
return fmt.Sprintf("Incorrect HTTP Status Code")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "connection refused")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Failed")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "can't assign requested address")
|
||||
if err {
|
||||
return fmt.Sprintf("Unable to Request Address")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "no route to host")
|
||||
if err {
|
||||
return fmt.Sprintf("Domain is offline or not found")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "i/o timeout")
|
||||
if err {
|
||||
return fmt.Sprintf("Connection Timed Out")
|
||||
}
|
||||
err = strings.Contains(f.Issue, "Client.Timeout exceeded while reading body")
|
||||
if err {
|
||||
return fmt.Sprintf("Timed Out on Response Body")
|
||||
}
|
||||
return f.Issue
|
||||
}
|
||||
|
||||
func (s *ServiceObj) Interval() time.Duration {
|
||||
return time.Duration(s.Service.Interval) * time.Second
|
||||
}
|
||||
|
||||
func (s *ServiceObj) Model() *types.Service {
|
||||
return s.Service
|
||||
}
|
||||
|
||||
func (s *ServiceObj) Hits() *HitObj {
|
||||
fmt.Println("hits")
|
||||
query := database.Hits().Where("service = ?", s.Id)
|
||||
return &HitObj{wrapObject(s.Id, nil, query)}
|
||||
}
|
||||
|
||||
func (s *ServiceObj) Failures() *FailureObj {
|
||||
q := database.Failures().Where("method != 'checkin' AND service = ?", s.Id)
|
||||
return &FailureObj{wrapObject(s.Id, nil, q)}
|
||||
}
|
||||
|
||||
func (s *ServiceObj) Group() *GroupObj {
|
||||
var group types.Group
|
||||
q := database.Groups().Where("id = ?", s.GroupId)
|
||||
finder := q.Find(&group)
|
||||
if finder.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
return &GroupObj{Group: &group, o: wrapObject(group.Id, &group, q)}
|
||||
}
|
||||
|
||||
func (s *ServiceObj) object() *Object {
|
||||
return s.o
|
||||
}
|
||||
|
||||
func (s *ServiceObj) UpdateStats() *types.Stats {
|
||||
s.Online24Hours = s.OnlineDaysPercent(1)
|
||||
s.Online7Days = s.OnlineDaysPercent(7)
|
||||
s.AvgResponse = s.AvgTime()
|
||||
s.FailuresLast24Hours = len(s.Failures().Since(time.Now().Add(-time.Hour * 24)))
|
||||
return s.Stats
|
||||
}
|
||||
|
||||
// AvgTime will return the average amount of time for a service to response back successfully
|
||||
func (s *ServiceObj) AvgTime() float64 {
|
||||
var sum float64
|
||||
database.Hits().
|
||||
Select("AVG(latency) as amount").
|
||||
Where("service = ?", s.Id).Pluck("amount", &sum).Debug()
|
||||
|
||||
total := s.Hits().Count()
|
||||
|
||||
if total == 0 {
|
||||
return 0
|
||||
}
|
||||
avg := sum / float64(total) * 100
|
||||
f, _ := strconv.ParseFloat(fmt.Sprintf("%0.0f", avg*10), 32)
|
||||
return f
|
||||
}
|
||||
|
||||
// OnlineDaysPercent returns the service's uptime percent within last 24 hours
|
||||
func (s *ServiceObj) OnlineDaysPercent(days int) float32 {
|
||||
ago := time.Now().UTC().Add((-24 * time.Duration(days)) * time.Hour)
|
||||
return s.OnlineSince(ago)
|
||||
}
|
||||
|
||||
// OnlineSince accepts a time since parameter to return the percent of a service's uptime.
|
||||
func (s *ServiceObj) OnlineSince(ago time.Time) float32 {
|
||||
failed := s.Failures().Since(ago)
|
||||
if len(failed) == 0 {
|
||||
s.Online24Hours = 100.00
|
||||
return s.Online24Hours
|
||||
}
|
||||
total := s.Hits().Since(ago)
|
||||
if len(total) == 0 {
|
||||
s.Online24Hours = 0
|
||||
return s.Online24Hours
|
||||
}
|
||||
avg := float64(len(failed)) / float64(len(total)) * 100
|
||||
avg = 100 - avg
|
||||
if avg < 0 {
|
||||
avg = 0
|
||||
}
|
||||
amount, _ := strconv.ParseFloat(fmt.Sprintf("%0.2f", avg), 10)
|
||||
s.Online24Hours = float32(amount)
|
||||
return s.Online24Hours
|
||||
}
|
||||
|
||||
// Downtime returns the amount of time of a offline service
|
||||
func (s *ServiceObj) Downtime() time.Duration {
|
||||
hits := s.Hits().Last(1)
|
||||
fail := s.Failures().Last(1)
|
||||
if fail == nil {
|
||||
return time.Duration(0)
|
||||
}
|
||||
if hits == nil {
|
||||
return time.Now().UTC().Sub(fail.CreatedAt.UTC())
|
||||
}
|
||||
since := fail.CreatedAt.UTC().Sub(hits.CreatedAt.UTC())
|
||||
return since
|
||||
}
|
||||
|
|
|
@ -4,16 +4,28 @@ import "github.com/hunterlong/statping/types"
|
|||
|
||||
type UserObj struct {
|
||||
*types.User
|
||||
}
|
||||
|
||||
func (o *Object) AsUser() *UserObj {
|
||||
return &UserObj{
|
||||
User: o.model.(*types.User),
|
||||
}
|
||||
o *Object
|
||||
}
|
||||
|
||||
func User(id int64) (*UserObj, error) {
|
||||
var user types.User
|
||||
query := database.Model(&types.User{}).Where("id = ?", id).Find(&user)
|
||||
return &UserObj{User: &user}, query.Error()
|
||||
query := database.Users().Where("id = ?", id)
|
||||
finder := query.First(&user)
|
||||
return &UserObj{User: &user, o: wrapObject(id, &user, query)}, finder.Error()
|
||||
}
|
||||
|
||||
func UserByUsername(username string) (*UserObj, error) {
|
||||
var user types.User
|
||||
query := database.Users().Where("username = ?", username)
|
||||
finder := query.First(&user)
|
||||
return &UserObj{User: &user, o: wrapObject(user.Id, &user, query)}, finder.Error()
|
||||
}
|
||||
|
||||
func AllUsers(input interface{}) error {
|
||||
err := database.Users().Find(&input)
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
func (u *UserObj) object() *Object {
|
||||
return u.o
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
|
@ -134,9 +135,9 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
|
|||
var objName string
|
||||
var objId int64
|
||||
switch v := obj.(type) {
|
||||
case types.ServiceInterface:
|
||||
case types.Servicer:
|
||||
objName = "service"
|
||||
objId = v.Select().Id
|
||||
objId = v.Model().Id
|
||||
case *notifier.Notification:
|
||||
objName = "notifier"
|
||||
objId = v.Id
|
||||
|
@ -151,9 +152,9 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht
|
|||
case *types.Group:
|
||||
objName = "group"
|
||||
objId = v.Id
|
||||
case *core.Group:
|
||||
case database.Grouper:
|
||||
objName = "group"
|
||||
objId = v.Id
|
||||
objId = v.Model().Id
|
||||
case *core.Checkin:
|
||||
objName = "checkin"
|
||||
objId = v.Id
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net"
|
||||
|
@ -27,23 +28,21 @@ import (
|
|||
)
|
||||
|
||||
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
checkins := core.AllCheckins()
|
||||
for _, c := range checkins {
|
||||
c.Hits = c.AllHits()
|
||||
c.Failures = c.GetFailures(64)
|
||||
}
|
||||
checkins := database.AllCheckins()
|
||||
returnJson(checkins, w, r)
|
||||
}
|
||||
|
||||
func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
checkin := core.SelectCheckin(vars["api"])
|
||||
if checkin == nil {
|
||||
checkin, err := database.CheckinByKey(vars["api"])
|
||||
if err != nil {
|
||||
sendErrorJson(fmt.Errorf("checkin %v was not found", vars["api"]), w, r)
|
||||
return
|
||||
}
|
||||
checkin.Hits = checkin.LimitedHits(32)
|
||||
checkin.Failures = checkin.GetFailures(32)
|
||||
out := checkin.Model()
|
||||
|
||||
out.Hits = checkin.Hits()
|
||||
out.Failures = checkin.Failures(32)
|
||||
returnJson(checkin, w, r)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
@ -12,37 +12,30 @@ var (
|
|||
basePath = "/"
|
||||
)
|
||||
|
||||
type HandlerFunc func(Responder, *Request)
|
||||
|
||||
func (f HandlerFunc) ServeHTTP(w Responder, r *Request) {
|
||||
f(w, r)
|
||||
type CustomResponseWriter struct {
|
||||
body []byte
|
||||
statusCode int
|
||||
header http.Header
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
ServeHTTP(Responder, *Request)
|
||||
func NewCustomResponseWriter() *CustomResponseWriter {
|
||||
return &CustomResponseWriter{
|
||||
header: http.Header{},
|
||||
}
|
||||
}
|
||||
|
||||
type Responder struct {
|
||||
Code int // the HTTP response code from WriteHeader
|
||||
HeaderMap http.Header // the HTTP response headers
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
Flushed bool
|
||||
func (w *CustomResponseWriter) Header() http.Header {
|
||||
return w.header
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
*http.Request
|
||||
func (w *CustomResponseWriter) Write(b []byte) (int, error) {
|
||||
w.body = b
|
||||
// implement it as per your requirement
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (r Responder) Header() http.Header {
|
||||
return r.HeaderMap
|
||||
}
|
||||
|
||||
func (r Responder) Write(p []byte) (int, error) {
|
||||
return r.Body.Write(p)
|
||||
}
|
||||
|
||||
func (r Responder) WriteHeader(statusCode int) {
|
||||
r.Code = statusCode
|
||||
func (w *CustomResponseWriter) WriteHeader(statusCode int) {
|
||||
w.statusCode = statusCode
|
||||
}
|
||||
|
||||
func parseForm(r *http.Request) url.Values {
|
||||
|
@ -55,6 +48,20 @@ func parseGet(r *http.Request) url.Values {
|
|||
return r.Form
|
||||
}
|
||||
|
||||
func decodeRequest(r *http.Request, object interface{}) error {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
defer r.Body.Close()
|
||||
return decoder.Decode(&object)
|
||||
}
|
||||
|
||||
type parsedObject struct {
|
||||
Error Error
|
||||
}
|
||||
|
||||
func serviceFromID(r *http.Request, object interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"VERSION": func() string {
|
||||
|
|
|
@ -4,13 +4,14 @@ import (
|
|||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hunterlong/statping/core"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func apiAllIncidentsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
incidents := core.AllIncidents()
|
||||
incidents := database.AllIncidents()
|
||||
returnJson(incidents, w, r)
|
||||
}
|
||||
|
||||
|
|
|
@ -130,8 +130,8 @@ func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect boo
|
|||
}
|
||||
|
||||
// cached is a middleware function that accepts a duration and content type and will cache the response of the original request
|
||||
func cached(duration, contentType string, handler func(w Responder, r *Request)) Responder {
|
||||
return HandlerFunc(func(w Responder, r *Request) {
|
||||
func cached(duration, contentType string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
content := CacheStorage.Get(r.RequestURI)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
|
|
@ -150,7 +150,7 @@ func apiServiceRunningHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendJsonAction(service, "running", w, r)
|
||||
}
|
||||
|
||||
func apiServiceDataHandler(w Responder, r *http.Request) {
|
||||
func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service, err := database.Service(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
|
@ -158,7 +158,7 @@ func apiServiceDataHandler(w Responder, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
groupQuery := database.ParseQueries(r, service.Hits().DB)
|
||||
groupQuery := database.ParseQueries(r, service.Hits())
|
||||
|
||||
obj := core.GraphData(groupQuery, &types.Hit{}, database.ByAverage("latency"))
|
||||
returnJson(obj, w, r)
|
||||
|
@ -171,7 +171,7 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
groupQuery := database.ParseQueries(r, service.Hits().DB)
|
||||
groupQuery := database.ParseQueries(r, service.Hits())
|
||||
|
||||
obj := core.GraphData(groupQuery, &types.Failure{}, database.ByCount)
|
||||
returnJson(obj, w, r)
|
||||
|
@ -184,7 +184,7 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
groupQuery := database.ParseQueries(r, service.Hits().DB)
|
||||
groupQuery := database.ParseQueries(r, service.Hits())
|
||||
|
||||
obj := core.GraphData(groupQuery, &types.Hit{}, database.ByAverage("ping_time"))
|
||||
returnJson(obj, w, r)
|
||||
|
@ -274,7 +274,7 @@ func joinServices(srvs []types.ServiceInterface) []*types.Service {
|
|||
return services
|
||||
}
|
||||
|
||||
func servicesDeleteFailuresHandler(w Responder, r *http.Request) {
|
||||
func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if service == nil {
|
||||
|
@ -293,12 +293,9 @@ func apiServiceFailuresHandler(r *http.Request) interface{} {
|
|||
return errors.New("service not found")
|
||||
}
|
||||
|
||||
service.Hits()
|
||||
|
||||
service.Failures()
|
||||
|
||||
var fails []types.Failure
|
||||
service.Failures().DB.Requests(r).Find(&fails)
|
||||
database.ParseQueries(r, service.Failures()).Find(&fails)
|
||||
|
||||
return fails
|
||||
}
|
||||
|
||||
|
@ -310,7 +307,7 @@ func apiServiceHitsHandler(r *http.Request) interface{} {
|
|||
}
|
||||
|
||||
var hits []types.Hit
|
||||
service.Hits().Find(&hits)
|
||||
database.ParseQueries(r, service.Hits()).Find(&hits)
|
||||
return hits
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,6 @@ type Checkin struct {
|
|||
Failures []*Failure `gorm:"-" json:"failures"`
|
||||
}
|
||||
|
||||
type CheckinInterface interface {
|
||||
Select() *Checkin
|
||||
}
|
||||
|
||||
// BeforeCreate for Checkin will set CreatedAt to UTC
|
||||
func (c *Checkin) BeforeCreate() (err error) {
|
||||
if c.CreatedAt.IsZero() {
|
||||
|
|
|
@ -29,28 +29,31 @@ type AllNotifiers interface{}
|
|||
// will be saved into 1 row in the 'core' table. You can use the core.CoreApp
|
||||
// global variable to interact with the attributes to the application, such as services.
|
||||
type Core struct {
|
||||
Name string `gorm:"not null;column:name" json:"name"`
|
||||
Description string `gorm:"not null;column:description" json:"description,omitempty"`
|
||||
ConfigFile string `gorm:"column:config" json:"-"`
|
||||
ApiKey string `gorm:"column:api_key" json:"api_key" scope:"admin"`
|
||||
ApiSecret string `gorm:"column:api_secret" json:"api_secret" scope:"admin"`
|
||||
Style string `gorm:"not null;column:style" json:"style,omitempty"`
|
||||
Footer NullString `gorm:"column:footer" json:"footer"`
|
||||
Domain string `gorm:"not null;column:domain" json:"domain"`
|
||||
Version string `gorm:"column:version" json:"version"`
|
||||
Setup bool `gorm:"-" json:"setup"`
|
||||
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
||||
UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
|
||||
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
|
||||
LoggedIn bool `gorm:"-" json:"logged_in"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
Started time.Time `gorm:"-" json:"started_on"`
|
||||
Services []ServiceInterface `gorm:"-" json:"-"`
|
||||
Plugins []*Info `gorm:"-" json:"-"`
|
||||
Repos []PluginJSON `gorm:"-" json:"-"`
|
||||
AllPlugins []PluginActions `gorm:"-" json:"-"`
|
||||
Notifications []AllNotifiers `gorm:"-" json:"-"`
|
||||
Config *DbConfig `gorm:"-" json:"-"`
|
||||
Integrations []Integrator `gorm:"-" json:"-"`
|
||||
Name string `gorm:"not null;column:name" json:"name"`
|
||||
Description string `gorm:"not null;column:description" json:"description,omitempty"`
|
||||
ConfigFile string `gorm:"column:config" json:"-"`
|
||||
ApiKey string `gorm:"column:api_key" json:"api_key" scope:"admin"`
|
||||
ApiSecret string `gorm:"column:api_secret" json:"api_secret" scope:"admin"`
|
||||
Style string `gorm:"not null;column:style" json:"style,omitempty"`
|
||||
Footer NullString `gorm:"column:footer" json:"footer"`
|
||||
Domain string `gorm:"not null;column:domain" json:"domain"`
|
||||
Version string `gorm:"column:version" json:"version"`
|
||||
Setup bool `gorm:"-" json:"setup"`
|
||||
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
||||
UseCdn NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
|
||||
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
|
||||
LoggedIn bool `gorm:"-" json:"logged_in"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
Started time.Time `gorm:"-" json:"started_on"`
|
||||
Plugins []*Info `gorm:"-" json:"-"`
|
||||
Repos []PluginJSON `gorm:"-" json:"-"`
|
||||
AllPlugins []PluginActions `gorm:"-" json:"-"`
|
||||
Notifications []AllNotifiers `gorm:"-" json:"-"`
|
||||
Config *DbConfig `gorm:"-" json:"-"`
|
||||
Integrations []Integrator `gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
type Servicer interface {
|
||||
Model() *Service
|
||||
}
|
||||
|
|
|
@ -33,10 +33,6 @@ type Failure struct {
|
|||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type FailureInterface interface {
|
||||
Select() *Failure
|
||||
}
|
||||
|
||||
// BeforeCreate for Failure will set CreatedAt to UTC
|
||||
func (f *Failure) BeforeCreate() (err error) {
|
||||
if f.CreatedAt.IsZero() {
|
||||
|
@ -45,7 +41,7 @@ func (f *Failure) BeforeCreate() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
type FailSort []FailureInterface
|
||||
type FailSort []Failure
|
||||
|
||||
func (s FailSort) Len() int {
|
||||
return len(s)
|
||||
|
@ -54,5 +50,5 @@ func (s FailSort) Swap(i, j int) {
|
|||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s FailSort) Less(i, j int) bool {
|
||||
return s[i].Select().Id < s[j].Select().Id
|
||||
return s[i].Id < s[j].Id
|
||||
}
|
||||
|
|
|
@ -46,8 +46,7 @@ type Service struct {
|
|||
Online24Hours float32 `gorm:"-" json:"online_24_hours"`
|
||||
Online7Days float32 `gorm:"-" json:"online_7_days"`
|
||||
AvgResponse float64 `gorm:"-" json:"avg_response"`
|
||||
FailuresLast24Hours uint64 `gorm:"-" json:"failures_24_hours"`
|
||||
LastFailure FailureInterface `gorm:"-" json:"last_failure,omitempty"`
|
||||
FailuresLast24Hours int `gorm:"-" json:"failures_24_hours"`
|
||||
Running chan bool `gorm:"-" json:"-"`
|
||||
Checkpoint time.Time `gorm:"-" json:"-"`
|
||||
SleepDuration time.Duration `gorm:"-" json:"-"`
|
||||
|
@ -59,9 +58,13 @@ type Service struct {
|
|||
SuccessNotified bool `gorm:"-" json:"-"` // Is 'true' if the user has already be informed that the Services now again available
|
||||
LastStatusCode int `gorm:"-" json:"status_code"`
|
||||
LastOnline time.Time `gorm:"-" json:"last_success"`
|
||||
Failures []FailureInterface `gorm:"-" json:"failures,omitempty" scope:"user,admin"`
|
||||
Failures []Failure `gorm:"-" json:"failures,omitempty" scope:"user,admin"`
|
||||
Checkins []CheckinInterface `gorm:"-" json:"checkins,omitempty" scope:"user,admin"`
|
||||
Stats Stater `gorm:"-" json:"stats,omitempty"`
|
||||
Stats *Stats `gorm:"-" json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
type CheckinInterface interface {
|
||||
Model() *Checkin
|
||||
}
|
||||
|
||||
type Stater interface {
|
||||
|
@ -82,15 +85,6 @@ func (s *Service) BeforeCreate() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
type ServiceInterface interface {
|
||||
Select() *Service
|
||||
CheckQueue(bool)
|
||||
Check(bool)
|
||||
Create(bool) (int64, error)
|
||||
Update(bool) error
|
||||
Delete() error
|
||||
}
|
||||
|
||||
// Start will create a channel for the service checking go routine
|
||||
func (s *Service) Start() {
|
||||
s.Running = make(chan bool)
|
||||
|
|
Loading…
Reference in New Issue