mirror of https://github.com/statping/statping
vue
parent
cb7787f26c
commit
05c77a5e98
|
@ -272,10 +272,16 @@ type DateScanObj struct {
|
|||
}
|
||||
|
||||
// GraphDataRaw will return all the hits between 2 times for a Service
|
||||
func GraphHitsDataRaw(service types.ServiceInterface, start, end time.Time, group string, column string) *DateScanObj {
|
||||
model := service.(*Service).HitsBetween(start, end, group, column)
|
||||
model = model.Order("timeframe asc", false).Group("timeframe")
|
||||
outgoing, err := model.ToChart()
|
||||
func GraphHitsDataRaw(service types.ServiceInterface, query types.GroupQuery, column string) *DateScanObj {
|
||||
srv := service.(*Service)
|
||||
|
||||
dbQuery := Database(&types.Hit{}).
|
||||
Where("service = ?", srv.Id).
|
||||
Between(query.Start, query.End).
|
||||
MultipleSelects(Database(&types.Hit{}).SelectByTime(query.Group), types.CountAmount()).
|
||||
GroupByTimeframe()
|
||||
|
||||
outgoing, err := dbQuery.ToChart()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
@ -283,16 +289,16 @@ func GraphHitsDataRaw(service types.ServiceInterface, start, end time.Time, grou
|
|||
}
|
||||
|
||||
// GraphDataRaw will return all the hits between 2 times for a Service
|
||||
func GraphFailuresDataRaw(service types.ServiceInterface, start, end time.Time, group string) []*types.TimeValue {
|
||||
func GraphFailuresDataRaw(service types.ServiceInterface, query types.GroupQuery) []*types.TimeValue {
|
||||
srv := service.(*Service)
|
||||
|
||||
query := Database(&types.Failure{}).
|
||||
dbQuery := Database(&types.Failure{}).
|
||||
Where("service = ?", srv.Id).
|
||||
Between(start, end).
|
||||
MultipleSelects(types.SelectByTime(group), types.CountAmount()).
|
||||
GroupByTimeframe().Debug()
|
||||
Between(query.Start, query.End).
|
||||
MultipleSelects(Database(&types.Failure{}).SelectByTime(query.Group), types.CountAmount()).
|
||||
GroupByTimeframe()
|
||||
|
||||
outgoing, err := query.ToTimeValue(start, end)
|
||||
outgoing, err := dbQuery.ToTimeValue(query.Start, query.End)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
|
|
@ -1,31 +1 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SparklineDayFailures returns a string array of daily service failures
|
||||
func (s *Service) SparklineDayFailures(days int) string {
|
||||
var arr []string
|
||||
ago := time.Now().UTC().Add((time.Duration(days) * -24) * time.Hour)
|
||||
for day := 1; day <= days; day++ {
|
||||
ago = ago.Add(24 * time.Hour)
|
||||
failures, _ := s.TotalFailuresOnDate(ago)
|
||||
arr = append(arr, utils.ToString(failures))
|
||||
}
|
||||
return "[" + strings.Join(arr, ",") + "]"
|
||||
}
|
||||
|
||||
// SparklineHourResponse returns a string array for the average response or ping time for a service
|
||||
func (s *Service) SparklineHourResponse(hours int, method string) string {
|
||||
var arr []string
|
||||
end := time.Now().UTC()
|
||||
start := end.Add(time.Duration(-hours) * time.Hour)
|
||||
obj := GraphHitsDataRaw(s, start, end, "hour", method)
|
||||
for _, v := range obj.Array {
|
||||
arr = append(arr, utils.ToString(v.Value))
|
||||
}
|
||||
return "[" + strings.Join(arr, ",") + "]"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
version: '3.1'
|
||||
|
||||
volumes:
|
||||
mysql_data: {}
|
||||
postgres_data: {}
|
||||
|
||||
services:
|
||||
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
restart: always
|
||||
ports:
|
||||
- 3306:3306
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: password123
|
||||
MYSQL_DATABASE: statping
|
||||
MYSQL_USER: root
|
||||
MYSQL_PASSWORD: password
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres:10.0-alpine
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/pg_data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password123
|
||||
POSTGRES_DB: statping
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_PORT: 5432
|
||||
PGDATA: /var/lib/postgresql/data/pg_data
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin/phpmyadmin
|
||||
container_name: phpmyadmin
|
||||
environment:
|
||||
- PMA_HOST=mysql
|
||||
- PMA_USER=root
|
||||
- PMA_PASSWORD=password123
|
||||
restart: always
|
||||
depends_on:
|
||||
- mysql
|
||||
links:
|
||||
- mysql
|
||||
ports:
|
||||
- 7474:80
|
||||
volumes:
|
||||
- /sessions
|
|
@ -2,9 +2,52 @@ statping:
|
|||
container_name: statping
|
||||
image: hunterlong/statping
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./statping:/app
|
||||
- statping_data:/app
|
||||
environment:
|
||||
DB_CONN: sqlite
|
||||
DB_CONN: sqlite
|
||||
LETSENCRYPT_HOST: demo.statping.com
|
||||
VIRTUAL_HOST: demo.statping.com
|
||||
VIRTUAL_PORT: 8080
|
||||
|
||||
prometheus:
|
||||
container_name: prometheus
|
||||
image: prom/prometheus:v2.0.0
|
||||
restart: always
|
||||
ports:
|
||||
- 9090:9090
|
||||
volumes:
|
||||
- prometheus_config_data:/etc/prometheus/
|
||||
- prometheus_data:/prometheus
|
||||
links:
|
||||
- statping
|
||||
depends_on:
|
||||
- statping
|
||||
|
||||
grafana:
|
||||
container_name: grafana
|
||||
image: grafana/grafana
|
||||
restart: always
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=password123
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
depends_on:
|
||||
- prometheus
|
||||
links:
|
||||
- prometheus
|
||||
|
||||
|
||||
global:
|
||||
scrape_interval: 30s
|
||||
evaluation_interval: 30s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'statping'
|
||||
scrape_interval: 30s
|
||||
bearer_token: 'SECRET API KEY HERE'
|
||||
static_configs:
|
||||
- targets: ['statping:8080']
|
||||
|
|
|
@ -37,7 +37,7 @@ class Api {
|
|||
}
|
||||
|
||||
async service_hits(id, start, end, group) {
|
||||
return axios.get('/api/services/' + id + '/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).then(response => (response.data))
|
||||
}
|
||||
|
||||
async service_failures_data(id, start, end, group) {
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.set1 = await this.getHits(24 * 2, "hour")
|
||||
this.set1 = await this.getHits(24, "minute")
|
||||
this.set1_name = this.calc(this.set1)
|
||||
this.set2 = await this.getHits(24 * 7, "hour")
|
||||
this.set2_name = this.calc(this.set2)
|
||||
|
|
|
@ -126,9 +126,9 @@ func Router() *mux.Router {
|
|||
api.Handle("/api/services/{id}/hits", scoped(apiServiceHitsHandler)).Methods("GET")
|
||||
|
||||
// API SERVICE CHART DATA Routes
|
||||
api.Handle("/api/services/{id}/data", cached("30s", "application/json", apiServiceDataHandler)).Methods("GET")
|
||||
api.Handle("/api/services/{id}/hits_data", cached("30s", "application/json", apiServiceDataHandler)).Methods("GET")
|
||||
api.Handle("/api/services/{id}/failure_data", cached("30s", "application/json", apiServiceFailureDataHandler)).Methods("GET")
|
||||
api.Handle("/api/services/{id}/ping", cached("30s", "application/json", apiServicePingDataHandler)).Methods("GET")
|
||||
api.Handle("/api/services/{id}/ping_data", cached("30s", "application/json", apiServicePingDataHandler)).Methods("GET")
|
||||
api.Handle("/api/services/{id}/heatmap", cached("30s", "application/json", apiServiceHeatmapHandler)).Methods("GET")
|
||||
|
||||
// API INCIDENTS Routes
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/hunterlong/statping/types"
|
||||
"github.com/hunterlong/statping/utils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -73,54 +72,6 @@ func reorderServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||
returnJson(newOrder, w, r)
|
||||
}
|
||||
|
||||
func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
fields := parseGet(r)
|
||||
r.ParseForm()
|
||||
|
||||
var serv *core.Service
|
||||
id := vars["id"]
|
||||
if _, err := strconv.Atoi(id); err == nil {
|
||||
serv = core.SelectService(utils.ToInt(id))
|
||||
} else {
|
||||
serv = core.SelectServiceLink(id)
|
||||
}
|
||||
if serv == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
startField := utils.ToInt(fields.Get("start"))
|
||||
endField := utils.ToInt(fields.Get("end"))
|
||||
group := r.Form.Get("group")
|
||||
|
||||
end := utils.Now().UTC()
|
||||
start := end.Add((-24 * 7) * time.Hour).UTC()
|
||||
|
||||
if startField != 0 {
|
||||
start = time.Unix(startField, 0).UTC()
|
||||
}
|
||||
if endField != 0 {
|
||||
end = time.Unix(endField, 0).UTC()
|
||||
}
|
||||
if group == "" {
|
||||
group = "hour"
|
||||
}
|
||||
|
||||
data := core.GraphHitsDataRaw(serv, start, end, group, "latency")
|
||||
|
||||
out := struct {
|
||||
Service *core.Service
|
||||
Start string
|
||||
End string
|
||||
StartUnix int64
|
||||
EndUnix int64
|
||||
Data string
|
||||
}{serv, start.Format(utils.FlatpickrReadable), end.Format(utils.FlatpickrReadable), start.Unix(), end.Unix(), data.ToString()}
|
||||
|
||||
ExecuteResponse(w, r, "service.gohtml", out, nil)
|
||||
}
|
||||
|
||||
func apiServiceHandler(r *http.Request) interface{} {
|
||||
vars := mux.Vars(r)
|
||||
servicer := core.SelectService(utils.ToInt(vars["id"]))
|
||||
|
@ -205,18 +156,9 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
fields := parseGet(r)
|
||||
grouping := fields.Get("group")
|
||||
if grouping == "" {
|
||||
grouping = "hour"
|
||||
}
|
||||
startField := utils.ToInt(fields.Get("start"))
|
||||
endField := utils.ToInt(fields.Get("end"))
|
||||
groupQuery := parseGroupQuery(r)
|
||||
|
||||
start := time.Unix(startField, 0)
|
||||
end := time.Unix(endField, 0)
|
||||
|
||||
obj := core.GraphHitsDataRaw(service, start, end, grouping, "latency")
|
||||
obj := core.GraphHitsDataRaw(service, groupQuery, "latency")
|
||||
returnJson(obj, w, r)
|
||||
}
|
||||
|
||||
|
@ -227,6 +169,13 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(errors.New("service data not found"), w, r)
|
||||
return
|
||||
}
|
||||
groupQuery := parseGroupQuery(r)
|
||||
|
||||
obj := core.GraphFailuresDataRaw(service, groupQuery)
|
||||
returnJson(obj, w, r)
|
||||
}
|
||||
|
||||
func parseGroupQuery(r *http.Request) types.GroupQuery {
|
||||
fields := parseGet(r)
|
||||
grouping := fields.Get("group")
|
||||
if grouping == "" {
|
||||
|
@ -235,11 +184,11 @@ func apiServiceFailureDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
startField := utils.ToInt(fields.Get("start"))
|
||||
endField := utils.ToInt(fields.Get("end"))
|
||||
|
||||
start := time.Unix(startField, 0).UTC()
|
||||
end := time.Unix(endField, 0).UTC()
|
||||
|
||||
obj := core.GraphFailuresDataRaw(service, start, end, grouping)
|
||||
returnJson(obj, w, r)
|
||||
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) {
|
||||
|
@ -249,15 +198,9 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(errors.New("service not found"), w, r)
|
||||
return
|
||||
}
|
||||
fields := parseGet(r)
|
||||
grouping := fields.Get("group")
|
||||
startField := utils.ToInt(fields.Get("start"))
|
||||
endField := utils.ToInt(fields.Get("end"))
|
||||
groupQuery := parseGroupQuery(r)
|
||||
|
||||
start := time.Unix(startField, 0)
|
||||
end := time.Unix(endField, 0)
|
||||
|
||||
obj := core.GraphHitsDataRaw(service, start, end, grouping, "ping_time")
|
||||
obj := core.GraphHitsDataRaw(service, groupQuery, "ping_time")
|
||||
returnJson(obj, w, r)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,9 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
"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"
|
||||
|
@ -112,7 +115,7 @@ type Database interface {
|
|||
|
||||
GroupByTimeframe() Database
|
||||
ToTimeValue(time.Time, time.Time) ([]*TimeValue, error)
|
||||
|
||||
SelectByTime(string) string
|
||||
MultipleSelects(args ...string) Database
|
||||
|
||||
Failurer
|
||||
|
@ -123,7 +126,26 @@ type Failurer interface {
|
|||
Fails() ([]*Failure, error)
|
||||
}
|
||||
|
||||
func sqlTimeframes(increment string) string {
|
||||
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"
|
||||
|
@ -151,8 +173,15 @@ func CountAmount() string {
|
|||
return fmt.Sprintf("COUNT(id) as amount")
|
||||
}
|
||||
|
||||
func SelectByTime(increment string) string {
|
||||
return fmt.Sprintf("strftime('%s', created_at, 'utc') as timeframe", sqlTimeframes(increment))
|
||||
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 {
|
||||
|
@ -528,11 +557,18 @@ func (it *Db) ToTimeValue(start, end time.Time) ([]*TimeValue, error) {
|
|||
Amount: amount,
|
||||
})
|
||||
}
|
||||
return fillMissing(data, start, end), nil
|
||||
return it.fillMissing(data, start, end), nil
|
||||
}
|
||||
|
||||
func parseTime(t time.Time) string {
|
||||
return t.Format("2006-01-02T00:00:00Z")
|
||||
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 {
|
||||
|
@ -540,19 +576,19 @@ func reparseTime(t string) time.Time {
|
|||
return re.UTC()
|
||||
}
|
||||
|
||||
func fillMissing(vals []*TimeValue, start, end time.Time) []*TimeValue {
|
||||
func (it *Db) fillMissing(vals []*TimeValue, start, end time.Time) []*TimeValue {
|
||||
timeMap := make(map[string]*TimeValue)
|
||||
var validSet []*TimeValue
|
||||
|
||||
for _, v := range vals {
|
||||
timeMap[parseTime(v.Timeframe)] = v
|
||||
timeMap[it.FormatTime(v.Timeframe)] = v
|
||||
}
|
||||
|
||||
current := start.UTC()
|
||||
maxTime := end
|
||||
for {
|
||||
amount := int64(0)
|
||||
currentStr := parseTime(current)
|
||||
currentStr := it.FormatTime(current)
|
||||
if timeMap[currentStr] != nil {
|
||||
amount = timeMap[currentStr].Amount
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ 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"`
|
||||
|
|
Loading…
Reference in New Issue