core updates - sql migrations

pull/10/head
Hunter Long 2018-07-04 02:00:16 -07:00
parent b58326a453
commit cb46bf6496
19 changed files with 163 additions and 202 deletions

View File

@ -18,7 +18,7 @@ services:
env: env:
global: global:
- VERSION=0.29 - VERSION=0.29.1
- DB_HOST=localhost - DB_HOST=localhost
- DB_USER=travis - DB_USER=travis
- DB_PASS= - DB_PASS=

View File

@ -5,7 +5,7 @@ gem install sass
sass source/scss/base.scss source/css/base.css sass source/scss/base.scss source/css/base.css
# MIGRATION SQL FILE FOR CURRENT VERSION # MIGRATION SQL FILE FOR CURRENT VERSION
printf "UPDATE core SET version='$VERSION';\n" >> source/sql/upgrade.sql #printf "UPDATE core SET version='$VERSION';\n" >> source/sql/upgrade.sql
# COMPILE SRC INTO BIN # COMPILE SRC INTO BIN
rice embed-go rice embed-go

View File

@ -1,6 +1,6 @@
FROM alpine:latest FROM alpine:latest
ENV VERSION=v0.29 ENV VERSION=v0.29.1
RUN apk --no-cache add libstdc++ ca-certificates RUN apk --no-cache add libstdc++ ca-certificates
RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \ RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \

View File

@ -13,12 +13,13 @@ var (
) )
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {
var config Config var config *Config
file, err := ioutil.ReadFile("config.yml") file, err := ioutil.ReadFile("config.yml")
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = yaml.Unmarshal(file, &config) err = yaml.Unmarshal(file, &config)
Configs = &config Configs = config
return &config, err CoreApp.DbConnection = config.Connection
return config, err
} }

View File

@ -4,6 +4,7 @@ import (
"github.com/GeertJohan/go.rice" "github.com/GeertJohan/go.rice"
"github.com/hunterlong/statup/plugin" "github.com/hunterlong/statup/plugin"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"time"
) )
type PluginJSON types.PluginJSON type PluginJSON types.PluginJSON
@ -25,6 +26,8 @@ type Core struct {
AllPlugins []plugin.PluginActions AllPlugins []plugin.PluginActions
Communications []*types.Communication Communications []*types.Communication
OfflineAssets bool OfflineAssets bool
DbConnection string
started time.Time
} }
var ( var (
@ -41,7 +44,13 @@ var (
) )
func init() { func init() {
CoreApp = NewCore()
}
func NewCore() *Core {
CoreApp = new(Core) CoreApp = new(Core)
CoreApp.started = time.Now()
return CoreApp
} }
func InitApp() { func InitApp() {
@ -56,9 +65,10 @@ func InitApp() {
func (c *Core) Update() (*Core, error) { func (c *Core) Update() (*Core, error) {
res := DbSession.Collection("core").Find().Limit(1) res := DbSession.Collection("core").Find().Limit(1)
res.Update(c) err := res.Update(c)
CoreApp = c CoreApp = c
return c, nil CoreApp.Services, err = SelectAllServices()
return c, err
} }
func (c Core) UsingAssets() bool { func (c Core) UsingAssets() bool {
@ -102,6 +112,10 @@ func SelectCore() (*Core, error) {
return nil, err return nil, err
} }
CoreApp = c CoreApp = c
CoreApp.DbConnection = Configs.Connection
CoreApp.Version = VERSION
CoreApp.Services, _ = SelectAllServices()
CoreApp.Update()
//store = sessions.NewCookieStore([]byte(core.ApiSecret)) //store = sessions.NewCookieStore([]byte(core.ApiSecret))
return CoreApp, err return CoreApp, err
} }

View File

@ -16,7 +16,6 @@ import (
) )
var ( var (
dbServer string
sqliteSettings sqlite.ConnectionURL sqliteSettings sqlite.ConnectionURL
postgresSettings postgresql.ConnectionURL postgresSettings postgresql.ConnectionURL
mysqlSettings mysql.ConnectionURL mysqlSettings mysql.ConnectionURL
@ -68,7 +67,6 @@ func DbConnection(dbType string) error {
} }
} }
//dbSession.SetLogging(true) //dbSession.SetLogging(true)
dbServer = dbType
return err return err
} }
@ -130,20 +128,87 @@ func (c *DbConfig) Save() error {
if err == nil { if err == nil {
CoreApp = newCore CoreApp = newCore
} }
CoreApp, err = SelectCore()
CoreApp.DbConnection = c.DbConn
return err return err
} }
func RunDatabaseUpgrades() { func versionSplit(v string) (int64, int64, int64) {
utils.Log(1, "Running Database Upgrade from 'upgrade.sql'...") currSplit := strings.Split(v, ".")
upgrade, _ := SqlBox.String("upgrade.sql") if len(currSplit) < 2 {
requests := strings.Split(upgrade, ";") return 9999, 9999, 9999
for _, request := range requests { }
_, err := DbSession.Exec(db.Raw(request + ";")) var major, mid, minor string
if err != nil { if len(currSplit) == 3 {
utils.Log(2, err) major = currSplit[0]
mid = currSplit[1]
minor = currSplit[2]
return utils.StringInt(major), utils.StringInt(mid), utils.StringInt(minor)
}
major = currSplit[0]
mid = currSplit[1]
return utils.StringInt(major), utils.StringInt(mid), 0
}
func versionHigher(migrate string) bool {
cM, cMi, cMn := versionSplit(CoreApp.Version)
mM, mMi, mMn := versionSplit(migrate)
if mM > cM {
return true
}
if mMi > cMi {
return true
}
if mMn > cMn {
return true
}
return false
}
func RunDatabaseUpgrades() error {
var err error
utils.Log(1, fmt.Sprintf("Checking Database Upgrades from v%v in '%v_upgrade.sql'...", CoreApp.Version, CoreApp.DbConnection))
upgrade, _ := SqlBox.String(CoreApp.DbConnection + "_upgrade.sql")
// parse db version and upgrade file
ups := strings.Split(upgrade, "=========================================== ")
var ran int
for _, v := range ups {
if len(v) == 0 {
continue
}
vers := strings.Split(v, "\n")
version := vers[0]
data := vers[1:]
//fmt.Printf("Checking Migration from v%v to v%v - %v\n", CoreApp.Version, version, versionHigher(version))
if !versionHigher(version) {
//fmt.Printf("Already up-to-date with v%v\n", version)
continue
}
fmt.Printf("Migration Database from v%v to v%v\n", CoreApp.Version, version)
for _, m := range data {
if m == "" {
continue
}
fmt.Printf("Running Migration: %v\n", m)
_, err := DbSession.Exec(db.Raw(m + ";"))
if err != nil {
utils.Log(2, err)
continue
}
ran++
CoreApp.Version = m
} }
} }
utils.Log(1, "Database Upgraded") CoreApp.Update()
CoreApp, err = SelectCore()
if ran > 0 {
utils.Log(1, fmt.Sprintf("Database Upgraded, %v query ran", ran))
} else {
utils.Log(1, fmt.Sprintf("Database is already up-to-date, latest v%v", CoreApp.Version))
}
return err
} }
func DropDatabase() { func DropDatabase() {
@ -161,9 +226,9 @@ func DropDatabase() {
func CreateDatabase() { func CreateDatabase() {
fmt.Println("Creating Tables...") fmt.Println("Creating Tables...")
sql := "postgres_up.sql" sql := "postgres_up.sql"
if dbServer == "mysql" { if CoreApp.DbConnection == "mysql" {
sql = "mysql_up.sql" sql = "mysql_up.sql"
} else if dbServer == "sqlite" { } else if CoreApp.DbConnection == "sqlite" {
sql = "sqlite_up.sql" sql = "sqlite_up.sql"
} }
up, _ := SqlBox.String(sql) up, _ := SqlBox.String(sql)

View File

@ -124,17 +124,27 @@ func (s *Service) SmallText() string {
return fmt.Sprintf("No Failures in the last 24 hours! %v", hits[0]) return fmt.Sprintf("No Failures in the last 24 hours! %v", hits[0])
} }
func GroupDataBy(column string, id int64, tm time.Time, increment string) string {
var sql string
fmt.Println("gropu by", column, CoreApp.DbConnection)
switch CoreApp.DbConnection {
case "mysql":
sql = fmt.Sprintf("SELECT CONCAT(date_format(created_at, '%%Y-%%m-%%dT%%H:%%i:00Z')) AS created_at, AVG(latency)*1000 AS value FROM %v WHERE service=%v AND DATE_FORMAT(created_at, '%%Y-%%m-%%dT%%TZ') BETWEEN DATE_FORMAT('%v', '%%Y-%%m-%%dT%%TZ') AND DATE_FORMAT(NOW(), '%%Y-%%m-%%dT%%TZ') GROUP BY 1 ORDER BY created_at ASC;", column, id, tm.Format(time.RFC3339))
case "sqlite":
sql = fmt.Sprintf("SELECT strftime('%%Y-%%m-%%dT%%H:%%M:00Z', created_at), AVG(latency)*1000 as value FROM %v WHERE service=%v AND created_at >= '%v' GROUP BY strftime('%%M:00', created_at) ORDER BY created_at ASC;", column, id, tm.Format(time.RFC3339))
case "postgres":
sql = fmt.Sprintf("SELECT date_trunc('%v', created_at), AVG(latency)*1000 AS value FROM %v WHERE service=%v AND created_at >= '%v' GROUP BY 1 ORDER BY date_trunc ASC;", increment, column, id, tm.Format(time.RFC3339))
}
fmt.Println(sql)
return sql
}
func (s *Service) GraphData() string { func (s *Service) GraphData() string {
var d []*DateScan var d []*DateScan
increment := "minute"
since := time.Now().Add(time.Hour*-24 + time.Minute*0 + time.Second*0) since := time.Now().Add(time.Hour*-24 + time.Minute*0 + time.Second*0)
// 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)) sql := GroupDataBy("hits", s.Id, since, "minute")
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 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) ORDER BY created_at ASC;", s.Id, since.Format(time.RFC3339))
}
dated, err := DbSession.Query(db.Raw(sql)) dated, err := DbSession.Query(db.Raw(sql))
if err != nil { if err != nil {
utils.Log(2, err) utils.Log(2, err)

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"fmt"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/hunterlong/statup/core" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
@ -22,14 +23,14 @@ var (
func RunHTTPServer() { func RunHTTPServer() {
utils.Log(1, "Statup HTTP Server running on http://localhost:8080") utils.Log(1, "Statup HTTP Server running on http://localhost:8080")
r := Router() r := Router()
//for _, p := range allPlugins { for _, p := range core.CoreApp.AllPlugins {
// info := p.GetInfo() info := p.GetInfo()
// for _, route := range p.Routes() { for _, route := range p.Routes() {
// path := fmt.Sprintf("/plugins/%v/%v", info.Name, route.URL) path := fmt.Sprintf("/plugins/%v/%v", info.Name, route.URL)
// r.Handle(path, http.HandlerFunc(route.Handler)).Methods(route.Method) r.Handle(path, http.HandlerFunc(route.Handler)).Methods(route.Method)
// fmt.Printf("Added Route %v for plugin %v\n", path, info.Name) fmt.Printf("Added Route %v for plugin %v\n", path, info.Name)
// } }
//} }
srv := &http.Server{ srv := &http.Server{
Addr: "0.0.0.0:8080", Addr: "0.0.0.0:8080",
WriteTimeout: time.Second * 15, WriteTimeout: time.Second * 15,

View File

@ -1,19 +1,19 @@
package handlers package handlers
import ( import (
"fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/hunterlong/statup/core" "github.com/hunterlong/statup/core"
"github.com/tdewolff/minify"
"net/http" "net/http"
"time"
) )
func Router() *mux.Router { func Router() *mux.Router {
r := mux.NewRouter() r := mux.NewRouter()
r.Handle("/", http.HandlerFunc(IndexHandler)) r.Handle("/", http.HandlerFunc(IndexHandler))
LocalizedAssets(r) LocalizedAssets(r)
m := minify.New() r.Handle("/charts.js", http.HandlerFunc(RenderServiceChartsHandler))
r.Handle("/charts.js", m.Middleware(http.HandlerFunc(RenderServiceChartsHandler)))
r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET") r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST") r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST")
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET") r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET")
@ -49,7 +49,12 @@ func Router() *mux.Router {
r.Handle("/api/users/{id}", http.HandlerFunc(ApiUserHandler)) r.Handle("/api/users/{id}", http.HandlerFunc(ApiUserHandler))
r.Handle("/metrics", http.HandlerFunc(PrometheusHandler)) r.Handle("/metrics", http.HandlerFunc(PrometheusHandler))
r.NotFoundHandler = http.HandlerFunc(Error404Handler) r.NotFoundHandler = http.HandlerFunc(Error404Handler)
Store = sessions.NewCookieStore([]byte("secretinfo")) if core.CoreApp != nil {
cookie := fmt.Sprintf("%v_%v", core.CoreApp.ApiSecret, time.Now().Nanosecond())
Store = sessions.NewCookieStore([]byte(cookie))
} else {
Store = sessions.NewCookieStore([]byte("secretinfo"))
}
return r return r
} }

View File

@ -11,8 +11,8 @@ import (
func RenderServiceChartsHandler(w http.ResponseWriter, r *http.Request) { func RenderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
services := core.CoreApp.Services services := core.CoreApp.Services
//w.Header().Set("Content-Type", "text/javascript") w.Header().Set("Content-Type", "text/javascript")
//w.Header().Set("Cache-Control", "no-cache, private, max-age=0") w.Header().Set("Cache-Control", "max-age=60")
ExecuteJSResponse(w, r, "charts.js", services) ExecuteJSResponse(w, r, "charts.js", services)
} }
@ -29,7 +29,6 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
fmt.Println("service adding")
r.ParseForm() r.ParseForm()
name := r.PostForm.Get("name") name := r.PostForm.Get("name")
domain := r.PostForm.Get("domain") domain := r.PostForm.Get("domain")

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"github.com/gorilla/sessions"
"github.com/hunterlong/statup/core" "github.com/hunterlong/statup/core"
"github.com/hunterlong/statup/types" "github.com/hunterlong/statup/types"
"github.com/hunterlong/statup/utils" "github.com/hunterlong/statup/utils"
@ -106,7 +107,7 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
admin := &core.User{ admin := &core.User{
Username: config.Username, Username: config.Username,
Password: config.Password, Password: config.Password,
Email: email, Email: config.Email,
Admin: true, Admin: true,
} }
admin.Create() admin.Create()
@ -116,6 +117,7 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
} }
core.InitApp() core.InitApp()
Store = sessions.NewCookieStore([]byte(core.CoreApp.ApiSecret))
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }

View File

@ -13,7 +13,6 @@ import (
"os" "os"
"strings" "strings"
"testing" "testing"
"time"
) )
var ( var (
@ -28,6 +27,7 @@ func RunInit(t *testing.T) {
os.Remove("./index.html") os.Remove("./index.html")
route = handlers.Router() route = handlers.Router()
LoadDotEnvs() LoadDotEnvs()
core.CoreApp = core.NewCore()
} }
var forceSequential chan bool = make(chan bool, 1) var forceSequential chan bool = make(chan bool, 1)
@ -49,6 +49,9 @@ func TestRunAll(t *testing.T) {
t.Run(dbt+" Sample Data", func(t *testing.T) { t.Run(dbt+" Sample Data", func(t *testing.T) {
RunInsertMysqlSample(t) RunInsertMysqlSample(t)
}) })
t.Run(dbt+" Load Configs", func(t *testing.T) {
RunLoadConfig(t)
})
t.Run(dbt+" Select Core", func(t *testing.T) { t.Run(dbt+" Select Core", func(t *testing.T) {
RunSelectCoreMYQL(t, dbt) RunSelectCoreMYQL(t, dbt)
}) })
@ -213,11 +216,20 @@ func RunInsertMysqlSample(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
} }
func RunLoadConfig(t *testing.T) {
var err error
core.Configs, err = core.LoadConfig()
assert.Nil(t, err)
assert.NotNil(t, core.Configs)
}
func RunSelectCoreMYQL(t *testing.T, db string) { func RunSelectCoreMYQL(t *testing.T, db string) {
var err error var err error
core.CoreApp, err = core.SelectCore() core.CoreApp, err = core.SelectCore()
assert.Nil(t, err) assert.Nil(t, err)
t.Log(core.CoreApp)
assert.Equal(t, "Testing "+db, core.CoreApp.Name) assert.Equal(t, "Testing "+db, core.CoreApp.Name)
assert.Equal(t, db, core.CoreApp.DbConnection)
assert.NotEmpty(t, core.CoreApp.ApiKey) assert.NotEmpty(t, core.CoreApp.ApiKey)
assert.NotEmpty(t, core.CoreApp.ApiSecret) assert.NotEmpty(t, core.CoreApp.ApiSecret)
assert.Equal(t, VERSION, core.CoreApp.Version) assert.Equal(t, VERSION, core.CoreApp.Version)
@ -363,7 +375,6 @@ func RunCreateService_Hits(t *testing.T) {
service := s.Check() service := s.Check()
assert.NotNil(t, service) assert.NotNil(t, service)
} }
time.Sleep(1 * time.Second)
} }
} }

View File

@ -1,7 +1,6 @@
package plugin package plugin
import ( import (
"fmt"
"net/http" "net/http"
"upper.io/db.v3/lib/sqlbuilder" "upper.io/db.v3/lib/sqlbuilder"
) )
@ -26,10 +25,6 @@ func SetDatabase(database sqlbuilder.Database) {
DB = database DB = database
} }
func Throw(err error) {
fmt.Println(err)
}
type PluginInfo struct { type PluginInfo struct {
Info Info Info Info
PluginActions PluginActions

View File

@ -1,7 +1,7 @@
[{ [{
"name": "slack", "name": "Example Plugin",
"description": "slack bot that send a message in a channel when server is down.", "description": "An example of a plugin for Statup",
"repo": "https://github.com/hunterlong/statup_slack", "repo": "https://github.com/hunterlong/statup_plugin",
"author": "Hunter Long", "author": "Hunter Long",
"namespace": "slack" "namespace": "example"
}] }]

View File

@ -1,146 +1,6 @@
{{ range . }}{{ if .AvgTime }}var ctx = document.getElementById("service_{{.Id}}").getContext('2d'); {{ range . }}{{ if .AvgTime }}var ctx_{{.Id}}=document.getElementById("service_{{.Id}}").getContext('2d');var chartdata=new Chart(ctx_{{.Id}},{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:!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}
var chartdata = new Chart(ctx, { if(hxL>=820){hxL=820}else if(70>=hxL){hxL=70}
type: 'line', 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}}}})
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 }}
{{ end }} {{ end }}

View File

View File

View File

@ -45,9 +45,7 @@
<div class="mt-4" id="service_id_{{.Id}}"> <div class="mt-4" id="service_id_{{.Id}}">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="col-12"> <div class="col-12">
<h4 class="mt-3"><a href="/service/{{.Id}}"{{if not .Online}} class="text-danger"{{end}}>{{ .Name }}</a> <h4 class="mt-3"><a href="/service/{{.Id}}"{{if not .Online}} class="text-danger"{{end}}>{{ .Name }}</a>
{{if .Online}} {{if .Online}}
<span class="badge bg-success float-right">ONLINE</span> <span class="badge bg-success float-right">ONLINE</span>