mirror of https://github.com/statping/statping
pull/429/head
parent
05c77a5e98
commit
871424f9c3
|
@ -19,6 +19,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-yaml/yaml"
|
"github.com/go-yaml/yaml"
|
||||||
"github.com/hunterlong/statping/core/notifier"
|
"github.com/hunterlong/statping/core/notifier"
|
||||||
|
"github.com/hunterlong/statping/database"
|
||||||
"github.com/hunterlong/statping/types"
|
"github.com/hunterlong/statping/types"
|
||||||
"github.com/hunterlong/statping/utils"
|
"github.com/hunterlong/statping/utils"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
@ -32,7 +33,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DbSession stores the Statping database session
|
// DbSession stores the Statping database session
|
||||||
DbSession types.Database
|
DbSession database.Database
|
||||||
DbModels []interface{}
|
DbModels []interface{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ func init() {
|
||||||
// DbConfig stores the config.yml file for the statup configuration
|
// DbConfig stores the config.yml file for the statup configuration
|
||||||
type DbConfig types.DbConfig
|
type DbConfig types.DbConfig
|
||||||
|
|
||||||
func Database(obj interface{}) types.Database {
|
func Database(obj interface{}) database.Database {
|
||||||
switch obj.(type) {
|
switch obj.(type) {
|
||||||
case *types.Service, *Service, []*Service:
|
case *types.Service, *Service, []*Service:
|
||||||
return DbSession.Model(&types.Service{})
|
return DbSession.Model(&types.Service{})
|
||||||
|
@ -77,7 +78,7 @@ func Database(obj interface{}) types.Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HitsBetween returns the gorm database query for a collection of service hits between a time range
|
// 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) types.Database {
|
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) database.Database {
|
||||||
selector := Dbtimestamp(group, column)
|
selector := Dbtimestamp(group, column)
|
||||||
if CoreApp.Config.DbConn == "postgres" {
|
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))
|
return Database(&Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
|
||||||
|
@ -87,7 +88,7 @@ func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) typ
|
||||||
}
|
}
|
||||||
|
|
||||||
// FailuresBetween returns the gorm database query for a collection of service hits between a time range
|
// 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) types.Database {
|
func (s *Service) FailuresBetween(t1, t2 time.Time, group string, column string) database.Database {
|
||||||
selector := Dbtimestamp(group, column)
|
selector := Dbtimestamp(group, column)
|
||||||
if CoreApp.Config.DbConn == "postgres" {
|
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))
|
return Database(&Failure{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME))
|
||||||
|
@ -226,7 +227,7 @@ func (c *Core) Connect(retry bool, location string) error {
|
||||||
conn = fmt.Sprintf("sqlserver://%v:%v@%v?database=%v", CoreApp.Config.DbUser, CoreApp.Config.DbPass, host, CoreApp.Config.DbData)
|
conn = fmt.Sprintf("sqlserver://%v:%v@%v?database=%v", CoreApp.Config.DbUser, CoreApp.Config.DbPass, host, CoreApp.Config.DbData)
|
||||||
}
|
}
|
||||||
log.WithFields(utils.ToFields(c, conn)).Debugln("attempting to connect to database")
|
log.WithFields(utils.ToFields(c, conn)).Debugln("attempting to connect to database")
|
||||||
dbSession, err := types.Openw(dbType, conn)
|
dbSession, err := database.Openw(dbType, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugln(fmt.Sprintf("Database connection error %v", err))
|
log.Debugln(fmt.Sprintf("Database connection error %v", err))
|
||||||
if retry {
|
if retry {
|
||||||
|
|
|
@ -18,6 +18,7 @@ package core
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ararog/timeago"
|
"github.com/ararog/timeago"
|
||||||
|
"github.com/hunterlong/statping/database"
|
||||||
"github.com/hunterlong/statping/types"
|
"github.com/hunterlong/statping/types"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -53,7 +54,7 @@ func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
|
||||||
// AllFailures will return all failures attached to a service
|
// AllFailures will return all failures attached to a service
|
||||||
func (s *Service) AllFailures() []types.Failure {
|
func (s *Service) AllFailures() []types.Failure {
|
||||||
var fails []types.Failure
|
var fails []types.Failure
|
||||||
err := DbSession.Failures(s.Id).Find(&fails)
|
err := Database(&types.Failure{}).Find(&fails)
|
||||||
if err.Error() != nil {
|
if err.Error() != nil {
|
||||||
log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
|
||||||
return nil
|
return nil
|
||||||
|
@ -61,7 +62,7 @@ func (s *Service) AllFailures() []types.Failure {
|
||||||
return fails
|
return fails
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) FailuresDb(r *http.Request) types.Database {
|
func (s *Service) FailuresDb(r *http.Request) database.Database {
|
||||||
return Database(&types.Failure{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
|
return Database(&types.Failure{}).Where("service = ?", s.Id).QuerySearch(r).Order("id desc")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/hunterlong/statping/database"
|
||||||
"github.com/hunterlong/statping/types"
|
"github.com/hunterlong/statping/types"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
@ -51,7 +52,7 @@ func (s *Service) HitsQuery(r *http.Request) ([]*types.Hit, error) {
|
||||||
return hits, err.Error()
|
return hits, err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) HitsDb(r *http.Request) types.Database {
|
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).QuerySearch(r).Order("id desc")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/hunterlong/statping/database"
|
||||||
"github.com/hunterlong/statping/types"
|
"github.com/hunterlong/statping/types"
|
||||||
"github.com/hunterlong/statping/utils"
|
"github.com/hunterlong/statping/utils"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +27,7 @@ import (
|
||||||
var (
|
var (
|
||||||
Integrations []types.Integrator
|
Integrations []types.Integrator
|
||||||
log = utils.Log.WithField("type", "integration")
|
log = utils.Log.WithField("type", "integration")
|
||||||
db types.Database
|
db database.Database
|
||||||
)
|
)
|
||||||
|
|
||||||
//func init() {
|
//func init() {
|
||||||
|
@ -38,12 +39,12 @@ var (
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// integrationsDb returns the 'integrations' database column
|
// integrationsDb returns the 'integrations' database column
|
||||||
func integrationsDb() types.Database {
|
func integrationsDb() database.Database {
|
||||||
return db.Model(&types.Integration{})
|
return db.Model(&types.Integration{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDB is called by core to inject the database for a integrator to use
|
// SetDB is called by core to inject the database for a integrator to use
|
||||||
func SetDB(d types.Database) {
|
func SetDB(d database.Database) {
|
||||||
db = d
|
db = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +107,7 @@ func Find(name string) (types.Integrator, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// db will return the notifier database column/record
|
// db will return the notifier database column/record
|
||||||
func integratorDb(n *types.Integration) types.Database {
|
func integratorDb(n *types.Integration) database.Database {
|
||||||
return db.Model(&types.Integration{}).Where("name = ?", n.Name).Find(n)
|
return db.Model(&types.Integration{}).Where("name = ?", n.Name).Find(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/hunterlong/statping/database"
|
||||||
"github.com/hunterlong/statping/types"
|
"github.com/hunterlong/statping/types"
|
||||||
"github.com/hunterlong/statping/utils"
|
"github.com/hunterlong/statping/utils"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -30,7 +31,7 @@ var (
|
||||||
// AllCommunications holds all the loaded notifiers
|
// AllCommunications holds all the loaded notifiers
|
||||||
AllCommunications []types.AllNotifiers
|
AllCommunications []types.AllNotifiers
|
||||||
// db holds the Statping database connection
|
// db holds the Statping database connection
|
||||||
db types.Database
|
db database.Database
|
||||||
timezone float32
|
timezone float32
|
||||||
log = utils.Log.WithField("type", "notifier")
|
log = utils.Log.WithField("type", "notifier")
|
||||||
)
|
)
|
||||||
|
@ -111,12 +112,12 @@ func (n *Notification) CanTest() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// db will return the notifier database column/record
|
// db will return the notifier database column/record
|
||||||
func modelDb(n *Notification) types.Database {
|
func modelDb(n *Notification) database.Database {
|
||||||
return db.Model(&Notification{}).Where("method = ?", n.Method).Find(n)
|
return db.Model(&Notification{}).Where("method = ?", n.Method).Find(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDB is called by core to inject the database for a notifier to use
|
// SetDB is called by core to inject the database for a notifier to use
|
||||||
func SetDB(d types.Database) {
|
func SetDB(d database.Database) {
|
||||||
db = d
|
db = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ararog/timeago"
|
"github.com/ararog/timeago"
|
||||||
"github.com/hunterlong/statping/core/notifier"
|
"github.com/hunterlong/statping/core/notifier"
|
||||||
|
"github.com/hunterlong/statping/database"
|
||||||
"github.com/hunterlong/statping/types"
|
"github.com/hunterlong/statping/types"
|
||||||
"github.com/hunterlong/statping/utils"
|
"github.com/hunterlong/statping/utils"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -268,41 +269,37 @@ func (s *Service) Downtime() time.Duration {
|
||||||
|
|
||||||
// DateScanObj struct is for creating the charts.js graph JSON array
|
// DateScanObj struct is for creating the charts.js graph JSON array
|
||||||
type DateScanObj struct {
|
type DateScanObj struct {
|
||||||
Array []*types.DateScan `json:"data"`
|
Array []*database.DateScan `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphDataRaw will return all the hits between 2 times for a Service
|
// GraphDataRaw will return all the hits between 2 times for a Service
|
||||||
func GraphHitsDataRaw(service types.ServiceInterface, query types.GroupQuery, column string) *DateScanObj {
|
func GraphHitsDataRaw(service types.ServiceInterface, query *types.GroupQuery, column string) []*database.TimeValue {
|
||||||
srv := service.(*Service)
|
srv := service.(*Service)
|
||||||
|
|
||||||
dbQuery := Database(&types.Hit{}).
|
dbQuery, err := Database(&types.Hit{}).
|
||||||
Where("service = ?", srv.Id).
|
Where("service = ?", srv.Id).
|
||||||
Between(query.Start, query.End).
|
GroupQuery(query).ToTimeValue()
|
||||||
MultipleSelects(Database(&types.Hit{}).SelectByTime(query.Group), types.CountAmount()).
|
|
||||||
GroupByTimeframe()
|
|
||||||
|
|
||||||
outgoing, err := dbQuery.ToChart()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return &DateScanObj{outgoing}
|
return dbQuery.FillMissing()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphDataRaw will return all the hits between 2 times for a Service
|
// GraphDataRaw will return all the hits between 2 times for a Service
|
||||||
func GraphFailuresDataRaw(service types.ServiceInterface, query types.GroupQuery) []*types.TimeValue {
|
func GraphFailuresDataRaw(service types.ServiceInterface, query *types.GroupQuery) []*database.TimeValue {
|
||||||
srv := service.(*Service)
|
srv := service.(*Service)
|
||||||
|
|
||||||
dbQuery := Database(&types.Failure{}).
|
dbQuery, err := Database(&types.Failure{}).
|
||||||
Where("service = ?", srv.Id).
|
Where("service = ?", srv.Id).
|
||||||
Between(query.Start, query.End).
|
GroupQuery(query).ToTimeValue()
|
||||||
MultipleSelects(Database(&types.Failure{}).SelectByTime(query.Group), types.CountAmount()).
|
|
||||||
GroupByTimeframe()
|
|
||||||
|
|
||||||
outgoing, err := dbQuery.ToTimeValue(query.Start, query.End)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return outgoing
|
return dbQuery.FillMissing()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToString will convert the DateScanObj into a JSON string for the charts to render
|
// ToString will convert the DateScanObj into a JSON string for the charts to render
|
||||||
|
|
|
@ -1,23 +1,9 @@
|
||||||
// Statup
|
package database
|
||||||
// Copyright (C) 2020. Hunter Long and the project contributors
|
|
||||||
// Written by Hunter Long <info@socialeck.com> and the project contributors
|
|
||||||
//
|
|
||||||
// https://github.com/hunterlong/statup
|
|
||||||
//
|
|
||||||
// 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 types
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/hunterlong/statping/types"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||||
|
@ -28,6 +14,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TIME_NANO = "2006-01-02T15:04:05Z"
|
||||||
|
TIME = "2006-01-02 15:04:05"
|
||||||
|
CHART_TIME = "2006-01-02T15:04:05.999999-07:00"
|
||||||
|
TIME_DAY = "2006-01-02"
|
||||||
|
)
|
||||||
|
|
||||||
// Database is an interface which DB implements
|
// Database is an interface which DB implements
|
||||||
type Database interface {
|
type Database interface {
|
||||||
Close() error
|
Close() error
|
||||||
|
@ -110,58 +103,16 @@ type Database interface {
|
||||||
|
|
||||||
Since(time.Time) Database
|
Since(time.Time) Database
|
||||||
Between(time.Time, time.Time) Database
|
Between(time.Time, time.Time) Database
|
||||||
Hits() ([]*Hit, error)
|
Hits() ([]*types.Hit, error)
|
||||||
ToChart() ([]*DateScan, error)
|
ToChart() ([]*DateScan, error)
|
||||||
|
|
||||||
GroupByTimeframe() Database
|
|
||||||
ToTimeValue(time.Time, time.Time) ([]*TimeValue, error)
|
|
||||||
SelectByTime(string) string
|
SelectByTime(string) string
|
||||||
MultipleSelects(args ...string) Database
|
MultipleSelects(args ...string) Database
|
||||||
|
|
||||||
Failurer
|
FormatTime(t time.Time) string
|
||||||
}
|
ParseTime(t string) (time.Time, error)
|
||||||
|
|
||||||
type Failurer interface {
|
GroupQuery(query *types.GroupQuery) GroupByer
|
||||||
Failures(id int64) Database
|
|
||||||
Fails() ([]*Failure, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mysqlTimestamps(increment string) string {
|
|
||||||
switch increment {
|
|
||||||
case "second":
|
|
||||||
return "%Y-%m-%d %H:%i:%S"
|
|
||||||
case "minute":
|
|
||||||
return "%Y-%m-%d %H:%i:00"
|
|
||||||
case "hour":
|
|
||||||
return "%Y-%m-%d %H:00:00"
|
|
||||||
case "day":
|
|
||||||
return "%Y-%m-%d 00:00:00"
|
|
||||||
case "month":
|
|
||||||
return "%Y-%m 00:00:00"
|
|
||||||
case "year":
|
|
||||||
return "%Y"
|
|
||||||
default:
|
|
||||||
return "%Y-%m-%d 00:00:00"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sqliteTimestamps(increment string) string {
|
|
||||||
switch increment {
|
|
||||||
case "second":
|
|
||||||
return "%Y-%m-%d %H:%M:%S"
|
|
||||||
case "minute":
|
|
||||||
return "%Y-%m-%d %H:%M:00"
|
|
||||||
case "hour":
|
|
||||||
return "%Y-%m-%d %H:00:00"
|
|
||||||
case "day":
|
|
||||||
return "%Y-%m-%d 00:00:00"
|
|
||||||
case "month":
|
|
||||||
return "%Y-%m 00:00:00"
|
|
||||||
case "year":
|
|
||||||
return "%Y"
|
|
||||||
default:
|
|
||||||
return "%Y-%m-%d 00:00:00"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Db) MultipleSelects(args ...string) Database {
|
func (it *Db) MultipleSelects(args ...string) Database {
|
||||||
|
@ -173,31 +124,6 @@ func CountAmount() string {
|
||||||
return fmt.Sprintf("COUNT(id) as amount")
|
return fmt.Sprintf("COUNT(id) as amount")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Db) SelectByTime(increment string) string {
|
|
||||||
switch it.Type {
|
|
||||||
case "mysql":
|
|
||||||
return fmt.Sprintf("CONCAT(date_format(created_at, '%s')) AS timeframe", mysqlTimestamps(increment))
|
|
||||||
case "postgres":
|
|
||||||
return fmt.Sprintf("date_trunc('%s', created_at) AS timeframe", increment)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("strftime('%s', created_at, 'utc') as timeframe", sqliteTimestamps(increment))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *Db) GroupByTimeframe() Database {
|
|
||||||
return it.Group("timeframe")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *Db) Failures(id int64) Database {
|
|
||||||
return it.Model(&Failure{}).Where("service = ?", id).Not("method = 'checkin'").Order("id desc")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *Db) Fails() ([]*Failure, error) {
|
|
||||||
var fails []*Failure
|
|
||||||
err := it.Find(&fails)
|
|
||||||
return fails, err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Db struct {
|
type Db struct {
|
||||||
Database *gorm.DB
|
Database *gorm.DB
|
||||||
Type string
|
Type string
|
||||||
|
@ -513,18 +439,18 @@ func (it *Db) Error() error {
|
||||||
return it.Database.Error
|
return it.Database.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Db) Hits() ([]*Hit, error) {
|
func (it *Db) Hits() ([]*types.Hit, error) {
|
||||||
var hits []*Hit
|
var hits []*types.Hit
|
||||||
err := it.Find(&hits)
|
err := it.Find(&hits)
|
||||||
return hits, err.Error()
|
return hits, err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Db) Since(ago time.Time) Database {
|
func (it *Db) Since(ago time.Time) Database {
|
||||||
return it.Where("created_at > ?", ago.UTC().Format(TIME))
|
return it.Where("created_at > ?", it.FormatTime(ago))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Db) Between(t1 time.Time, t2 time.Time) Database {
|
func (it *Db) Between(t1 time.Time, t2 time.Time) Database {
|
||||||
return it.Where("created_at BETWEEN ? AND ?", t1.UTC().Format(TIME), t2.UTC().Format(TIME))
|
return it.Where("created_at BETWEEN ? AND ?", it.FormatTime(t1), it.FormatTime(t2))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DateScan struct is for creating the charts.js graph JSON array
|
// DateScan struct is for creating the charts.js graph JSON array
|
||||||
|
@ -538,75 +464,6 @@ type TimeValue struct {
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Db) ToTimeValue(start, end time.Time) ([]*TimeValue, error) {
|
|
||||||
rows, err := it.Database.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, _ := time.Parse(TIME, timeframe)
|
|
||||||
fmt.Println("got: ", createdTime.UTC(), amount)
|
|
||||||
data = append(data, &TimeValue{
|
|
||||||
Timeframe: createdTime.UTC(),
|
|
||||||
Amount: amount,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return it.fillMissing(data, start, end), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *Db) FormatTime(t time.Time) string {
|
|
||||||
switch it.Type {
|
|
||||||
case "mysql":
|
|
||||||
return t.UTC().Format("2006-01-02T00:00:00Z")
|
|
||||||
case "postgres":
|
|
||||||
return t.UTC().Format("2006-01-02T00:00:00Z")
|
|
||||||
default:
|
|
||||||
return t.UTC().Format("2006-01-02T00:00:00Z")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func reparseTime(t string) time.Time {
|
|
||||||
re, _ := time.Parse("2006-01-02T00:00:00Z", t)
|
|
||||||
return re.UTC()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *Db) fillMissing(vals []*TimeValue, start, end time.Time) []*TimeValue {
|
|
||||||
timeMap := make(map[string]*TimeValue)
|
|
||||||
var validSet []*TimeValue
|
|
||||||
|
|
||||||
for _, v := range vals {
|
|
||||||
timeMap[it.FormatTime(v.Timeframe)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
current := start.UTC()
|
|
||||||
maxTime := end
|
|
||||||
for {
|
|
||||||
amount := int64(0)
|
|
||||||
currentStr := it.FormatTime(current)
|
|
||||||
if timeMap[currentStr] != nil {
|
|
||||||
amount = timeMap[currentStr].Amount
|
|
||||||
}
|
|
||||||
|
|
||||||
validSet = append(validSet, &TimeValue{
|
|
||||||
Timeframe: reparseTime(currentStr),
|
|
||||||
Amount: amount,
|
|
||||||
})
|
|
||||||
|
|
||||||
if current.After(maxTime) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
current = current.Add(24 * time.Hour)
|
|
||||||
}
|
|
||||||
|
|
||||||
return validSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *Db) ToChart() ([]*DateScan, error) {
|
func (it *Db) ToChart() ([]*DateScan, error) {
|
||||||
rows, err := it.Database.Rows()
|
rows, err := it.Database.Rows()
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -0,0 +1,129 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hunterlong/statping/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupBy struct {
|
||||||
|
db Database
|
||||||
|
query *types.GroupQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupByer interface {
|
||||||
|
ToTimeValue() (*TimeVar, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupMethod interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ByCount = func() GroupMethod {
|
||||||
|
return fmt.Sprintf("COUNT(id) as amount")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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(query *types.GroupQuery) GroupByer {
|
||||||
|
return &GroupBy{execute(db, query), query}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeVar struct {
|
||||||
|
g *GroupBy
|
||||||
|
data []*TimeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GroupBy) ToTimeValue() (*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)
|
||||||
|
data = append(data, &TimeValue{
|
||||||
|
Timeframe: createdTime,
|
||||||
|
Amount: amount,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
return &TimeVar{g, data}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TimeVar) Values() []*TimeValue {
|
||||||
|
var validSet []*TimeValue
|
||||||
|
for _, v := range t.data {
|
||||||
|
validSet = append(validSet, &TimeValue{
|
||||||
|
Timeframe: v.Timeframe,
|
||||||
|
Amount: v.Amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return validSet
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
validSet = append(validSet, &TimeValue{
|
||||||
|
Timeframe: current,
|
||||||
|
Amount: amount,
|
||||||
|
})
|
||||||
|
if current.After(maxTime) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
current = current.Add(t.g.duration())
|
||||||
|
}
|
||||||
|
|
||||||
|
return validSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GroupBy) duration() time.Duration {
|
||||||
|
switch g.query.Group {
|
||||||
|
case "second":
|
||||||
|
return time.Second
|
||||||
|
case "minute":
|
||||||
|
return time.Minute
|
||||||
|
case "hour":
|
||||||
|
return time.Hour
|
||||||
|
case "day":
|
||||||
|
return time.Hour * 24
|
||||||
|
case "month":
|
||||||
|
return time.Hour * 730
|
||||||
|
case "year":
|
||||||
|
return time.Hour * 8760
|
||||||
|
default:
|
||||||
|
return time.Hour
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import "github.com/hunterlong/statping/types"
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
db Database
|
||||||
|
group *types.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
type Groupser interface {
|
||||||
|
Services() Database
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Db) GetGroup(id int64) (Groupser, error) {
|
||||||
|
var group types.Group
|
||||||
|
query := it.Model(&types.Group{}).Where("id = ?", id).Find(&group)
|
||||||
|
return &Group{it, &group}, query.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Group) Services() Database {
|
||||||
|
return it.db.Model(&types.Service{}).Where("group = ?", it.group.Id)
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import "github.com/hunterlong/statping/types"
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
db Database
|
||||||
|
service *types.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
type Servicer interface {
|
||||||
|
Failures() Database
|
||||||
|
Hits() Database
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Db) GetService(id int64) (Servicer, error) {
|
||||||
|
var service types.Service
|
||||||
|
query := it.Model(&types.Service{}).Where("id = ?", id).Find(&service)
|
||||||
|
return &Service{it, &service}, query.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Failures() Database {
|
||||||
|
return s.db.Model(&types.Failure{}).Where("service = ?", s.service.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Hits() Database {
|
||||||
|
return s.db.Model(&types.Hit{}).Where("service = ?", s.service.Id)
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeGroup interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Db) ParseTime(t string) (time.Time, error) {
|
||||||
|
switch it.Type {
|
||||||
|
case "mysql":
|
||||||
|
return time.Parse("2006-01-02 15:04:05", t)
|
||||||
|
case "postgres":
|
||||||
|
return time.Parse("2006-01-02T15:04:05Z", t)
|
||||||
|
default:
|
||||||
|
return time.Parse("2006-01-02 15:04:05", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Db) FormatTime(t time.Time) string {
|
||||||
|
switch it.Type {
|
||||||
|
case "mysql":
|
||||||
|
return t.UTC().Format("2006-01-02 15:04:05")
|
||||||
|
case "postgres":
|
||||||
|
return t.UTC().Format("2006-01-02 15:04:05.999999999")
|
||||||
|
default:
|
||||||
|
return t.UTC().Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Db) SelectByTime(increment string) string {
|
||||||
|
switch it.Type {
|
||||||
|
case "mysql":
|
||||||
|
return fmt.Sprintf("CONCAT(date_format(created_at, '%s')) AS timeframe", it.correctTimestamp(increment))
|
||||||
|
case "postgres":
|
||||||
|
return fmt.Sprintf("date_trunc('%s', created_at) AS timeframe", increment)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("strftime('%s', created_at, 'utc') as timeframe", it.correctTimestamp(increment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Db) correctTimestamp(increment string) string {
|
||||||
|
var timestamper string
|
||||||
|
switch increment {
|
||||||
|
case "second":
|
||||||
|
timestamper = "%Y-%m-%d %H:%M:%S"
|
||||||
|
case "minute":
|
||||||
|
timestamper = "%Y-%m-%d %H:%M:00"
|
||||||
|
case "hour":
|
||||||
|
timestamper = "%Y-%m-%d %H:00:00"
|
||||||
|
case "day":
|
||||||
|
timestamper = "%Y-%m-%d 00:00:00"
|
||||||
|
case "month":
|
||||||
|
timestamper = "%Y-%m-01 00:00:00"
|
||||||
|
case "year":
|
||||||
|
timestamper = "%Y-01-01 00:00:00"
|
||||||
|
default:
|
||||||
|
timestamper = "%Y-%m-%d 00:00:00"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch it.Type {
|
||||||
|
case "mysql":
|
||||||
|
case "second":
|
||||||
|
timestamper = "%Y-%m-%d %H:%i:%S"
|
||||||
|
case "minute":
|
||||||
|
timestamper = "%Y-%m-%d %H:%i:00"
|
||||||
|
}
|
||||||
|
|
||||||
|
return timestamper
|
||||||
|
}
|
|
@ -126,14 +126,18 @@
|
||||||
visible: function(newVal, oldVal) {
|
visible: function(newVal, oldVal) {
|
||||||
if (newVal && !this.showing) {
|
if (newVal && !this.showing) {
|
||||||
this.showing = true
|
this.showing = true
|
||||||
this.chartHits()
|
this.chartHits("hour")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async chartHits() {
|
async chartHits(group) {
|
||||||
const start = this.nowSubtract((3600 * 24) * 7)
|
const start = this.nowSubtract((3600 * 24) * 7)
|
||||||
this.data = await Api.service_hits(this.service.id, this.toUnix(start), this.toUnix(new Date()), "hour")
|
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")
|
||||||
|
}
|
||||||
this.series = [{
|
this.series = [{
|
||||||
name: this.service.name,
|
name: this.service.name,
|
||||||
...this.data
|
...this.data
|
||||||
|
|
|
@ -175,7 +175,7 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
returnJson(obj, w, r)
|
returnJson(obj, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGroupQuery(r *http.Request) types.GroupQuery {
|
func parseGroupQuery(r *http.Request) *types.GroupQuery {
|
||||||
fields := parseGet(r)
|
fields := parseGet(r)
|
||||||
grouping := fields.Get("group")
|
grouping := fields.Get("group")
|
||||||
if grouping == "" {
|
if grouping == "" {
|
||||||
|
@ -184,7 +184,7 @@ func parseGroupQuery(r *http.Request) types.GroupQuery {
|
||||||
startField := utils.ToInt(fields.Get("start"))
|
startField := utils.ToInt(fields.Get("start"))
|
||||||
endField := utils.ToInt(fields.Get("end"))
|
endField := utils.ToInt(fields.Get("end"))
|
||||||
|
|
||||||
return types.GroupQuery{
|
return &types.GroupQuery{
|
||||||
Start: time.Unix(startField, 0).UTC(),
|
Start: time.Unix(startField, 0).UTC(),
|
||||||
End: time.Unix(endField, 0).UTC(),
|
End: time.Unix(endField, 0).UTC(),
|
||||||
Group: grouping,
|
Group: grouping,
|
||||||
|
@ -305,10 +305,7 @@ func apiServiceFailuresHandler(r *http.Request) interface{} {
|
||||||
if servicer == nil {
|
if servicer == nil {
|
||||||
return errors.New("service not found")
|
return errors.New("service not found")
|
||||||
}
|
}
|
||||||
fails, err := servicer.FailuresDb(r).Fails()
|
fails := servicer.LimitedFailures(100)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fails
|
return fails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue