luci2: Initial network interface configuration view
authorJo-Philipp Wich <jow@openwrt.org>
Sat, 1 Feb 2014 18:25:50 +0000 (18:25 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Sat, 1 Feb 2014 18:27:37 +0000 (18:27 +0000)
luci2/htdocs/luci2/template/network.interfaces.htm [new file with mode: 0644]
luci2/htdocs/luci2/view/network.interfaces.js [new file with mode: 0644]
luci2/share/acl.d/luci2.json
luci2/share/menu.d/network.json

diff --git a/luci2/htdocs/luci2/template/network.interfaces.htm b/luci2/htdocs/luci2/template/network.interfaces.htm
new file mode 100644 (file)
index 0000000..ad19e7d
--- /dev/null
@@ -0,0 +1 @@
+<div id="map"></div>
diff --git a/luci2/htdocs/luci2/view/network.interfaces.js b/luci2/htdocs/luci2/view/network.interfaces.js
new file mode 100644 (file)
index 0000000..a344327
--- /dev/null
@@ -0,0 +1,361 @@
+L.ui.view.extend({
+       title: L.tr('Interface Overview'),
+
+       pendingRestart: [ ],
+       pendingShutdown: [ ],
+
+       setUp: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'ifup',
+               params: [ 'data' ],
+               expect: { '': { code: -1 } }
+       }),
+
+       setDown: L.rpc.declare({
+               object: 'luci2.network',
+               method: 'ifdown',
+               params: [ 'data' ],
+               expect: { '': { code: -1 } }
+       }),
+
+       renderDeviceIcon: function(dev, up)
+       {
+               var icon = dev ? dev.icon(up) : L.globals.resource + '/icons/ethernet_disabled.png';
+               var desc = dev ? '%s (%s)'.format(dev.description(), dev.name()) : L.tr('Network interface not present');
+
+               return $('<img />')
+                       .attr('title', desc)
+                       .attr('src', icon);
+       },
+
+       renderNetworkBadge: function(network, div)
+       {
+               var dest = div || $('#network-badge-%s'.format(network.name()));
+               var device = network.getDevice(); //network.device || { type: 'Network device', device: '?' };
+               var subdevs = network.getSubdevices();
+
+               if (div)
+               {
+                       var h = $('<div />')
+                               .addClass('ifacebox-head')
+                               .text(network.name());
+
+                       if (network.zone)
+                               h.css('background-color', network.zone.color).attr('title', L.trc('Interface status', 'Part of zone "%s"').format(network.zone.name));
+                       else
+                               h.css('background-color', '#cccccc').attr('title', L.trc('Interface status', 'Not part of any zone'));
+
+                       dest.append(h);
+               }
+               else
+               {
+                       dest.children('div.ifacebox-body').remove();
+               }
+
+               var b = $('<div />')
+                       .addClass('ifacebox-body');
+
+               b.append(this.renderDeviceIcon(device, network.isUp()));
+
+               if (subdevs.length)
+               {
+                       b.append('(');
+
+                       for (var i = 0; i < subdevs.length; i++)
+                               b.append(this.renderDeviceIcon(subdevs[i], subdevs[i].isUp()));
+
+                       b.append(')');
+               }
+
+               b.append($('<br />')).append($('<small />').text(device ? device.name() : '?'));
+
+               return dest.append(b);
+       },
+
+       renderNetworkStatus: function(network, div)
+       {
+               var rv = '';
+
+               if (network.isUp())
+               {
+                       rv += '<strong>%s</strong>: %t<br />'.format(
+                               L.tr('Uptime'),
+                               network.getUptime()
+                       );
+               }
+               else
+               {
+                       rv += '<strong>%s</strong>: %s<br />'.format(
+                               L.tr('Uptime'),
+                               L.tr('Interface is down')
+                       );
+               }
+
+               var v4 = network.getIPv4Addrs();
+               if (v4.length)
+                       rv += '<strong>%s</strong>: %s<br />'.format(
+                               L.trc('Interface status', 'IPv4'),
+                               v4.join(', ')
+                       );
+
+               var v6 = network.getIPv6Addrs();
+               if (v6.length)
+                       rv += '<strong>%s</strong>: %s<br />'.format(
+                               L.trc('Interface status', 'IPv6'),
+                               v6.join(', ')
+                       );
+
+               return (div || $('#network-status-%s'.format(network.name())))
+                       .empty()
+                       .append(rv);
+       },
+
+       renderNetworkChart: function(network, div)
+       {
+               var dest = (div || $('#network-chart-%s'.format(network.name())));
+
+               dest.empty();
+
+               dest.append($('<div />')
+                       .addClass('traffic-chart')
+                       .append($('<span />')
+                               .attr('id', 'network-chart-tx-%s'.format(network.name()))
+                               .hide())
+                       .append($('<label />')));
+
+               dest.append($('<div />')
+                       .addClass('traffic-chart')
+                       .append($('<span />')
+                               .attr('id', 'network-chart-rx-%s'.format(network.name()))
+                               .hide())
+                       .append($('<label />')));
+
+               dest.append($('<small />')
+                       .addClass('traffic-stats')
+                       .text(L.tr('Loading statistics…')));
+
+               return dest;
+       },
+
+       refreshNetworkStatus: function()
+       {
+               var self = this;
+               var deferreds = [ ];
+
+               while (self.pendingRestart.length)
+                       deferreds.push(self.setUp(self.pendingRestart.shift()));
+
+               while (self.pendingShutdown.length)
+                       deferreds.push(self.setDown(self.pendingShutdown.shift()));
+
+               return $.when.apply($, deferreds).then(function() {
+                       $('button').prop('disabled', false);
+                       return $.when(
+                               L.NetworkModel.refreshDeviceStatus(),
+                               L.NetworkModel.refreshInterfaceStatus()
+                       );
+               }).then(function() {
+                       var networks = L.NetworkModel.getInterfaces();
+
+                       for (var i = 0; i < networks.length; i++)
+                       {
+                               self.renderNetworkBadge(networks[i]);
+                               self.renderNetworkStatus(networks[i]);
+                       }
+
+                       var max = 0.1;
+                       var networks = L.NetworkModel.getInterfaces();
+
+                       for (var i = 0; i < networks.length; i++)
+                       {
+                               var network = networks[i];
+                               var history = network.getTrafficHistory();
+                               var stats = network.getStatistics();
+
+                               var tx = $('#network-chart-tx-%s'.format(network.name()));
+                               var rx = $('#network-chart-rx-%s'.format(network.name()));
+
+                               var tx_rate = history.tx_bytes[history.tx_bytes.length - 1];
+                               var rx_rate = history.rx_bytes[history.rx_bytes.length - 1];
+
+                               max = Math.max(Math.max.apply(Math, history.rx_bytes),
+                                                          Math.max.apply(Math, history.tx_bytes),
+                                                          max);
+
+                               for (var j = 0; j < history.rx_bytes.length; j++)
+                                       history.rx_bytes[j] = -Math.abs(history.rx_bytes[j]);
+
+                               tx.text(history.tx_bytes.join(','));
+                               rx.text(history.rx_bytes.join(','));
+
+                               tx.next().attr('title', '%.2mB/s'.format(tx_rate));
+                               rx.next().attr('title', '%.2mB/s'.format(rx_rate));
+
+                               tx.nextAll('label').html('↑ %.2mB/s'.format(tx_rate));
+                               rx.nextAll('label').html('↓ %.2mB/s'.format(rx_rate));
+
+                               tx.parent().nextAll('small.traffic-stats').html(
+                                       '<strong>%s</strong>: %.2mB (%d Pkts.)<br />'.format(
+                                               L.trc('Interface status', 'TX'),
+                                               stats.tx_bytes, stats.tx_packets) +
+                                       '<strong>%s</strong>: %.2mB (%d Pkts.)<br />'.format(
+                                               L.trc('Interface status', 'RX'),
+                                               stats.rx_bytes, stats.rx_packets));
+                       }
+
+                       for (var i = 0; i < networks.length; i++)
+                       {
+                               var network = networks[i];
+
+                               var tx = $('#network-chart-tx-%s'.format(network.name()));
+                               var rx = $('#network-chart-rx-%s'.format(network.name()));
+
+                               tx.peity('line', { width: 200, min: 0, max: max });
+                               rx.peity('line', { width: 200, min: -max, max: 0 });
+                       }
+
+                       L.ui.loading(false);
+               });
+       },
+
+       renderContents: function(networks)
+       {
+               var self = this;
+
+               var list = new L.ui.table({
+                       columns: [ {
+                               caption: L.tr('Network'),
+                               width:   '120px',
+                               format:  function(v) {
+                                       var div = $('<div />')
+                                               .attr('id', 'network-badge-%s'.format(v.name()))
+                                               .addClass('ifacebox');
+
+                                       return self.renderNetworkBadge(v, div);
+                               }
+                       }, {
+                               caption: L.tr('Traffic'),
+                               width:   '215px',
+                               format:  function(v) {
+                                       var div = $('<div />').attr('id', 'network-chart-%s'.format(v.name()));
+                                       return self.renderNetworkChart(v, div);
+                               }
+                       }, {
+                               caption: L.tr('Status'),
+                               format:  function(v) {
+                                       var div = $('<small />').attr('id', 'network-status-%s'.format(v.name()));
+                                       return self.renderNetworkStatus(v, div);
+                               }
+                       }, {
+                               caption: L.tr('Actions'),
+                               format:  function(v, n) {
+                                       return $('<div />')
+                                               .addClass('btn-group btn-group-sm')
+                                               .append(L.ui.button(L.tr('Restart'), 'default', L.tr('Enable or restart interface'))
+                                                       .click({ self: self, network: v }, self.handleIfup))
+                                               .append(L.ui.button(L.tr('Shutdown'), 'default', L.tr('Shut down interface'))
+                                                       .click({ self: self, network: v }, self.handleIfdown))
+                                               .append(L.ui.button(L.tr('Edit'), 'primary', L.tr('Edit interface'))
+                                                       .click({ self: self, network: v }, self.handleEdit))
+                                               .append(L.ui.button(L.tr('Delete'), 'danger', L.tr('Delete interface'))
+                                                       .click({ self: self, network: v }, self.handleRemove));
+                               }
+                       } ]
+               });
+
+               for (var i = 0; i < networks.length; i++)
+                       list.row([ networks[i], networks[i], networks[i], networks[i] ]);
+
+               self.repeat(self.refreshNetworkStatus, 5000);
+
+               $('#map')
+                       .append(list.render());
+       },
+
+       renderInterfaceForm: function(network)
+       {
+               var m = new L.cbi.Modal('network', {
+                       tabbed:      true,
+                       caption:     'Interface config',
+                       description: 'I can config interface!!!!'
+               });
+
+
+
+               var s4 = m.section(L.cbi.TypedSection, 'route', {
+                       caption:     L.tr('Static IPv4 Routes'),
+                       anonymous:   true,
+                       addremove:   true,
+                       sortable:    true,
+                       add_caption: L.tr('Add new route'),
+                       remove_caption: L.tr('Remove route')
+               });
+
+               var ifc = s4.option(L.cbi.ListValue, 'interface', {
+                       caption:     L.tr('Interface')
+               });
+
+               ifc.value('foo');
+
+               s4.option(L.cbi.InputValue, 'target', {
+                       caption:     L.tr('Target'),
+                       datatype:    'ip4addr'
+               });
+
+               s4.option(L.cbi.InputValue, 'netmask', {
+                       caption:     L.tr('IPv4-Netmask'),
+                       datatype:    'ip4addr',
+                       placeholder: '255.255.255.255',
+                       optional:    true
+               });
+
+               s4.option(L.cbi.InputValue, 'gateway', {
+                       caption:     L.tr('IPv4-Gateway'),
+                       datatype:    'ip4addr',
+                       optional:    true
+               });
+
+               s4.option(L.cbi.InputValue, 'metric', {
+                       caption:     L.tr('Metric'),
+                       datatype:    'range(0,255)',
+                       placeholder: 0,
+                       optional:    true
+               });
+
+               s4.option(L.cbi.InputValue, 'mtu', {
+                       caption:     L.tr('MTU'),
+                       datatype:    'range(64,9000)',
+                       placeholder: 1500,
+                       optional:    true
+               });
+
+               return m;
+       },
+
+       handleIfup: function(ev) {
+               this.disabled = true;
+               this.blur();
+               ev.data.self.pendingRestart.push(ev.data.network['interface']);
+       },
+
+       handleIfdown: function(ev) {
+               this.disabled = true;
+               this.blur();
+               ev.data.self.pendingShutdown.push(ev.data.network['interface']);
+       },
+
+       handleEdit: function(ev) {
+               var self = ev.data.self;
+               var network = ev.data.network;
+
+               return network.createForm(L.cbi.Modal).show();
+       },
+
+       execute: function() {
+               var self = this;
+
+               return L.NetworkModel.init().then(function() {
+                       self.renderContents(L.NetworkModel.getInterfaces());
+               });
+       }
+});
index 4d32466e0670b03cb3fbaa617dbe57b260661608..9e2147467e26ea11d4fee31f34cd8d462be2275f 100644 (file)
                "description": "Network, switch and routing configuration",
                "read": {
                        "ubus": {
+                               "network": [
+                                       "get_proto_handlers"
+                               ],
+                               "network.device": [
+                                       "status"
+                               ],
+                               "network.interface": [
+                                       "dump"
+                               ],
+                               "network.wireless": [
+                                       "status"
+                               ],
                                "luci2.network": [
                                        "switch_list",
                                        "switch_info",
-                                       "switch_status"
+                                       "switch_status",
+                                       "device_list"
+                               ],
+                               "luci2.network.bwmon": [
+                                       "devices",
+                                       "statistics"
                                ]
                        },
                        "uci": [
-                               "network"
+                               "network",
+                               "wireless"
+                       ]
+               },
+               "write": {
+                       "uci": [
+                               "network",
+                               "wireless"
+                       ]
+               }
+       },
+
+       "firewall": {
+               "description": "Firewall configuration",
+               "read": {
+                       "uci": [
+                               "firewall"
                        ]
                },
                "write": {
                        "uci": [
-                               "network"
+                               "firewall"
                        ]
                }
        }
index e577d7044e5f7d99e45509f4010233a89239dc90..d590046d568484a6525040e7dc703330494c0afd 100644 (file)
@@ -3,6 +3,12 @@
         "title": "Network",
         "index": 30
     },
+    "network/interfaces": {
+        "title": "Interfaces",
+        "acls": [ "network" ],
+        "view": "network/interfaces",
+        "index": 10
+    },
     "network/switch": {
         "title": "Switch",
         "acls": [ "network" ],