luci-app-statistics: rearrange graph display
- Add a per-plugin overview tab that contains the (non-detail) graphs of each plugin instance, similar to the old Lua based implementation - Numerically order plugin instances starting with numbers - Avoid multiple renderings of the same graphs - Fix legend of tcpconns graphs - Move cpufreq distribution and transition charts to detail tabs Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
parent
d4a475163e
commit
72da3c4c55
4 changed files with 84 additions and 13 deletions
|
@ -259,7 +259,17 @@ return L.Class.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
pluginInstances: function(hostInstance, pluginName) {
|
pluginInstances: function(hostInstance, pluginName) {
|
||||||
return Object.keys((rrdtree[hostInstance] || {})[pluginName] || {}).sort();
|
return Object.keys((rrdtree[hostInstance] || {})[pluginName] || {}).sort(function(a, b) {
|
||||||
|
var x = a.match(/^(\d+)\b/),
|
||||||
|
y = b.match(/^(\d+)\b/);
|
||||||
|
|
||||||
|
if (!x != !y)
|
||||||
|
return !x - !y;
|
||||||
|
else if (x && y && x[0] != y[0])
|
||||||
|
return +x[0] - +y[0];
|
||||||
|
else
|
||||||
|
return a > b;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
dataTypes: function(hostInstance, pluginName, pluginInstance) {
|
dataTypes: function(hostInstance, pluginName, pluginInstance) {
|
||||||
|
@ -279,6 +289,21 @@ return L.Class.extend({
|
||||||
return (graphdefs[pluginName] != null);
|
return (graphdefs[pluginName] != null);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasInstanceDetails: function(hostInstance, pluginName, pluginInstance) {
|
||||||
|
var def = graphdefs[pluginName];
|
||||||
|
|
||||||
|
if (!def || typeof(def.rrdargs) != 'function')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var optlist = this._forcelol(def.rrdargs(this, hostInstance, pluginName, pluginInstance, null, false));
|
||||||
|
|
||||||
|
for (var i = 0; i < optlist.length; i++)
|
||||||
|
if (optlist[i].detail)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
_mkpath: function(host, plugin, plugin_instance, dtype, data_instance) {
|
_mkpath: function(host, plugin, plugin_instance, dtype, data_instance) {
|
||||||
var path = host + '/' + plugin;
|
var path = host + '/' + plugin;
|
||||||
|
|
||||||
|
@ -304,7 +329,7 @@ return L.Class.extend({
|
||||||
return L.isObject(list[0]) ? list : [ list ];
|
return L.isObject(list[0]) ? list : [ list ];
|
||||||
},
|
},
|
||||||
|
|
||||||
_rrdtool: function(def, rrd, timespan, width, height) {
|
_rrdtool: function(def, rrd, timespan, width, height, cache) {
|
||||||
var cmdline = [
|
var cmdline = [
|
||||||
'graph', '-', '-a', 'PNG',
|
'graph', '-', '-a', 'PNG',
|
||||||
'-s', 'NOW-%s'.format(timespan || this.opts.timespan),
|
'-s', 'NOW-%s'.format(timespan || this.opts.timespan),
|
||||||
|
@ -322,6 +347,15 @@ return L.Class.extend({
|
||||||
cmdline.push(opt);
|
cmdline.push(opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (L.isObject(cache)) {
|
||||||
|
var key = sfh(cmdline.join('\0'));
|
||||||
|
|
||||||
|
if (!cache.hasOwnProperty(key))
|
||||||
|
cache[key] = fs.exec_direct('/usr/bin/rrdtool', cmdline, 'blob', true);
|
||||||
|
|
||||||
|
return cache[key];
|
||||||
|
}
|
||||||
|
|
||||||
return fs.exec_direct('/usr/bin/rrdtool', cmdline, 'blob', true);
|
return fs.exec_direct('/usr/bin/rrdtool', cmdline, 'blob', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -682,7 +716,7 @@ return L.Class.extend({
|
||||||
return defs;
|
return defs;
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(plugin, plugin_instance, is_index, hostname, timespan, width, height) {
|
render: function(plugin, plugin_instance, is_index, hostname, timespan, width, height, cache) {
|
||||||
var pngs = [];
|
var pngs = [];
|
||||||
|
|
||||||
/* check for a whole graph handler */
|
/* check for a whole graph handler */
|
||||||
|
@ -705,7 +739,7 @@ return L.Class.extend({
|
||||||
/* render all diagrams */
|
/* render all diagrams */
|
||||||
for (var j = 0; j < diagrams.length; j++) {
|
for (var j = 0; j < diagrams.length; j++) {
|
||||||
/* exec */
|
/* exec */
|
||||||
_images[i][j] = this._rrdtool(diagrams[j], null, timespan, width, height);
|
_images[i][j] = this._rrdtool(diagrams[j], null, timespan, width, height, cache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ return L.Class.extend({
|
||||||
|
|
||||||
if (uci.get("luci_statistics", "collectd_cpufreq", "ExtraItems")) {
|
if (uci.get("luci_statistics", "collectd_cpufreq", "ExtraItems")) {
|
||||||
var transitions = {
|
var transitions = {
|
||||||
|
detail: true,
|
||||||
title: "%H: Frequency transitions - core %pi",
|
title: "%H: Frequency transitions - core %pi",
|
||||||
alt_autoscale: true,
|
alt_autoscale: true,
|
||||||
vlabel: "Transitions",
|
vlabel: "Transitions",
|
||||||
|
@ -35,6 +36,7 @@ return L.Class.extend({
|
||||||
};
|
};
|
||||||
|
|
||||||
var percentage = {
|
var percentage = {
|
||||||
|
detail: true,
|
||||||
title: "%H: Frequency distribution - core %pi",
|
title: "%H: Frequency distribution - core %pi",
|
||||||
alt_autoscale: true,
|
alt_autoscale: true,
|
||||||
vlabel: "Percent",
|
vlabel: "Percent",
|
||||||
|
|
|
@ -21,6 +21,11 @@ return L.Class.extend({
|
||||||
options: {
|
options: {
|
||||||
load__ESTABLISHED: { title: "%di", noarea: true }
|
load__ESTABLISHED: { title: "%di", noarea: true }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
tcp_connections__value: {
|
||||||
|
title: '%di'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,8 +14,11 @@ return L.view.extend({
|
||||||
|
|
||||||
updatePluginTab: function(host, span, time, ev) {
|
updatePluginTab: function(host, span, time, ev) {
|
||||||
var container = ev.target,
|
var container = ev.target,
|
||||||
|
width = Math.max(200, container.offsetWidth - 100),
|
||||||
plugin = ev.detail.tab,
|
plugin = ev.detail.tab,
|
||||||
plugin_instances = rrdtool.pluginInstances(host.value, plugin);
|
render_instances = [],
|
||||||
|
plugin_instances = rrdtool.pluginInstances(host.value, plugin),
|
||||||
|
cache = {};
|
||||||
|
|
||||||
activePlugin = plugin;
|
activePlugin = plugin;
|
||||||
|
|
||||||
|
@ -25,20 +28,41 @@ return L.view.extend({
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Promise.all(plugin_instances.map(function(instance) {
|
for (var i = 0; i < plugin_instances.length; i++)
|
||||||
return rrdtool.render(plugin, instance, false, host.value, span.value, Math.max(200, container.offsetWidth - 100));
|
if (rrdtool.hasInstanceDetails(host.value, plugin, plugin_instances[i]))
|
||||||
|
render_instances.push(plugin_instances[i]);
|
||||||
|
|
||||||
|
if (render_instances.length == 0 || render_instances.length > 1)
|
||||||
|
render_instances.unshift('-');
|
||||||
|
|
||||||
|
Promise.all(render_instances.map(function(instance) {
|
||||||
|
if (instance == '-') {
|
||||||
|
var tasks = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < plugin_instances.length; i++)
|
||||||
|
tasks.push(rrdtool.render(plugin, plugin_instances[i], true, host.value, span.value, width, null, cache));
|
||||||
|
|
||||||
|
return Promise.all(tasks).then(function(blobs) {
|
||||||
|
return Array.prototype.concat.apply([], blobs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return rrdtool.render(plugin, instance, false, host.value, span.value, width, null, cache);
|
||||||
|
}
|
||||||
})).then(function(blobs) {
|
})).then(function(blobs) {
|
||||||
var multiple = blobs.length > 1;
|
var multiple = blobs.length > 1;
|
||||||
|
|
||||||
L.dom.content(container, E('div', {}, blobs.map(function(blobs, i) {
|
L.dom.content(container, E('div', {}, blobs.map(function(blobs, i) {
|
||||||
var plugin_instance = plugin_instances[i];
|
var plugin_instance = i ? plugin_instances[i-1] : plugin_instances.join('|'),
|
||||||
|
title = '%s: %s'.format(rrdtool.pluginTitle(plugin), i ? plugin_instance : _('Overview'));
|
||||||
|
|
||||||
return E('div', {
|
return E('div', {
|
||||||
'class': 'center',
|
'class': 'center',
|
||||||
'data-tab': multiple ? i : null,
|
'data-tab': multiple ? i : null,
|
||||||
'data-tab-title': multiple ? '%s: %s'.format(rrdtool.pluginTitle(plugin), plugin_instances[i]) : null,
|
'data-tab-title': multiple ? title : null,
|
||||||
'data-plugin': plugin,
|
'data-plugin': plugin,
|
||||||
'data-plugin-instance': plugin_instances[i],
|
'data-plugin-instance': plugin_instance,
|
||||||
|
'data-is-index': i ? null : true,
|
||||||
'cbi-tab-active': function(ev) { activeInstance = ev.target.getAttribute('data-plugin-instance') }
|
'cbi-tab-active': function(ev) { activeInstance = ev.target.getAttribute('data-plugin-instance') }
|
||||||
}, blobs.map(function(blob) {
|
}, blobs.map(function(blob) {
|
||||||
return E('img', {
|
return E('img', {
|
||||||
|
@ -50,7 +74,7 @@ return L.view.extend({
|
||||||
if (multiple)
|
if (multiple)
|
||||||
ui.tabs.initTabGroup(container.lastElementChild.childNodes);
|
ui.tabs.initTabGroup(container.lastElementChild.childNodes);
|
||||||
else
|
else
|
||||||
activeInstance = plugin_instances[0];
|
activeInstance = plugin_instances.join('|');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -87,9 +111,15 @@ return L.view.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshGraphs: function(host, span, time, container) {
|
refreshGraphs: function(host, span, time, container) {
|
||||||
var div = document.querySelector('[data-plugin="%s"][data-plugin-instance="%s"]'.format(activePlugin, activeInstance || ''));
|
var div = document.querySelector('[data-plugin="%s"][data-plugin-instance="%s"]'.format(activePlugin, activeInstance || '')),
|
||||||
|
width = Math.max(200, container.offsetWidth - 100),
|
||||||
|
render_instances = activeInstance.split(/\|/);
|
||||||
|
|
||||||
return rrdtool.render(activePlugin, activeInstance || '', false, host.value, span.value, Math.max(200, container.offsetWidth - 100)).then(function(blobs) {
|
return Promise.all(render_instances.map(function(render_instance) {
|
||||||
|
return rrdtool.render(activePlugin, render_instance || '', div.hasAttribute('data-is-index'), host.value, span.value, width);
|
||||||
|
})).then(function(blobs) {
|
||||||
|
return Array.prototype.concat.apply([], blobs);
|
||||||
|
}).then(function(blobs) {
|
||||||
return Promise.all(blobs.map(function(blob) {
|
return Promise.all(blobs.map(function(blob) {
|
||||||
return new Promise(function(resolveFn, rejectFn) {
|
return new Promise(function(resolveFn, rejectFn) {
|
||||||
var img = E('img', { 'src': URL.createObjectURL(new Blob([blob], { type: 'image/png' })) });
|
var img = E('img', { 'src': URL.createObjectURL(new Blob([blob], { type: 'image/png' })) });
|
||||||
|
|
Loading…
Reference in a new issue