luci-mod-network: fix name of network.device.isolate uci option
[project/luci.git] / modules / luci-mod-network / htdocs / luci-static / resources / tools / network.js
1 'use strict';
2 'require ui';
3 'require dom';
4 'require uci';
5 'require form';
6 'require network';
7 'require baseclass';
8 'require validation';
9 'require tools.widgets as widgets';
10
11 function validateAddr(section_id, value) {
12 if (value == '')
13 return true;
14
15 var ipv6 = /6$/.test(this.section.formvalue(section_id, 'mode')),
16 addr = ipv6 ? validation.parseIPv6(value) : validation.parseIPv4(value);
17
18 return addr ? true : (ipv6 ? _('Expecting a valid IPv6 address') : _('Expecting a valid IPv4 address'));
19 }
20
21 function validateQoSMap(section_id, value) {
22 if (value == '')
23 return true;
24
25 var m = value.match(/^(\d+):(\d+)$/);
26
27 if (!m || +m[1] > 0xFFFFFFFF || +m[2] > 0xFFFFFFFF)
28 return _('Expecting two priority values separated by a colon');
29
30 return true;
31 }
32
33 function deviceSectionExists(section_id, devname) {
34 var exists = false;
35
36 uci.sections('network', 'device', function(ss) {
37 exists = exists || (
38 ss['.name'] != section_id &&
39 ss.name == devname
40 );
41 });
42
43 return exists;
44 }
45
46 function isBridgePort(dev) {
47 if (!dev)
48 return false;
49
50 if (dev.isBridgePort())
51 return true;
52
53 var isPort = false;
54
55 uci.sections('network', null, function(s) {
56 if (s['.type'] != 'interface' && s['.type'] != 'device')
57 return;
58
59 if (s.type == 'bridge' && L.toArray(s.ifname).indexOf(dev.getName()) > -1)
60 isPort = true;
61 });
62
63 return isPort;
64 }
65
66 function updateDevBadge(node, dev) {
67 var type = dev.getType(),
68 up = dev.getCarrier();
69
70 dom.content(node, [
71 E('img', {
72 'class': 'middle',
73 'src': L.resource('icons/%s%s.png').format(type, up ? '' : '_disabled')
74 }),
75 '\x0a', dev.getName()
76 ]);
77
78 return node;
79 }
80
81 function renderDevBadge(dev) {
82 return updateDevBadge(E('span', {
83 'class': 'ifacebadge port-status-device',
84 'style': 'font-weight:normal',
85 'data-device': dev.getName()
86 }), dev);
87 }
88
89 function updatePortStatus(node, dev) {
90 var carrier = dev.getCarrier(),
91 duplex = dev.getDuplex(),
92 speed = dev.getSpeed(),
93 desc, title;
94
95 if (carrier && speed > 0 && duplex != null) {
96 desc = '%d%s'.format(speed, duplex == 'full' ? 'FD' : 'HD');
97 title = '%s, %d MBit/s, %s'.format(_('Connected'), speed, duplex == 'full' ? _('full-duplex') : _('half-duplex'));
98 }
99 else if (carrier) {
100 desc = _('Connected');
101 }
102 else {
103 desc = _('no link');
104 }
105
106 dom.content(node, [
107 E('img', {
108 'class': 'middle',
109 'src': L.resource('icons/port_%s.png').format(carrier ? 'up' : 'down')
110 }),
111 '\x0a', desc
112 ]);
113
114 if (title)
115 node.setAttribute('data-tooltip', title);
116 else
117 node.removeAttribute('data-tooltip');
118
119 return node;
120 }
121
122 function renderPortStatus(dev) {
123 return updatePortStatus(E('span', {
124 'class': 'ifacebadge port-status-link',
125 'data-device': dev.getName()
126 }), dev);
127 }
128
129 function updatePlaceholders(opt, section_id) {
130 var dev = network.instantiateDevice(opt.getUIElement(section_id).getValue());
131
132 for (var i = 0, co; (co = opt.section.children[i]) != null; i++) {
133 if (co !== opt) {
134 switch (co.option) {
135 case 'mtu':
136 case 'mtu6':
137 co.getUIElement(section_id).setPlaceholder(dev.getMTU());
138 break;
139
140 case 'macaddr':
141 co.getUIElement(section_id).setPlaceholder(dev.getMAC());
142 break;
143
144 case 'txqueuelen':
145 co.getUIElement(section_id).setPlaceholder(dev._devstate('qlen'));
146 break;
147 }
148 }
149 }
150 }
151
152
153 var cbiTagValue = form.Value.extend({
154 renderWidget: function(section_id, option_index, cfgvalue) {
155 var widget = new ui.Dropdown(cfgvalue || ['-'], {
156 '-': E([], [
157 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '—' ]),
158 E('span', { 'class': 'hide-close' }, [ _('Do not participate', 'VLAN port state') ])
159 ]),
160 'u': E([], [
161 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'u' ]),
162 E('span', { 'class': 'hide-close' }, [ _('Egress untagged', 'VLAN port state') ])
163 ]),
164 't': E([], [
165 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 't' ]),
166 E('span', { 'class': 'hide-close' }, [ _('Egress tagged', 'VLAN port state') ])
167 ]),
168 '*': E([], [
169 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '*' ]),
170 E('span', { 'class': 'hide-close' }, [ _('Primary VLAN ID', 'VLAN port state') ])
171 ])
172 }, {
173 id: this.cbid(section_id),
174 sort: [ '-', 'u', 't', '*' ],
175 optional: false,
176 multiple: true
177 });
178
179 var field = this;
180
181 widget.toggleItem = function(sb, li, force_state) {
182 var lis = li.parentNode.querySelectorAll('li'),
183 toggle = ui.Dropdown.prototype.toggleItem;
184
185 toggle.apply(this, [sb, li, force_state]);
186
187 if (force_state != null)
188 return;
189
190 switch (li.getAttribute('data-value'))
191 {
192 case '-':
193 if (li.hasAttribute('selected')) {
194 for (var i = 0; i < lis.length; i++) {
195 switch (lis[i].getAttribute('data-value')) {
196 case '-':
197 break;
198
199 case '*':
200 toggle.apply(this, [sb, lis[i], false]);
201 lis[i].setAttribute('unselectable', '');
202 break;
203
204 default:
205 toggle.apply(this, [sb, lis[i], false]);
206 }
207 }
208 }
209 break;
210
211 case 't':
212 case 'u':
213 if (li.hasAttribute('selected')) {
214 for (var i = 0; i < lis.length; i++) {
215 switch (lis[i].getAttribute('data-value')) {
216 case li.getAttribute('data-value'):
217 break;
218
219 case '*':
220 lis[i].removeAttribute('unselectable');
221 break;
222
223 default:
224 toggle.apply(this, [sb, lis[i], false]);
225 }
226 }
227 }
228 else {
229 toggle.apply(this, [sb, li, true]);
230 }
231 break;
232
233 case '*':
234 if (li.hasAttribute('selected')) {
235 var section_ids = field.section.cfgsections();
236
237 for (var i = 0; i < section_ids.length; i++) {
238 var other_widget = field.getUIElement(section_ids[i]),
239 other_value = L.toArray(other_widget.getValue());
240
241 if (other_widget === this)
242 continue;
243
244 var new_value = other_value.filter(function(v) { return v != '*' });
245
246 if (new_value.length == other_value.length)
247 continue;
248
249 other_widget.setValue(new_value);
250 break;
251 }
252 }
253 }
254 };
255
256 var node = widget.render();
257
258 node.style.minWidth = '4em';
259
260 if (cfgvalue == '-')
261 node.querySelector('li[data-value="*"]').setAttribute('unselectable', '');
262
263 return E('div', { 'style': 'display:inline-block' }, node);
264 },
265
266 cfgvalue: function(section_id) {
267 var ports = L.toArray(uci.get('network', section_id, 'ports'));
268
269 for (var i = 0; i < ports.length; i++) {
270 var s = ports[i].split(/:/);
271
272 if (s[0] != this.port)
273 continue;
274
275 var t = /t/.test(s[1] || '') ? 't' : 'u';
276
277 return /\x2a/.test(s[1] || '') ? [t, '*'] : [t];
278 }
279
280 return ['-'];
281 },
282
283 write: function(section_id, value) {
284 var ports = [];
285
286 for (var i = 0; i < this.section.children.length; i++) {
287 var opt = this.section.children[i];
288
289 if (opt.port) {
290 var val = L.toArray(opt.formvalue(section_id)).join('');
291
292 switch (val) {
293 case '-':
294 break;
295
296 case 'u':
297 ports.push(opt.port);
298 break;
299
300 default:
301 ports.push('%s:%s'.format(opt.port, val));
302 break;
303 }
304 }
305 }
306
307 uci.set('network', section_id, 'ports', ports.length ? ports : null);
308 },
309
310 remove: function() {}
311 });
312
313 return baseclass.extend({
314 replaceOption: function(s, tabName, optionClass, optionName, optionTitle, optionDescription) {
315 var o = s.getOption(optionName);
316
317 if (o) {
318 if (o.tab) {
319 s.tabs[o.tab].children = s.tabs[o.tab].children.filter(function(opt) {
320 return opt.option != optionName;
321 });
322 }
323
324 s.children = s.children.filter(function(opt) {
325 return opt.option != optionName;
326 });
327 }
328
329 return s.taboption(tabName, optionClass, optionName, optionTitle, optionDescription);
330 },
331
332 addDeviceOptions: function(s, dev, isNew) {
333 var parent_dev = dev ? dev.getParent() : null,
334 o, ss;
335
336 s.tab('devgeneral', _('General device options'));
337 s.tab('devadvanced', _('Advanced device options'));
338 s.tab('brport', _('Bridge port specific options'));
339 s.tab('bridgevlan', _('Bridge VLAN filtering'));
340
341 o = this.replaceOption(s, 'devgeneral', form.ListValue, 'type', _('Device type'));
342 o.readonly = !isNew;
343 o.value('', _('Network device'));
344 o.value('bridge', _('Bridge device'));
345 o.value('8021q', _('VLAN (802.1q)'));
346 o.value('8021ad', _('VLAN (802.1ad)'));
347 o.value('macvlan', _('MAC VLAN'));
348 o.value('veth', _('Virtual Ethernet'));
349 o.validate = function(section_id, value) {
350 if (value == 'bridge' || value == 'veth')
351 updatePlaceholders(this.section.getOption('name_complex'), section_id);
352
353 return true;
354 };
355
356 o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, 'name_simple', _('Existing device'));
357 o.readonly = !isNew;
358 o.rmempty = false;
359 o.noaliases = true;
360 o.default = (dev ? dev.getName() : '');
361 o.ucioption = 'name';
362 o.filter = function(section_id, value) {
363 var dev = network.instantiateDevice(value);
364 return !deviceSectionExists(section_id, value) && (dev.getType() != 'wifi' || dev.isUp());
365 };
366 o.validate = function(section_id, value) {
367 updatePlaceholders(this, section_id);
368
369 return deviceSectionExists(section_id, value)
370 ? _('A configuration for the device "%s" already exists').format(value) : true;
371 };
372 o.onchange = function(ev, section_id, values) {
373 updatePlaceholders(this, section_id);
374 };
375 o.depends('type', '');
376
377 o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_single', _('Base device'));
378 o.readonly = !isNew;
379 o.rmempty = false;
380 o.noaliases = true;
381 o.default = (dev ? dev.getName() : '').match(/^.+\.\d+$/) ? dev.getName().replace(/\.\d+$/, '') : '';
382 o.ucioption = 'ifname';
383 o.filter = function(section_id, value) {
384 var dev = network.instantiateDevice(value);
385 return (dev.getType() != 'wifi' || dev.isUp());
386 };
387 o.validate = function(section_id, value) {
388 updatePlaceholders(this, section_id);
389
390 if (isNew) {
391 var type = this.section.formvalue(section_id, 'type'),
392 name = this.section.getUIElement(section_id, 'name_complex');
393
394 if (type == 'macvlan' && value && name && !name.isChanged()) {
395 var i = 0;
396
397 while (deviceSectionExists(section_id, '%smac%d'.format(value, i)))
398 i++;
399
400 name.setValue('%smac%d'.format(value, i));
401 name.triggerValidation();
402 }
403 }
404
405 return true;
406 };
407 o.onchange = function(ev, section_id, values) {
408 updatePlaceholders(this, section_id);
409 };
410 o.depends('type', '8021q');
411 o.depends('type', '8021ad');
412 o.depends('type', 'macvlan');
413
414 o = this.replaceOption(s, 'devgeneral', form.Value, 'vid', _('VLAN ID'));
415 o.readonly = !isNew;
416 o.datatype = 'range(1, 4094)';
417 o.rmempty = false;
418 o.default = (dev ? dev.getName() : '').match(/^.+\.\d+$/) ? dev.getName().replace(/^.+\./, '') : '';
419 o.validate = function(section_id, value) {
420 var base = this.section.formvalue(section_id, 'ifname_single'),
421 vid = this.section.formvalue(section_id, 'vid'),
422 name = this.section.getUIElement(section_id, 'name_complex');
423
424 if (base && vid && name && !name.isChanged()) {
425 name.setValue('%s.%d'.format(base, vid));
426 name.triggerValidation();
427 }
428
429 return true;
430 };
431 o.depends('type', '8021q');
432 o.depends('type', '8021ad');
433
434 o = this.replaceOption(s, 'devgeneral', form.ListValue, 'mode', _('Mode'));
435 o.value('vepa', _('VEPA (Virtual Ethernet Port Aggregator)', 'MACVLAN mode'));
436 o.value('private', _('Private (Prevent communication between MAC VLANs)', 'MACVLAN mode'));
437 o.value('bridge', _('Bridge (Support direct communication between MAC VLANs)', 'MACVLAN mode'));
438 o.value('passthru', _('Pass-through (Mirror physical device to single MAC VLAN)', 'MACVLAN mode'));
439 o.depends('type', 'macvlan');
440
441 o = this.replaceOption(s, 'devgeneral', form.Value, 'name_complex', _('Device name'));
442 o.rmempty = false;
443 o.datatype = 'maxlength(15)';
444 o.readonly = !isNew;
445 o.ucioption = 'name';
446 o.validate = function(section_id, value) {
447 var dev = network.instantiateDevice(value);
448
449 if (deviceSectionExists(section_id, value) || (isNew && (dev.dev || {}).idx))
450 return _('The device name "%s" is already taken').format(value);
451
452 return true;
453 };
454 o.depends({ type: '', '!reverse': true });
455
456 o = this.replaceOption(s, 'devadvanced', form.DynamicList, 'ingress_qos_mapping', _('Ingress QoS mapping'), _('Defines a mapping of VLAN header priority to the Linux internal packet priority on incoming frames'));
457 o.rmempty = true;
458 o.validate = validateQoSMap;
459 o.depends('type', '8021q');
460 o.depends('type', '8021ad');
461
462 o = this.replaceOption(s, 'devadvanced', form.DynamicList, 'egress_qos_mapping', _('Egress QoS mapping'), _('Defines a mapping of Linux internal packet priority to VLAN header priority but for outgoing frames'));
463 o.rmempty = true;
464 o.validate = validateQoSMap;
465 o.depends('type', '8021q');
466 o.depends('type', '8021ad');
467
468 o = this.replaceOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_multi', _('Bridge ports'));
469 o.size = 10;
470 o.rmempty = true;
471 o.multiple = true;
472 o.noaliases = true;
473 o.nobridges = true;
474 o.ucioption = 'ports';
475 o.default = L.toArray(dev ? dev.getPorts() : null).filter(function(p) { return p.getType() != 'wifi' }).map(function(p) { return p.getName() });
476 o.filter = function(section_id, device_name) {
477 var bridge_name = uci.get('network', section_id, 'name'),
478 choice_dev = network.instantiateDevice(device_name),
479 parent_dev = choice_dev.getParent();
480
481 /* only show wifi networks which are already present in "option ifname" */
482 if (choice_dev.getType() == 'wifi') {
483 var ifnames = L.toArray(uci.get('network', section_id, 'ports'));
484
485 for (var i = 0; i < ifnames.length; i++)
486 if (ifnames[i] == device_name)
487 return true;
488
489 return false;
490 }
491
492 return (!parent_dev || parent_dev.getName() != bridge_name);
493 };
494 o.description = _('Specifies the wired ports to attach to this bridge. In order to attach wireless networks, choose the associated interface as network in the wireless settings.')
495 o.onchange = function(ev, section_id, values) {
496 ss.updatePorts(values);
497
498 return ss.parse().then(function() {
499 ss.redraw();
500 });
501 };
502 o.depends('type', 'bridge');
503
504 o = this.replaceOption(s, 'devgeneral', form.Flag, 'bridge_empty', _('Bring up empty bridge'), _('Bring up the bridge interface even if no ports are attached'));
505 o.default = o.disabled;
506 o.depends('type', 'bridge');
507
508 o = this.replaceOption(s, 'devadvanced', form.Value, 'priority', _('Priority'));
509 o.placeholder = '32767';
510 o.datatype = 'range(0, 65535)';
511 o.depends('type', 'bridge');
512
513 o = this.replaceOption(s, 'devadvanced', form.Value, 'ageing_time', _('Ageing time'), _('Timeout in seconds for learned MAC addresses in the forwarding database'));
514 o.placeholder = '30';
515 o.datatype = 'uinteger';
516 o.depends('type', 'bridge');
517
518 o = this.replaceOption(s, 'devadvanced', form.Flag, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge'));
519 o.default = o.disabled;
520 o.depends('type', 'bridge');
521
522 o = this.replaceOption(s, 'devadvanced', form.Value, 'hello_time', _('Hello interval'), _('Interval in seconds for STP hello packets'));
523 o.placeholder = '2';
524 o.datatype = 'range(1, 10)';
525 o.depends({ type: 'bridge', stp: '1' });
526
527 o = this.replaceOption(s, 'devadvanced', form.Value, 'forward_delay', _('Forward delay'), _('Time in seconds to spend in listening and learning states'));
528 o.placeholder = '15';
529 o.datatype = 'range(2, 30)';
530 o.depends({ type: 'bridge', stp: '1' });
531
532 o = this.replaceOption(s, 'devadvanced', form.Value, 'max_age', _('Maximum age'), _('Timeout in seconds until topology updates on link loss'));
533 o.placeholder = '20';
534 o.datatype = 'range(6, 40)';
535 o.depends({ type: 'bridge', stp: '1' });
536
537
538 o = this.replaceOption(s, 'devadvanced', form.Flag, 'igmp_snooping', _('Enable <abbr title="Internet Group Management Protocol">IGMP</abbr> snooping'), _('Enables IGMP snooping on this bridge'));
539 o.default = o.disabled;
540 o.depends('type', 'bridge');
541
542 o = this.replaceOption(s, 'devadvanced', form.Value, 'hash_max', _('Maximum snooping table size'));
543 o.placeholder = '512';
544 o.datatype = 'uinteger';
545 o.depends({ type: 'bridge', igmp_snooping: '1' });
546
547 o = this.replaceOption(s, 'devadvanced', form.Flag, 'multicast_querier', _('Enable multicast querier'));
548 o.defaults = { '1': [{'igmp_snooping': '1'}], '0': [{'igmp_snooping': '0'}] };
549 o.depends('type', 'bridge');
550
551 o = this.replaceOption(s, 'devadvanced', form.Value, 'robustness', _('Robustness'), _('The robustness value allows tuning for the expected packet loss on the network. If a network is expected to be lossy, the robustness value may be increased. IGMP is robust to (Robustness-1) packet losses'));
552 o.placeholder = '2';
553 o.datatype = 'min(1)';
554 o.depends({ type: 'bridge', multicast_querier: '1' });
555
556 o = this.replaceOption(s, 'devadvanced', form.Value, 'query_interval', _('Query interval'), _('Interval in centiseconds between multicast general queries. By varying the value, an administrator may tune the number of IGMP messages on the subnet; larger values cause IGMP Queries to be sent less often'));
557 o.placeholder = '12500';
558 o.datatype = 'uinteger';
559 o.depends({ type: 'bridge', multicast_querier: '1' });
560
561 o = this.replaceOption(s, 'devadvanced', form.Value, 'query_response_interval', _('Query response interval'), _('The max response time in centiseconds inserted into the periodic general queries. By varying the value, an administrator may tune the burstiness of IGMP messages on the subnet; larger values make the traffic less bursty, as host responses are spread out over a larger interval'));
562 o.placeholder = '1000';
563 o.datatype = 'uinteger';
564 o.validate = function(section_id, value) {
565 var qiopt = L.toArray(this.map.lookupOption('query_interval', section_id))[0],
566 qival = qiopt ? (qiopt.formvalue(section_id) || qiopt.placeholder) : '';
567
568 if (value != '' && qival != '' && +value >= +qival)
569 return _('The query response interval must be lower than the query interval value');
570
571 return true;
572 };
573 o.depends({ type: 'bridge', multicast_querier: '1' });
574
575 o = this.replaceOption(s, 'devadvanced', form.Value, 'last_member_interval', _('Last member interval'), _('The max response time in centiseconds inserted into group-specific queries sent in response to leave group messages. It is also the amount of time between group-specific query messages. This value may be tuned to modify the "leave latency" of the network. A reduced value results in reduced time to detect the loss of the last member of a group'));
576 o.placeholder = '100';
577 o.datatype = 'uinteger';
578 o.depends({ type: 'bridge', multicast_querier: '1' });
579
580 o = this.replaceOption(s, 'devgeneral', form.Value, 'mtu', _('MTU'));
581 o.datatype = 'range(576, 9200)';
582 o.validate = function(section_id, value) {
583 var parent_mtu = (dev && dev.getType() == 'vlan') ? (parent_dev ? parent_dev.getMTU() : null) : null;
584
585 if (parent_mtu !== null && +value > parent_mtu)
586 return _('The MTU must not exceed the parent device MTU of %d bytes').format(parent_mtu);
587
588 return true;
589 };
590
591 o = this.replaceOption(s, 'devgeneral', form.Value, 'macaddr', _('MAC address'));
592 o.datatype = 'macaddr';
593
594 o = this.replaceOption(s, 'devgeneral', form.Value, 'peer_name', _('Peer device name'));
595 o.rmempty = true;
596 o.datatype = 'maxlength(15)';
597 o.depends('type', 'veth');
598 o.load = function(section_id) {
599 var sections = uci.sections('network', 'device'),
600 idx = 0;
601
602 for (var i = 0; i < sections.length; i++)
603 if (sections[i]['.name'] == section_id)
604 break;
605 else if (sections[i].type == 'veth')
606 idx++;
607
608 this.placeholder = 'veth%d'.format(idx);
609
610 return form.Value.prototype.load.apply(this, arguments);
611 };
612
613 o = this.replaceOption(s, 'devgeneral', form.Value, 'peer_macaddr', _('Peer MAC address'));
614 o.rmempty = true;
615 o.datatype = 'macaddr';
616 o.depends('type', 'veth');
617
618 o = this.replaceOption(s, 'devgeneral', form.Value, 'txqueuelen', _('TX queue length'));
619 o.placeholder = dev ? dev._devstate('qlen') : '';
620 o.datatype = 'uinteger';
621
622 o = this.replaceOption(s, 'devadvanced', form.Flag, 'promisc', _('Enable promiscuous mode'));
623 o.default = o.disabled;
624
625 o = this.replaceOption(s, 'devadvanced', form.ListValue, 'rpfilter', _('Reverse path filter'));
626 o.default = '';
627 o.value('', _('disabled'));
628 o.value('loose', _('Loose filtering'));
629 o.value('strict', _('Strict filtering'));
630 o.cfgvalue = function(section_id) {
631 var val = form.ListValue.prototype.cfgvalue.apply(this, [section_id]);
632
633 switch (val || '') {
634 case 'loose':
635 case '1':
636 return 'loose';
637
638 case 'strict':
639 case '2':
640 return 'strict';
641
642 default:
643 return '';
644 }
645 };
646
647 o = this.replaceOption(s, 'devadvanced', form.Flag, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
648 o.default = o.disabled;
649
650 o = this.replaceOption(s, 'devadvanced', form.Flag, 'sendredirects', _('Send ICMP redirects'));
651 o.default = o.enabled;
652
653 o = this.replaceOption(s, 'devadvanced', form.Value, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds'));
654 o.placeholder = '30000';
655 o.datatype = 'uinteger';
656
657 o = this.replaceOption(s, 'devadvanced', form.Value, 'neighgcstaletime', _('Stale neighbour cache timeout'), _('Timeout in seconds'));
658 o.placeholder = '60';
659 o.datatype = 'uinteger';
660
661 o = this.replaceOption(s, 'devadvanced', form.Value, 'neighlocktime', _('Minimum ARP validity time'), _('Minimum required time in seconds before an ARP entry may be replaced. Prevents ARP cache thrashing.'));
662 o.placeholder = '0';
663 o.datatype = 'uinteger';
664
665 o = this.replaceOption(s, 'devgeneral', form.Flag, 'ipv6', _('Enable IPv6'));
666 o.migrate = false;
667 o.default = o.enabled;
668
669 o = this.replaceOption(s, 'devgeneral', form.Value, 'mtu6', _('IPv6 MTU'));
670 o.datatype = 'max(9200)';
671 o.depends('ipv6', '1');
672
673 o = this.replaceOption(s, 'devgeneral', form.Value, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send'));
674 o.placeholder = '1';
675 o.datatype = 'uinteger';
676 o.depends('ipv6', '1');
677
678
679 o = this.replaceOption(s, 'devadvanced', form.Flag, 'multicast', _('Enable multicast support'));
680 o.default = o.enabled;
681
682 o = this.replaceOption(s, 'devadvanced', form.ListValue, 'igmpversion', _('Force IGMP version'));
683 o.value('', _('No enforcement'));
684 o.value('1', _('Enforce IGMPv1'));
685 o.value('2', _('Enforce IGMPv2'));
686 o.value('3', _('Enforce IGMPv3'));
687 o.depends('multicast', '1');
688
689 o = this.replaceOption(s, 'devadvanced', form.ListValue, 'mldversion', _('Force MLD version'));
690 o.value('', _('No enforcement'));
691 o.value('1', _('Enforce MLD version 1'));
692 o.value('2', _('Enforce MLD version 2'));
693 o.depends('multicast', '1');
694
695 if (isBridgePort(dev)) {
696 o = this.replaceOption(s, 'brport', form.Flag, 'learning', _('Enable MAC address learning'));
697 o.default = o.enabled;
698
699 o = this.replaceOption(s, 'brport', form.Flag, 'unicast_flood', _('Enable unicast flooding'));
700 o.default = o.enabled;
701
702 o = this.replaceOption(s, 'brport', form.Flag, 'isolate', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
703 o.default = o.disabled;
704
705 o = this.replaceOption(s, 'brport', form.ListValue, 'multicast_router', _('Multicast routing'));
706 o.value('', _('Never'));
707 o.value('1', _('Learn'));
708 o.value('2', _('Always'));
709 o.depends('multicast', '1');
710
711 o = this.replaceOption(s, 'brport', form.Flag, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
712 o.default = o.disabled;
713 o.depends('multicast', '1');
714
715 o = this.replaceOption(s, 'brport', form.Flag, 'multicast_fast_leave', _('Enable multicast fast leave'));
716 o.default = o.disabled;
717 o.depends('multicast', '1');
718 }
719
720 o = this.replaceOption(s, 'bridgevlan', form.Flag, 'vlan_filtering', _('Enable VLAN filtering'));
721 o.depends('type', 'bridge');
722 o.updateDefaultValue = function(section_id) {
723 var device = uci.get('network', s.section, 'name'),
724 uielem = this.getUIElement(section_id),
725 has_vlans = false;
726
727 uci.sections('network', 'bridge-vlan', function(bvs) {
728 has_vlans = has_vlans || (bvs.device == device);
729 });
730
731 this.default = has_vlans ? this.enabled : this.disabled;
732
733 if (uielem && !uielem.isChanged())
734 uielem.setValue(this.default);
735 };
736
737 o = this.replaceOption(s, 'bridgevlan', form.SectionValue, 'bridge-vlan', form.TableSection, 'bridge-vlan');
738 o.depends('type', 'bridge');
739
740 ss = o.subsection;
741 ss.addremove = true;
742 ss.anonymous = true;
743
744 ss.renderHeaderRows = function(/* ... */) {
745 var node = form.TableSection.prototype.renderHeaderRows.apply(this, arguments);
746
747 node.querySelectorAll('.th').forEach(function(th) {
748 th.classList.add('left');
749 th.classList.add('middle');
750 });
751
752 return node;
753 };
754
755 ss.filter = function(section_id) {
756 var devname = uci.get('network', s.section, 'name');
757 return (uci.get('network', section_id, 'device') == devname);
758 };
759
760 ss.render = function(/* ... */) {
761 return form.TableSection.prototype.render.apply(this, arguments).then(L.bind(function(node) {
762 node.style.overflow = 'auto hidden';
763 node.style.paddingTop = '1em';
764
765 if (this.node)
766 this.node.parentNode.replaceChild(node, this.node);
767
768 this.node = node;
769
770 return node;
771 }, this));
772 };
773
774 ss.redraw = function() {
775 return this.load().then(L.bind(this.render, this));
776 };
777
778 ss.updatePorts = function(ports) {
779 var devices = ports.map(function(port) {
780 return network.instantiateDevice(port)
781 }).filter(function(dev) {
782 return dev.getType() != 'wifi' || dev.isUp();
783 });
784
785 this.children = this.children.filter(function(opt) { return !opt.option.match(/^port_/) });
786
787 for (var i = 0; i < devices.length; i++) {
788 o = ss.option(cbiTagValue, 'port_%s'.format(sfh(devices[i].getName())), renderDevBadge(devices[i]), renderPortStatus(devices[i]));
789 o.port = devices[i].getName();
790 }
791
792 var section_ids = this.cfgsections(),
793 device_names = devices.reduce(function(names, dev) { names[dev.getName()] = true; return names }, {});
794
795 for (var i = 0; i < section_ids.length; i++) {
796 var old_spec = L.toArray(uci.get('network', section_ids[i], 'ports')),
797 new_spec = old_spec.filter(function(spec) { return device_names[spec.replace(/:[ut*]+$/, '')] });
798
799 if (old_spec.length != new_spec.length)
800 uci.set('network', section_ids[i], 'ports', new_spec.length ? new_spec : null);
801 }
802 };
803
804 ss.handleAdd = function(ev) {
805 return s.parse().then(L.bind(function() {
806 var device = uci.get('network', s.section, 'name'),
807 section_ids = this.cfgsections(),
808 section_id = null,
809 max_vlan_id = 0;
810
811 if (!device)
812 return;
813
814 for (var i = 0; i < section_ids.length; i++) {
815 var vid = +uci.get('network', section_ids[i], 'vlan');
816
817 if (vid > max_vlan_id)
818 max_vlan_id = vid;
819 }
820
821 section_id = uci.add('network', 'bridge-vlan');
822 uci.set('network', section_id, 'device', device);
823 uci.set('network', section_id, 'vlan', max_vlan_id + 1);
824
825 s.children.forEach(function(opt) {
826 switch (opt.option) {
827 case 'type':
828 case 'name_complex':
829 var input = opt.map.findElement('id', 'widget.%s'.format(opt.cbid(s.section)));
830 if (input)
831 input.disabled = true;
832 break;
833 }
834 });
835
836 s.getOption('vlan_filtering').updateDefaultValue(s.section);
837
838 s.map.addedVLANs = s.map.addedVLANs || [];
839 s.map.addedVLANs.push(section_id);
840
841 return this.redraw();
842 }, this));
843 };
844
845 o = ss.option(form.Value, 'vlan', _('VLAN ID'));
846 o.datatype = 'range(1, 4094)';
847
848 o.renderWidget = function(/* ... */) {
849 var node = form.Value.prototype.renderWidget.apply(this, arguments);
850
851 node.style.width = '5em';
852
853 return node;
854 };
855
856 o.validate = function(section_id, value) {
857 var section_ids = this.section.cfgsections();
858
859 for (var i = 0; i < section_ids.length; i++) {
860 if (section_ids[i] == section_id)
861 continue;
862
863 if (uci.get('network', section_ids[i], 'vlan') == value)
864 return _('The VLAN ID must be unique');
865 }
866
867 return true;
868 };
869
870 o = ss.option(form.Flag, 'local', _('Local'));
871 o.default = o.enabled;
872
873 var ports = [];
874
875 var seen_ports = {};
876
877 L.toArray(uci.get('network', s.section, 'ports')).forEach(function(port) {
878 seen_ports[port] = true;
879 });
880
881 uci.sections('network', 'bridge-vlan', function(bvs) {
882 if (uci.get('network', s.section, 'name') != bvs.device)
883 return;
884
885 L.toArray(bvs.ports).forEach(function(portspec) {
886 var m = portspec.match(/^([^:]+)(?::[ut*]+)?$/);
887
888 if (m)
889 seen_ports[m[1]] = true;
890 });
891 });
892
893 for (var port_name in seen_ports)
894 ports.push(port_name);
895
896 ports.sort(function(a, b) {
897 var m1 = a.match(/^(.+?)([0-9]*)$/),
898 m2 = b.match(/^(.+?)([0-9]*)$/);
899
900 if (m1[1] < m2[1])
901 return -1;
902 else if (m1[1] > m2[1])
903 return 1;
904 else
905 return +(m1[2] || 0) - +(m2[2] || 0);
906 });
907
908 ss.updatePorts(ports);
909 },
910
911 updateDevBadge: updateDevBadge,
912 updatePortStatus: updatePortStatus
913 });