From 5c61c377c14730ac2de9d022d890f5918b46b001 Mon Sep 17 00:00:00 2001 From: Steven Barth Date: Sun, 7 Dec 2008 19:38:31 +0000 Subject: [PATCH] Completed LuCI Livestats --- .../resources/livestats/GraphRPC.js | 53 +++- .../luci-static/resources/livestats/Legend.js | 228 ++++++++++++++++++ .../luasrc/controller/livestats.lua | 10 +- .../luasrc/i18n/livestats.de.lua | 8 + .../luasrc/i18n/livestats.en.lua | 8 + .../luasrc/view/livestats/loadavg.htm | 23 +- .../luasrc/view/livestats/traffic.htm | 32 ++- .../luasrc/view/livestats/wireless.htm | 20 +- contrib/package/luci/Makefile | 4 +- contrib/uci/hostfiles/etc/config/network | 4 +- 10 files changed, 340 insertions(+), 50 deletions(-) create mode 100644 applications/luci-livestats/htdocs/luci-static/resources/livestats/Legend.js create mode 100644 applications/luci-livestats/luasrc/i18n/livestats.de.lua create mode 100644 applications/luci-livestats/luasrc/i18n/livestats.en.lua diff --git a/applications/luci-livestats/htdocs/luci-static/resources/livestats/GraphRPC.js b/applications/luci-livestats/htdocs/luci-static/resources/livestats/GraphRPC.js index a7a89ffd2..712d8b1f7 100644 --- a/applications/luci-livestats/htdocs/luci-static/resources/livestats/GraphRPC.js +++ b/applications/luci-livestats/htdocs/luci-static/resources/livestats/GraphRPC.js @@ -1,4 +1,4 @@ -function Graph(container, id, options, transform) { +function Graph(container, id, options, transform, legend) { if( !options ) options = { }; this.id = id; @@ -7,11 +7,15 @@ function Graph(container, id, options, transform) { this.options = options; this.transform = transform; this.dataset = {}; - + this.legend = legend; + this.lastvalue = {}; + + var name = (options.instanceNames && options.instanceNames[id]) + ? options.instanceNames[id] : id; var graph = document.createElement('div'); var label = document.createElement('h2'); label.innerHTML = options.title - ? options.title.replace("%s", id ) : id; + ? options.title.replace("%s", name) : name; container.appendChild( label ); container.appendChild( graph ); @@ -49,8 +53,9 @@ Graph.prototype.updateDataset = function(name, value) { value = Math.abs( parseFloat(value) || 0 ); if( this.transform ) { - value = ( ds[this.cols-1][1] > 0 ) - ? this.transform(value, ds[this.cols-1][1]) : 0.01; + var orgvalue = value; + value = (this.lastvalue[name]) ? this.transform(value, this.lastvalue[name]) : 0; + this.lastvalue[name] = orgvalue; } ds[this.cols-1][1] = value; @@ -66,6 +71,13 @@ Graph.prototype.draw = function( options ) { this.layout.evaluate(); this.plotter.render(); + + legend_opt = { + "legendStyle": 'li' + }; + + legend = new LegendRenderer(this.legend, this.layout, legend_opt); + legend.render(); } } @@ -78,7 +90,7 @@ Graph.prototype.redraw = function() { } -function GraphRPC(container, uri, action, interval, datasources, options, transform) { +function GraphRPC(container, uri, action, interval, datasources, options, transform, legend) { this.ds = datasources; this.uri = uri this.action = action; @@ -87,6 +99,7 @@ function GraphRPC(container, uri, action, interval, datasources, options, transf this.transform = transform; this.proxy = new MochiKit.JsonRpc.JsonRpcProxy(uri, [action]); this.graphs = new Object(); + this.legend = legend; this.requestData(); @@ -126,7 +139,7 @@ GraphRPC.prototype.dispatchResponse = function(response) { if( !this.graphs[gid] ) { this.options.title = otle.replace('%s', instance) + ': ' + name; this.graphs[gid] = new Graph( - this.container, gid, this.options, this.transform + this.container, gid, this.options, this.transform, this.legend ); this.graphs[gid].addDataset(name); @@ -135,10 +148,18 @@ GraphRPC.prototype.dispatchResponse = function(response) { } else { - this.graphs[gid].updateDataset( - name, instance + var datum = null; + if (typeof (this.ds[i]) == "function") { + datum = this.ds[i]( + instance ? response[instance] : response + ); + } else { + datum = instance ? response[instance][this.ds[i]] : response[this.ds[i]] + } + this.graphs[gid].updateDataset( + name, datum ); this.graphs[gid].redraw(); } @@ -148,7 +169,7 @@ GraphRPC.prototype.dispatchResponse = function(response) { var gid = instance || 'livegraph'; if( !this.graphs[gid] ) { this.graphs[gid] = new Graph( - this.container, gid, this.options, this.transform + this.container, gid, this.options, this.transform, this.legend ); for( var i = 0; i < this.ds.length; i += 2 ) { @@ -161,10 +182,18 @@ GraphRPC.prototype.dispatchResponse = function(response) { else { for( var i = 0; i < this.ds.length; i += 2 ) { var name = this.ds[i+1] || this.ds[i]; - this.graphs[gid].updateDataset( - name, instance + var datum = null; + if (typeof (this.ds[i]) == "function") { + datum = this.ds[i]( + instance ? response[instance] : response + ); + } else { + datum = instance ? response[instance][this.ds[i]] : response[this.ds[i]] + } + this.graphs[gid].updateDataset( + name, datum ); } diff --git a/applications/luci-livestats/htdocs/luci-static/resources/livestats/Legend.js b/applications/luci-livestats/htdocs/luci-static/resources/livestats/Legend.js new file mode 100644 index 000000000..4707c1ed3 --- /dev/null +++ b/applications/luci-livestats/htdocs/luci-static/resources/livestats/Legend.js @@ -0,0 +1,228 @@ +/* + PlotKit Legend + ============== + + Handles laying out legend into a DIV element. + Design taken from comments of Julien Wajsberg (http:// +groups.google.com/group/plotkit/browse_thread/thread/2494bd88e6e9956d) + and Niel Domingo (http://nieldomingo.blogspot.com/2007/03/legend- +for-plotkit-bar-charts.html). + + Copyright + --------- + Copyright 2007 (c) Ashley Martens + For use under the BSD license. + +*/ + +try { + if (typeof(MochiKit.Base) == 'undefined' || + typeof(MochiKit.DOM) == 'undefined' || + typeof(MochiKit.Color) == 'undefined' || + typeof(MochiKit.Format) == 'undefined' || + typeof(PlotKit.Layout) == 'undefined' || + typeof(PlotKit.Base) == 'undefined') + { + throw ""; + } +} +catch (e) { + throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format}" +} + +if (typeof(PlotKit.LegendRenderer) == 'undefined') { + PlotKit.LegendRenderer = {}; +} + +PlotKit.LegendRenderer = function(element, layout, options) { + if (arguments.length > 0) + this.__init__(element, layout, options); +}; + + +PlotKit.LegendRenderer.NAME = "PlotKit.LegendRenderer"; +PlotKit.LegendRenderer.VERSION = PlotKit.VERSION; + +PlotKit.LegendRenderer.__repr__ = function() { + return "[" + this.NAME + " " + this.VERSION + "]"; +}; + +PlotKit.LegendRenderer.toString = function() { + return this.__repr__(); +} + +PlotKit.LegendRenderer.prototype.__init__ = function(element, layout, +options) { + var isNil = MochiKit.Base.isUndefinedOrNull; + var Color = MochiKit.Color.Color; + + this.options = { + "colorScheme": PlotKit.Base.palette(PlotKit.Base.baseColors() +[0]), + "legendStyle": "table", + "tableColumns": 1 + }; + MochiKit.Base.update(this.options, options ? options : {}); + + this.layout = layout; + this.element = MochiKit.DOM.getElement(element); + // --- check whether everything is ok before we return + + if (isNil(this.element)) + throw "CRILegend() - passed legend is not found"; +}; + +PlotKit.LegendRenderer.prototype.render = function() { + var colorScheme = this.options.colorScheme; + var setNames = PlotKit.Base.keys(this.layout.datasets); + + MochiKit.DOM.updateNodeAttributes(this.element, + {"style": + {"margin":"0" + ,"padding":"0" + } + }); + + + var ul = null; + if (this.options.legendStyle == "table") + ul = this._renderListTable(colorScheme, setNames); + else + ul = this._renderList(colorScheme, setNames); + MochiKit.DOM.appendChildNodes(this.element, ul); + +}; + +PlotKit.LegendRenderer.prototype._renderList = function(colorScheme, +setNames) { + var ul = document.createElement("ul"); + ul.style.listStyle="none"; + ul.style.margin="0"; + ul.style.padding="0"; + + var colorCount = colorScheme.length; + var setCount = setNames.length; + + for (var i = 0; i < setCount; i++) { + var setName = setNames[i]; + var color = colorScheme[i%colorCount]; + var le = this._renderElement(setName, color.toRGBString()); + ul.appendChild(le); + } + + return ul; +}; + +PlotKit.LegendRenderer.prototype._renderElement = function(title, +color) { + var le = MochiKit.DOM.createDOM("li"); + le.style.listStyle="none"; + le.style.margin="0 0 5px 0"; + le.style.padding="0"; + + var box = MochiKit.DOM.createDOM("div"); + box.style.backgroundColor=color; + box.style.width="2em"; + box.style.height=".9em"; + box.style.border="1px solid black"; + box.style.margin="0 5px 0 0"; + box.style.padding="0"; + box.style.float="left"; + box.style.cssFloat="left"; + box.style.clear="left"; + box.style.cssClear="left"; + + var span = MochiKit.DOM.createDOM("span"); + MochiKit.DOM.appendChildNodes(span, +document.createTextNode(title)); + + MochiKit.DOM.appendChildNodes(le, box, span); + + return le; +}; + +PlotKit.LegendRenderer.prototype._renderListTable = +function(colorScheme, setNames) { + var tabhead = THEAD(null); + var tabfoot = TFOOT(null); + + var tabbody = partial(TBODY, null); + var i = 0; + var colorcount = colorScheme.length; + var tabrow; + var columns = this.options.tableColumns; + for (var label in setNames) + { + var legendcolor = colorScheme[i%colorcount]; + var legendbox = DIV({'class': 'legendbox', 'className': +'legendbox'}); + legendbox.style.width = "10px"; + legendbox.style.height = "10px"; + legendbox.style.backgroundColor = legendcolor.toHexString(); + legendbox.style.borderWidth = "1px"; + legendbox.style.borderStyle = "solid"; + legendbox.style.borderColor = "#000000"; + var boxcell = TD(null, legendbox); + + var labelcell = TD({'class': 'legendlabel', 'className': +'legendlabel'}, setNames[i]); + labelcell.style.font = 'normal 10pt arial'; + + if (!(i % columns)) + { + tabrow = partial(TR, null); + } + tabrow = partial(tabrow, boxcell, labelcell); + if (i % columns) + { + tabrow = tabrow(null); + tabbody = partial(tabbody, tabrow); + } + i++; + } + if ((setNames % columns)) + { + tabrow = tabrow(TD(null), TD(null)); + tabbody = partial(tabbody, tabrow); + } + tabbody = tabbody(null); + + tab = TABLE({'class': 'legendcontainer', 'className': +'legendcontainer'}, tabhead, tabfoot, tabbody); + tab.style.marginTop = '1em'; + tab.style.marginLeft = '1.5em'; + tab.style.marginBottom = '1em'; + tab.style.borderWidth = '1px'; + tab.style.borderStyle = 'solid'; + tab.style.borderColor = '#000000'; + + return tab; +}; + +// Namespace Iniitialisation + +PlotKit.Legend = {} +PlotKit.Legend.LegendRenderer = PlotKit.LegendRenderer; + + +PlotKit.Legend.EXPORT = [ + "LegendRenderer" +]; + +PlotKit.Legend.EXPORT_OK = [ + "LegendRenderer" +]; + +PlotKit.Legend.__new__ = function() { + var m = MochiKit.Base; + + m.nameFunctions(this); + + this.EXPORT_TAGS = { + ":common": this.EXPORT, + ":all": m.concat(this.EXPORT, this.EXPORT_OK) + }; +}; + +PlotKit.Legend.__new__(); +MochiKit.Base._exportSymbols(this, PlotKit.Legend); \ No newline at end of file diff --git a/applications/luci-livestats/luasrc/controller/livestats.lua b/applications/luci-livestats/luasrc/controller/livestats.lua index 5211f7925..521b32bac 100644 --- a/applications/luci-livestats/luasrc/controller/livestats.lua +++ b/applications/luci-livestats/luasrc/controller/livestats.lua @@ -19,7 +19,11 @@ function index() require("luci.i18n") luci.i18n.loadc("livestats") - entry( {"admin", "status", "wifistat"}, template("livestats/wireless"), luci.i18n.translate("livestat_wireless", "Live Wireless Statistics"), 90 ).i18n = "livestat" - entry( {"admin", "status", "trafstat"}, template("livestats/traffic"), luci.i18n.translate("livestat_traffic", "Live Traffic Statistics"), 91 ).i18n = "livestat" - entry( {"admin", "status", "loadavg"}, template("livestats/loadavg"), luci.i18n.translate("livestat_loadavg", "Live Load Statistics"), 92 ).i18n = "livestat" + entry( {"admin", "status", "wifistat"}, template("livestats/wireless"), luci.i18n.translate("livestats_stat_wireless"), 90 ).i18n = "livestats" + entry( {"admin", "status", "trafstat"}, template("livestats/traffic"), luci.i18n.translate("livestats_stat_traffic"), 91 ).i18n = "livestats" + entry( {"admin", "status", "loadavg"}, template("livestats/loadavg"), luci.i18n.translate("livestats_stat_loadavg"), 92 ).i18n = "livestats" + + entry( {"mini", "network", "wifistat"}, template("livestats/wireless"), luci.i18n.translate("livestats_stat_wireless"), 90 ).i18n = "livestats" + entry( {"mini", "network", "trafstat"}, template("livestats/traffic"), luci.i18n.translate("livestats_stat_traffic"), 91 ).i18n = "livestats" + entry( {"mini", "system", "loadavg"}, template("livestats/loadavg"), luci.i18n.translate("livestats_stat_loadavg"), 92 ).i18n = "livestats" end diff --git a/applications/luci-livestats/luasrc/i18n/livestats.de.lua b/applications/luci-livestats/luasrc/i18n/livestats.de.lua new file mode 100644 index 000000000..33c1eb19c --- /dev/null +++ b/applications/luci-livestats/luasrc/i18n/livestats.de.lua @@ -0,0 +1,8 @@ +livestats_incoming = "eingehend" +livestats_outgoing = "ausgehend" +livestats_traffic = "Netzverkehr auf" +livestats_wifi = "Signal-Rauschabstand für" +livestats_loadavg = "Durchschnittliche Systemlast" +livestats_stat_wireless = "Echtzeit-Drahtlosstatus" +livestats_stat_traffic = "Echtzeit-Netzwerkverkehr" +livestats_stat_loadavg = "Echtzeit-Systemlast" \ No newline at end of file diff --git a/applications/luci-livestats/luasrc/i18n/livestats.en.lua b/applications/luci-livestats/luasrc/i18n/livestats.en.lua new file mode 100644 index 000000000..04d326a1a --- /dev/null +++ b/applications/luci-livestats/luasrc/i18n/livestats.en.lua @@ -0,0 +1,8 @@ +livestats_incoming = "incoming" +livestats_outgoing = "outgoing" +livestats_traffic = "traffic on" +livestats_wifi = "signal-to-noise ratio for" +livestats_loadavg = "load average" +livestats_stat_wireless = "Realtime Wireless Status" +livestats_stat_traffic = "Realtime Network Traffic" +livestats_stat_loadavg = "Realtime System Load" \ No newline at end of file diff --git a/applications/luci-livestats/luasrc/view/livestats/loadavg.htm b/applications/luci-livestats/luasrc/view/livestats/loadavg.htm index f1d434b5d..49f800ead 100644 --- a/applications/luci-livestats/luasrc/view/livestats/loadavg.htm +++ b/applications/luci-livestats/luasrc/view/livestats/loadavg.htm @@ -5,19 +5,9 @@ +
- <%+footer%> diff --git a/applications/luci-livestats/luasrc/view/livestats/traffic.htm b/applications/luci-livestats/luasrc/view/livestats/traffic.htm index 083b5cc2e..f0c194dac 100644 --- a/applications/luci-livestats/luasrc/view/livestats/traffic.htm +++ b/applications/luci-livestats/luasrc/view/livestats/traffic.htm @@ -5,9 +5,11 @@ + <% local interfaces = { } + local ifnames = {} local uci = luci.model.uci.cursor_state() uci:foreach("network", "interface", @@ -16,6 +18,7 @@ table.insert( interfaces, "'" .. ( s.ifname or s['.name'] ) .. "'" ) + ifnames[s.ifname or s['.name']] = s['.name'] end end ) @@ -29,20 +32,29 @@ 2000, // Data sources - [ "1", "received Bytes/s", "9", "transmitted Bytes/s" ], + [ "0", "<%:livestats_incoming%> (kiB/s)", "8", "<%:livestats_outgoing%> (kiB/s)" ], // Graph layout options - { shouldFill: true, drawBackground: false, strokeColor: null, - strokeColorTransform: "asFillColor", - title: 'Traffic on interface "%s"', - separateDS: true, strokeWidth: 0.5, height: 140, - padding: { left: 70, right: 10, top: 10, bottom: 20 }, - instances: [ <%=table.concat(interfaces, ", ") %> ] }, + { + shouldFill: false, + drawBackground: false, + strokeColor: null, + title: '<%:livestats_traffic%> %s', + strokeWidth: 2.5, height: 140, + padding: { left: 70, right: 10, top: 10, bottom: 20 }, + instances: [ <%=table.concat(interfaces, ", ") %> ], + instanceNames: { + <%- for iface, network in pairs(ifnames) do %> + <%-="%q:%q," % {iface, network}-%> + <% end %> + "0": "" + }}, // transform function - function(thisval, lastval) { - return ( ( thisval - lastval ) / 2 ); - } + function (cur, last) { + return (cur - last) / 2048; + }, + 'live_graphs' ); } diff --git a/applications/luci-livestats/luasrc/view/livestats/wireless.htm b/applications/luci-livestats/luasrc/view/livestats/wireless.htm index 2e34df978..670d9fbf6 100644 --- a/applications/luci-livestats/luasrc/view/livestats/wireless.htm +++ b/applications/luci-livestats/luasrc/view/livestats/wireless.htm @@ -5,6 +5,7 @@ +