-function Graph(container, id, options, transform) {
+function Graph(container, id, options, transform, legend) {
if( !options ) options = { };
this.id = id;
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 );
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;
this.layout.evaluate();
this.plotter.render();
+
+ legend_opt = {
+ "legendStyle": 'li'
+ };
+
+ legend = new LegendRenderer(this.legend, this.layout, legend_opt);
+ legend.render();
}
}
}
-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;
this.transform = transform;
this.proxy = new MochiKit.JsonRpc.JsonRpcProxy(uri, [action]);
this.graphs = new Object();
+ this.legend = legend;
this.requestData();
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);
}
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();
}
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 ) {
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
);
}
--- /dev/null
+/*
+ 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 <ashleym_72^yahoo.com>
+ For use under the BSD license. <http://www.liquidx.net/plotkit>
+
+*/
+
+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
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
--- /dev/null
+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
--- /dev/null
+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
<script type="text/javascript" src="<%=resource%>/livestats/JsonRpc.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/PlotKit.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/GraphRPC.js"></script>
+<script type="text/javascript" src="<%=resource%>/livestats/Legend.js"></script>
<script type="text/javascript">
- PlotKit.Base.baseColors = function () {
- var hexColor = MochiKit.Color.Color.fromHexString;
- return [hexColor("#ff0000"),
- hexColor("#ff6000"),
- hexColor("#fff000"),
- hexColor("#00ff00"),
- hexColor("#00ff77"),
- hexColor("#0090ff"),
- hexColor("#000000")];
- };
-
function initGraphs() {
var rpc = new GraphRPC(
document.getElementById('live_graphs'),
2000,
// Data sources
- [ 0, "1 Minute Load", 1, "5 Minutes Load", 2, "15 Minutes Load" ],
+ [ 0, "1 min", 1, "5 min", 2, "15 min" ],
// Graph layout options
- { shouldFill: false, drawBackground: false, strokeColor: null,
- strokeColorTransform: "asFillColor",
- title: 'Average Load', strokeWidth: 1,
+ { title: '<%:livestats_loadavg%>', strokeWidth: 2.5, shouldFill: false, strokeColor: null,
padding: { left: 70, right: 10, top: 10, bottom: 20 },
- instances: [ false ], yAxis: [ 0, 2 ] }
+ instances: [ false ], yAxis: [ 0, 2 ], drawBackground: false },
+ null,
+ 'live_graphs'
);
}
</script>
<div id="live_graphs"></div>
-
<%+footer%>
<script type="text/javascript" src="<%=resource%>/livestats/JsonRpc.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/PlotKit.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/GraphRPC.js"></script>
+<script type="text/javascript" src="<%=resource%>/livestats/Legend.js"></script>
<%
local interfaces = { }
+ local ifnames = {}
local uci = luci.model.uci.cursor_state()
uci:foreach("network", "interface",
table.insert( interfaces,
"'" .. ( s.ifname or s['.name'] ) .. "'"
)
+ ifnames[s.ifname or s['.name']] = s['.name']
end
end
)
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'
);
}
<script type="text/javascript" src="<%=resource%>/livestats/JsonRpc.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/PlotKit.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/GraphRPC.js"></script>
+<script type="text/javascript" src="<%=resource%>/livestats/Legend.js"></script>
<script type="text/javascript">
function initGraphs() {
1500,
// Data sources
- [ "Noise level", null, "Signal level", null ],
+ [ function(data) {
+ return parseFloat(data["Signal level"])
+ - parseFloat(data["Noise level"]);
+ }, "S/N (dBm)"],
// Graph layout options
- { drawBackground: false, yAxis: [ 0, 150 ],
- title: 'Wifi Interface "%s": Signal and Noise',
- padding: { left: 40, right: 10, top: 10, bottom: 20 } }
+ { drawBackground: false, yAxis: [ 0, 50 ],
+ title: '<%:livestats_wifi%> %s',
+ padding: { left: 40, right: 10, top: 10, bottom: 20 },
+ instanceNames: {
+ <%- for k,v in pairs(luci.sys.wifi.getiwconfig()) do %>
+ <%-="%q:%q," % {k, "%s (%s)" % {k, tostring(v.ESSID)}}-%>
+ <% end %>
+ "0": ""
+ }},
+ null,
+ 'live_graphs'
);
}
define Package/luci-app-livestats
$(call Package/luci/webtemplate)
- DEPENDS+=+luci-admin-full +luci-admin-rpc
- TITLE:=LuCI Realtime Statistics (Experimental)
+ DEPENDS+=+luci-admin-core +luci-admin-rpc
+ TITLE:=LuCI Realtime Statistics
endef
define Package/luci-app-livestats/install
#### LAN configuration
config interface lan
option type bridge
- option ifname "eth0.0"
+ option ifname "eth0"
option proto static
option ipaddr 192.168.1.1
option netmask 255.255.255.0
#### WAN configuration
config interface wan
- option ifname "eth0.1"
+ option ifname "wlan0"
option proto dhcp