luci2: add initial switch configuration view
authorJo-Philipp Wich <jow@openwrt.org>
Wed, 9 Oct 2013 18:58:24 +0000 (18:58 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Wed, 9 Oct 2013 18:58:24 +0000 (18:58 +0000)
luci2/htdocs/luci2/luci2.js
luci2/htdocs/luci2/template/network.switch.htm [new file with mode: 0644]
luci2/htdocs/luci2/view/network.switch.js [new file with mode: 0644]
luci2/share/acl.d/luci2.json
luci2/share/menu.d/network.json [new file with mode: 0644]

index 43cd54e842c835c2290017ba1ea75eaba11226dd..172505949d43dd27c0302b0350944152a8575c1a 100644 (file)
@@ -946,6 +946,37 @@ function LuCI2()
                        object: 'luci2.network',
                        method: 'conntrack_count',
                        expect: { '': { count: 0, limit: 0 } }
+               }),
+
+               listSwitchNames: _luci2.rpc.declare({
+                       object: 'luci2.network',
+                       method: 'switch_list',
+                       expect: { switches: [ ] }
+               }),
+
+               getSwitchInfo: _luci2.rpc.declare({
+                       object: 'luci2.network',
+                       method: 'switch_info',
+                       params: [ 'switch' ],
+                       expect: { info: { } },
+                       filter: function(data, params) {
+                               data['attrs']      = data['switch'];
+                               data['vlan_attrs'] = data['vlan'];
+                               data['port_attrs'] = data['port'];
+                               data['switch']     = params['switch'];
+
+                               delete data.vlan;
+                               delete data.port;
+
+                               return data;
+                       }
+               }),
+
+               getSwitchStatus: _luci2.rpc.declare({
+                       object: 'luci2.network',
+                       method: 'switch_status',
+                       params: [ 'switch' ],
+                       expect: { ports: [ ] }
                })
        };
 
diff --git a/luci2/htdocs/luci2/template/network.switch.htm b/luci2/htdocs/luci2/template/network.switch.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.switch.js b/luci2/htdocs/luci2/view/network.switch.js
new file mode 100644 (file)
index 0000000..659c431
--- /dev/null
@@ -0,0 +1,286 @@
+L.ui.view.extend({
+       title: L.tr('Switch'),
+       description: L.tr('The network ports on this device can be combined to several VLANs in which computers can communicate directly with each other. VLANs are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network.'),
+
+       switchPortState: L.cbi.ListValue.extend({
+               choices: [
+                       [ 'n', L.trc('Switch port state', 'off')      ],
+                       [ 'u', L.trc('Switch port state', 'untagged') ],
+                       [ 't', L.trc('Switch port state', 'tagged')   ]
+               ],
+
+               init: function(name, options)
+               {
+                       var self = this;
+
+                       options.datatype = function(val, elem)
+                       {
+                               if (val == 'u')
+                               {
+                                       var u = false;
+                                       var sections = self.section.sections();
+
+                                       for (var i = 0; i < sections.length; i++)
+                                       {
+                                               var v = self.formvalue(sections[i]['.name']);
+                                               if (v == 'u')
+                                               {
+                                                       if (u)
+                                                               return L.tr('Port must not be untagged in multiple VLANs');
+
+                                                       u = true;
+                                               }
+                                       }
+                               }
+
+                               return true;
+                       };
+
+                       this.callSuper('init', name, options);
+               },
+
+               ucivalue: function(sid)
+               {
+                       var ports = (this.map.get('network', sid, 'ports') || '').match(/[0-9]+[tu]?/g);
+
+                       if (ports)
+                               for (var i = 0; i < ports.length; i++)
+                                       if (ports[i].match(/^([0-9]+)([tu]?)$/))
+                                               if (RegExp.$1 == this.name)
+                                                       return RegExp.$2 || 'u';
+
+                       return 'n';
+               },
+
+               save: function(sid)
+               {
+                       return;
+               }
+       }),
+
+       execute: function() {
+               var self = this;
+               L.network.listSwitchNames().then(function(switches) {
+                       L.rpc.batch();
+
+                       for (var i = 0; i < switches.length; i++)
+                               L.network.getSwitchInfo(switches[i]);
+
+                       return L.rpc.flush();
+               }).then(function(switches) {
+                       var m = new L.cbi.Map('network', {
+                               readonly:    !self.options.acls['switch']
+                       });
+
+                       for (var i = 0; i < switches.length; i++)
+                       {
+                               var vid_opt   = 'vlan';
+                               var v4k_opt   = undefined;
+                               var pvid_opt  = undefined;
+                               var max_vid   = switches[i].num_vlans - 1;
+                               var num_vlans = switches[i].num_vlans;
+
+                               for (var j = 0; j < switches[i].vlan_attrs.length; j++)
+                               {
+                                       switch (switches[i].vlan_attrs[j].name)
+                                       {
+                                       case 'tag':
+                                       case 'vid':
+                                       case 'pvid':
+                                               vid_opt = switches[i].vlan_attrs[j].name;
+                                               max_vid = 4095;
+                                               break;
+                                       }
+                               }
+
+                               for (var j = 0; j < switches[i].port_attrs.length; j++)
+                               {
+                                       switch (switches[i].port_attrs[j].name)
+                                       {
+                                       case 'pvid':
+                                               pvid_opt = switches[i].port_attrs[j].name;
+                                               break;
+                                       }
+                               }
+
+
+                               var sw = m.section(L.cbi.TypedSection, 'switch', {
+                                       caption:  L.tr('Switch "%s"').format(switches[i].model),
+                                       swname:   switches[i]['switch']
+                               });
+
+                               sw.filter = function(section) {
+                                       return (section['.name'] == this.options.swname ||
+                                                       section.name     == this.options.swname);
+                               };
+
+                               for (var j = 0; j < switches[i].attrs.length; j++)
+                               {
+                                       switch (switches[i].attrs[j].name)
+                                       {
+                                       case 'enable_vlan':
+                                               sw.option(L.cbi.CheckboxValue, 'enable_vlan', {
+                                                       caption:     L.tr('Enable VLAN functionality')
+                                               });
+                                               break;
+
+                                       case 'enable_learning':
+                                               sw.option(L.cbi.CheckboxValue, 'enable_learning', {
+                                                       caption:     L.tr('Enable learning and aging'),
+                                                       initial:     true,
+                                                       optional:    true
+                                               });
+                                               break;
+
+                                       case 'max_length':
+                                               sw.option(L.cbi.CheckboxValue, 'max_length', {
+                                                       caption:     L.tr('Enable Jumbo Frame passthrough'),
+                                                       enabled:     '3',
+                                                       optional:    true
+                                               });
+                                               break;
+
+                                       case 'enable_vlan4k':
+                                               v4k_opt = switches[i].attrs[j].name;
+                                               break;
+                                       }
+                               }
+
+                               var vlans = m.section(L.cbi.TableSection, 'switch_vlan', {
+                                       caption:     L.tr('VLANs on "%s"').format(switches[i].model),
+                                       swname:      switches[i]['switch'],
+                                       addremove:   true,
+                                       add_caption: L.tr('Add VLAN entry …')
+                               });
+
+                               vlans.add = function() {
+                                       var sections = this.sections();
+                                       var used_vids = { };
+
+                                       for (var j = 0; j < sections.length; j++)
+                                       {
+                                               var v = this.map.get('network', sections[j]['.name'], 'vlan');
+                                               if (v)
+                                                       used_vids[v] = true;
+                                       }
+
+                                       for (var j = 1; j < num_vlans; j++)
+                                       {
+                                               if (used_vids[j.toString()])
+                                                       continue;
+
+                                               var sid = this.map.add('network', 'switch_vlan');
+                                               this.map.set('network', sid, 'device', this.options.swname);
+                                               this.map.set('network', sid, 'vlan', j);
+                                               break;
+                                       }
+                               };
+
+                               vlans.filter = function(section) {
+                                       return (section.device == this.options.swname);
+                               };
+
+                               vlans.sections = function() {
+                                       var s = this.callSuper('sections');
+
+                                       s.sort(function(a, b) {
+                                               var x = parseInt(a[vid_opt] || a.vlan);
+                                               if (isNaN(x))
+                                                       x = 9999;
+
+                                               var y = parseInt(b[vid_opt] || b.vlan);
+                                               if (isNaN(y))
+                                                       y = 9999;
+
+                                               return (x - y);
+                                       });
+
+                                       return s;
+                               };
+
+                               var port_opts = [ ];
+
+                               var vo = vlans.option(L.cbi.InputValue, vid_opt, {
+                                       caption:     L.tr('VLAN ID'),
+                                       datatype:    function(val) {
+                                               var sections = vlans.sections();
+                                               var used_vids = { };
+
+                                               for (var j = 0; j < sections.length; j++)
+                                               {
+                                                       var v = vlans.fields[vid_opt].formvalue(sections[j]['.name']);
+                                                       if (!v)
+                                                               continue;
+
+                                                       if (used_vids[v])
+                                                               return L.tr('VLAN ID must be unique');
+
+                                                       used_vids[v] = true;
+                                               }
+
+                                               v = parseInt(v, 10);
+
+                                               if (isNaN(v))
+                                                       return L.tr('Invalid VLAN ID');
+
+                                               if (v < 1 || v > max_vid)
+                                                       return L.tr('VLAN ID must be value between %u and %u').format(1, max_vid);
+
+                                               return true;
+                                       }
+                               });
+
+                               vo.ucivalue = function(sid) {
+                                       var id = this.map.get('network', sid, vid_opt);
+
+                                       if (isNaN(parseInt(id)))
+                                               id = this.map.get('network', sid, 'vlan');
+
+                                       return id;
+                               };
+
+                               vo.save = function(sid) {
+                                       var old_ports = this.map.get('network', sid, 'ports');
+                                       var new_ports = '';
+
+                                       for (var j = 0; j < port_opts.length; j++)
+                                       {
+                                               var v = port_opts[j].formvalue(sid);
+                                               if (v != 'n')
+                                                       new_ports += '%s%d%s'.format(
+                                                               new_ports ? ' ' : '', j,
+                                                               (v == 'u') ? '' : 't');
+                                       }
+
+                                       if (new_ports != old_ports)
+                                               this.map.set('network', sid, 'ports', new_ports);
+
+                                       if (v4k_opt)
+                                       {
+                                               var s = sw.sections();
+                                               for (var j = 0; j < s.length; j++)
+                                                       this.map.set('network', s[j]['.name'], v4k_opt, '1');
+                                       }
+
+                                       this.callSuper('save', sid);
+                               };
+
+                               for (var j = 0; j < switches[i].num_ports; j++)
+                               {
+                                       var label = L.trc('Switch port label', 'Port %d').format(j);
+
+                                       if (j == switches[i].cpu_port)
+                                               label = L.trc('Switch port label', 'CPU');
+
+                                       var po = vlans.option(self.switchPortState, j.toString(), {
+                                               caption: label
+                                       });
+
+                                       port_opts.push(po);
+                               }
+                       }
+
+                       return m.insertInto('#map');
+               });
+       }
+});
index ca5dd3c8ddaec7b453fb640d5c81a4c70d52a8f0..62057f3674c93845ab47fb27a3ee05d43034fe17 100644 (file)
                                "system"
                        ]
                }
+       },
+
+       "switch": {
+               "description": "Ethernet switch configuration",
+               "read": {
+                       "ubus": {
+                               "luci2.network": [
+                                       "switch_list",
+                                       "switch_info",
+                                       "switch_status"
+                               ]
+                       },
+                       "uci": [
+                               "network"
+                       ]
+               },
+               "write": {
+                       "uci": [
+                               "network"
+                       ]
+               }
        }
 }
diff --git a/luci2/share/menu.d/network.json b/luci2/share/menu.d/network.json
new file mode 100644 (file)
index 0000000..94078e0
--- /dev/null
@@ -0,0 +1,12 @@
+{
+    "network": {
+        "title": "Network",
+        "index": 30
+    },
+    "network/switch": {
+        "title": "Switch",
+        "acls": [ "switch" ],
+        "view": "network/switch",
+        "index": 30
+    }
+}