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