mirror of https://github.com/statping/statping
pull/429/head
parent
871424f9c3
commit
f64fc9e682
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -94,8 +93,8 @@ func (c *Checkin) CreateFailure() (int64, error) {
|
|||
PingTime: c.Expected().Seconds(),
|
||||
}}
|
||||
row := Database(&Failure{}).Create(&fail)
|
||||
sort.Sort(types.FailSort(c.Failures))
|
||||
c.Failures = append(c.Failures, fail)
|
||||
//sort.Sort(types.FailSort(c.Failures))
|
||||
//c.Failures = append(c.Failures, fail)
|
||||
if len(c.Failures) > limitedFailures {
|
||||
c.Failures = c.Failures[1:]
|
||||
}
|
||||
|
@ -167,23 +166,27 @@ func (c *Checkin) AllHits() []*types.CheckinHit {
|
|||
return checkins
|
||||
}
|
||||
|
||||
// Hits returns all of the CheckinHits for a given Checkin
|
||||
func (c *Checkin) LimitedFailures(amount int) []types.FailureInterface {
|
||||
var failures []*Failure
|
||||
var failInterfaces []types.FailureInterface
|
||||
col := Database(&types.Failure{}).Where("checkin = ?", c.Id).Where("method = 'checkin'").Limit(amount).Order("id desc")
|
||||
col.Find(&failures)
|
||||
for _, f := range failures {
|
||||
failInterfaces = append(failInterfaces, f)
|
||||
}
|
||||
return failInterfaces
|
||||
}
|
||||
|
||||
// Hits returns all of the CheckinHits for a given Checkin
|
||||
func (c *Checkin) AllFailures() []*types.Failure {
|
||||
var failures []*types.Failure
|
||||
col := Database(&types.Failure{}).Where("checkin = ?", c.Id).Where("method = 'checkin'").Order("id desc")
|
||||
col.Find(&failures)
|
||||
Database(&types.Failure{}).
|
||||
Where("checkin = ?", c.Id).
|
||||
Where("method = 'checkin'").
|
||||
Order("id desc").
|
||||
Find(&failures)
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
func (c *Checkin) GetFailures(count int) []*types.Failure {
|
||||
var failures []*types.Failure
|
||||
Database(&types.Failure{}).
|
||||
Where("checkin = ?", c.Id).
|
||||
Where("method = 'checkin'").
|
||||
Limit(count).
|
||||
Order("id desc").
|
||||
Find(&failures)
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
|
|
|
@ -18,9 +18,7 @@ package core
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -62,10 +60,6 @@ func (s *Service) AllFailures() []types.Failure {
|
|||
return fails
|
||||
}
|
||||
|
||||
func (s *Service) FailuresDb(r *http.Request) database.Database {
|
||||
return Database(&types.Failure{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
|
||||
}
|
||||
|
||||
// DeleteFailures will delete all failures for a service
|
||||
func (s *Service) DeleteFailures() {
|
||||
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
|
||||
|
@ -75,13 +69,6 @@ func (s *Service) DeleteFailures() {
|
|||
s.Failures = nil
|
||||
}
|
||||
|
||||
// LimitedFailures will return the last amount of failures from a service
|
||||
func (s *Service) LimitedFailures(amount int) []*Failure {
|
||||
var failArr []*Failure
|
||||
Database(&types.Failure{}).Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc").Limit(amount).Find(&failArr)
|
||||
return failArr
|
||||
}
|
||||
|
||||
// LimitedFailures will return the last amount of failures from a service
|
||||
func (s *Service) LimitedCheckinFailures(amount int) []*Failure {
|
||||
var failArr []*Failure
|
||||
|
|
|
@ -47,13 +47,13 @@ func (s *Service) CountHits() (int64, 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).QuerySearch(r).Order("id desc")
|
||||
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).QuerySearch(r).Order("id desc")
|
||||
return Database(&types.Hit{}).Where("service = ?", s.Id).Requests(r).Order("id desc")
|
||||
}
|
||||
|
||||
// Hits returns all successful hits for a service
|
||||
|
|
134
core/services.go
134
core/services.go
|
@ -16,9 +16,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ararog/timeago"
|
||||
"github.com/hunterlong/statping/core/notifier"
|
||||
"github.com/hunterlong/statping/database"
|
||||
"github.com/hunterlong/statping/types"
|
||||
|
@ -58,6 +56,13 @@ func SelectService(id int64) *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)
|
||||
|
@ -108,7 +113,8 @@ func (s *Service) AllCheckins() []*Checkin {
|
|||
return checkin
|
||||
}
|
||||
|
||||
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services, should only be called once on startup.
|
||||
// SelectAllServices returns a slice of *core.Service to be store on []*core.Services
|
||||
// should only be called once on startup.
|
||||
func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
|
||||
var services []*Service
|
||||
db := Database(&Service{}).Find(&services).Order("order_id desc")
|
||||
|
@ -122,13 +128,13 @@ func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
|
|||
service.Start()
|
||||
service.CheckinProcess()
|
||||
}
|
||||
fails := service.LimitedFailures(limitedFailures)
|
||||
fails := service.GetFailures(limitedFailures)
|
||||
for _, f := range fails {
|
||||
service.Failures = append(service.Failures, f)
|
||||
}
|
||||
checkins := service.AllCheckins()
|
||||
for _, c := range checkins {
|
||||
c.Failures = c.LimitedFailures(limitedFailures)
|
||||
c.Failures = c.GetFailures(limitedFailures)
|
||||
c.Hits = c.LimitedHits(limitedHits)
|
||||
service.Checkins = append(service.Checkins, c)
|
||||
}
|
||||
|
@ -185,8 +191,8 @@ func (s *Service) OnlineSince(ago time.Time) float32 {
|
|||
}
|
||||
|
||||
// lastFailure returns the last Failure a service had
|
||||
func (s *Service) lastFailure() *Failure {
|
||||
limited := s.LimitedFailures(1)
|
||||
func (s *Service) lastFailure() types.FailureInterface {
|
||||
limited := s.GetFailures(1)
|
||||
if len(limited) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -194,29 +200,6 @@ func (s *Service) lastFailure() *Failure {
|
|||
return last
|
||||
}
|
||||
|
||||
// SmallText returns a short description about a services status
|
||||
// service.SmallText()
|
||||
// // Online since Monday 3:04:05PM, Jan _2 2006
|
||||
func (s *Service) SmallText() string {
|
||||
last := s.LimitedFailures(1)
|
||||
//hits, _ := s.LimitedHits(1)
|
||||
zone := CoreApp.Timezone
|
||||
if s.Online {
|
||||
if len(last) == 0 {
|
||||
return fmt.Sprintf("Online since %v", utils.Timezoner(s.CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
|
||||
} else {
|
||||
return fmt.Sprintf("Online, last Failure was %v", utils.Timezoner(last[0].CreatedAt, zone).Format("Monday 3:04:05PM, Jan _2 2006"))
|
||||
}
|
||||
}
|
||||
if len(last) > 0 {
|
||||
lastFailure := s.lastFailure()
|
||||
got, _ := timeago.TimeAgoWithTime(time.Now().UTC().Add(s.Downtime()), time.Now().UTC())
|
||||
return fmt.Sprintf("Reported offline %v, %v", got, lastFailure.ParseError())
|
||||
} else {
|
||||
return fmt.Sprintf("%v is currently offline", s.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// DowntimeText will return the amount of downtime for a service based on the duration
|
||||
// service.DowntimeText()
|
||||
// // Service has been offline for 15 minutes
|
||||
|
@ -261,99 +244,24 @@ func (s *Service) Downtime() time.Duration {
|
|||
return time.Duration(0)
|
||||
}
|
||||
if len(hits) == 0 {
|
||||
return time.Now().UTC().Sub(fail.CreatedAt.UTC())
|
||||
return time.Now().UTC().Sub(fail.Select().CreatedAt.UTC())
|
||||
}
|
||||
since := fail.CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC())
|
||||
since := fail.Select().CreatedAt.UTC().Sub(hits[0].CreatedAt.UTC())
|
||||
return since
|
||||
}
|
||||
|
||||
// DateScanObj struct is for creating the charts.js graph JSON array
|
||||
type DateScanObj struct {
|
||||
Array []*database.DateScan `json:"data"`
|
||||
}
|
||||
|
||||
// GraphDataRaw will return all the hits between 2 times for a Service
|
||||
func GraphHitsDataRaw(service types.ServiceInterface, query *types.GroupQuery, column string) []*database.TimeValue {
|
||||
srv := service.(*Service)
|
||||
|
||||
dbQuery, err := Database(&types.Hit{}).
|
||||
Where("service = ?", srv.Id).
|
||||
GroupQuery(query).ToTimeValue()
|
||||
// GraphData will return all hits or failures
|
||||
func GraphData(q *database.GroupQuery, dbType interface{}, by database.By) []*database.TimeValue {
|
||||
dbQuery, err := q.Database().GroupQuery(q, by).ToTimeValue(dbType)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil
|
||||
}
|
||||
return dbQuery.FillMissing()
|
||||
}
|
||||
|
||||
// GraphDataRaw will return all the hits between 2 times for a Service
|
||||
func GraphFailuresDataRaw(service types.ServiceInterface, query *types.GroupQuery) []*database.TimeValue {
|
||||
srv := service.(*Service)
|
||||
|
||||
dbQuery, err := Database(&types.Failure{}).
|
||||
Where("service = ?", srv.Id).
|
||||
GroupQuery(query).ToTimeValue()
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil
|
||||
if q.FillEmpty {
|
||||
return dbQuery.FillMissing(q.Start, q.End)
|
||||
}
|
||||
return dbQuery.FillMissing()
|
||||
}
|
||||
|
||||
// ToString will convert the DateScanObj into a JSON string for the charts to render
|
||||
func (d *DateScanObj) ToString() string {
|
||||
data, err := json.Marshal(d.Array)
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
return "{}"
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// AvgUptime24 returns a service's average online status for last 24 hours
|
||||
func (s *Service) AvgUptime24() string {
|
||||
ago := time.Now().UTC().Add(-24 * time.Hour)
|
||||
return s.AvgUptime(ago)
|
||||
}
|
||||
|
||||
// AvgUptime returns average online status for last 24 hours
|
||||
func (s *Service) AvgUptime(ago time.Time) string {
|
||||
failed, _ := s.TotalFailuresSince(ago)
|
||||
if failed == 0 {
|
||||
return "100"
|
||||
}
|
||||
total, _ := s.TotalHitsSince(ago)
|
||||
if total == 0 {
|
||||
return "0.00"
|
||||
}
|
||||
percent := float64(failed) / float64(total) * 100
|
||||
percent = 100 - percent
|
||||
if percent < 0 {
|
||||
percent = 0
|
||||
}
|
||||
amount := fmt.Sprintf("%0.2f", percent)
|
||||
if amount == "100.00" {
|
||||
amount = "100"
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
// TotalUptime returns the total uptime percent of a service
|
||||
func (s *Service) TotalUptime() string {
|
||||
hits, _ := s.TotalHits()
|
||||
failures, _ := s.TotalFailures()
|
||||
percent := float64(failures) / float64(hits) * 100
|
||||
percent = 100 - percent
|
||||
if percent < 0 {
|
||||
percent = 0
|
||||
}
|
||||
amount := fmt.Sprintf("%0.2f", percent)
|
||||
if amount == "100.00" {
|
||||
amount = "100"
|
||||
}
|
||||
return amount
|
||||
return dbQuery.ToValues()
|
||||
}
|
||||
|
||||
// index returns a services index int for updating the []*core.Services slice
|
||||
|
|
|
@ -127,12 +127,6 @@ func TestServiceOnline24Hours(t *testing.T) {
|
|||
assert.True(t, service3.OnlineSince(since) > float32(49))
|
||||
}
|
||||
|
||||
func TestServiceSmallText(t *testing.T) {
|
||||
service := SelectService(5)
|
||||
text := service.SmallText()
|
||||
assert.Contains(t, text, "Online since")
|
||||
}
|
||||
|
||||
func TestServiceAvgUptime(t *testing.T) {
|
||||
since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
|
||||
service := SelectService(1)
|
||||
|
|
|
@ -2,14 +2,12 @@ package database
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
@ -21,6 +19,10 @@ const (
|
|||
TIME_DAY = "2006-01-02"
|
||||
)
|
||||
|
||||
var (
|
||||
database Database
|
||||
)
|
||||
|
||||
// Database is an interface which DB implements
|
||||
type Database interface {
|
||||
Close() error
|
||||
|
@ -99,11 +101,9 @@ type Database interface {
|
|||
// extra
|
||||
Error() error
|
||||
RowsAffected() int64
|
||||
QuerySearch(*http.Request) Database
|
||||
|
||||
Since(time.Time) Database
|
||||
Between(time.Time, time.Time) Database
|
||||
Hits() ([]*types.Hit, error)
|
||||
ToChart() ([]*DateScan, error)
|
||||
|
||||
SelectByTime(string) string
|
||||
|
@ -112,7 +112,14 @@ type Database interface {
|
|||
FormatTime(t time.Time) string
|
||||
ParseTime(t string) (time.Time, error)
|
||||
|
||||
GroupQuery(query *types.GroupQuery) GroupByer
|
||||
Requests(*http.Request) Database
|
||||
|
||||
GroupQuery(query *GroupQuery, by By) GroupByer
|
||||
}
|
||||
|
||||
func (it *Db) Requests(r *http.Request) Database {
|
||||
g := ParseQueries(r, it)
|
||||
return g.db
|
||||
}
|
||||
|
||||
func (it *Db) MultipleSelects(args ...string) Database {
|
||||
|
@ -120,10 +127,6 @@ func (it *Db) MultipleSelects(args ...string) Database {
|
|||
return it.Select(joined)
|
||||
}
|
||||
|
||||
func CountAmount() string {
|
||||
return fmt.Sprintf("COUNT(id) as amount")
|
||||
}
|
||||
|
||||
type Db struct {
|
||||
Database *gorm.DB
|
||||
Type string
|
||||
|
@ -132,7 +135,11 @@ type Db struct {
|
|||
// Openw is a drop-in replacement for Open()
|
||||
func Openw(dialect string, args ...interface{}) (db Database, err error) {
|
||||
gormdb, err := gorm.Open(dialect, args...)
|
||||
return Wrap(gormdb), err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
database = Wrap(gormdb)
|
||||
return database, err
|
||||
}
|
||||
|
||||
// Wrap wraps gorm.DB in an interface
|
||||
|
@ -460,8 +467,8 @@ type DateScan struct {
|
|||
}
|
||||
|
||||
type TimeValue struct {
|
||||
Timeframe time.Time `json:"timeframe"`
|
||||
Amount int64 `json:"amount"`
|
||||
Timeframe string `json:"timeframe"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
func (it *Db) ToChart() ([]*DateScan, error) {
|
||||
|
@ -487,55 +494,3 @@ func (it *Db) ToChart() ([]*DateScan, error) {
|
|||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (it *Db) QuerySearch(r *http.Request) Database {
|
||||
if r == nil {
|
||||
return it
|
||||
}
|
||||
db := it.Database
|
||||
start := defaultField(r, "start")
|
||||
end := defaultField(r, "end")
|
||||
limit := defaultField(r, "limit")
|
||||
offset := defaultField(r, "offset")
|
||||
params := &Params{
|
||||
Start: start,
|
||||
End: end,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}
|
||||
if params.Start != nil && params.End != nil {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", time.Unix(*params.Start, 0).Format(TIME), time.Unix(*params.End, 0).UTC().Format(TIME))
|
||||
} else if params.Start != nil && params.End == nil {
|
||||
db = db.Where("created_at > ?", time.Unix(*params.Start, 0).UTC().Format(TIME))
|
||||
}
|
||||
if params.Limit != nil {
|
||||
db = db.Limit(*params.Limit)
|
||||
} else {
|
||||
db = db.Limit(10000)
|
||||
}
|
||||
if params.Offset != nil {
|
||||
db = db.Offset(*params.Offset)
|
||||
} else {
|
||||
db = db.Offset(0)
|
||||
}
|
||||
return Wrap(db)
|
||||
}
|
||||
|
||||
type Params struct {
|
||||
Start *int64
|
||||
End *int64
|
||||
Limit *int64
|
||||
Offset *int64
|
||||
}
|
||||
|
||||
func defaultField(r *http.Request, key string) *int64 {
|
||||
r.ParseForm()
|
||||
val := r.Form.Get(key)
|
||||
if val == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
gg, _ := strconv.Atoi(val)
|
||||
num := int64(gg)
|
||||
return &num
|
||||
}
|
||||
|
|
|
@ -3,42 +3,60 @@ package database
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GroupBy struct {
|
||||
db Database
|
||||
query *types.GroupQuery
|
||||
query *GroupQuery
|
||||
}
|
||||
|
||||
type GroupByer interface {
|
||||
ToTimeValue() (*TimeVar, error)
|
||||
ToTimeValue(interface{}) (*TimeVar, error)
|
||||
}
|
||||
|
||||
type GroupMethod interface {
|
||||
type By string
|
||||
|
||||
func (b By) String() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
type GroupQuery struct {
|
||||
db Database
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Group string
|
||||
Order string
|
||||
Limit int
|
||||
Offset int
|
||||
FillEmpty bool
|
||||
}
|
||||
|
||||
func (b GroupQuery) Database() Database {
|
||||
return b.db
|
||||
}
|
||||
|
||||
var (
|
||||
ByCount = func() GroupMethod {
|
||||
return fmt.Sprintf("COUNT(id) as amount")
|
||||
ByCount = By("COUNT(id) as amount")
|
||||
BySum = func(column string) By {
|
||||
return By(fmt.Sprintf("SUM(%s) as amount", column))
|
||||
}
|
||||
BySum = func(column string) GroupMethod {
|
||||
return fmt.Sprintf("SUM(%s) as amount", column)
|
||||
}
|
||||
ByAverage = func(column string) GroupMethod {
|
||||
return fmt.Sprintf("SUM(%s) as amount", column)
|
||||
ByAverage = func(column string) By {
|
||||
return By(fmt.Sprintf("AVG(%s) as amount", column))
|
||||
}
|
||||
)
|
||||
|
||||
func execute(db Database, query *types.GroupQuery) Database {
|
||||
return db.MultipleSelects(
|
||||
db.SelectByTime(query.Group),
|
||||
CountAmount(),
|
||||
).Between(query.Start, query.End).Group("timeframe").Debug()
|
||||
}
|
||||
func (db *Db) GroupQuery(q *GroupQuery, by By) GroupByer {
|
||||
dbQuery := db.MultipleSelects(
|
||||
db.SelectByTime(q.Group),
|
||||
by.String(),
|
||||
).Group("timeframe")
|
||||
|
||||
func (db *Db) GroupQuery(query *types.GroupQuery) GroupByer {
|
||||
return &GroupBy{execute(db, query), query}
|
||||
return &GroupBy{dbQuery, q}
|
||||
}
|
||||
|
||||
type TimeVar struct {
|
||||
|
@ -46,64 +64,72 @@ type TimeVar struct {
|
|||
data []*TimeValue
|
||||
}
|
||||
|
||||
func (g *GroupBy) ToTimeValue() (*TimeVar, error) {
|
||||
func (t *TimeVar) ToValues() []*TimeValue {
|
||||
return t.data
|
||||
}
|
||||
|
||||
func (g *GroupBy) toFloatRows() []*TimeValue {
|
||||
rows, err := g.db.Rows()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var data []*TimeValue
|
||||
for rows.Next() {
|
||||
var timeframe time.Time
|
||||
amount := float64(0)
|
||||
rows.Scan(&timeframe, &amount)
|
||||
newTs := types.FixedTime(timeframe, g.duration())
|
||||
data = append(data, &TimeValue{
|
||||
Timeframe: newTs,
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (g *GroupBy) ToTimeValue(dbType interface{}) (*TimeVar, error) {
|
||||
rows, err := g.db.Rows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []*TimeValue
|
||||
for rows.Next() {
|
||||
var timeframe string
|
||||
var amount int64
|
||||
if err := rows.Scan(&timeframe, &amount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createdTime, _ := g.db.ParseTime(timeframe)
|
||||
var timeframe time.Time
|
||||
amount := float64(0)
|
||||
rows.Scan(&timeframe, &amount)
|
||||
newTs := types.FixedTime(timeframe, g.duration())
|
||||
data = append(data, &TimeValue{
|
||||
Timeframe: createdTime,
|
||||
Timeframe: newTs,
|
||||
Amount: amount,
|
||||
})
|
||||
|
||||
}
|
||||
return &TimeVar{g, data}, nil
|
||||
}
|
||||
|
||||
func (t *TimeVar) Values() []*TimeValue {
|
||||
func (t *TimeVar) FillMissing(current, end time.Time) []*TimeValue {
|
||||
timeMap := make(map[string]float64)
|
||||
var validSet []*TimeValue
|
||||
dur := t.g.duration()
|
||||
for _, v := range t.data {
|
||||
validSet = append(validSet, &TimeValue{
|
||||
Timeframe: v.Timeframe,
|
||||
Amount: v.Amount,
|
||||
})
|
||||
timeMap[v.Timeframe] = v.Amount
|
||||
}
|
||||
|
||||
return validSet
|
||||
}
|
||||
currentStr := types.FixedTime(current, t.g.duration())
|
||||
|
||||
func (t *TimeVar) FillMissing() []*TimeValue {
|
||||
timeMap := make(map[time.Time]*TimeValue)
|
||||
var validSet []*TimeValue
|
||||
if len(t.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
current := t.data[0].Timeframe
|
||||
for _, v := range t.data {
|
||||
timeMap[v.Timeframe] = v
|
||||
}
|
||||
maxTime := t.g.query.End
|
||||
for {
|
||||
amount := int64(0)
|
||||
if timeMap[current] != nil {
|
||||
amount = timeMap[current].Amount
|
||||
var amount float64
|
||||
if timeMap[currentStr] != 0 {
|
||||
amount = timeMap[currentStr]
|
||||
}
|
||||
validSet = append(validSet, &TimeValue{
|
||||
Timeframe: current,
|
||||
Timeframe: currentStr,
|
||||
Amount: amount,
|
||||
})
|
||||
if current.After(maxTime) {
|
||||
if current.After(end) {
|
||||
break
|
||||
}
|
||||
current = current.Add(t.g.duration())
|
||||
current = current.Add(dur)
|
||||
currentStr = types.FixedTime(current, t.g.duration())
|
||||
}
|
||||
|
||||
return validSet
|
||||
|
@ -112,18 +138,78 @@ func (t *TimeVar) FillMissing() []*TimeValue {
|
|||
func (g *GroupBy) duration() time.Duration {
|
||||
switch g.query.Group {
|
||||
case "second":
|
||||
return time.Second
|
||||
return types.Second
|
||||
case "minute":
|
||||
return time.Minute
|
||||
return types.Minute
|
||||
case "hour":
|
||||
return time.Hour
|
||||
return types.Hour
|
||||
case "day":
|
||||
return time.Hour * 24
|
||||
return types.Day
|
||||
case "month":
|
||||
return time.Hour * 730
|
||||
return types.Month
|
||||
case "year":
|
||||
return time.Hour * 8760
|
||||
return types.Year
|
||||
default:
|
||||
return time.Hour
|
||||
return types.Hour
|
||||
}
|
||||
}
|
||||
|
||||
func ParseQueries(r *http.Request, db Database) *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"))
|
||||
fill, _ := strconv.ParseBool(fields.Get("fill"))
|
||||
orderBy := fields.Get("order")
|
||||
if limit == 0 {
|
||||
limit = 10000
|
||||
}
|
||||
|
||||
query := &GroupQuery{
|
||||
Start: time.Unix(startField, 0).UTC(),
|
||||
End: time.Unix(endField, 0).UTC(),
|
||||
Group: grouping,
|
||||
Order: orderBy,
|
||||
Limit: int(limit),
|
||||
Offset: int(offset),
|
||||
FillEmpty: fill,
|
||||
}
|
||||
|
||||
if query.Limit != 0 {
|
||||
db = db.Limit(query.Limit)
|
||||
}
|
||||
if query.Offset > 0 {
|
||||
db = db.Offset(query.Offset)
|
||||
}
|
||||
if !query.Start.IsZero() && !query.End.IsZero() {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", db.FormatTime(query.Start), db.FormatTime(query.End))
|
||||
} else {
|
||||
if !query.Start.IsZero() {
|
||||
db = db.Where("created_at > ?", db.FormatTime(query.Start))
|
||||
}
|
||||
if !query.End.IsZero() {
|
||||
db = db.Where("created_at < ?", db.FormatTime(query.End))
|
||||
}
|
||||
}
|
||||
if query.Order != "" {
|
||||
db = db.Order(query.Order)
|
||||
}
|
||||
query.db = db.Debug()
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func parseForm(r *http.Request) url.Values {
|
||||
r.ParseForm()
|
||||
return r.PostForm
|
||||
}
|
||||
|
||||
func parseGet(r *http.Request) url.Values {
|
||||
r.ParseForm()
|
||||
return r.Form
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package database
|
|||
|
||||
import "github.com/hunterlong/statping/types"
|
||||
|
||||
type Service struct {
|
||||
type ServiceObj struct {
|
||||
db Database
|
||||
service *types.Service
|
||||
}
|
||||
|
@ -12,16 +12,16 @@ type Servicer interface {
|
|||
Hits() Database
|
||||
}
|
||||
|
||||
func (it *Db) GetService(id int64) (Servicer, error) {
|
||||
func Service(id int64) (Servicer, error) {
|
||||
var service types.Service
|
||||
query := it.Model(&types.Service{}).Where("id = ?", id).Find(&service)
|
||||
return &Service{it, &service}, query.Error()
|
||||
query := database.Model(&types.Service{}).Where("id = ?", id).Find(&service)
|
||||
return &ServiceObj{query, &service}, query.Error()
|
||||
}
|
||||
|
||||
func (s *Service) Failures() Database {
|
||||
return s.db.Model(&types.Failure{}).Where("service = ?", s.service.Id)
|
||||
func (s *ServiceObj) Failures() Database {
|
||||
return database.Model(&types.Failure{}).Where("service = ?", s.service.Id)
|
||||
}
|
||||
|
||||
func (s *Service) Hits() Database {
|
||||
return s.db.Model(&types.Hit{}).Where("service = ?", s.service.Id)
|
||||
func (s *ServiceObj) Hits() Database {
|
||||
return database.Model(&types.Hit{}).Where("service = ?", s.service.Id)
|
||||
}
|
||||
|
|
|
@ -37,11 +37,11 @@ class Api {
|
|||
}
|
||||
|
||||
async service_hits(id, start, end, group) {
|
||||
return axios.get('/api/services/' + id + '/hits_data?start=' + start + '&end=' + end + '&group=' + group).then(response => (response.data))
|
||||
return axios.get('/api/services/' + id + '/hits_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=true').then(response => (response.data))
|
||||
}
|
||||
|
||||
async service_failures_data(id, start, end, group) {
|
||||
return axios.get('/api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group).then(response => (response.data))
|
||||
return axios.get('/api/services/' + id + '/failure_data?start=' + start + '&end=' + end + '&group=' + group + '&fill=true').then(response => (response.data))
|
||||
}
|
||||
|
||||
async service_heatmap(id, start, end, group) {
|
||||
|
|
|
@ -132,15 +132,15 @@
|
|||
},
|
||||
methods: {
|
||||
async chartHits(group) {
|
||||
const start = this.nowSubtract((3600 * 24) * 7)
|
||||
const start = this.nowSubtract((3600 * 24) * 30)
|
||||
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), group)
|
||||
|
||||
if (this.data.length === 0 && group !== "minute") {
|
||||
await this.chartHits("minute")
|
||||
if (this.data.length === 0 && group !== "hour") {
|
||||
await this.chartHits("hour")
|
||||
}
|
||||
this.series = [{
|
||||
name: this.service.name,
|
||||
...this.data
|
||||
...this.convertToChartData(this.data)
|
||||
}]
|
||||
this.ready = true
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<span v-for="(failure, index) in failures" v-bind:key="index" class="alert alert-light">
|
||||
Failed {{ago(parseTime(failure.created_at))}}<br>
|
||||
Failed {{failure.created_at}}<br>
|
||||
{{failure.issue}}
|
||||
</span>
|
||||
|
||||
|
@ -49,9 +49,9 @@
|
|||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.set1 = await this.getHits(24, "minute")
|
||||
this.set1 = await this.getHits(24, "hour")
|
||||
this.set1_name = this.calc(this.set1)
|
||||
this.set2 = await this.getHits(24 * 7, "hour")
|
||||
this.set2 = await this.getHits(24 * 7, "day")
|
||||
this.set2_name = this.calc(this.set2)
|
||||
this.loaded = true
|
||||
},
|
||||
|
@ -62,15 +62,16 @@
|
|||
this.failures = await Api.service_failures(this.service.id, this.toUnix(start), this.toUnix(this.now()), 5)
|
||||
return [{name: "None", data: []}]
|
||||
}
|
||||
const data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(this.now()), group)
|
||||
const fetched = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(this.now()), group)
|
||||
if (!data) {
|
||||
return [{name: "None", data: []}]
|
||||
}
|
||||
return [{name: "Latency", data: data.data}]
|
||||
const data = this.convertToChartData(fetched, 1000, true)
|
||||
return [{name: "Latency", data}]
|
||||
},
|
||||
calc(s) {
|
||||
let data = s[0].data
|
||||
if (data.length > 1) {
|
||||
if (data) {
|
||||
let total = 0
|
||||
data.forEach((f) => {
|
||||
total += f.y
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<button @click.prevent="saveService" type="submit" class="btn btn-success btn-block">
|
||||
<button :disabled="loading" @click.prevent="saveService" type="submit" class="btn btn-success btn-block">
|
||||
{{service.id ? "Update Service" : "Create Service"}}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -208,6 +208,7 @@
|
|||
methods: {
|
||||
async saveService() {
|
||||
let s = this.service
|
||||
this.loading = true
|
||||
delete s.failures
|
||||
delete s.created_at
|
||||
delete s.updated_at
|
||||
|
@ -219,6 +220,10 @@
|
|||
} else {
|
||||
await this.createService(s)
|
||||
}
|
||||
const services = await Api.services()
|
||||
this.$store.commit('setServices', services)
|
||||
this.loading = false
|
||||
this.$router.push('/dashboard/services')
|
||||
},
|
||||
async createService(s) {
|
||||
await Api.service_create(s)
|
||||
|
|
|
@ -93,5 +93,19 @@ export default Vue.mixin({
|
|||
return "bars"
|
||||
}
|
||||
},
|
||||
convertToChartData(data = [], multiplier=1, asInt=false) {
|
||||
let newSet = [];
|
||||
data.forEach((f) => {
|
||||
let amount = f.amount * multiplier;
|
||||
if (asInt) {
|
||||
amount = amount.toFixed(0)
|
||||
}
|
||||
newSet.push({
|
||||
x: f.timeframe,
|
||||
y: amount
|
||||
})
|
||||
})
|
||||
return {data: newSet}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
1
go.mod
1
go.mod
|
@ -24,6 +24,7 @@ require (
|
|||
github.com/joho/godotenv v1.3.0
|
||||
github.com/lib/pq v1.2.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f
|
||||
github.com/russross/blackfriday/v2 v2.0.1
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
|
|
|
@ -30,7 +30,7 @@ func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
checkins := core.AllCheckins()
|
||||
for _, c := range checkins {
|
||||
c.Hits = c.AllHits()
|
||||
c.Failures = c.LimitedFailures(64)
|
||||
c.Failures = c.GetFailures(64)
|
||||
}
|
||||
returnJson(checkins, w, r)
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
checkin.Hits = checkin.LimitedHits(32)
|
||||
checkin.Failures = checkin.LimitedFailures(32)
|
||||
checkin.Failures = checkin.GetFailures(32)
|
||||
returnJson(checkin, w, r)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
err error
|
||||
code int
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
var (
|
||||
NewError = func(e error) Error {
|
||||
return Error{
|
||||
err: e,
|
||||
code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
NotFound = func(err error) Error {
|
||||
return Error{
|
||||
err: errors.Wrap(err, "not found"),
|
||||
code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
Unauthorized = func(e error) Error {
|
||||
return Error{
|
||||
err: e,
|
||||
code: http.StatusUnauthorized,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
func RespondError(w http.ResponseWriter, err Error) {
|
||||
output := apiResponse{
|
||||
Status: "error",
|
||||
Error: err.Error(),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(err.code)
|
||||
json.NewEncoder(w).Encode(output)
|
||||
}
|
|
@ -4,12 +4,23 @@ import (
|
|||
"github.com/hunterlong/statping/core"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var (
|
||||
basePath = "/"
|
||||
)
|
||||
|
||||
func parseForm(r *http.Request) url.Values {
|
||||
r.ParseForm()
|
||||
return r.PostForm
|
||||
}
|
||||
|
||||
func parseGet(r *http.Request) url.Values {
|
||||
r.ParseForm()
|
||||
return r.Form
|
||||
}
|
||||
|
||||
var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"VERSION": func() string {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
package handlers
|
|
@ -21,6 +21,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/http"
|
||||
|
@ -151,56 +152,41 @@ func apiServiceRunningHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if service == nil {
|
||||
service, err := database.Service(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
groupQuery := parseGroupQuery(r)
|
||||
|
||||
obj := core.GraphHitsDataRaw(service, groupQuery, "latency")
|
||||
groupQuery := database.ParseQueries(r, service.Hits())
|
||||
|
||||
obj := core.GraphData(groupQuery, &types.Hit{}, database.ByAverage("latency"))
|
||||
returnJson(obj, w, r)
|
||||
}
|
||||
|
||||
func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if service == nil {
|
||||
service, err := database.Service(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
groupQuery := parseGroupQuery(r)
|
||||
groupQuery := database.ParseQueries(r, service.Failures())
|
||||
|
||||
obj := core.GraphFailuresDataRaw(service, groupQuery)
|
||||
obj := core.GraphData(groupQuery, &types.Failure{}, database.ByCount)
|
||||
returnJson(obj, w, r)
|
||||
}
|
||||
|
||||
func parseGroupQuery(r *http.Request) *types.GroupQuery {
|
||||
fields := parseGet(r)
|
||||
grouping := fields.Get("group")
|
||||
if grouping == "" {
|
||||
grouping = "hour"
|
||||
}
|
||||
startField := utils.ToInt(fields.Get("start"))
|
||||
endField := utils.ToInt(fields.Get("end"))
|
||||
|
||||
return &types.GroupQuery{
|
||||
Start: time.Unix(startField, 0).UTC(),
|
||||
End: time.Unix(endField, 0).UTC(),
|
||||
Group: grouping,
|
||||
}
|
||||
}
|
||||
|
||||
func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
service := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if service == nil {
|
||||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
service, err := database.Service(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
groupQuery := parseGroupQuery(r)
|
||||
groupQuery := database.ParseQueries(r, service.Hits())
|
||||
|
||||
obj := core.GraphHitsDataRaw(service, groupQuery, "ping_time")
|
||||
obj := core.GraphData(groupQuery, &types.Hit{}, database.ByAverage("ping_time"))
|
||||
returnJson(obj, w, r)
|
||||
}
|
||||
|
||||
|
@ -301,24 +287,26 @@ func servicesDeleteFailuresHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func apiServiceFailuresHandler(r *http.Request) interface{} {
|
||||
vars := mux.Vars(r)
|
||||
servicer := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if servicer == nil {
|
||||
|
||||
service, err := database.Service(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
return errors.New("service not found")
|
||||
}
|
||||
fails := servicer.LimitedFailures(100)
|
||||
|
||||
var fails []types.Failure
|
||||
service.Failures().Requests(r).Find(&fails)
|
||||
return fails
|
||||
}
|
||||
|
||||
func apiServiceHitsHandler(r *http.Request) interface{} {
|
||||
vars := mux.Vars(r)
|
||||
servicer := core.SelectService(utils.ToInt(vars["id"]))
|
||||
if servicer == nil {
|
||||
service, err := database.Service(utils.ToInt(vars["id"]))
|
||||
if err != nil {
|
||||
return errors.New("service not found")
|
||||
}
|
||||
hits, err := servicer.HitsDb(r).Hits()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hits []types.Hit
|
||||
service.Hits().Find(&hits)
|
||||
return hits
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
// Statping
|
||||
// Copyright (C) 2018. Hunter Long and the project contributors
|
||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
||||
//
|
||||
// https://github.com/hunterlong/statping
|
||||
//
|
||||
// The licenses for most software and other practical works are designed
|
||||
// to take away your freedom to share and change the works. By contrast,
|
||||
// the GNU General Public License is intended to guarantee your freedom to
|
||||
// share and change all versions of a program--to make sure it remains free
|
||||
// software for all its users.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func parseForm(r *http.Request) url.Values {
|
||||
r.ParseForm()
|
||||
return r.PostForm
|
||||
}
|
||||
|
||||
func parseGet(r *http.Request) url.Values {
|
||||
r.ParseForm()
|
||||
return r.Form
|
||||
}
|
|
@ -21,19 +21,19 @@ import (
|
|||
|
||||
// Checkin struct will allow an application to send a recurring HTTP GET to confirm a service is online
|
||||
type Checkin struct {
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
|
||||
Name string `gorm:"column:name" json:"name"`
|
||||
Interval int64 `gorm:"column:check_interval" json:"interval"`
|
||||
GracePeriod int64 `gorm:"column:grace_period" json:"grace"`
|
||||
ApiKey string `gorm:"column:api_key" json:"api_key"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
Running chan bool `gorm:"-" json:"-"`
|
||||
Failing bool `gorm:"-" json:"failing"`
|
||||
LastHit time.Time `gorm:"-" json:"last_hit"`
|
||||
Hits []*CheckinHit `gorm:"-" json:"hits"`
|
||||
Failures []FailureInterface `gorm:"-" json:"failures"`
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
ServiceId int64 `gorm:"index;column:service" json:"service_id"`
|
||||
Name string `gorm:"column:name" json:"name"`
|
||||
Interval int64 `gorm:"column:check_interval" json:"interval"`
|
||||
GracePeriod int64 `gorm:"column:grace_period" json:"grace"`
|
||||
ApiKey string `gorm:"column:api_key" json:"api_key"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
Running chan bool `gorm:"-" json:"-"`
|
||||
Failing bool `gorm:"-" json:"failing"`
|
||||
LastHit time.Time `gorm:"-" json:"last_hit"`
|
||||
Hits []*CheckinHit `gorm:"-" json:"hits"`
|
||||
Failures []*Failure `gorm:"-" json:"failures"`
|
||||
}
|
||||
|
||||
type CheckinInterface interface {
|
||||
|
|
|
@ -35,8 +35,6 @@ type Failure struct {
|
|||
|
||||
type FailureInterface interface {
|
||||
Select() *Failure
|
||||
Ago() string // Ago returns a human readable timestamp
|
||||
ParseError() string // ParseError returns a human readable error for a service failure
|
||||
}
|
||||
|
||||
// BeforeCreate for Failure will set CreatedAt to UTC
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -27,11 +28,41 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
NOW = func() time.Time { return time.Now().UTC() }()
|
||||
//HOUR_1_AGO = time.Now().Add(-1 * time.Hour)
|
||||
//HOUR_24_AGO = time.Now().Add(-24 * time.Hour)
|
||||
//HOUR_72_AGO = time.Now().Add(-72 * time.Hour)
|
||||
//DAY_7_AGO = NOW.AddDate(0, 0, -7)
|
||||
//MONTH_1_AGO = NOW.AddDate(0, -1, 0)
|
||||
//YEAR_1_AGO = NOW.AddDate(-1, 0, 0)
|
||||
Second = time.Second
|
||||
Minute = time.Minute
|
||||
Hour = time.Hour
|
||||
Day = Hour * 24
|
||||
Week = Day * 7
|
||||
Month = Week * 4
|
||||
Year = Day * 365
|
||||
)
|
||||
|
||||
func FixedTime(t time.Time, d time.Duration) string {
|
||||
switch d {
|
||||
case Month:
|
||||
month := fmt.Sprintf("%v", int(t.Month()))
|
||||
if int(t.Month()) < 10 {
|
||||
month = fmt.Sprintf("0%v", int(t.Month()))
|
||||
}
|
||||
return fmt.Sprintf("%v-%v-01T00:00:00Z", t.Year(), month)
|
||||
case Year:
|
||||
return fmt.Sprintf("%v-01-01T00:00:00Z", t.Year())
|
||||
default:
|
||||
return t.Format(durationStr(d))
|
||||
}
|
||||
}
|
||||
|
||||
func durationStr(d time.Duration) string {
|
||||
switch d {
|
||||
case Second:
|
||||
return "2006-01-02T15:04:05Z"
|
||||
case Minute:
|
||||
return "2006-01-02T15:04:00Z"
|
||||
case Hour:
|
||||
return "2006-01-02T15:00:00Z"
|
||||
case Day:
|
||||
return "2006-01-02T00:00:00Z"
|
||||
default:
|
||||
return "2006-01-02T00:00:00Z"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFixedTime(t *testing.T) {
|
||||
|
||||
timeVal, err := time.Parse("2006-01-02T15:04:05Z", "2020-05-22T06:02:13Z")
|
||||
require.Nil(t, err)
|
||||
|
||||
examples := []struct {
|
||||
Time time.Time
|
||||
Duration time.Duration
|
||||
Expected string
|
||||
}{{
|
||||
timeVal,
|
||||
time.Second,
|
||||
"2020-05-22T06:02:13Z",
|
||||
}, {
|
||||
timeVal,
|
||||
time.Minute,
|
||||
"2020-05-22T06:02:00Z",
|
||||
}, {
|
||||
timeVal,
|
||||
time.Hour,
|
||||
"2020-05-22T06:00:00Z",
|
||||
}, {
|
||||
timeVal,
|
||||
time.Hour * 24,
|
||||
"2020-05-22T00:00:00Z",
|
||||
}, {
|
||||
timeVal,
|
||||
time.Hour * 24 * 30,
|
||||
"2020-05-01T00:00:00Z",
|
||||
}}
|
||||
|
||||
for _, e := range examples {
|
||||
assert.Equal(t, e.Expected, FixedTime(e.Time, e.Duration), fmt.Sprintf("reformating for: %v %v", e.Time, e.Duration))
|
||||
}
|
||||
|
||||
}
|
|
@ -19,13 +19,6 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
type GroupQuery struct {
|
||||
Id int64
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Group string
|
||||
}
|
||||
|
||||
// Hit struct is a 'successful' ping or web response entry for a service.
|
||||
type Hit struct {
|
||||
Id int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
|
@ -49,7 +42,7 @@ type DbConfig struct {
|
|||
DbHost string `yaml:"host" json:"-"`
|
||||
DbUser string `yaml:"user" json:"-"`
|
||||
DbPass string `yaml:"password" json:"-"`
|
||||
DbData string `yaml:"Db" json:"-"`
|
||||
DbData string `yaml:"database" json:"-"`
|
||||
DbPort int64 `yaml:"port" json:"-"`
|
||||
ApiKey string `yaml:"api_key" json:"-"`
|
||||
ApiSecret string `yaml:"api_secret" json:"-"`
|
||||
|
|
Loading…
Reference in New Issue