Merge pull request #1735 from sumpfralle/olsr-jsoninfo-parser-handle-empty-result
[oweals/luci.git] / modules / luci-mod-network / htdocs / luci-static / resources / view / network / switch.js
1 'use strict';
2 'require rpc';
3 'require uci';
4 'require form';
5 'require network';
6
7 function parse_portvalue(section_id) {
8         var ports = L.toArray(uci.get('network', section_id, 'ports'));
9
10         for (var i = 0; i < ports.length; i++) {
11                 var m = ports[i].match(/^(\d+)([tu]?)/);
12
13                 if (m && m[1] == this.option)
14                         return m[2] || 'u';
15         }
16
17         return '';
18 }
19
20 function validate_portvalue(section_id, value) {
21         if (value != 'u')
22                 return true;
23
24         var sections = this.section.cfgsections();
25
26         for (var i = 0; i < sections.length; i++) {
27                 if (sections[i] == section_id)
28                         continue;
29
30                 if (this.formvalue(sections[i]) == 'u')
31                         return _('%s is untagged in multiple VLANs!').format(this.title);
32         }
33
34         return true;
35 }
36
37 function update_interfaces(old_ifname, new_ifname) {
38         var interfaces = uci.sections('network', 'interface');
39
40         for (var i = 0; i < interfaces.length; i++) {
41                 var old_ifnames = L.toArray(interfaces[i].ifname),
42                     new_ifnames = [],
43                     changed = false;
44
45                 for (var j = 0; j < old_ifnames.length; j++) {
46                         if (old_ifnames[j] == old_ifname) {
47                                 new_ifnames.push(new_ifname);
48                                 changed = true;
49                         }
50                         else {
51                                 new_ifnames.push(old_ifnames[j]);
52                         }
53                 }
54
55                 if (changed) {
56                         uci.set('network', interfaces[i]['.name'], 'ifname', new_ifnames.join(' '));
57
58                         L.ui.addNotification(null, E('p', _('Interface %q device auto-migrated from %q to %q.')
59                                 .replace(/%q/g, '"%s"').format(interfaces[i]['.name'], old_ifname, new_ifname)));
60                 }
61         }
62 }
63
64 function render_port_status(node, portstate) {
65         if (!node)
66                 return null;
67
68         if (!portstate.link)
69                 L.dom.content(node, [
70                         E('img', { src: L.resource('icons/port_down.png') }),
71                         E('br'),
72                         _('no link')
73                 ]);
74         else
75                 L.dom.content(node, [
76                         E('img', { src: L.resource('icons/port_up.png') }),
77                         E('br'),
78                         '%d'.format(portstate.speed) + _('baseT'),
79                         E('br'),
80                         portstate.duplex ? _('full-duplex') : _('half-duplex')
81                 ]);
82
83         return node;
84 }
85
86 function update_port_status(topologies) {
87         var tasks = [];
88
89         for (var switch_name in topologies)
90                 tasks.push(callSwconfigPortState(switch_name).then(L.bind(function(switch_name, ports) {
91                         for (var i = 0; i < ports.length; i++) {
92                                 var node = document.querySelector('[data-switch="%s"][data-port="%d"]'.format(switch_name, ports[i].port));
93                                 render_port_status(node, ports[i]);
94                         }
95                 }, topologies[switch_name], switch_name)));
96
97         return Promise.all(tasks);
98 }
99
100 var callSwconfigFeatures = rpc.declare({
101         object: 'luci',
102         method: 'getSwconfigFeatures',
103         params: [ 'switch' ],
104         expect: { '': {} }
105 });
106
107 var callSwconfigPortState = rpc.declare({
108         object: 'luci',
109         method: 'getSwconfigPortState',
110         params: [ 'switch' ],
111         expect: { result: [] }
112 });
113
114 return L.view.extend({
115         load: function() {
116                 return network.getSwitchTopologies().then(function(topologies) {
117                         var tasks = [];
118
119                         for (var switch_name in topologies) {
120                                 tasks.push(callSwconfigFeatures(switch_name).then(L.bind(function(features) {
121                                         this.features = features;
122                                 }, topologies[switch_name])));
123                                 tasks.push(callSwconfigPortState(switch_name).then(L.bind(function(ports) {
124                                         this.portstate = ports;
125                                 }, topologies[switch_name])));
126                         }
127
128                         return Promise.all(tasks).then(function() { return topologies });
129                 });
130         },
131
132         render: function(topologies) {
133                 var m, s, o;
134
135                 m = new form.Map('network', _('Switch'), _('The network ports on this device can be combined to several <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s in which computers can communicate directly with each other. <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s 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.'));
136
137                 var switchSections = uci.sections('network', 'switch');
138
139                 for (var i = 0; i < switchSections.length; i++) {
140                         var switchSection   = switchSections[i],
141                             sid             = switchSection['.name'],
142                             switch_name     = switchSection.name || sid,
143                             topology        = topologies[switch_name];
144
145                         if (!topology) {
146                                 L.ui.addNotification(null, _('Switch %q has an unknown topology - the VLAN settings might not be accurate.').replace(/%q/, switch_name));
147
148                                 topology = {
149                                         features: {},
150                                         netdevs: {
151                                                 5: 'eth0'
152                                         },
153                                         ports: [
154                                                 { num: 0, label: 'Port 1' },
155                                                 { num: 1, label: 'Port 2' },
156                                                 { num: 2, label: 'Port 3' },
157                                                 { num: 3, label: 'Port 4' },
158                                                 { num: 4, label: 'Port 5' },
159                                                 { num: 5, label: 'CPU (eth0)', device: 'eth0', need_tag: false }
160                                         ]
161                                 };
162                         }
163
164                         var feat = topology.features,
165                             min_vid = feat.min_vid || 0,
166                             max_vid = feat.max_vid || 16,
167                             num_vlans = feat.num_vlans || 16,
168                             switch_title = _('Switch %q').replace(/%q/, '"%s"'.format(switch_name)),
169                             vlan_title = _('VLANs on %q').replace(/%q/, '"%s"'.format(switch_name));
170
171                         if (feat.switch_title) {
172                                 switch_title += ' (%s)'.format(feat.switch_title);
173                                 vlan_title += ' (%s)'.format(feat.switch_title);
174                         }
175
176                         s = m.section(form.NamedSection, sid, 'switch', switch_title);
177                         s.addremove = false;
178
179                         if (feat.vlan_option)
180                                 s.option(form.Flag, feat.vlan_option, _('Enable VLAN functionality'));
181
182                         if (feat.learning_option) {
183                                 o = s.option(form.Flag, feat.learning_option, _('Enable learning and aging'));
184                                 o.default = o.enabled;
185                         }
186
187                         if (feat.jumbo_option) {
188                                 o = s.option(form.Flag, feat.jumbo_option, _('Enable Jumbo Frame passthrough'));
189                                 o.enabled = '3';
190                                 o.rmempty = true;
191                         }
192
193                         if (feat.mirror_option) {
194                                 s.option(form.Flag, 'enable_mirror_rx', _('Enable mirroring of incoming packets'));
195                                 s.option(form.Flag, 'enable_mirror_tx', _('Enable mirroring of outgoing packets'));
196
197                                 var sp = s.option(form.ListValue, 'mirror_source_port', _('Mirror source port')),
198                                     mp = s.option(form.ListValue, 'mirror_monitor_port', _('Mirror monitor port'));
199
200                                 sp.depends('enable_mirror_rx', '1');
201                                 sp.depends('enable_mirror_tx', '1');
202
203                                 mp.depends('enable_mirror_rx', '1');
204                                 mp.depends('enable_mirror_tx', '1');
205
206                                 for (var j = 0; j < topology.ports.length; j++) {
207                                         sp.value(topology.ports[j].num, topology.ports[j].label);
208                                         mp.value(topology.ports[j].num, topology.ports[j].label);
209                                 }
210                         }
211
212                         s = m.section(form.TableSection, 'switch_vlan', vlan_title);
213                         s.anonymous = true;
214                         s.addremove = true;
215                         s.addbtntitle = _('Add VLAN');
216                         s.topology = topology;
217                         s.device = switch_name;
218
219                         s.filter = function(section_id) {
220                                 var device = uci.get('network', section_id, 'device');
221                                 return (device == switch_name);
222                         };
223
224                         s.cfgsections = function() {
225                                 var sections = form.TableSection.prototype.cfgsections.apply(this);
226
227                                 return sections.sort(function(a, b) {
228                                         var vidA = feat.vid_option ? uci.get('network', a, feat.vid_option) : null,
229                                             vidB = feat.vid_option ? uci.get('network', b, feat.vid_option) : null;
230
231                                         vidA = +(vidA != null ? vidA : uci.get('network', a, 'vlan') || 9999);
232                                         vidB = +(vidB != null ? vidB : uci.get('network', b, 'vlan') || 9999);
233
234                                         return (vidA - vidB);
235                                 });
236                         };
237
238                         s.handleAdd = function(ev) {
239                                 var sections = uci.sections('network', 'switch_vlan'),
240                                     section_id = uci.add('network', 'switch_vlan'),
241                                     max_vlan = 0,
242                                     max_vid = 0;
243
244                                 for (var j = 0; j < sections.length; j++) {
245                                         if (sections[j].device != s.device)
246                                                 continue;
247
248                                         var vlan = +sections[j].vlan,
249                                             vid = feat.vid_option ? +sections[j][feat.vid_option] : null;
250
251                                         if (vlan > max_vlan)
252                                                 max_vlan = vlan;
253
254                                         if (vid > max_vid)
255                                                 max_vid = vid;
256                                 }
257
258                                 uci.set('network', section_id, 'device', s.device);
259                                 uci.set('network', section_id, 'vlan', max_vlan + 1);
260
261                                 if (feat.vid_option)
262                                         uci.set('network', section_id, feat.vid_option, max_vid + 1);
263
264                                 return this.map.save(null, true);
265                         };
266
267                         var port_opts = [];
268
269                         o = s.option(form.Value, feat.vid_option || 'vlan', 'VLAN ID');
270                         o.rmempty = false;
271                         o.forcewrite = true;
272                         o.vlan_used = {};
273                         o.datatype = 'range(%u,%u)'.format(min_vid, feat.vid_option ? 4094 : num_vlans - 1);
274                         o.description = _('Port status:');
275
276                         o.validate = function(section_id, value) {
277                                 var v = +value,
278                                     m = feat.vid_option ? 4094 : num_vlans - 1;
279
280                                 if (isNaN(v) || v < min_vid || v > m)
281                                         return _('Invalid VLAN ID given! Only IDs between %d and %d are allowed.').format(min_vid, m);
282
283                                 var sections = this.section.cfgsections();
284
285                                 for (var i = 0; i < sections.length; i++) {
286                                         if (sections[i] == section_id)
287                                                 continue;
288
289                                         if (this.formvalue(sections[i]) == v)
290                                                 return _('Invalid VLAN ID given! Only unique IDs are allowed');
291                                 }
292
293                                 return true;
294                         };
295
296                         o.write = function(section_id, value) {
297                                 var topology = this.section.topology,
298                                     values = [];
299
300                                 for (var i = 0; i < port_opts.length; i++) {
301                                         var tagging = port_opts[i].formvalue(section_id),
302                                             portspec = Array.isArray(topology.ports) ? topology.ports[i] : null;
303
304                                         if (tagging == 't')
305                                                 values.push(port_opts[i].option + tagging);
306                                         else if (tagging == 'u')
307                                                 values.push(port_opts[i].option);
308
309                                         if (portspec && portspec.device) {
310                                                 var old_tag = port_opts[i].cfgvalue(section_id),
311                                                     old_vid = this.cfgvalue(section_id);
312
313                                                 if (old_tag != tagging || old_vid != value) {
314                                                         var old_ifname = portspec.device + (old_tag != 'u' ? '.' + old_vid : ''),
315                                                             new_ifname = portspec.device + (tagging != 'u' ? '.' + value : '');
316
317                                                         if (old_ifname != new_ifname)
318                                                                 update_interfaces(old_ifname, new_ifname);
319                                                 }
320                                         }
321                                 }
322
323                                 if (feat.vlan4k_option)
324                                         uci.set('network', sid, feat.vlan4k_option, '1');
325
326                                 uci.set('network', section_id, 'ports', values.join(' '));
327
328                                 return form.Value.prototype.write.apply(this, [section_id, value]);
329                         };
330
331                         o.cfgvalue = function(section_id) {
332                                 var value = feat.vid_option ? uci.get('network', section_id, feat.vid_option) : null;
333                                 return (value || uci.get('network', section_id, 'vlan'));
334                         };
335
336                         for (var j = 0; Array.isArray(topology.ports) && j < topology.ports.length; j++) {
337                                 var portspec = topology.ports[j],
338                                     portstate = Array.isArray(topology.portstate) ? topology.portstate[portspec.num] : null;
339
340                                 o = s.option(form.ListValue, String(portspec.num), portspec.label);
341                                 o.value('', _('off'));
342
343                                 if (!portspec.need_tag)
344                                         o.value('u', _('untagged'));
345
346                                 o.value('t', _('tagged'));
347
348                                 o.cfgvalue = parse_portvalue;
349                                 o.validate = validate_portvalue;
350                                 o.write    = function() {};
351
352                                 o.description = render_port_status(E('small', {
353                                         'data-switch': switch_name,
354                                         'data-port': portspec.num
355                                 }), portstate);
356
357                                 port_opts.push(o);
358                         }
359
360                         port_opts.sort(function(a, b) {
361                                 return a.option < b.option;
362                         });
363                 }
364
365                 L.Poll.add(L.bind(update_port_status, m, topologies));
366
367                 return m.render();
368         }
369 });