infobox/node: render statistic charts based on prometheus backend
history: Frontend generated node charts with graphite backend Resolution for bower conflict Let the user configure the metrics in config.json Fix grunt task for deploying c3 css Fix tooltip layout issue
This commit is contained in:
parent
11a157c58a
commit
2a58bcf5f1
10 changed files with 347 additions and 11 deletions
255
lib/infobox/charts.js
Normal file
255
lib/infobox/charts.js
Normal file
|
@ -0,0 +1,255 @@
|
|||
define(["c3", "d3"], function (c3, d3) {
|
||||
|
||||
var charts = function (node, config) {
|
||||
this.node = node
|
||||
|
||||
this.chartConfig = config
|
||||
this.chart = null
|
||||
|
||||
this.zoomConfig = {
|
||||
"levels": [
|
||||
{"label": "8h", "from": 8 * 3600, "interval": 15 * 60}
|
||||
/*{"label": "24h", "from": "26h", "interval": "1h"},
|
||||
{"label": "1m", "from": "1mon", "interval": "1d"},
|
||||
{"label": "1y", "from": 31536000, "interval": 2628000}*/
|
||||
]
|
||||
}
|
||||
this.zoomLevel = 0
|
||||
|
||||
this.c3Config = {
|
||||
"size": {
|
||||
"height": 240
|
||||
},
|
||||
padding: {
|
||||
bottom: 30
|
||||
},
|
||||
"legend": {
|
||||
"item": {
|
||||
"onclick": function (id) {
|
||||
this.api.hide()
|
||||
this.api.show(id)
|
||||
}
|
||||
}
|
||||
},
|
||||
"tooltip": {
|
||||
"format": {
|
||||
"value": this.c3FormatToolTip.bind(this)
|
||||
}
|
||||
},
|
||||
"axis": {
|
||||
"x": {
|
||||
"type": "timeseries",
|
||||
"tick": {
|
||||
"format": this.c3FormatXAxis.bind(this),
|
||||
"rotate": -45
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"min": 0,
|
||||
"padding": {
|
||||
"bottom": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.cache = []
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
charts.prototype = {
|
||||
|
||||
init: function () {
|
||||
// Workaround for endless loop bug
|
||||
if (this.c3Config.axis.x.tick && this.c3Config.axis.x.tick.format && typeof this.c3Config.axis.x.tick.format === "function") {
|
||||
if (this.c3Config.axis.x.tick.format && !this.c3Config.axis.x.tick._format)
|
||||
this.c3Config.axis.x.tick._format = this.c3Config.axis.x.tick.format
|
||||
this.c3Config.axis.x.tick.format = function (val) {
|
||||
return this.c3Config.axis.x.tick._format(val)
|
||||
}.bind(this)
|
||||
}
|
||||
|
||||
// Configure metrics
|
||||
this.c3Config.data = {
|
||||
"keys": {
|
||||
"x": "time",
|
||||
"value": this.chartConfig.metrics.map(function (metric) {
|
||||
return metric.id
|
||||
})
|
||||
},
|
||||
"colors": this.chartConfig.metrics.reduce(function (collector, metric) {
|
||||
collector[metric.id] = metric.color
|
||||
return collector
|
||||
}, {}),
|
||||
"names": this.chartConfig.metrics.reduce(function (collector, metric) {
|
||||
collector[metric.id] = metric.label
|
||||
return collector
|
||||
}, {}),
|
||||
"hide": this.chartConfig.metrics.map(function (metric) {
|
||||
return metric.id
|
||||
}).filter(function (id) {
|
||||
return id !== this.chartConfig.defaultMetric
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var div = document.createElement("div")
|
||||
div.classList.add("chart")
|
||||
var h4 = document.createElement("h4")
|
||||
h4.textContent = this.chartConfig.name
|
||||
div.appendChild(h4)
|
||||
|
||||
|
||||
// Render chart
|
||||
this.load(function (data) {
|
||||
div.appendChild(this.renderChart(data))
|
||||
|
||||
// Render zoom controls
|
||||
if (this.zoomConfig.levels.length > 0)
|
||||
div.appendChild(this.renderZoomControls())
|
||||
|
||||
}.bind(this))
|
||||
|
||||
return div
|
||||
},
|
||||
|
||||
renderChart: function (data) {
|
||||
this.c3Config.data.json = data
|
||||
this.chart = c3.generate(this.c3Config)
|
||||
return this.chart.element
|
||||
},
|
||||
|
||||
updateChart: function (data) {
|
||||
this.c3Config.data.json = data
|
||||
this.chart.load(this.c3Config.data)
|
||||
},
|
||||
|
||||
renderZoomControls: function () {
|
||||
// Draw zoom controls
|
||||
var zoomDiv = document.createElement("div")
|
||||
zoomDiv.classList.add("zoom-buttons")
|
||||
|
||||
var zoomButtons = []
|
||||
this.zoomConfig.levels.forEach(function (v, level) {
|
||||
var btn = document.createElement("button")
|
||||
btn.classList.add("zoom-button")
|
||||
btn.setAttribute("data-zoom-level", level)
|
||||
|
||||
if (level === this.zoomLevel)
|
||||
btn.classList.add("active")
|
||||
|
||||
btn.onclick = function () {
|
||||
if (level !== this.zoomLevel) {
|
||||
zoomButtons.forEach(function (v, k) {
|
||||
if (level !== k)
|
||||
v.classList.remove("active")
|
||||
else
|
||||
v.classList.add("active")
|
||||
})
|
||||
this.setZoomLevel(level)
|
||||
}
|
||||
}.bind(this)
|
||||
btn.textContent = v.label
|
||||
zoomButtons[level] = btn
|
||||
zoomDiv.appendChild(btn)
|
||||
}.bind(this))
|
||||
return zoomDiv
|
||||
},
|
||||
|
||||
setZoomLevel: function (level) {
|
||||
if (level !== this.zoomLevel) {
|
||||
this.zoomLevel = level
|
||||
this.load(this.updateChart.bind(this))
|
||||
}
|
||||
},
|
||||
|
||||
load: function (callback) {
|
||||
if (this.cache[this.zoomLevel])
|
||||
callback(this.cache[this.zoomLevel])
|
||||
else {
|
||||
var urls = []
|
||||
var id = this.node.nodeinfo.node_id
|
||||
var unixStamp = Math.floor(Date.now() / 1000)
|
||||
var zoomConfig = this.zoomConfig.levels[this.zoomLevel]
|
||||
|
||||
this.chartConfig.metrics.forEach(function(metric) {
|
||||
var parameters = [
|
||||
"start=" + (unixStamp - zoomConfig.from),
|
||||
"end=" + unixStamp,
|
||||
"step=" + zoomConfig.interval,
|
||||
"query=" + metric.query.replace("{{NODE_ID}}", id)
|
||||
]
|
||||
|
||||
var url = this.chartConfig.url + "?" + parameters.join("&")
|
||||
|
||||
urls.push(url)
|
||||
|
||||
}.bind(this))
|
||||
|
||||
Promise.all(urls.map(getJSON)).then(function (data) {
|
||||
this.cache[this.zoomLevel] = this.parse(data)
|
||||
callback(this.cache[this.zoomLevel])
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
|
||||
parse: function (results) {
|
||||
var data = []
|
||||
results[0].data.result[0].values.forEach(function (tp) {
|
||||
var time = {"time": new Date(tp[0] * 1000)}
|
||||
results.forEach(function(result) {
|
||||
var metric = this.chartConfig.metrics[results.indexOf(result)]
|
||||
var index = results[0].data.result[0].values.indexOf(tp)
|
||||
time[metric.id] = this.formatValue(metric.id, result.data.result[0].values[index][1])
|
||||
}.bind(this))
|
||||
data.push(time)
|
||||
}.bind(this))
|
||||
return data
|
||||
},
|
||||
|
||||
c3FormatToolTip: function (d, ratio, id) {
|
||||
switch (id) {
|
||||
case "uptime":
|
||||
return d.toFixed(1) + " Tage"
|
||||
default:
|
||||
return d
|
||||
}
|
||||
},
|
||||
|
||||
c3FormatXAxis: function (d) {
|
||||
var pad = function (number, pad) {
|
||||
var N = Math.pow(10, pad)
|
||||
return number < N ? ("" + (N + number)).slice(1) : "" + number
|
||||
}
|
||||
switch (this.zoomLevel) {
|
||||
case 0: // 8h
|
||||
case 1: // 24h
|
||||
return pad(d.getHours(), 2) + ":" + pad(d.getMinutes(), 2)
|
||||
case 2: // 1m
|
||||
return pad(d.getDate(), 2) + "." + pad(d.getMonth() + 1, 2)
|
||||
case 3: // 1y
|
||||
return ["Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"][d.getMonth()]
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
formatValue: function (id, value) {
|
||||
switch (id) {
|
||||
case "loadavg":
|
||||
return (d3.format(".2r")(value))
|
||||
case "clientcount":
|
||||
return (Math.ceil(value))
|
||||
case "uptime":
|
||||
return (value / 86400)
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return charts
|
||||
})
|
|
@ -1,5 +1,5 @@
|
|||
define(["moment", "numeral", "tablesort", "tablesort.numeric"],
|
||||
function (moment, numeral, Tablesort) {
|
||||
define(["moment", "numeral", "tablesort", "infobox/charts", "tablesort.numeric"],
|
||||
function (moment, numeral, Tablesort, Charts) {
|
||||
function showGeoURI(d) {
|
||||
function showLatitude(d) {
|
||||
var suffix = Math.sign(d) > -1 ? "' N" : "' S"
|
||||
|
@ -284,6 +284,10 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
|
|||
|
||||
el.appendChild(attributes)
|
||||
|
||||
if (!d.flags.gateway && config.nodeCharts)
|
||||
config.nodeCharts.forEach( function (config) {
|
||||
el.appendChild((new Charts(d, config)).render())
|
||||
})
|
||||
|
||||
if (config.nodeInfos)
|
||||
config.nodeInfos.forEach( function (nodeInfo) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue