apexcharts - heatmap

pull/127/head
hunterlong 2019-01-29 04:02:13 -08:00
parent 652b565d4e
commit 30b6a2df6b
17 changed files with 354 additions and 174 deletions

View File

@ -244,6 +244,7 @@ func recordFailure(s *Service, issue string) {
Issue: issue,
PingTime: s.PingTime,
CreatedAt: time.Now(),
ErrorCode: s.LastStatusCode,
}}
utils.Log(2, fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
s.CreateFailure(fail)

View File

@ -124,6 +124,16 @@ func CountFailures() uint64 {
return count
}
// TotalFailuresOnDate returns the total amount of failures for a service on a specific time/date
func (s *Service) TotalFailuresOnDate(ago time.Time) (uint64, error) {
var count uint64
date := ago.UTC().Format("2006-01-02 00:00:00")
dateend := ago.UTC().Format("2006-01-02 23:59:59")
rows := failuresDB().Where("service = ? AND created_at BETWEEN ? AND ?", s.Id, date, dateend).Not("method = 'checkin'")
err := rows.Count(&count)
return count, err.Error
}
// TotalFailures24 returns the amount of failures for a service within the last 24 hours
func (s *Service) TotalFailures24() (uint64, error) {
ago := time.Now().Add(-24 * time.Hour)

View File

@ -28,7 +28,7 @@ func InsertSampleData() error {
utils.Log(1, "Inserting Sample Data...")
insertSampleGroups()
createdOn := time.Now().Add((-24 * 90) * time.Hour).UTC()
s1 := ReturnService(&types.Service{
Name: "Google",
Domain: "https://google.com",
@ -39,6 +39,7 @@ func InsertSampleData() error {
Timeout: 10,
Order: 1,
GroupId: 1,
CreatedAt: createdOn,
})
s2 := ReturnService(&types.Service{
Name: "Statping Github",
@ -49,6 +50,7 @@ func InsertSampleData() error {
Method: "GET",
Timeout: 20,
Order: 2,
CreatedAt: createdOn,
})
s3 := ReturnService(&types.Service{
Name: "JSON Users Test",
@ -61,6 +63,7 @@ func InsertSampleData() error {
Order: 3,
Public: types.NewNullBool(true),
GroupId: 2,
CreatedAt: createdOn,
})
s4 := ReturnService(&types.Service{
Name: "JSON API Tester",
@ -75,17 +78,19 @@ func InsertSampleData() error {
Order: 4,
Public: types.NewNullBool(true),
GroupId: 2,
CreatedAt: createdOn,
})
s5 := ReturnService(&types.Service{
Name: "Google DNS",
Domain: "8.8.8.8",
Interval: 20,
Type: "tcp",
Port: 53,
Timeout: 120,
Order: 5,
Public: types.NewNullBool(true),
GroupId: 1,
Name: "Google DNS",
Domain: "8.8.8.8",
Interval: 20,
Type: "tcp",
Port: 53,
Timeout: 120,
Order: 5,
Public: types.NewNullBool(true),
GroupId: 1,
CreatedAt: createdOn,
})
s1.Create(false)
@ -249,6 +254,7 @@ func InsertLargeSampleData() error {
if err := insertMessages(); err != nil {
return err
}
createdOn := time.Now().Add((-24 * 90) * time.Hour).UTC()
s6 := ReturnService(&types.Service{
Name: "JSON Lint",
Domain: "https://jsonlint.com",
@ -258,6 +264,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 10,
Order: 6,
CreatedAt: createdOn,
})
s7 := ReturnService(&types.Service{
@ -269,6 +276,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 15,
Order: 7,
CreatedAt: createdOn,
})
s8 := ReturnService(&types.Service{
@ -291,6 +299,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 10,
Order: 9,
CreatedAt: createdOn,
})
s10 := ReturnService(&types.Service{
@ -302,6 +311,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 10,
Order: 10,
CreatedAt: createdOn,
})
s11 := ReturnService(&types.Service{
@ -313,6 +323,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 20,
Order: 11,
CreatedAt: createdOn,
})
s12 := ReturnService(&types.Service{
@ -324,6 +335,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 20,
Order: 12,
CreatedAt: createdOn,
})
s13 := ReturnService(&types.Service{
@ -335,6 +347,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 10,
Order: 13,
CreatedAt: createdOn,
})
s14 := ReturnService(&types.Service{
@ -346,6 +359,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 12,
Order: 14,
CreatedAt: createdOn,
})
s15 := ReturnService(&types.Service{
@ -357,6 +371,7 @@ func InsertLargeSampleData() error {
Method: "GET",
Timeout: 12,
Order: 15,
CreatedAt: createdOn,
})
s6.Create(false)
@ -370,7 +385,7 @@ func InsertLargeSampleData() error {
s14.Create(false)
s15.Create(false)
var dayAgo = time.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
var dayAgo = time.Now().Add((-24 * 90) * time.Hour)
insertHitRecords(dayAgo, 1450)

View File

@ -103,6 +103,7 @@ func Router() *mux.Router {
r.Handle("/api/reorder", http.HandlerFunc(reorderServiceHandler)).Methods("POST")
r.Handle("/api/services/{id}/data", cached("30s", "application/json", http.HandlerFunc(apiServiceDataHandler))).Methods("GET")
r.Handle("/api/services/{id}/ping", http.HandlerFunc(apiServicePingDataHandler)).Methods("GET")
r.Handle("/api/services/{id}/heatmap", http.HandlerFunc(apiServiceHeatmapHandler)).Methods("GET")
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}/failures", http.HandlerFunc(apiServiceFailuresHandler)).Methods("GET")

View File

@ -221,6 +221,53 @@ func apiServicePingDataHandler(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(obj)
}
type dataXy struct {
X int `json:"x"`
Y int `json:"y"`
}
type dataXyMonth struct {
Date time.Time `json:"date"`
Data []*dataXy `json:"data"`
}
func apiServiceHeatmapHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
service := core.SelectService(utils.ToInt(vars["id"]))
if service == nil {
sendErrorJson(errors.New("service data not found"), w, r)
return
}
var monthOutput []*dataXyMonth
start := service.CreatedAt
if start.Year() <= 2 {
start = service.CreatedAt.Add(time.Duration((-3 * 24) * time.Hour))
}
for y := start; y.Year() == start.Year(); y = y.AddDate(1, 0, 0) {
for m := y; m.Month() == y.Month(); m = m.AddDate(0, 1, 0) {
var output []*dataXy
for day := 1; day <= 31; day++ {
date := time.Date(y.Year(), y.Month(), day, 0, 0, 0, 0, time.UTC)
failures, _ := service.TotalFailuresOnDate(date)
output = append(output, &dataXy{day, int(failures)})
}
monthOutput = append(monthOutput, &dataXyMonth{m, output})
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(monthOutput)
}
func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
if !IsFullAuthenticated(r) {
sendUnauthorizedJson(w, r)

View File

@ -139,6 +139,7 @@ HTML, BODY {
position: relative;
height: 170px;
width: 100%;
overflow: hidden;
}
.service-chart-container {

File diff suppressed because one or more lines are too long

6
source/js/apexcharts.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,86 +1,139 @@
{{define "charts"}}
{{$start := .Start}}
{{$end := .End}}
const axisOptions = {
labels: {
show: false
},
crosshairs: {
show: false
},
lines: {
show: false
},
tooltip: {
enabled: false
},
axisTicks: {
show: false
},
grid: {
show: false
},
marker: {
show: false
}
};
let options = {
chart: {
height: 210,
width: "100%",
type: "area",
animations: {
enabled: false,
initialAnimation: {
enabled: false
}
},
selection: {
enabled: false
},
zoom: {
enabled: false
},
toolbar: {
show: false
}
},
grid: {
show: false,
padding: {
top: 0,
right: 0,
bottom: 0,
left: 0,
},
},
tooltip: {
enabled: false,
marker: {
show: false,
},
x: {
show: false,
}
},
legend: {
show: false,
},
dataLabels: {
enabled: false
},
floating: true,
axisTicks: {
show: false
},
axisBorder: {
show: false
},
fill: {
colors: ["#48d338"],
opacity: 1,
type: 'solid'
},
stroke: {
show: true,
curve: 'smooth',
lineCap: 'butt',
colors: ["#3aa82d"],
},
series: [
{
name: "Series 1",
data: [
{
x: "02-10-2017 GMT",
y: 34
},
{
x: "02-11-2017 GMT",
y: 43
},
{
x: "02-12-2017 GMT",
y: 31
},
{
x: "02-13-2017 GMT",
y: 43
},
{
x: "02-14-2017 GMT",
y: 33
},
{
x: "02-15-2017 GMT",
y: 52
}
]
}
],
xaxis: {
type: "datetime",
...axisOptions
},
yaxis: {
...axisOptions
}
};
{{ 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: false,
scaleShowValues: false,
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: -10
}
},
hover: {
animationDuration: 0,
},
responsiveAnimationDuration: 0,
animation: {
duration: 3500,
onComplete: onChartComplete
},
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,
time: {
displayFormats: {
'hour': 'MMM DD hA'
},
source: 'auto'
},
gridLines: {
display: false
},
ticks: {
source: 'auto',
stepSize: 1,
min: 0,
fontColor: "white",
fontSize: 20,
display: false
}
}]
},
elements: {
point: {
radius: 0
}
}
}
});
let chart{{.Id}} = new ApexCharts(document.querySelector("#service_{{js .Id}}"), options);
{{end}}
function onChartComplete(chart) {
@ -140,7 +193,7 @@ function onChartComplete(chart) {
}
$( document ).ready(function() {
{{ range .Services }}
AjaxChart(chartdata_{{js .Id}},{{js .Id}},{{$start}},9999999999,"hour");{{end}}
});
{{ range .Services }}AjaxChart(chart{{js .Id}}, {{js .Id}}, 0, 9999999999);
{{end}}
});
{{end}}

View File

@ -124,11 +124,10 @@ function AjaxChart(chart, service, start=0, end=9999999999, group="hour", retry=
} else if (data.data.length === 0) {
return;
}
chart.data.labels.pop();
data.data.forEach(function(d) {
chart.data.datasets[0].data.push(d);
});
chart.update();
chart.render();
chart.updateSeries([{
data: data.data
}]);
}
});
}

View File

@ -136,6 +136,7 @@ HTML,BODY {
position: relative;
height: 170px;
width: 100%;
overflow: hidden;
}
.service-chart-container {

View File

@ -63,7 +63,7 @@
<span class="badge bg-danger float-right pulse">OFFLINE</span>
{{end}}</h4>
<div class="row stats_area mt-5 mb-5">
<div class="row stats_area mt-5">
<div class="col-4">
<span class="lg_number">{{.AvgTime}}ms</span>
Average Response
@ -82,7 +82,7 @@
</div>
{{ if .AvgUptime24 }}
<div class="chart-container">
<canvas id="service_{{ .Id }}"></canvas>
<div id="service_{{ .Id }}"></div>
</div>
{{ end }}
<div class="row lower_canvas full-col-12 text-white{{if not .Online}} bg-danger{{end}}">

View File

@ -2,12 +2,12 @@
{{if USE_CDN}}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script src="https://assets.statping.com/main.js"></script>
{{ else }}
<script src="/js/jquery-3.3.1.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/Chart.bundle.min.js"></script>
<script src="/js/apexcharts.min.js"></script>
<script src="/js/main.js"></script>
{{end}}
{{block "extra_scripts" .}} {{end}}

View File

@ -59,7 +59,11 @@
{{end}}
<div class="service-chart-container">
<canvas id="service"></canvas>
<div id="service"></div>
</div>
<div class="col-12">
<div id="service_heatmap"></div>
</div>
<form id="service_date_form" class="col-12 mt-2 mb-3">
@ -205,69 +209,79 @@
<script>
$(document).ready(function() {
var ctx = document.getElementById("service").getContext('2d');
var chartdata = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: 'Response Time (Milliseconds)',
data: [],
backgroundColor: [
'rgba(47, 206, 30, 0.92)'
],
borderColor: [
'rgb(47, 171, 34)'
],
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
display: true
}
}],
xAxes: [{
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: {
display: true
},
ticks: {
source: 'auto'
}
}],
},
elements: {
point: {
radius: 0
let options = {
chart: {
height: "100%",
width: "100%",
type: "area",
animations: {
enabled: false,
initialAnimation: {
enabled: false
}
},
},
fill: {
colors: ["#48d338"],
opacity: 1,
type: 'solid'
},
stroke: {
show: true,
curve: 'smooth',
lineCap: 'butt',
colors: ["#3aa82d"],
},
series: [
{
name: "Response Time",
data: [
{
x: "02-10-2017 GMT",
y: 34
},
{
x: "02-11-2017 GMT",
y: 43
},
{
x: "02-12-2017 GMT",
y: 31
},
{
x: "02-13-2017 GMT",
y: 43
},
{
x: "02-14-2017 GMT",
y: 33
},
{
x: "02-15-2017 GMT",
y: 52
}
]
}
}
});
],
xaxis: {
type: "datetime",
},
yaxis: {
labels: {
formatter: (value) => {
return (value * 0.1).toFixed(0) + "ms"
},
},
},
dataLabels: {
enabled: false
},
};
AjaxChart(chartdata,{{$s.Id}},{{.StartUnix}},{{.EndUnix}},"hour");
let chart = new ApexCharts(document.querySelector("#service"), options);
AjaxChart(chart,{{$s.Id}},{{.StartUnix}},{{.EndUnix}},"hour");
let startDate = $("#service_start").flatpickr({
enableTime: false,
@ -290,6 +304,47 @@ $(document).ready(function() {
startDate.open()
});
var heat_options = {
chart: {
height: "100%",
width: "100%",
type: 'heatmap',
toolbar: {
show: false
}
},
dataLabels: {
enabled: false,
},
enableShades: true,
shadeIntensity: 0.5,
colors: ["#d53a3b"],
series: [{}],
};
var heatChart = new ApexCharts(
document.querySelector("#service_heatmap"),
heat_options
);
var heatmapData = [];
$.ajax({
url: "/api/services/{{$s.Id}}/heatmap",
type: 'GET',
success: function(data) {
data.forEach(function(d) {
var date = new Date(d.date);
heatmapData.push({name: date.toLocaleString('en-us', { month: 'long' }), data: d.data});
});
heatChart.render();
heatChart.updateSeries(heatmapData);
}
});
});
</script>
{{end}}

View File

@ -1,6 +1,6 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2019-01-17 01:23:49.15263 -0800 PST m=+0.644968431
// 2019-01-29 04:01:15.747029 -0800 PST m=+0.799396747
//
// This contains the most recently Markdown source for the Statping Wiki.
package source

View File

@ -26,6 +26,7 @@ type Failure struct {
Issue string `gorm:"column:issue" json:"issue"`
Method string `gorm:"column:method" json:"method,omitempty"`
MethodId int64 `gorm:"column:method_id" json:"method_id,omitempty"`
ErrorCode int `gorm:"column:error_code" json:"error_code"`
Service int64 `gorm:"index;column:service" json:"-"`
Checkin int64 `gorm:"index;column:checkin" json:"-"`
PingTime float64 `gorm:"column:ping_time" json:"ping"`

View File

@ -1 +1 @@
0.80.39
0.80.40