Completed LuCI Livestats

This commit is contained in:
Steven Barth 2008-12-07 19:38:31 +00:00
parent dd74b986a0
commit 5c61c377c1
10 changed files with 340 additions and 50 deletions

View File

@ -1,4 +1,4 @@
function Graph(container, id, options, transform) { function Graph(container, id, options, transform, legend) {
if( !options ) options = { }; if( !options ) options = { };
this.id = id; this.id = id;
@ -7,11 +7,15 @@ function Graph(container, id, options, transform) {
this.options = options; this.options = options;
this.transform = transform; this.transform = transform;
this.dataset = {}; this.dataset = {};
this.legend = legend;
this.lastvalue = {};
var name = (options.instanceNames && options.instanceNames[id])
? options.instanceNames[id] : id;
var graph = document.createElement('div'); var graph = document.createElement('div');
var label = document.createElement('h2'); var label = document.createElement('h2');
label.innerHTML = options.title label.innerHTML = options.title
? options.title.replace("%s", id ) : id; ? options.title.replace("%s", name) : name;
container.appendChild( label ); container.appendChild( label );
container.appendChild( graph ); container.appendChild( graph );
@ -49,8 +53,9 @@ Graph.prototype.updateDataset = function(name, value) {
value = Math.abs( parseFloat(value) || 0 ); value = Math.abs( parseFloat(value) || 0 );
if( this.transform ) { if( this.transform ) {
value = ( ds[this.cols-1][1] > 0 ) var orgvalue = value;
? this.transform(value, ds[this.cols-1][1]) : 0.01; value = (this.lastvalue[name]) ? this.transform(value, this.lastvalue[name]) : 0;
this.lastvalue[name] = orgvalue;
} }
ds[this.cols-1][1] = value; ds[this.cols-1][1] = value;
@ -66,6 +71,13 @@ Graph.prototype.draw = function( options ) {
this.layout.evaluate(); this.layout.evaluate();
this.plotter.render(); 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.ds = datasources;
this.uri = uri this.uri = uri
this.action = action; this.action = action;
@ -87,6 +99,7 @@ function GraphRPC(container, uri, action, interval, datasources, options, transf
this.transform = transform; this.transform = transform;
this.proxy = new MochiKit.JsonRpc.JsonRpcProxy(uri, [action]); this.proxy = new MochiKit.JsonRpc.JsonRpcProxy(uri, [action]);
this.graphs = new Object(); this.graphs = new Object();
this.legend = legend;
this.requestData(); this.requestData();
@ -126,7 +139,7 @@ GraphRPC.prototype.dispatchResponse = function(response) {
if( !this.graphs[gid] ) { if( !this.graphs[gid] ) {
this.options.title = otle.replace('%s', instance) + ': ' + name; this.options.title = otle.replace('%s', instance) + ': ' + name;
this.graphs[gid] = new Graph( 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); this.graphs[gid].addDataset(name);
@ -135,10 +148,18 @@ GraphRPC.prototype.dispatchResponse = function(response) {
} }
else else
{ {
this.graphs[gid].updateDataset( var datum = null;
name, instance if (typeof (this.ds[i]) == "function") {
datum = this.ds[i](
instance ? response[instance] : response
);
} else {
datum = instance
? response[instance][this.ds[i]] ? response[instance][this.ds[i]]
: response[this.ds[i]] : response[this.ds[i]]
}
this.graphs[gid].updateDataset(
name, datum
); );
this.graphs[gid].redraw(); this.graphs[gid].redraw();
} }
@ -148,7 +169,7 @@ GraphRPC.prototype.dispatchResponse = function(response) {
var gid = instance || 'livegraph'; var gid = instance || 'livegraph';
if( !this.graphs[gid] ) { if( !this.graphs[gid] ) {
this.graphs[gid] = new Graph( 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 ) { for( var i = 0; i < this.ds.length; i += 2 ) {
@ -161,10 +182,18 @@ GraphRPC.prototype.dispatchResponse = function(response) {
else { else {
for( var i = 0; i < this.ds.length; i += 2 ) { for( var i = 0; i < this.ds.length; i += 2 ) {
var name = this.ds[i+1] || this.ds[i]; var name = this.ds[i+1] || this.ds[i];
this.graphs[gid].updateDataset( var datum = null;
name, instance if (typeof (this.ds[i]) == "function") {
datum = this.ds[i](
instance ? response[instance] : response
);
} else {
datum = instance
? response[instance][this.ds[i]] ? response[instance][this.ds[i]]
: response[this.ds[i]] : response[this.ds[i]]
}
this.graphs[gid].updateDataset(
name, datum
); );
} }

View File

@ -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 <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);

View File

@ -19,7 +19,11 @@ function index()
require("luci.i18n") require("luci.i18n")
luci.i18n.loadc("livestats") 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", "wifistat"}, template("livestats/wireless"), luci.i18n.translate("livestats_stat_wireless"), 90 ).i18n = "livestats"
entry( {"admin", "status", "trafstat"}, template("livestats/traffic"), luci.i18n.translate("livestat_traffic", "Live Traffic Statistics"), 91 ).i18n = "livestat" 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("livestat_loadavg", "Live Load Statistics"), 92 ).i18n = "livestat" 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 end

View File

@ -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"

View File

@ -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"

View File

@ -5,19 +5,9 @@
<script type="text/javascript" src="<%=resource%>/livestats/JsonRpc.js"></script> <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/PlotKit.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/GraphRPC.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"> <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() { function initGraphs() {
var rpc = new GraphRPC( var rpc = new GraphRPC(
document.getElementById('live_graphs'), document.getElementById('live_graphs'),
@ -25,14 +15,14 @@
2000, 2000,
// Data sources // 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 // Graph layout options
{ shouldFill: false, drawBackground: false, strokeColor: null, { title: '<%:livestats_loadavg%>', strokeWidth: 2.5, shouldFill: false, strokeColor: null,
strokeColorTransform: "asFillColor",
title: 'Average Load', strokeWidth: 1,
padding: { left: 70, right: 10, top: 10, bottom: 20 }, padding: { left: 70, right: 10, top: 10, bottom: 20 },
instances: [ false ], yAxis: [ 0, 2 ] } instances: [ false ], yAxis: [ 0, 2 ], drawBackground: false },
null,
'live_graphs'
); );
} }
@ -40,5 +30,4 @@
</script> </script>
<div id="live_graphs"></div> <div id="live_graphs"></div>
<%+footer%> <%+footer%>

View File

@ -5,9 +5,11 @@
<script type="text/javascript" src="<%=resource%>/livestats/JsonRpc.js"></script> <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/PlotKit.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/GraphRPC.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 interfaces = { }
local ifnames = {}
local uci = luci.model.uci.cursor_state() local uci = luci.model.uci.cursor_state()
uci:foreach("network", "interface", uci:foreach("network", "interface",
@ -16,6 +18,7 @@
table.insert( interfaces, table.insert( interfaces,
"'" .. ( s.ifname or s['.name'] ) .. "'" "'" .. ( s.ifname or s['.name'] ) .. "'"
) )
ifnames[s.ifname or s['.name']] = s['.name']
end end
end end
) )
@ -29,20 +32,29 @@
2000, 2000,
// Data sources // Data sources
[ "1", "received Bytes/s", "9", "transmitted Bytes/s" ], [ "0", "<%:livestats_incoming%> (kiB/s)", "8", "<%:livestats_outgoing%> (kiB/s)" ],
// Graph layout options // Graph layout options
{ shouldFill: true, drawBackground: false, strokeColor: null, {
strokeColorTransform: "asFillColor", shouldFill: false,
title: 'Traffic on interface "%s"', drawBackground: false,
separateDS: true, strokeWidth: 0.5, height: 140, strokeColor: null,
padding: { left: 70, right: 10, top: 10, bottom: 20 }, title: '<%:livestats_traffic%> %s',
instances: [ <%=table.concat(interfaces, ", ") %> ] }, 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 // transform function
function(thisval, lastval) { function (cur, last) {
return ( ( thisval - lastval ) / 2 ); return (cur - last) / 2048;
} },
'live_graphs'
); );
} }

View File

@ -5,6 +5,7 @@
<script type="text/javascript" src="<%=resource%>/livestats/JsonRpc.js"></script> <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/PlotKit.js"></script>
<script type="text/javascript" src="<%=resource%>/livestats/GraphRPC.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"> <script type="text/javascript">
function initGraphs() { function initGraphs() {
@ -14,12 +15,23 @@
1500, 1500,
// Data sources // 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 // Graph layout options
{ drawBackground: false, yAxis: [ 0, 150 ], { drawBackground: false, yAxis: [ 0, 50 ],
title: 'Wifi Interface "%s": Signal and Noise', title: '<%:livestats_wifi%> %s',
padding: { left: 40, right: 10, top: 10, bottom: 20 } } 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'
); );
} }

View File

@ -537,8 +537,8 @@ endef
define Package/luci-app-livestats define Package/luci-app-livestats
$(call Package/luci/webtemplate) $(call Package/luci/webtemplate)
DEPENDS+=+luci-admin-full +luci-admin-rpc DEPENDS+=+luci-admin-core +luci-admin-rpc
TITLE:=LuCI Realtime Statistics (Experimental) TITLE:=LuCI Realtime Statistics
endef endef
define Package/luci-app-livestats/install define Package/luci-app-livestats/install

View File

@ -15,7 +15,7 @@ config interface loopback
#### LAN configuration #### LAN configuration
config interface lan config interface lan
option type bridge option type bridge
option ifname "eth0.0" option ifname "eth0"
option proto static option proto static
option ipaddr 192.168.1.1 option ipaddr 192.168.1.1
option netmask 255.255.255.0 option netmask 255.255.255.0
@ -23,5 +23,5 @@ config interface lan
#### WAN configuration #### WAN configuration
config interface wan config interface wan
option ifname "eth0.1" option ifname "wlan0"
option proto dhcp option proto dhcp