From 040ed60573605041313680e51b285cecff45f8cf Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Wed, 9 Oct 2013 18:58:24 +0000 Subject: [PATCH] luci2: add initial switch configuration view --- luci2/htdocs/luci2/luci2.js | 31 ++ .../htdocs/luci2/template/network.switch.htm | 1 + luci2/htdocs/luci2/view/network.switch.js | 286 ++++++++++++++++++ luci2/share/acl.d/luci2.json | 21 ++ luci2/share/menu.d/network.json | 12 + 5 files changed, 351 insertions(+) create mode 100644 luci2/htdocs/luci2/template/network.switch.htm create mode 100644 luci2/htdocs/luci2/view/network.switch.js create mode 100644 luci2/share/menu.d/network.json diff --git a/luci2/htdocs/luci2/luci2.js b/luci2/htdocs/luci2/luci2.js index 43cd54e..1725059 100644 --- a/luci2/htdocs/luci2/luci2.js +++ b/luci2/htdocs/luci2/luci2.js @@ -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 index 0000000..ad19e7d --- /dev/null +++ b/luci2/htdocs/luci2/template/network.switch.htm @@ -0,0 +1 @@ +
diff --git a/luci2/htdocs/luci2/view/network.switch.js b/luci2/htdocs/luci2/view/network.switch.js new file mode 100644 index 0000000..659c431 --- /dev/null +++ b/luci2/htdocs/luci2/view/network.switch.js @@ -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'); + }); + } +}); diff --git a/luci2/share/acl.d/luci2.json b/luci2/share/acl.d/luci2.json index ca5dd3c..62057f3 100644 --- a/luci2/share/acl.d/luci2.json +++ b/luci2/share/acl.d/luci2.json @@ -273,5 +273,26 @@ "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 index 0000000..94078e0 --- /dev/null +++ b/luci2/share/menu.d/network.json @@ -0,0 +1,12 @@ +{ + "network": { + "title": "Network", + "index": 30 + }, + "network/switch": { + "title": "Switch", + "acls": [ "switch" ], + "view": "network/switch", + "index": 30 + } +} -- 2.30.2