mirror of https://github.com/statping/statping
chart updates - ping time - core updates
parent
6b5660b8d8
commit
baf6ca3d34
|
@ -74,20 +74,34 @@ func (s *Service) duration() time.Duration {
|
|||
return amount
|
||||
}
|
||||
|
||||
func (s *Service) parseHost() string {
|
||||
if s.Type == "tcp" {
|
||||
return s.Domain
|
||||
} else {
|
||||
domain := s.Domain
|
||||
hasPort, _ := regexp.MatchString(`\:([0-9]+)`, domain)
|
||||
if hasPort {
|
||||
splitDomain := strings.Split(s.Domain, ":")
|
||||
domain = splitDomain[len(splitDomain)-2]
|
||||
}
|
||||
host, err := url.Parse(domain)
|
||||
if err != nil {
|
||||
return s.Domain
|
||||
}
|
||||
return host.Host
|
||||
}
|
||||
}
|
||||
|
||||
// dnsCheck will check the domain name and return a float64 for the amount of time the DNS check took
|
||||
func (s *Service) dnsCheck() (float64, error) {
|
||||
var err error
|
||||
t1 := time.Now()
|
||||
domain := s.Domain
|
||||
hasPort, _ := regexp.MatchString(`\:([0-9]+)`, domain)
|
||||
if hasPort {
|
||||
splitDomain := strings.Split(s.Domain, ":")
|
||||
domain = splitDomain[len(splitDomain)-2]
|
||||
host := s.parseHost()
|
||||
if s.Type == "tcp" {
|
||||
_, err = net.LookupHost(host)
|
||||
} else {
|
||||
_, err = net.LookupIP(host)
|
||||
}
|
||||
url, err := url.Parse(domain)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = net.LookupIP(url.Host)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -98,6 +112,14 @@ func (s *Service) dnsCheck() (float64, error) {
|
|||
|
||||
// checkTcp will check a TCP service
|
||||
func (s *Service) checkTcp(record bool) *Service {
|
||||
dnsLookup, err := s.dnsCheck()
|
||||
if err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("Could not get IP address for TCP service %v, %v", s.Domain, err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
s.PingTime = dnsLookup
|
||||
t1 := time.Now()
|
||||
domain := fmt.Sprintf("%v", s.Domain)
|
||||
if s.Port != 0 {
|
||||
|
@ -134,7 +156,7 @@ func (s *Service) checkHttp(record bool) *Service {
|
|||
}
|
||||
return s
|
||||
}
|
||||
s.DnsLookup = dnsLookup
|
||||
s.PingTime = dnsLookup
|
||||
t1 := time.Now()
|
||||
timeout := time.Duration(s.Timeout)
|
||||
client := http.Client{
|
||||
|
@ -213,9 +235,10 @@ func recordSuccess(s *Service) {
|
|||
hit := &types.Hit{
|
||||
Service: s.Id,
|
||||
Latency: s.Latency,
|
||||
PingTime: s.PingTime,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
utils.Log(1, fmt.Sprintf("Service %v Successful: %0.2f ms", s.Name, hit.Latency*1000))
|
||||
utils.Log(1, fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
|
||||
s.CreateHit(hit)
|
||||
notifier.OnSuccess(s.Service)
|
||||
}
|
||||
|
@ -226,9 +249,10 @@ func recordFailure(s *Service, issue string) {
|
|||
fail := &types.Failure{
|
||||
Service: s.Id,
|
||||
Issue: issue,
|
||||
PingTime: s.PingTime,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
utils.Log(2, fmt.Sprintf("Service %v Failing: %v", s.Name, issue))
|
||||
utils.Log(2, fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
|
||||
s.CreateFailure(fail)
|
||||
notifier.OnFailure(s.Service, fail)
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ func checkinDB() *gorm.DB {
|
|||
}
|
||||
|
||||
// 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) *gorm.DB {
|
||||
selector := Dbtimestamp(group)
|
||||
func (s *Service) HitsBetween(t1, t2 time.Time, group string, column string) *gorm.DB {
|
||||
selector := Dbtimestamp(group, column)
|
||||
return DbSession.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")
|
||||
}
|
||||
|
||||
|
@ -291,7 +291,7 @@ func (db *DbConfig) DropDatabase() error {
|
|||
func (db *DbConfig) CreateDatabase() error {
|
||||
utils.Log(1, "Creating Database Tables...")
|
||||
err := DbSession.CreateTable(&types.Checkin{})
|
||||
err = DbSession.CreateTable(&types.CheckinHit{})
|
||||
//err = DbSession.CreateTable(&types.CheckinHit{})
|
||||
err = DbSession.CreateTable(¬ifier.Notification{})
|
||||
err = DbSession.Table("core").CreateTable(&types.Core{})
|
||||
err = DbSession.CreateTable(&types.Failure{})
|
||||
|
@ -317,7 +317,7 @@ func (db *DbConfig) MigrateDatabase() error {
|
|||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
tx = tx.AutoMigrate(&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}).Table("core").AutoMigrate(&types.Core{})
|
||||
tx = tx.AutoMigrate(&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Checkin{}, ¬ifier.Notification{}).Table("core").AutoMigrate(&types.Core{})
|
||||
if tx.Error != nil {
|
||||
tx.Rollback()
|
||||
utils.Log(3, fmt.Sprintf("Statup Database could not be migrated: %v", tx.Error))
|
||||
|
|
|
@ -172,7 +172,7 @@ func (s *Service) DowntimeText() string {
|
|||
return fmt.Sprintf("%v has been offline for %v", s.Name, utils.DurationReadable(s.Downtime()))
|
||||
}
|
||||
|
||||
func Dbtimestamp(group string) string {
|
||||
func Dbtimestamp(group string, column string) string {
|
||||
seconds := 60
|
||||
if group == "second" {
|
||||
seconds = 60
|
||||
|
@ -183,11 +183,11 @@ func Dbtimestamp(group string) string {
|
|||
}
|
||||
switch CoreApp.DbConnection {
|
||||
case "mysql":
|
||||
return fmt.Sprintf("CONCAT(date_format(created_at, '%%Y-%%m-%%d %%H:00:00')) AS timeframe, AVG(latency) AS value")
|
||||
return fmt.Sprintf("CONCAT(date_format(created_at, '%%Y-%%m-%%d %%H:00:00')) AS timeframe, AVG(%v) AS value", column)
|
||||
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(%v) as value", seconds, seconds, column)
|
||||
case "postgres":
|
||||
return fmt.Sprintf("date_trunc('%v', created_at) AS timeframe, AVG(latency) AS value", group)
|
||||
return fmt.Sprintf("date_trunc('%v', created_at) AS timeframe, AVG(%v) AS value", group, column)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
@ -207,9 +207,9 @@ func (s *Service) Downtime() time.Duration {
|
|||
return since
|
||||
}
|
||||
|
||||
func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group string) *DateScanObj {
|
||||
func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group string, column string) *DateScanObj {
|
||||
var d []DateScan
|
||||
model := service.(*Service).HitsBetween(start, end, group)
|
||||
model := service.(*Service).HitsBetween(start, end, group, column)
|
||||
rows, _ := model.Rows()
|
||||
for rows.Next() {
|
||||
var gd DateScan
|
||||
|
@ -241,7 +241,7 @@ func (d *DateScanObj) ToString() string {
|
|||
func (s *Service) GraphData() string {
|
||||
start := time.Now().Add((-24 * 7) * time.Hour)
|
||||
end := time.Now()
|
||||
obj := GraphDataRaw(s, start, end, "hour")
|
||||
obj := GraphDataRaw(s, start, end, "hour", "latency")
|
||||
data, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
utils.Log(2, err)
|
||||
|
|
|
@ -100,6 +100,7 @@ func TestCheckHTTPService(t *testing.T) {
|
|||
assert.True(t, service.Online)
|
||||
assert.Equal(t, 200, service.LastStatusCode)
|
||||
assert.NotZero(t, service.Latency)
|
||||
assert.NotZero(t, service.PingTime)
|
||||
}
|
||||
|
||||
func TestServiceTCPCheck(t *testing.T) {
|
||||
|
@ -114,6 +115,7 @@ func TestCheckTCPService(t *testing.T) {
|
|||
assert.Equal(t, "Changed Google DNS", service.Name)
|
||||
assert.True(t, service.Online)
|
||||
assert.NotZero(t, service.Latency)
|
||||
assert.NotZero(t, service.PingTime)
|
||||
}
|
||||
|
||||
func TestServiceOnline24Hours(t *testing.T) {
|
||||
|
|
|
@ -89,7 +89,24 @@ func apiServiceDataHandler(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
obj := core.GraphDataRaw(service, time.Unix(startField, 0), time.Unix(endField, 0), grouping, "latency")
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(obj)
|
||||
}
|
||||
|
||||
func apiServicePingDataHandler(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, "ping_time")
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(obj)
|
||||
|
|
|
@ -164,7 +164,7 @@ func executeResponse(w http.ResponseWriter, r *http.Request, file string, data i
|
|||
|
||||
templates := []string{"base.html", "head.html", "nav.html", "footer.html", "scripts.html", "form_service.html", "form_notifier.html", "form_user.html"}
|
||||
|
||||
javascripts := []string{"chart_index.js"}
|
||||
javascripts := []string{"charts.js", "chart_index.js"}
|
||||
|
||||
render, err := source.TmplBox.String(file)
|
||||
if err != nil {
|
||||
|
@ -221,9 +221,19 @@ func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data
|
|||
"safe": func(html string) template.HTML {
|
||||
return template.HTML(html)
|
||||
},
|
||||
"Services": func() []types.ServiceInterface {
|
||||
return core.CoreApp.Services
|
||||
},
|
||||
})
|
||||
t.Parse(render)
|
||||
t.Execute(w, data)
|
||||
_, err = t.Parse(render)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
}
|
||||
|
||||
err = t.Execute(w, data)
|
||||
if err != nil {
|
||||
utils.Log(4, err)
|
||||
}
|
||||
}
|
||||
|
||||
// error404Handler is a HTTP handler for 404 error pages
|
||||
|
|
|
@ -47,7 +47,7 @@ func Router() *mux.Router {
|
|||
r.PathPrefix("/statup.png").Handler(http.FileServer(source.TmplBox.HTTPBox()))
|
||||
}
|
||||
r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(source.JsBox.HTTPBox())))
|
||||
r.Handle("/charts/{id}.js", http.HandlerFunc(renderServiceChartHandler))
|
||||
//r.Handle("/charts/{id}.js", http.HandlerFunc(renderServiceChartHandler))
|
||||
r.Handle("/charts.js", http.HandlerFunc(renderServiceChartsHandler))
|
||||
r.Handle("/setup", http.HandlerFunc(setupHandler)).Methods("GET")
|
||||
r.Handle("/setup", http.HandlerFunc(processSetupHandler)).Methods("POST")
|
||||
|
@ -87,6 +87,7 @@ func Router() *mux.Router {
|
|||
r.Handle("/api/services", http.HandlerFunc(apiCreateServiceHandler)).Methods("POST")
|
||||
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}/ping", http.HandlerFunc(apiServicePingDataHandler)).Methods("GET")
|
||||
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceUpdateHandler)).Methods("POST")
|
||||
r.Handle("/api/services/{id}", http.HandlerFunc(apiServiceDeleteHandler)).Methods("DELETE")
|
||||
|
||||
|
|
|
@ -54,14 +54,14 @@ func renderServiceChartHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
service := core.SelectService(utils.StringInt(vars["id"]))
|
||||
data := core.GraphDataRaw(service, start, end, "hour").ToString()
|
||||
data := core.GraphDataRaw(service, start, end, "hour", "latency").ToString()
|
||||
|
||||
out := struct {
|
||||
Services []*core.Service
|
||||
Data []string
|
||||
}{[]*core.Service{service}, []string{data}}
|
||||
|
||||
executeJSResponse(w, r, "charts.js", out)
|
||||
executeResponse(w, r, "charts.js", out, nil)
|
||||
}
|
||||
|
||||
func renderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -69,19 +69,16 @@ func renderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Content-Type", "text/javascript")
|
||||
w.Header().Set("Cache-Control", "max-age=60")
|
||||
|
||||
var data []string
|
||||
end := now.EndOfDay().UTC()
|
||||
start := now.BeginningOfDay().UTC()
|
||||
|
||||
//var data []string
|
||||
//end := now.EndOfDay().UTC()
|
||||
//start := now.BeginningOfDay().UTC()
|
||||
var srvs []*core.Service
|
||||
for _, s := range services {
|
||||
d := core.GraphDataRaw(s, start, end, "hour").ToString()
|
||||
data = append(data, d)
|
||||
srvs = append(srvs, s.(*core.Service))
|
||||
}
|
||||
|
||||
out := struct {
|
||||
Services []types.ServiceInterface
|
||||
Data []string
|
||||
}{services, data}
|
||||
Services []*core.Service
|
||||
}{srvs}
|
||||
|
||||
executeJSResponse(w, r, "charts.js", out)
|
||||
}
|
||||
|
@ -194,7 +191,7 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
|
|||
end = time.Unix(endField, 0)
|
||||
}
|
||||
|
||||
data := core.GraphDataRaw(serv, start, end, "hour")
|
||||
data := core.GraphDataRaw(serv, start, end, "hour", "latency")
|
||||
|
||||
out := struct {
|
||||
Service *core.Service
|
||||
|
|
|
@ -6,8 +6,8 @@ var chartdata_{{js .Id}} = new Chart(ctx_{{js .Id}}, {
|
|||
datasets: [{
|
||||
label: 'Response Time (Milliseconds)',
|
||||
data: [],
|
||||
backgroundColor: ['rgba(47, 206, 30, 0.92)'],
|
||||
borderColor: ['rgb(47, 171, 34)'],
|
||||
backgroundColor: ['{{if .Online}}rgba(47, 206, 30, 0.92){{else}}rgb(221, 53, 69){{end}}'],
|
||||
borderColor: ['{{if .Online}}rgb(47, 171, 34){{else}}rgb(183, 32, 47){{end}}'],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
|
@ -88,7 +88,7 @@ var chartdata_{{js .Id}} = new Chart(ctx_{{js .Id}}, {
|
|||
display: !1
|
||||
},
|
||||
tooltips: {
|
||||
"enabled": !1
|
||||
enabled: !1
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
|
|
|
@ -1,23 +1,137 @@
|
|||
/*
|
||||
* Statup
|
||||
* Copyright (C) 2018. 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/>.
|
||||
*/
|
||||
{{define "charts"}}{{ range Services }}
|
||||
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: ['{{if .Online}}rgba(47, 206, 30, 0.92){{else}}rgb(221, 53, 69){{end}}'],
|
||||
borderColor: ['{{if .Online}}rgb(47, 171, 34){{else}}rgb(183, 32, 47){{end}}'],
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
{{$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(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,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 }}
|
||||
AjaxChart(chartdata_{{js .Id}},{{js .Id}},0,99999999999,"hour");
|
||||
{{end}}{{end}}
|
||||
|
|
|
@ -95,6 +95,25 @@ function AjaxChart(chart, service, start=0, end=9999999999, group="hour") {
|
|||
});
|
||||
}
|
||||
|
||||
function PingAjaxChart(chart, service, start=0, end=9999999999, group="hour") {
|
||||
$.ajax({
|
||||
url: "/api/services/"+service+"/ping?start="+start+"&end="+end+"&group="+group,
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
chart.data.labels.pop();
|
||||
chart.data.datasets.push({
|
||||
label: "Ping Time",
|
||||
backgroundColor: "#bababa"
|
||||
});
|
||||
chart.update();
|
||||
data.data.forEach(function(d) {
|
||||
chart.data.datasets[1].data.push(d);
|
||||
});
|
||||
chart.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('select#service_check_type').on('change', function() {
|
||||
var selected = $('#service_check_type option:selected').val();
|
||||
if (selected === 'POST') {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{define "form_service"}}
|
||||
<form action="{{if .}}/service/{{.Id}}{{else}}/service{{end}}" method="POST">
|
||||
<form action="{{if ne .Id 0}}/service/{{.Id}}{{else}}/service{{end}}" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
|
||||
<div class="col-sm-8">
|
||||
|
@ -49,26 +49,26 @@
|
|||
<div class="form-group row{{if eq .Type "tcp"}} d-none{{end}}">
|
||||
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="expected_status" class="form-control" value="{{.ExpectedStatus}}" id="service_response_code">
|
||||
<input type="number" name="expected_status" class="form-control" value="{{if ne .ExpectedStatus 0}}{{.ExpectedStatus}}{{end}}" placeholder="200" id="service_response_code">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row{{if eq .Type "http"}} d-none{{end}}">
|
||||
<label for="service_port" class="col-sm-4 col-form-label">TCP Port</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="port" class="form-control" value="{{.Port}}" id="service_port" placeholder="8080">
|
||||
<input type="number" name="port" class="form-control" value="{{if ne .Port 0}}{{.Port}}{{end}}" id="service_port" placeholder="8080">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="interval" class="form-control" value="{{.Interval}}" min="1" id="service_interval" required>
|
||||
<input type="number" name="interval" class="form-control" value="{{if ne .Interval 0}}{{.Interval}}{{end}}" min="1" id="service_interval" required>
|
||||
<small id="emailHelp" class="form-text text-muted">10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="service_timeout" class="col-sm-4 col-form-label">Timeout in Seconds</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="number" name="timeout" class="form-control" value="{{.Timeout}}" id="service_timeout" min="1">
|
||||
<input type="number" name="timeout" class="form-control" value="{{if ne .Timeout 0}}{{.Timeout}}{{end}}" placeholder="15" id="service_timeout" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
|
@ -78,12 +78,14 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-6">
|
||||
<button type="submit" class="btn btn-success btn-block">Update Service</button>
|
||||
<div class="{{if ne .Id 0}}col-6{{else}}col-12{{end}}">
|
||||
<button type="submit" class="btn btn-success btn-block">{{if ne .Id 0}}Update Service{{else}}Create Service{{end}}</button>
|
||||
</div>
|
||||
{{if ne .Id 0}}
|
||||
<div class="col-6">
|
||||
<a href="/service/{{ .Id }}/delete_failures" class="btn btn-danger btn-block confirm-btn">Delete All Failures</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{define "form_user"}}
|
||||
<form action="{{if .}}/user/{{.Id}}{{else}}/user{{end}}" method="POST">
|
||||
<form action="{{if ne .Id 0}}/user/{{.Id}}{{else}}/user{{end}}" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-4 col-form-label">Username</label>
|
||||
<div class="col-6 col-md-4">
|
||||
|
@ -32,7 +32,7 @@
|
|||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-block">Update User</button>
|
||||
<button type="submit" class="btn btn-primary btn-block">{{if ne .Id 0}}Update User{{else}}Create User{{end}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -80,9 +80,5 @@
|
|||
</div>
|
||||
{{end}}
|
||||
{{define "extra_scripts"}}
|
||||
<script>
|
||||
{{ range Services }}
|
||||
{{template "chartIndex" .}}
|
||||
{{end}}
|
||||
</script>
|
||||
<script src="/charts.js"></script>
|
||||
{{end}}
|
||||
|
|
|
@ -26,6 +26,7 @@ type Failure struct {
|
|||
Issue string `gorm:"column:issue" json:"issue"`
|
||||
Method string `gorm:"column:method" json:"method,omitempty"`
|
||||
Service int64 `gorm:"index;column:service" json:"-"`
|
||||
PingTime float64 `gorm:"column:ping_time"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
FailureInterface `gorm:"-" json:"-"`
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ type Service struct {
|
|||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
Online bool `gorm:"-" json:"online"`
|
||||
Latency float64 `gorm:"-" json:"latency"`
|
||||
PingTime float64 `gorm:"-" json:"ping_time"`
|
||||
Online24Hours float32 `gorm:"-" json:"24_hours_online"`
|
||||
AvgResponse string `gorm:"-" json:"avg_response"`
|
||||
Running chan bool `gorm:"-" json:"-"`
|
||||
|
@ -44,7 +45,6 @@ type Service struct {
|
|||
LastResponse string `gorm:"-" json:"-"`
|
||||
LastStatusCode int `gorm:"-" json:"status_code"`
|
||||
LastOnline time.Time `gorm:"-" json:"last_online"`
|
||||
DnsLookup float64 `gorm:"-" json:"dns_lookup_time"`
|
||||
Failures []interface{} `gorm:"-" json:"failures,omitempty"`
|
||||
Checkins []*Checkin `gorm:"-" json:"checkins,omitempty"`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue