Completed LuCI Livestats
authorSteven Barth <steven@midlink.org>
Sun, 7 Dec 2008 19:38:31 +0000 (19:38 +0000)
committerSteven Barth <steven@midlink.org>
Sun, 7 Dec 2008 19:38:31 +0000 (19:38 +0000)
applications/luci-livestats/htdocs/luci-static/resources/livestats/GraphRPC.js
applications/luci-livestats/htdocs/luci-static/resources/livestats/Legend.js [new file with mode: 0644]
applications/luci-livestats/luasrc/controller/livestats.lua
applications/luci-livestats/luasrc/i18n/livestats.de.lua [new file with mode: 0644]
applications/luci-livestats/luasrc/i18n/livestats.en.lua [new file with mode: 0644]
applications/luci-livestats/luasrc/view/livestats/loadavg.htm
applications/luci-livestats/luasrc/view/livestats/traffic.htm
applications/luci-livestats/luasrc/view/livestats/wireless.htm
contrib/package/luci/Makefile
contrib/uci/hostfiles/etc/config/network

index a7a89ffd293823df12e72c67f9813a10c93ba765..712d8b1f76be62b9ccb5956ad7c869f21dbde442 100644 (file)
@@ -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 (file)
index 0000000..4707c1e
--- /dev/null
@@ -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);
\ No newline at end of file
index 5211f7925bb6206ec43d6aa6faff07b6db95284a..521b32bacbd6e56d839b91265d972e942fbdee98 100644 (file)
@@ -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 (file)
index 0000000..33c1eb1
--- /dev/null
@@ -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 (file)
index 0000000..04d326a
--- /dev/null
@@ -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
index f1d434b5d2ea8d53c03ab5f76eceb40889ef6880..49f800ead46767b89d758f53742ba5755a23949b 100644 (file)
@@ -5,19 +5,9 @@
 <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'
                );
        }
 
@@ -40,5 +30,4 @@
 </script>
 
 <div id="live_graphs"></div>
-
 <%+footer%>
index 083b5cc2e6d04723c12910122ca7b83f20980de2..f0c194dac5a98e738c89851498b24a7a519b7ee2 100644 (file)
@@ -5,9 +5,11 @@
 <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",
@@ -16,6 +18,7 @@
                                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'
                );
        }
 
index 2e34df978e69ada25d31164f793a40c8e1b6bde5..670d9fbf6cca0a4f53accfc05cf9fcfc8f087052 100644 (file)
@@ -5,6 +5,7 @@
 <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'
                );
        }
 
index 51cc44f84270313fa3c594fb0f43105a70c6077f..d748e47bb395b46fab80bae06799d9725661349d 100644 (file)
@@ -537,8 +537,8 @@ endef
 
 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
index 1caf3e73401a574ae3ad3a3c25d9f69928a37682..6f499c99e7244dd57e773e65056e6eaeec3abcff 100644 (file)
@@ -15,7 +15,7 @@ config interface loopback
 #### 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
@@ -23,5 +23,5 @@ config interface lan
 
 #### WAN configuration
 config interface       wan
-       option ifname   "eth0.1"
+       option ifname   "wlan0"
        option proto    dhcp