mirror of https://github.com/statping/statping
				
				
				
			
			
			
			
				pull/429/head
			
			
		
		
							parent
							
								
									05c77a5e98
								
							
						
					
					
						commit
						871424f9c3
					
				| 
						 | 
				
			
			@ -19,6 +19,7 @@ import (
 | 
			
		|||
	"fmt"
 | 
			
		||||
	"github.com/go-yaml/yaml"
 | 
			
		||||
	"github.com/hunterlong/statping/core/notifier"
 | 
			
		||||
	"github.com/hunterlong/statping/database"
 | 
			
		||||
	"github.com/hunterlong/statping/types"
 | 
			
		||||
	"github.com/hunterlong/statping/utils"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +33,7 @@ import (
 | 
			
		|||
 | 
			
		||||
var (
 | 
			
		||||
	// DbSession stores the Statping database session
 | 
			
		||||
	DbSession types.Database
 | 
			
		||||
	DbSession database.Database
 | 
			
		||||
	DbModels  []interface{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +48,7 @@ func init() {
 | 
			
		|||
// DbConfig stores the config.yml file for the statup configuration
 | 
			
		||||
type DbConfig types.DbConfig
 | 
			
		||||
 | 
			
		||||
func Database(obj interface{}) types.Database {
 | 
			
		||||
func Database(obj interface{}) database.Database {
 | 
			
		||||
	switch obj.(type) {
 | 
			
		||||
	case *types.Service, *Service, []*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
 | 
			
		||||
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)
 | 
			
		||||
	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))
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
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)
 | 
			
		||||
	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))
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
	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 {
 | 
			
		||||
		log.Debugln(fmt.Sprintf("Database connection error %v", err))
 | 
			
		||||
		if retry {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ package core
 | 
			
		|||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/ararog/timeago"
 | 
			
		||||
	"github.com/hunterlong/statping/database"
 | 
			
		||||
	"github.com/hunterlong/statping/types"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"sort"
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +54,7 @@ func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
 | 
			
		|||
// AllFailures will return all failures attached to a service
 | 
			
		||||
func (s *Service) AllFailures() []types.Failure {
 | 
			
		||||
	var fails []types.Failure
 | 
			
		||||
	err := DbSession.Failures(s.Id).Find(&fails)
 | 
			
		||||
	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
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +62,7 @@ func (s *Service) AllFailures() []types.Failure {
 | 
			
		|||
	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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@
 | 
			
		|||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/hunterlong/statping/database"
 | 
			
		||||
	"github.com/hunterlong/statping/types"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +52,7 @@ func (s *Service) HitsQuery(r *http.Request) ([]*types.Hit, 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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ import (
 | 
			
		|||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/hunterlong/statping/database"
 | 
			
		||||
	"github.com/hunterlong/statping/types"
 | 
			
		||||
	"github.com/hunterlong/statping/utils"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +27,7 @@ import (
 | 
			
		|||
var (
 | 
			
		||||
	Integrations []types.Integrator
 | 
			
		||||
	log          = utils.Log.WithField("type", "integration")
 | 
			
		||||
	db           types.Database
 | 
			
		||||
	db           database.Database
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//func init() {
 | 
			
		||||
| 
						 | 
				
			
			@ -38,12 +39,12 @@ var (
 | 
			
		|||
//}
 | 
			
		||||
 | 
			
		||||
// integrationsDb returns the 'integrations' database column
 | 
			
		||||
func integrationsDb() types.Database {
 | 
			
		||||
func integrationsDb() database.Database {
 | 
			
		||||
	return db.Model(&types.Integration{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +107,7 @@ func Find(name string) (types.Integrator, error) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ import (
 | 
			
		|||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/hunterlong/statping/database"
 | 
			
		||||
	"github.com/hunterlong/statping/types"
 | 
			
		||||
	"github.com/hunterlong/statping/utils"
 | 
			
		||||
	"reflect"
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +31,7 @@ var (
 | 
			
		|||
	// AllCommunications holds all the loaded notifiers
 | 
			
		||||
	AllCommunications []types.AllNotifiers
 | 
			
		||||
	// db holds the Statping database connection
 | 
			
		||||
	db       types.Database
 | 
			
		||||
	db       database.Database
 | 
			
		||||
	timezone float32
 | 
			
		||||
	log      = utils.Log.WithField("type", "notifier")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -111,12 +112,12 @@ func (n *Notification) CanTest() bool {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ import (
 | 
			
		|||
	"fmt"
 | 
			
		||||
	"github.com/ararog/timeago"
 | 
			
		||||
	"github.com/hunterlong/statping/core/notifier"
 | 
			
		||||
	"github.com/hunterlong/statping/database"
 | 
			
		||||
	"github.com/hunterlong/statping/types"
 | 
			
		||||
	"github.com/hunterlong/statping/utils"
 | 
			
		||||
	"sort"
 | 
			
		||||
| 
						 | 
				
			
			@ -268,41 +269,37 @@ func (s *Service) Downtime() time.Duration {
 | 
			
		|||
 | 
			
		||||
// DateScanObj struct is for creating the charts.js graph JSON array
 | 
			
		||||
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
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
	dbQuery := Database(&types.Hit{}).
 | 
			
		||||
	dbQuery, err := Database(&types.Hit{}).
 | 
			
		||||
		Where("service = ?", srv.Id).
 | 
			
		||||
		Between(query.Start, query.End).
 | 
			
		||||
		MultipleSelects(Database(&types.Hit{}).SelectByTime(query.Group), types.CountAmount()).
 | 
			
		||||
		GroupByTimeframe()
 | 
			
		||||
		GroupQuery(query).ToTimeValue()
 | 
			
		||||
 | 
			
		||||
	outgoing, err := dbQuery.ToChart()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return &DateScanObj{outgoing}
 | 
			
		||||
	return dbQuery.FillMissing()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
 | 
			
		||||
	dbQuery := Database(&types.Failure{}).
 | 
			
		||||
	dbQuery, err := Database(&types.Failure{}).
 | 
			
		||||
		Where("service = ?", srv.Id).
 | 
			
		||||
		Between(query.Start, query.End).
 | 
			
		||||
		MultipleSelects(Database(&types.Failure{}).SelectByTime(query.Group), types.CountAmount()).
 | 
			
		||||
		GroupByTimeframe()
 | 
			
		||||
		GroupQuery(query).ToTimeValue()
 | 
			
		||||
 | 
			
		||||
	outgoing, err := dbQuery.ToTimeValue(query.Start, query.End)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return outgoing
 | 
			
		||||
	return dbQuery.FillMissing()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToString will convert the DateScanObj into a JSON string for the charts to render
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,23 +1,9 @@
 | 
			
		|||
// Statup
 | 
			
		||||
// 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
 | 
			
		||||
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"
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +14,13 @@ import (
 | 
			
		|||
	"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
 | 
			
		||||
type Database interface {
 | 
			
		||||
	Close() error
 | 
			
		||||
| 
						 | 
				
			
			@ -110,58 +103,16 @@ type Database interface {
 | 
			
		|||
 | 
			
		||||
	Since(time.Time) Database
 | 
			
		||||
	Between(time.Time, time.Time) Database
 | 
			
		||||
	Hits() ([]*Hit, error)
 | 
			
		||||
	Hits() ([]*types.Hit, error)
 | 
			
		||||
	ToChart() ([]*DateScan, error)
 | 
			
		||||
 | 
			
		||||
	GroupByTimeframe() Database
 | 
			
		||||
	ToTimeValue(time.Time, time.Time) ([]*TimeValue, error)
 | 
			
		||||
	SelectByTime(string) string
 | 
			
		||||
	MultipleSelects(args ...string) Database
 | 
			
		||||
 | 
			
		||||
	Failurer
 | 
			
		||||
}
 | 
			
		||||
	FormatTime(t time.Time) string
 | 
			
		||||
	ParseTime(t string) (time.Time, error)
 | 
			
		||||
 | 
			
		||||
type Failurer interface {
 | 
			
		||||
	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"
 | 
			
		||||
	}
 | 
			
		||||
	GroupQuery(query *types.GroupQuery) GroupByer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (it *Db) MultipleSelects(args ...string) Database {
 | 
			
		||||
| 
						 | 
				
			
			@ -173,31 +124,6 @@ func CountAmount() string {
 | 
			
		|||
	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 {
 | 
			
		||||
	Database *gorm.DB
 | 
			
		||||
	Type     string
 | 
			
		||||
| 
						 | 
				
			
			@ -513,18 +439,18 @@ func (it *Db) Error() error {
 | 
			
		|||
	return it.Database.Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (it *Db) Hits() ([]*Hit, error) {
 | 
			
		||||
	var hits []*Hit
 | 
			
		||||
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 > ?", ago.UTC().Format(TIME))
 | 
			
		||||
	return it.Where("created_at > ?", it.FormatTime(ago))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -538,75 +464,6 @@ type TimeValue struct {
 | 
			
		|||
	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) {
 | 
			
		||||
	rows, err := it.Database.Rows()
 | 
			
		||||
	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) {
 | 
			
		||||
              if (newVal && !this.showing) {
 | 
			
		||||
                  this.showing = true
 | 
			
		||||
                  this.chartHits()
 | 
			
		||||
                  this.chartHits("hour")
 | 
			
		||||
              }
 | 
			
		||||
          }
 | 
			
		||||
      },
 | 
			
		||||
      methods: {
 | 
			
		||||
          async chartHits() {
 | 
			
		||||
          async chartHits(group) {
 | 
			
		||||
              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 = [{
 | 
			
		||||
                  name: this.service.name,
 | 
			
		||||
                  ...this.data
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -175,7 +175,7 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
	returnJson(obj, w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseGroupQuery(r *http.Request) types.GroupQuery {
 | 
			
		||||
func parseGroupQuery(r *http.Request) *types.GroupQuery {
 | 
			
		||||
	fields := parseGet(r)
 | 
			
		||||
	grouping := fields.Get("group")
 | 
			
		||||
	if grouping == "" {
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +184,7 @@ func parseGroupQuery(r *http.Request) types.GroupQuery {
 | 
			
		|||
	startField := utils.ToInt(fields.Get("start"))
 | 
			
		||||
	endField := utils.ToInt(fields.Get("end"))
 | 
			
		||||
 | 
			
		||||
	return types.GroupQuery{
 | 
			
		||||
	return &types.GroupQuery{
 | 
			
		||||
		Start: time.Unix(startField, 0).UTC(),
 | 
			
		||||
		End:   time.Unix(endField, 0).UTC(),
 | 
			
		||||
		Group: grouping,
 | 
			
		||||
| 
						 | 
				
			
			@ -305,10 +305,7 @@ func apiServiceFailuresHandler(r *http.Request) interface{} {
 | 
			
		|||
	if servicer == nil {
 | 
			
		||||
		return errors.New("service not found")
 | 
			
		||||
	}
 | 
			
		||||
	fails, err := servicer.FailuresDb(r).Fails()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	fails := servicer.LimitedFailures(100)
 | 
			
		||||
	return fails
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue