mirror of https://github.com/statping/statping
service api data - charts js updates (ajax rendering
parent
aba8456b90
commit
b24f939541
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
||||||
VERSION=0.70
|
VERSION=0.71
|
||||||
BINARY_NAME=statup
|
BINARY_NAME=statup
|
||||||
GOPATH:=$(GOPATH)
|
GOPATH:=$(GOPATH)
|
||||||
GOCMD=go
|
GOCMD=go
|
||||||
|
|
|
@ -67,9 +67,9 @@ func checkinDB() *gorm.DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) *gorm.DB {
|
func (s *Service) HitsBetween(t1, t2 time.Time, group string) *gorm.DB {
|
||||||
selector := Dbtimestamp(3600)
|
selector := Dbtimestamp(group)
|
||||||
return DbSession.Model(&types.Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.UTC().Format(types.TIME), t2.UTC().Format(types.TIME)).Group("timeframe")
|
return DbSession.Debug().Model(&types.Hit{}).Select(selector).Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, t1.Format(types.TIME_DAY), t2.Format(types.TIME_DAY)).Order("timeframe asc", false).Group("timeframe")
|
||||||
}
|
}
|
||||||
|
|
||||||
func CloseDB() {
|
func CloseDB() {
|
||||||
|
|
|
@ -128,7 +128,7 @@ type DateScan struct {
|
||||||
|
|
||||||
// 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 []DateScan
|
Array []DateScan `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// lastFailure returns the last failure a service had
|
// lastFailure returns the last failure a service had
|
||||||
|
@ -166,12 +166,14 @@ func (s *Service) DowntimeText() string {
|
||||||
return fmt.Sprintf("%v has been offline for %v", s.Name, utils.DurationReadable(s.Downtime()))
|
return fmt.Sprintf("%v has been offline for %v", s.Name, utils.DurationReadable(s.Downtime()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Dbtimestamp(seconds int64) string {
|
func Dbtimestamp(group string) string {
|
||||||
incrementTime := "second"
|
seconds := 60
|
||||||
if seconds == 60 {
|
if group == "second" {
|
||||||
incrementTime = "minute"
|
seconds = 60
|
||||||
} else if seconds == 3600 {
|
} else if group == "hour" {
|
||||||
incrementTime = "hour"
|
seconds = 3600
|
||||||
|
} else if group == "day" {
|
||||||
|
seconds = 86400
|
||||||
}
|
}
|
||||||
switch CoreApp.DbConnection {
|
switch CoreApp.DbConnection {
|
||||||
case "mysql":
|
case "mysql":
|
||||||
|
@ -179,7 +181,7 @@ func Dbtimestamp(seconds int64) string {
|
||||||
case "sqlite":
|
case "sqlite":
|
||||||
return fmt.Sprintf("datetime((strftime('%%s', created_at) / %v) * %v, 'unixepoch') AS timeframe, AVG(latency) as value", seconds, seconds)
|
return fmt.Sprintf("datetime((strftime('%%s', created_at) / %v) * %v, 'unixepoch') AS timeframe, AVG(latency) as value", seconds, seconds)
|
||||||
case "postgres":
|
case "postgres":
|
||||||
return fmt.Sprintf("date_trunc('%v', created_at) AS timeframe, AVG(latency) AS value", incrementTime)
|
return fmt.Sprintf("date_trunc('%v', created_at) AS timeframe, AVG(latency) AS value", group)
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -199,16 +201,20 @@ func (s *Service) Downtime() time.Duration {
|
||||||
return since
|
return since
|
||||||
}
|
}
|
||||||
|
|
||||||
func GraphDataRaw(service types.ServiceInterface, start, end time.Time) *DateScanObj {
|
func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group string) *DateScanObj {
|
||||||
var d []DateScan
|
var d []DateScan
|
||||||
model := service.(*Service).HitsBetween(start, end)
|
model := service.(*Service).HitsBetween(start, end, group)
|
||||||
rows, _ := model.Rows()
|
rows, _ := model.Rows()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var gd DateScan
|
var gd DateScan
|
||||||
var createdAt string
|
var createdAt string
|
||||||
var value float64
|
var value float64
|
||||||
|
var createdTime time.Time
|
||||||
rows.Scan(&createdAt, &value)
|
rows.Scan(&createdAt, &value)
|
||||||
createdTime, _ := time.Parse(types.TIME, createdAt)
|
createdTime, _ = time.Parse(types.TIME, createdAt)
|
||||||
|
if CoreApp.DbConnection == "postgres" {
|
||||||
|
createdTime, _ = time.Parse(types.TIME_NANO, createdAt)
|
||||||
|
}
|
||||||
gd.CreatedAt = utils.Timezoner(createdTime, CoreApp.Timezone).Format(types.TIME)
|
gd.CreatedAt = utils.Timezoner(createdTime, CoreApp.Timezone).Format(types.TIME)
|
||||||
gd.Value = int64(value * 1000)
|
gd.Value = int64(value * 1000)
|
||||||
d = append(d, gd)
|
d = append(d, gd)
|
||||||
|
@ -227,9 +233,9 @@ func (d *DateScanObj) ToString() string {
|
||||||
|
|
||||||
// GraphData returns the JSON object used by Charts.js to render the chart
|
// GraphData returns the JSON object used by Charts.js to render the chart
|
||||||
func (s *Service) GraphData() string {
|
func (s *Service) GraphData() string {
|
||||||
start := time.Now().Add(-24 * time.Hour)
|
start := time.Now().Add((-24 * 7) * time.Hour)
|
||||||
end := time.Now()
|
end := time.Now()
|
||||||
obj := GraphDataRaw(s, start, end)
|
obj := GraphDataRaw(s, start, end, "hour")
|
||||||
data, err := json.Marshal(obj)
|
data, err := json.Marshal(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(2, err)
|
utils.Log(2, err)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApiResponse struct {
|
type ApiResponse struct {
|
||||||
|
@ -77,6 +78,23 @@ func apiCheckinHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(checkin)
|
json.NewEncoder(w).Encode(checkin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||||
|
if service == nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fields := parseGet(r)
|
||||||
|
grouping := fields.Get("group")
|
||||||
|
startField := utils.StringInt(fields.Get("start"))
|
||||||
|
endField := utils.StringInt(fields.Get("end"))
|
||||||
|
obj := core.GraphDataRaw(service, time.Unix(startField, 0), time.Unix(endField, 0), grouping)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(obj)
|
||||||
|
}
|
||||||
|
|
||||||
func apiServiceHandler(w http.ResponseWriter, r *http.Request) {
|
func apiServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !isAPIAuthorized(r) {
|
if !isAPIAuthorized(r) {
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
|
|
@ -237,6 +237,20 @@ func TestApiDeleteUserHandler(t *testing.T) {
|
||||||
assert.Equal(t, "success", obj.Status)
|
assert.Equal(t, "success", obj.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApiServiceDataHandler(t *testing.T) {
|
||||||
|
grouping := []string{"minute", "hour", "day"}
|
||||||
|
for _, g := range grouping {
|
||||||
|
params := "?start=0&end=999999999999&group=" + g
|
||||||
|
rr, err := httpRequestAPI(t, "GET", "/api/services/1/data"+params, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
body := rr.Body.String()
|
||||||
|
var obj core.DateScanObj
|
||||||
|
formatJSON(body, &obj)
|
||||||
|
assert.Equal(t, 200, rr.Code)
|
||||||
|
assert.NotZero(t, len(obj.Array))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func httpRequestAPI(t *testing.T, method, url string, body io.Reader) (*httptest.ResponseRecorder, error) {
|
func httpRequestAPI(t *testing.T, method, url string, body io.Reader) (*httptest.ResponseRecorder, error) {
|
||||||
req, err := http.NewRequest(method, url, body)
|
req, err := http.NewRequest(method, url, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -84,26 +84,13 @@ func IsAuthenticated(r *http.Request) bool {
|
||||||
return session.Values["authenticated"].(bool)
|
return session.Values["authenticated"].(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeResponse will render a HTTP response for the front end user
|
var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap {
|
||||||
func executeResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}, redirect interface{}) {
|
return template.FuncMap{
|
||||||
utils.Http(r)
|
"js": func(html interface{}) template.JS {
|
||||||
if url, ok := redirect.(string); ok {
|
return template.JS(utils.ToString(html))
|
||||||
http.Redirect(w, r, url, http.StatusSeeOther)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nav, _ := source.TmplBox.String("nav.html")
|
|
||||||
footer, _ := source.TmplBox.String("footer.html")
|
|
||||||
render, err := source.TmplBox.String(file)
|
|
||||||
if err != nil {
|
|
||||||
utils.Log(4, err)
|
|
||||||
}
|
|
||||||
t := template.New("message")
|
|
||||||
t.Funcs(template.FuncMap{
|
|
||||||
"js": func(html string) template.JS {
|
|
||||||
return template.JS(html)
|
|
||||||
},
|
},
|
||||||
"safe": func(html string) template.HTML {
|
"safe": func(html interface{}) template.HTML {
|
||||||
return template.HTML(html)
|
return template.HTML(utils.ToString(html))
|
||||||
},
|
},
|
||||||
"Auth": func() bool {
|
"Auth": func() bool {
|
||||||
return IsAuthenticated(r)
|
return IsAuthenticated(r)
|
||||||
|
@ -156,7 +143,25 @@ func executeResponse(w http.ResponseWriter, r *http.Request, file string, data i
|
||||||
"FromUnix": func(t int64) string {
|
"FromUnix": func(t int64) string {
|
||||||
return utils.Timezoner(time.Unix(t, 0), core.CoreApp.Timezone).Format("Monday, January 02")
|
return utils.Timezoner(time.Unix(t, 0), core.CoreApp.Timezone).Format("Monday, January 02")
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeResponse will render a HTTP response for the front end user
|
||||||
|
func executeResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}, redirect interface{}) {
|
||||||
|
utils.Http(r)
|
||||||
|
if url, ok := redirect.(string); ok {
|
||||||
|
http.Redirect(w, r, url, http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nav, _ := source.TmplBox.String("nav.html")
|
||||||
|
footer, _ := source.TmplBox.String("footer.html")
|
||||||
|
chartIndex, _ := source.JsBox.String("chart_index.js")
|
||||||
|
render, err := source.TmplBox.String(file)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log(4, err)
|
||||||
|
}
|
||||||
|
t := template.New("message")
|
||||||
|
t.Funcs(handlerFuncs(w, r))
|
||||||
t, err = t.Parse(nav)
|
t, err = t.Parse(nav)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(4, err)
|
utils.Log(4, err)
|
||||||
|
@ -169,6 +174,15 @@ func executeResponse(w http.ResponseWriter, r *http.Request, file string, data i
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(4, err)
|
utils.Log(4, err)
|
||||||
}
|
}
|
||||||
|
_, err = t.Parse(chartIndex)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log(4, err)
|
||||||
|
}
|
||||||
|
fmt.Println(t.Templates())
|
||||||
|
fmt.Println(t.DefinedTemplates())
|
||||||
|
|
||||||
|
t.Lookup("chartIndex").Funcs(handlerFuncs(w, r))
|
||||||
|
|
||||||
err = t.Execute(w, data)
|
err = t.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log(4, err)
|
utils.Log(4, err)
|
||||||
|
|
|
@ -86,6 +86,7 @@ func Router() *mux.Router {
|
||||||
r.Handle("/api/services", http.HandlerFunc(apiAllServicesHandler)).Methods("GET")
|
r.Handle("/api/services", http.HandlerFunc(apiAllServicesHandler)).Methods("GET")
|
||||||
r.Handle("/api/services", http.HandlerFunc(apiCreateServiceHandler)).Methods("POST")
|
r.Handle("/api/services", http.HandlerFunc(apiCreateServiceHandler)).Methods("POST")
|
||||||
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceHandler)).Methods("GET")
|
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceHandler)).Methods("GET")
|
||||||
|
r.Handle("/api/services/{id}/data", http.HandlerFunc(apiServiceDataHandler)).Methods("GET")
|
||||||
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceUpdateHandler)).Methods("POST")
|
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceUpdateHandler)).Methods("POST")
|
||||||
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceDeleteHandler)).Methods("DELETE")
|
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceDeleteHandler)).Methods("DELETE")
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ func renderServiceChartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||||
data := core.GraphDataRaw(service, start, end).ToString()
|
data := core.GraphDataRaw(service, start, end, "hour").ToString()
|
||||||
|
|
||||||
out := struct {
|
out := struct {
|
||||||
Services []*core.Service
|
Services []*core.Service
|
||||||
|
@ -74,7 +74,7 @@ func renderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
start := now.BeginningOfDay().UTC()
|
start := now.BeginningOfDay().UTC()
|
||||||
|
|
||||||
for _, s := range services {
|
for _, s := range services {
|
||||||
d := core.GraphDataRaw(s, start, end).ToString()
|
d := core.GraphDataRaw(s, start, end, "hour").ToString()
|
||||||
data = append(data, d)
|
data = append(data, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
end = time.Unix(endField, 0)
|
end = time.Unix(endField, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := core.GraphDataRaw(serv, start, end)
|
data := core.GraphDataRaw(serv, start, end, "hour")
|
||||||
|
|
||||||
out := struct {
|
out := struct {
|
||||||
Service *core.Service
|
Service *core.Service
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
{{define "chartIndex"}}
|
||||||
|
var ctx_{{js .Id}} = document.getElementById("service_{{js .Id}}").getContext('2d');
|
||||||
|
var chartdata_{{js .Id}} = new Chart(ctx_{{js .Id}}, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [{
|
||||||
|
label: 'Response Time (Milliseconds)',
|
||||||
|
data: [],
|
||||||
|
backgroundColor: ['rgba(47, 206, 30, 0.92)'],
|
||||||
|
borderColor: ['rgb(47, 171, 34)'],
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
maintainAspectRatio: !1,
|
||||||
|
scaleShowValues: !0,
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: -10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
animationDuration: 0,
|
||||||
|
},
|
||||||
|
responsiveAnimationDuration: 0,
|
||||||
|
animation: {
|
||||||
|
duration: 3500,
|
||||||
|
onComplete: function() {
|
||||||
|
var chartInstance = this.chart,
|
||||||
|
ctx = chartInstance.ctx;
|
||||||
|
var controller = this.chart.controller;
|
||||||
|
var xAxis = controller.scales['x-axis-0'];
|
||||||
|
var yAxis = controller.scales['y-axis-0'];
|
||||||
|
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'bottom';
|
||||||
|
var numTicks = xAxis.ticks.length;
|
||||||
|
var yOffsetStart = xAxis.width / numTicks;
|
||||||
|
var halfBarWidth = (xAxis.width / (numTicks * 2));
|
||||||
|
xAxis.ticks.forEach(function(value, index) {
|
||||||
|
var xOffset = 20;
|
||||||
|
var yOffset = (yOffsetStart * index) + halfBarWidth;
|
||||||
|
ctx.fillStyle = '#e2e2e2';
|
||||||
|
ctx.fillText(value, yOffset, xOffset)
|
||||||
|
});
|
||||||
|
this.data.datasets.forEach(function(dataset, i) {
|
||||||
|
var meta = chartInstance.controller.getDatasetMeta(i);
|
||||||
|
var hxH = 0;
|
||||||
|
var hyH = 0;
|
||||||
|
var hxL = 0;
|
||||||
|
var hyL = 0;
|
||||||
|
var highestNum = 0;
|
||||||
|
var lowestnum = 999999999999;
|
||||||
|
meta.data.forEach(function(bar, index) {
|
||||||
|
var data = dataset.data[index];
|
||||||
|
if (lowestnum > data.y) {
|
||||||
|
lowestnum = data.y;
|
||||||
|
hxL = bar._model.x;
|
||||||
|
hyL = bar._model.y
|
||||||
|
}
|
||||||
|
if (data.y > highestNum) {
|
||||||
|
highestNum = data.y;
|
||||||
|
hxH = bar._model.x;
|
||||||
|
hyH = bar._model.y
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (hxH >= 820) {
|
||||||
|
hxH = 820
|
||||||
|
} else if (50 >= hxH) {
|
||||||
|
hxH = 50
|
||||||
|
}
|
||||||
|
if (hxL >= 820) {
|
||||||
|
hxL = 820
|
||||||
|
} else if (70 >= hxL) {
|
||||||
|
hxL = 70
|
||||||
|
}
|
||||||
|
ctx.fillStyle = '#ffa7a2';
|
||||||
|
ctx.fillText(highestNum + "ms", hxH - 40, hyH + 15);
|
||||||
|
ctx.fillStyle = '#45d642';
|
||||||
|
ctx.fillText(lowestnum + "ms", hxL, hyL + 10);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: !1
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
"enabled": !1
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
display: !1,
|
||||||
|
ticks: {
|
||||||
|
fontSize: 20,
|
||||||
|
display: !1,
|
||||||
|
beginAtZero: !1
|
||||||
|
},
|
||||||
|
gridLines: {
|
||||||
|
display: !1
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
xAxes: [{
|
||||||
|
type: 'time',
|
||||||
|
distribution: 'series',
|
||||||
|
autoSkip: !1,
|
||||||
|
time: {
|
||||||
|
displayFormats: {
|
||||||
|
'hour': 'MMM DD hA'
|
||||||
|
},
|
||||||
|
source: 'auto'
|
||||||
|
},
|
||||||
|
gridLines: {
|
||||||
|
display: !1
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
source: 'auto',
|
||||||
|
stepSize: 1,
|
||||||
|
min: 0,
|
||||||
|
fontColor: "white",
|
||||||
|
fontSize: 20,
|
||||||
|
display: !1
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
radius: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AjaxChart(chartdata_{{js .Id}},{{js .Id}},0,99999999999,"hour");
|
||||||
|
{{end}}
|
|
@ -18,6 +18,6 @@
|
||||||
{{$d := .Data}}{{ range $i, $s := .Services }}{{ if $s.AvgTime }}var ctx_{{$s.Id}}=document.getElementById("service_{{$s.Id}}").getContext('2d');var chartdata=new Chart(ctx_{{$s.Id}},{type:'line',data:{datasets:[{label:'Response Time (Milliseconds)',data:{{safe (index $d $i)}},backgroundColor:['rgba(47, 206, 30, 0.92)'],borderColor:['rgb(47, 171, 34)'],borderWidth:1}]},options:{maintainAspectRatio:!1,scaleShowValues:!0,layout:{padding:{left:0,right:0,top:0,bottom:-10}},hover:{animationDuration:0,},responsiveAnimationDuration:0,animation:{duration:3500,onComplete:function(){var chartInstance=this.chart,ctx=chartInstance.ctx;var controller=this.chart.controller;var xAxis=controller.scales['x-axis-0'];var yAxis=controller.scales['y-axis-0'];ctx.font=Chart.helpers.fontString(Chart.defaults.global.defaultFontSize,Chart.defaults.global.defaultFontStyle,Chart.defaults.global.defaultFontFamily);ctx.textAlign='center';ctx.textBaseline='bottom';var numTicks=xAxis.ticks.length;var yOffsetStart=xAxis.width/numTicks;var halfBarWidth=(xAxis.width/(numTicks*2));xAxis.ticks.forEach(function(value,index){var xOffset=20;var yOffset=(yOffsetStart*index)+halfBarWidth;ctx.fillStyle='#e2e2e2';ctx.fillText(value,yOffset,xOffset)});this.data.datasets.forEach(function(dataset,i){var meta=chartInstance.controller.getDatasetMeta(i);var hxH=0;var hyH=0;var hxL=0;var hyL=0;var highestNum=0;var lowestnum=999999999999;meta.data.forEach(function(bar,index){var data=dataset.data[index];if(lowestnum>data.y){lowestnum=data.y;hxL=bar._model.x;hyL=bar._model.y}
|
{{$d := .Data}}{{ range $i, $s := .Services }}{{ if $s.AvgTime }}var ctx_{{$s.Id}}=document.getElementById("service_{{$s.Id}}").getContext('2d');var chartdata=new Chart(ctx_{{$s.Id}},{type:'line',data:{datasets:[{label:'Response Time (Milliseconds)',data:{{safe (index $d $i)}},backgroundColor:['rgba(47, 206, 30, 0.92)'],borderColor:['rgb(47, 171, 34)'],borderWidth:1}]},options:{maintainAspectRatio:!1,scaleShowValues:!0,layout:{padding:{left:0,right:0,top:0,bottom:-10}},hover:{animationDuration:0,},responsiveAnimationDuration:0,animation:{duration:3500,onComplete:function(){var chartInstance=this.chart,ctx=chartInstance.ctx;var controller=this.chart.controller;var xAxis=controller.scales['x-axis-0'];var yAxis=controller.scales['y-axis-0'];ctx.font=Chart.helpers.fontString(Chart.defaults.global.defaultFontSize,Chart.defaults.global.defaultFontStyle,Chart.defaults.global.defaultFontFamily);ctx.textAlign='center';ctx.textBaseline='bottom';var numTicks=xAxis.ticks.length;var yOffsetStart=xAxis.width/numTicks;var halfBarWidth=(xAxis.width/(numTicks*2));xAxis.ticks.forEach(function(value,index){var xOffset=20;var yOffset=(yOffsetStart*index)+halfBarWidth;ctx.fillStyle='#e2e2e2';ctx.fillText(value,yOffset,xOffset)});this.data.datasets.forEach(function(dataset,i){var meta=chartInstance.controller.getDatasetMeta(i);var hxH=0;var hyH=0;var hxL=0;var hyL=0;var highestNum=0;var lowestnum=999999999999;meta.data.forEach(function(bar,index){var data=dataset.data[index];if(lowestnum>data.y){lowestnum=data.y;hxL=bar._model.x;hyL=bar._model.y}
|
||||||
if(data.y>highestNum){highestNum=data.y;hxH=bar._model.x;hyH=bar._model.y}});if(hxH>=820){hxH=820}else if(50>=hxH){hxH=50}
|
if(data.y>highestNum){highestNum=data.y;hxH=bar._model.x;hyH=bar._model.y}});if(hxH>=820){hxH=820}else if(50>=hxH){hxH=50}
|
||||||
if(hxL>=820){hxL=820}else if(70>=hxL){hxL=70}
|
if(hxL>=820){hxL=820}else if(70>=hxL){hxL=70}
|
||||||
ctx.fillStyle='#ffa7a2';ctx.fillText(highestNum+"ms",hxH-40,hyH+15);ctx.fillStyle='#45d642';ctx.fillText(lowestnum+"ms",hxL,hyL+10);console.log("done service_id_{{.Id}}")})}},legend:{display:!1},tooltips:{"enabled":!1},scales:{yAxes:[{display:!1,ticks:{fontSize:20,display:!1,beginAtZero:!1},gridLines:{display:!1}}],xAxes:[{type:'time',distribution:'series',autoSkip:!1,gridLines:{display:!1},ticks:{stepSize:1,min:0,fontColor:"white",fontSize:20,display:!1,}}]},elements:{point:{radius:0}}}})
|
ctx.fillStyle='#ffa7a2';ctx.fillText(highestNum+"ms",hxH-40,hyH+15);ctx.fillStyle='#45d642';ctx.fillText(lowestnum+"ms",hxL,hyL+10);console.log("done service_id_{{.Id}}")})}},legend:{display:!1},tooltips:{"enabled":!1},scales:{yAxes:[{display:!1,ticks:{fontSize:20,display:!1,beginAtZero:!1},gridLines:{display:!1}}],xAxes:[{type:'time',distribution:'series',autoSkip:!1,time:{displayFormats:{'hour': 'MMM DD hA'},source: 'auto'},gridLines:{display:!1},ticks:{source:'auto',stepSize:1,min:0,fontColor:"white",fontSize:20,display:!1}}]},elements:{point:{radius:0}}}})
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -81,6 +81,20 @@ $('select#service_type').on('change', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function AjaxChart(chart, service, start=0, end=9999999999, group="hour") {
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/services/"+service+"/data?start="+start+"&end="+end+"&group="+group,
|
||||||
|
type: 'GET',
|
||||||
|
success: function(data) {
|
||||||
|
chart.data.labels.pop();
|
||||||
|
data.data.forEach(function(d) {
|
||||||
|
chart.data.datasets[0].data.push(d);
|
||||||
|
});
|
||||||
|
chart.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$('select#service_check_type').on('change', function() {
|
$('select#service_check_type').on('change', function() {
|
||||||
var selected = $('#service_check_type option:selected').val();
|
var selected = $('#service_check_type option:selected').val();
|
||||||
if (selected === 'POST') {
|
if (selected === 'POST') {
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1.0, user-scalable=0">
|
||||||
|
{{if USE_CDN}}
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="https://assets.statup.io/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="https://assets.statup.io/base.css">
|
||||||
|
{{ else }}
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/base.css">
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<title>{{.Name}} Status</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -112,18 +112,17 @@
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if CHART_DATA}}
|
{{ if .Style }}
|
||||||
<script>{{ js CHART_DATA }}</script>
|
<style>
|
||||||
{{ else }}
|
{{ safe .Style }}
|
||||||
<script src="/charts.js"></script>
|
</style>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if .Style }}
|
<script>
|
||||||
<style>
|
{{ range Services }}
|
||||||
{{ safe .Style }}
|
{{template "chartIndex" .}}
|
||||||
</style>
|
{{end}}
|
||||||
{{ end }}
|
</script>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -255,7 +255,7 @@
|
||||||
data: {
|
data: {
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Response Time (Milliseconds)',
|
label: 'Response Time (Milliseconds)',
|
||||||
data: {{js .Data}},
|
data: [],
|
||||||
backgroundColor: [
|
backgroundColor: [
|
||||||
'rgba(47, 206, 30, 0.92)'
|
'rgba(47, 206, 30, 0.92)'
|
||||||
],
|
],
|
||||||
|
@ -280,10 +280,27 @@
|
||||||
}],
|
}],
|
||||||
xAxes: [{
|
xAxes: [{
|
||||||
type: 'time',
|
type: 'time',
|
||||||
|
distribution: 'series',
|
||||||
|
time: {
|
||||||
|
displayFormats: {
|
||||||
|
'millisecond': 'MMM DD',
|
||||||
|
'second': 'MMM DD',
|
||||||
|
'minute': 'MMM DD',
|
||||||
|
'hour': 'MMM DD hA',
|
||||||
|
'day': 'MMM DD',
|
||||||
|
'week': 'MMM DD',
|
||||||
|
'month': 'MMM DD',
|
||||||
|
'quarter': 'MMM DD',
|
||||||
|
'year': 'MMM DD',
|
||||||
|
}
|
||||||
|
},
|
||||||
gridLines: {
|
gridLines: {
|
||||||
display: true
|
display: true
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
source: 'auto'
|
||||||
}
|
}
|
||||||
}]
|
}],
|
||||||
},
|
},
|
||||||
elements: {
|
elements: {
|
||||||
point: {
|
point: {
|
||||||
|
@ -332,6 +349,8 @@
|
||||||
endPick.show()
|
endPick.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
AjaxChart(chartdata,{{$s.Id}},{{.Start}},{{.End}},"hour");
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -23,6 +23,7 @@ const (
|
||||||
TIME_NANOZ = "2006-01-02 15:04:05.999999-0700 MST"
|
TIME_NANOZ = "2006-01-02 15:04:05.999999-0700 MST"
|
||||||
TIME_NANO = "2006-01-02T15:04:05Z"
|
TIME_NANO = "2006-01-02T15:04:05Z"
|
||||||
TIME = "2006-01-02 15:04:05"
|
TIME = "2006-01-02 15:04:05"
|
||||||
|
TIME_DAY = "2006-01-02"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
Loading…
Reference in New Issue