diff --git a/.travis.yml b/.travis.yml index ef1eebaf..5b44fc55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ services: env: global: - - VERSION=0.28.91 + - VERSION=0.29 - DB_HOST=localhost - DB_USER=travis - DB_PASS= diff --git a/Dockerfile b/Dockerfile index f7e636df..4136105a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -ENV VERSION=v0.28.91 +ENV VERSION=v0.29 RUN apk --no-cache add libstdc++ ca-certificates RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \ diff --git a/core/services.go b/core/services.go index c98cd87d..edb9d2ee 100644 --- a/core/services.go +++ b/core/services.go @@ -131,9 +131,9 @@ func (s *Service) GraphData() string { // group by interval sql query for postgres, mysql and sqlite sql := fmt.Sprintf("SELECT date_trunc('%v', created_at), AVG(latency)*1000 AS value FROM hits WHERE service=%v AND created_at > '%v' GROUP BY 1 ORDER BY date_trunc ASC;", increment, s.Id, since.Format(time.RFC3339)) if dbServer == "mysql" { - sql = fmt.Sprintf("SELECT CONCAT(date_format(created_at, '%%Y-%%m-%%dT%%TZ')) AS created_at, AVG(latency)*1000 AS value FROM hits WHERE service=%v AND DATE_FORMAT(created_at, '%%Y-%%m-%%dT%%TZ') BETWEEN DATE_FORMAT(NOW() - INTERVAL 12 HOUR, '%%Y-%%m-%%dT%%TZ') AND DATE_FORMAT(NOW(), '%%Y-%%m-%%dT%%TZ') GROUP BY created_at", s.Id) + sql = fmt.Sprintf("SELECT CONCAT(date_format(created_at, '%%Y-%%m-%%dT%%TZ')) AS created_at, AVG(latency)*1000 AS value FROM hits WHERE service=%v AND DATE_FORMAT(created_at, '%%Y-%%m-%%dT%%TZ') BETWEEN DATE_FORMAT(NOW() - INTERVAL 12 HOUR, '%%Y-%%m-%%dT%%TZ') AND DATE_FORMAT(NOW(), '%%Y-%%m-%%dT%%TZ') GROUP BY created_at ORDER BY created_at ASC;", s.Id) } else if dbServer == "sqlite" { - sql = fmt.Sprintf("SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', created_at), AVG(latency)*1000 as value FROM hits WHERE service=%v AND created_at >= '%v' GROUP BY strftime('%%M', created_at)", s.Id, since.Format(time.RFC3339)) + sql = fmt.Sprintf("SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', created_at), AVG(latency)*1000 as value FROM hits WHERE service=%v AND created_at >= '%v' GROUP BY strftime('%%M', created_at) ORDER BY created_at ASC;", s.Id, since.Format(time.RFC3339)) } dated, err := DbSession.Query(db.Raw(sql)) if err != nil { diff --git a/handlers/handlers.go b/handlers/handlers.go index d96e017e..7ef36d06 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -98,4 +98,19 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i t.Execute(w, data) } +func ExecuteJSResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) { + render, err := core.JsBox.String(file) + if err != nil { + utils.Log(4, err) + } + t := template.New("charts") + t.Funcs(template.FuncMap{ + "safe": func(html string) template.HTML { + return template.HTML(html) + }, + }) + t.Parse(render) + t.Execute(w, data) +} + type DbConfig types.DbConfig diff --git a/handlers/routes.go b/handlers/routes.go index 5e83a0c7..ba038a10 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -4,6 +4,7 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/sessions" "github.com/hunterlong/statup/core" + "github.com/tdewolff/minify" "net/http" ) @@ -11,6 +12,8 @@ func Router() *mux.Router { r := mux.NewRouter() r.Handle("/", http.HandlerFunc(IndexHandler)) LocalizedAssets(r) + m := minify.New() + r.Handle("/charts.js", m.Middleware(http.HandlerFunc(RenderServiceChartsHandler))) r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET") r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST") r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET") diff --git a/handlers/services.go b/handlers/services.go index 93fe525b..bb33d33c 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -9,6 +9,13 @@ import ( "strconv" ) +func RenderServiceChartsHandler(w http.ResponseWriter, r *http.Request) { + services := core.CoreApp.Services + //w.Header().Set("Content-Type", "text/javascript") + //w.Header().Set("Cache-Control", "no-cache, private, max-age=0") + ExecuteJSResponse(w, r, "charts.js", services) +} + func ServicesHandler(w http.ResponseWriter, r *http.Request) { if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) diff --git a/source/js/charts.js b/source/js/charts.js new file mode 100644 index 00000000..82a2c570 --- /dev/null +++ b/source/js/charts.js @@ -0,0 +1,146 @@ +{{ range . }}{{ if .AvgTime }}var ctx = document.getElementById("service_{{.Id}}").getContext('2d'); + +var chartdata = new Chart(ctx, { + type: 'line', + data: { + datasets: [{ + label: 'Response Time (Milliseconds)', + data: {{safe .GraphData}}, + backgroundColor: [ + 'rgba(47, 206, 30, 0.92)' +], + borderColor: [ + 'rgb(47, 171, 34)' +], + borderWidth: 1 +}] +}, +options: { + maintainAspectRatio: false, + scaleShowValues: true, + 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 (data.y {{safe "<"}} lowestnum) { + 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 {{safe ">"}}= 820) { + hxH = 820; + } else if (hxH {{safe "<"}}= 50) { + hxH = 50; + } + + if (hxL {{safe ">"}}= 820) { + hxL = 820; + } else if (hxL {{safe "<"}}= 70) { + 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: false + }, + tooltips: { + "enabled": false + }, + scales: { + yAxes: [{ + display: false, + ticks: { + fontSize: 20, + display: false, + beginAtZero: false + }, + gridLines: { + display:false + } + }], + xAxes: [{ + type: 'time', + distribution: 'series', + autoSkip: false, + gridLines: { + display:false + }, + ticks: { + stepSize: 1, + min: 0, + fontColor: "white", + fontSize: 20, + display: false, + } + }] + }, + elements: { + point: { + radius: 0 + } + } +} +}); +{{ end }} +{{ end }} \ No newline at end of file diff --git a/source/tmpl/index.html b/source/tmpl/index.html index f68fdb5b..6999663c 100644 --- a/source/tmpl/index.html +++ b/source/tmpl/index.html @@ -104,159 +104,8 @@ {{end}} - - - + {{ if .Core.Style }}