8 'require tools.widgets as widgets';
10 function validateAddr(section_id
, value
) {
14 var ipv6
= /6$/.test(this.section
.formvalue(section_id
, 'mode')),
15 addr
= ipv6
? validation
.parseIPv6(value
) : validation
.parseIPv4(value
);
17 return addr
? true : (ipv6
? _('Expecting a valid IPv6 address') : _('Expecting a valid IPv4 address'));
20 function setIfActive(section_id
, value
) {
21 if (this.isActive(section_id
)) {
22 uci
.set('network', section_id
, this.ucioption
, value
);
24 /* Requires http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html */
25 if (false && this.option == 'ifname_multi') {
26 var devname = this.section.formvalue(section_id, 'name_complex'),
27 m = devname ? devname.match(/^br-([A-Za-z0-9_]+)$/) : null;
29 if (m && uci.get('network', m[1], 'type') == 'bridge') {
30 uci.set('network', m[1], 'ifname', devname);
31 uci.unset('network', m[1], 'type');
37 function validateQoSMap(section_id, value) {
41 var m = value.match(/^(\d+):(\d+)$/);
43 if (!m || +m[1] > 0xFFFFFFFF || +m[2] > 0xFFFFFFFF)
44 return _('Expecting two priority values separated by a colon');
49 function deviceSectionExists(section_id, devname, devtype) {
52 uci.sections('network', 'device', function(ss) {
53 exists = exists || (ss['.name'] != section_id && ss.name == devname && (!devtype || devtype == ss.type));
59 function isBridgePort(dev) {
63 if (dev.isBridgePort())
68 uci.sections('network', null, function(s) {
69 if (s['.type'] != 'interface' && s['.type'] != 'device')
72 if (s.type == 'bridge' && L.toArray(s.ifname).indexOf(dev.getName()) > -1)
79 function renderDevBadge(dev) {
80 var type = dev.getType(), up = dev.isUp();
82 return E('span', { 'class': 'ifacebadge', 'style': 'font-weight:normal' }, [
85 'src': L.resource('icons/%s%s.png').format(type, up ? '' : '_disabled')
91 function lookupDevName(s, section_id) {
92 var typeui = s.getUIElement(section_id, 'type'),
93 typeval = typeui ? typeui.getValue() : s.cfgvalue(section_id, 'type'),
94 ifnameui = s.getUIElement(section_id, 'ifname_single'),
95 ifnameval = ifnameui ? ifnameui.getValue() : s.cfgvalue(section_id, 'ifname_single');
97 return (typeval == 'bridge') ? 'br-%s'.format(section_id) : ifnameval;
100 function lookupDevSection(s, section_id, autocreate) {
101 var devname = lookupDevName(s, section_id),
104 uci.sections('network', 'device', function(ds) {
105 if (ds.name == devname)
106 devsection = ds['.name'];
109 if (autocreate && !devsection) {
110 devsection = uci.add('network', 'device');
111 uci.set('network', devsection, 'name', devname);
117 function getDeviceValue(dev, method) {
118 if (dev && dev.getL3Device)
119 dev = dev.getL3Device();
121 if (dev && typeof(dev[method]) == 'function')
122 return dev[method].apply(dev);
127 function deviceCfgValue(section_id) {
128 if (arguments.length == 2)
131 var ds = lookupDevSection(this.section, section_id, false);
133 return (ds ? uci.get('network', ds, this.option) : null) ||
134 uci.get('network', section_id, this.option) ||
138 function deviceWrite(section_id, formvalue) {
139 var ds = lookupDevSection(this.section, section_id, true);
141 uci.set('network', ds, this.option, formvalue);
142 uci.unset('network', section_id, this.option);
145 function deviceRemove(section_id) {
146 var ds = lookupDevSection(this.section, section_id, false),
147 sv = ds ? uci.get('network', ds) : null;
152 for (var opt in sv) {
153 if (opt.charAt(0) == '.' || opt == 'name' || opt == this.option)
160 uci.remove('network', ds);
162 uci.unset('network', ds, this.option);
165 uci.unset('network', section_id, this.option);
168 function deviceRefresh(section_id) {
169 var dev = network.instantiateDevice(lookupDevName(this.section, section_id)),
170 uielem = this.getUIElement(section_id);
173 switch (this.option) {
176 uielem.setPlaceholder(dev.getMTU());
180 uielem.setPlaceholder(dev.getMAC());
184 uielem.setValue(this.cfgvalue(section_id));
189 var cbiTagValue = form.Value.extend({
190 renderWidget: function(section_id, option_index, cfgvalue) {
191 var widget = new ui.Dropdown(cfgvalue || ['-'], {
193 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '—' ]),
194 E('span', { 'class': 'hide-close' }, [ _('Do not participate', 'VLAN port state') ])
197 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'u' ]),
198 E('span', { 'class': 'hide-close' }, [ _('Egress untagged', 'VLAN port state') ])
201 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 't' ]),
202 E('span', { 'class': 'hide-close' }, [ _('Egress tagged', 'VLAN port state') ])
205 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '*' ]),
206 E('span', { 'class': 'hide-close' }, [ _('Primary VLAN ID', 'VLAN port state') ])
209 id: this.cbid(section_id),
210 sort: [ '-', 'u', 't', '*' ],
217 widget.toggleItem = function(sb, li, force_state) {
218 var lis = li.parentNode.querySelectorAll('li'),
219 toggle = ui.Dropdown.prototype.toggleItem;
221 toggle.apply(this, [sb, li, force_state]);
223 if (force_state != null)
226 switch (li.getAttribute('data-value'))
229 if (li.hasAttribute('selected')) {
230 for (var i = 0; i < lis.length; i++) {
231 switch (lis[i].getAttribute('data-value')) {
236 toggle.apply(this, [sb, lis[i], false]);
237 lis[i].setAttribute('unselectable', '');
241 toggle.apply(this, [sb, lis[i], false]);
249 if (li.hasAttribute('selected')) {
250 for (var i = 0; i < lis.length; i++) {
251 switch (lis[i].getAttribute('data-value')) {
252 case li.getAttribute('data-value'):
256 lis[i].removeAttribute('unselectable');
260 toggle.apply(this, [sb, lis[i], false]);
265 toggle.apply(this, [sb, li, true]);
270 if (li.hasAttribute('selected')) {
271 var section_ids = field.section.cfgsections();
273 for (var i = 0; i < section_ids.length; i++) {
274 var other_widget = field.getUIElement(section_ids[i]),
275 other_value = L.toArray(other_widget.getValue());
277 if (other_widget === this)
280 var new_value = other_value.filter(function(v) { return v != '*' });
282 if (new_value.length == other_value.length)
285 other_widget.setValue(new_value);
292 var node = widget.render();
294 node.style.minWidth = '4em';
297 node.querySelector('li[data-value="*"]').setAttribute('unselectable', '');
299 return E('div', { 'style': 'display:inline-block' }, node);
302 cfgvalue: function(section_id) {
303 var pname = this.port,
304 spec = L.toArray(uci.get('network', section_id, 'ports')).filter(function(p) { return p.replace(/:[ut*]+$/, '') == pname })[0];
306 if (spec && spec.match(/t/))
307 return spec.match(/\*/) ? ['t', '*'] : ['t'];
309 return spec.match(/\*/) ? ['u', '*'] : ['u'];
314 write: function(section_id, value) {
317 for (var i = 0; i < this.section.children.length; i++) {
318 var opt = this.section.children[i];
321 var val = L.toArray(opt.formvalue(section_id)).join('');
328 ports.push(opt.port);
332 ports.push('%s:%s'.format(opt.port, val));
338 uci.set('network', section_id, 'ports', ports);
341 remove: function() {}
344 return baseclass.extend({
345 replaceOption: function(s, tabName, optionClass, optionName, optionTitle, optionDescription) {
346 var o = s.getOption(optionName);
350 s.tabs[o.tab].children = s.tabs[o.tab].children.filter(function(opt) {
351 return opt.option != optionName;
355 s.children = s.children.filter(function(opt) {
356 return opt.option != optionName;
360 return s.taboption(tabName, optionClass, optionName, optionTitle, optionDescription);
363 addOption: function(s, tabName, optionClass, optionName, optionTitle, optionDescription) {
364 var o = this.replaceOption(s, tabName, optionClass, optionName, optionTitle, optionDescription);
366 if (s.sectiontype == 'interface' && optionName != 'type' && optionName != 'vlan_filtering') {
367 o.cfgvalue = deviceCfgValue;
368 o.write = deviceWrite;
369 o.remove = deviceRemove;
370 o.refresh = deviceRefresh;
376 addDeviceOptions: function(s, dev, isNew) {
377 var isIface = (s.sectiontype == 'interface'),
378 ifc = isIface ? network.instantiateNetwork(s.section) : null,
379 gensection = ifc ? 'physical' : 'devgeneral',
380 advsection = ifc ? 'physical' : 'devadvanced',
381 simpledep = ifc ? { type: '', ifname_single: /^[^@]/ } : { type: '' },
382 disableLegacyBridging = isIface && deviceSectionExists(null, 'br-%s'.format(ifc.getName()), 'bridge'),
385 /* If an externally configured br-xxx interface already exists,
386 * then disable legacy bridge configuration */
387 if (disableLegacyBridging
) {
388 o
= this.addOption(s
, gensection
, form
.HiddenValue
, 'type');
389 o
.cfgvalue = function() { return '' };
394 type
= this.addOption(s
, gensection
, form
.Flag
, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
395 type
.modalonly
= true;
397 type
.enabled
= 'bridge';
398 type
.write
= type
.remove = function(section_id
, value
) {
399 var protoname
= this.section
.formvalue(section_id
, 'proto'),
400 protocol
= network
.getProtocol(protoname
),
401 new_ifnames
= this.isActive(section_id
) ? L
.toArray(this.section
.formvalue(section_id
, value
? 'ifname_multi' : 'ifname_single')) : [];
403 if (!protocol
.isVirtual() && !this.isActive(section_id
))
406 var old_ifnames
= [],
407 devs
= ifc
.getDevices() || L
.toArray(ifc
.getDevice());
409 for (var i
= 0; i
< devs
.length
; i
++)
410 old_ifnames
.push(devs
[i
].getName());
413 new_ifnames
.length
= Math
.max(new_ifnames
.length
, 1);
418 for (var i
= 0; i
< Math
.max(old_ifnames
.length
, new_ifnames
.length
); i
++) {
419 if (old_ifnames
[i
] != new_ifnames
[i
]) {
421 for (var j
= 0; j
< old_ifnames
.length
; j
++)
422 ifc
.deleteDevice(old_ifnames
[j
]);
424 for (var j
= 0; j
< new_ifnames
.length
; j
++)
425 ifc
.addDevice(new_ifnames
[j
]);
432 uci
.set('network', section_id
, 'type', 'bridge');
434 uci
.unset('network', section_id
, 'type');
438 s
.tab('devgeneral', _('General device options'));
439 s
.tab('devadvanced', _('Advanced device options'));
440 s
.tab('brport', _('Bridge port specific options'));
441 s
.tab('bridgevlan', _('Bridge VLAN filtering'));
443 o
= this.addOption(s
, gensection
, form
.ListValue
, 'type', _('Device type'));
445 o
.value('', _('Network device'));
446 o
.value('bridge', _('Bridge device'));
447 o
.value('8021q', _('VLAN (802.1q)'));
448 o
.value('8021ad', _('VLAN (802.1ad)'));
449 o
.value('macvlan', _('MAC VLAN'));
450 o
.value('veth', _('Virtual Ethernet'));
452 o
= this.addOption(s
, gensection
, widgets
.DeviceSelect
, 'name_simple', _('Existing device'));
456 o
.default = (dev
? dev
.getName() : '');
457 o
.ucioption
= 'name';
458 o
.write
= o
.remove
= setIfActive
;
459 o
.filter = function(section_id
, value
) {
460 return !deviceSectionExists(section_id
, value
);
462 o
.validate = function(section_id
, value
) {
463 return deviceSectionExists(section_id
, value
) ? _('A configuration for the device "%s" already exists').format(value
) : true;
465 o
.depends('type', '');
468 o
= this.addOption(s
, gensection
, widgets
.DeviceSelect
, 'ifname_single', isIface
? _('Interface') : _('Base device'));
471 o
.noaliases
= !isIface
;
472 o
.default = (dev
? dev
.getName() : '').match(/^.+\.\d+$/) ? dev
.getName().replace(/\.\d+$/, '') : '';
473 o
.ucioption
= 'ifname';
474 o
.validate = function(section_id
, value
) {
475 var type
= this.section
.formvalue(section_id
, 'type'),
476 name
= this.section
.getUIElement(section_id
, 'name_complex');
478 if (type
== 'macvlan' && value
&& name
&& !name
.isChanged()) {
481 while (deviceSectionExists(section_id
, '%smac%d'.format(value
, i
)))
484 name
.setValue('%smac%d'.format(value
, i
));
485 name
.triggerValidation();
491 o
.write
= o
.remove = function() {};
492 o
.cfgvalue = function(section_id
) {
493 return (ifc
.getDevices() || L
.toArray(ifc
.getDevice())).map(function(dev
) {
494 return dev
.getName();
497 o
.onchange = function(ev
, section_id
, values
) {
498 for (var i
= 0, co
; (co
= this.section
.children
[i
]) != null; i
++)
499 if (co
!== this && co
.refresh
)
500 co
.refresh(section_id
);
503 o
.depends('type', '');
506 o
.write
= o
.remove
= setIfActive
;
507 o
.depends('type', '8021q');
508 o
.depends('type', '8021ad');
509 o
.depends('type', 'macvlan');
512 o
= this.addOption(s
, gensection
, form
.Value
, 'vid', _('VLAN ID'));
514 o
.datatype
= 'range(1, 4094)';
516 o
.default = (dev
? dev
.getName() : '').match(/^.+\.\d+$/) ? dev
.getName().replace(/^.+\./, '') : '';
517 o
.validate = function(section_id
, value
) {
518 var base
= this.section
.formvalue(section_id
, 'ifname_single'),
519 vid
= this.section
.formvalue(section_id
, 'vid'),
520 name
= this.section
.getUIElement(section_id
, 'name_complex');
522 if (base
&& vid
&& name
&& !name
.isChanged()) {
523 name
.setValue('%s.%d'.format(base
, vid
));
524 name
.triggerValidation();
529 o
.depends('type', '8021q');
530 o
.depends('type', '8021ad');
532 o
= this.addOption(s
, gensection
, form
.ListValue
, 'mode', _('Mode'));
533 o
.value('vepa', _('VEPA (Virtual Ethernet Port Aggregator)', 'MACVLAN mode'));
534 o
.value('private', _('Private (Prevent communication between MAC VLANs)', 'MACVLAN mode'));
535 o
.value('bridge', _('Bridge (Support direct communication between MAC VLANs)', 'MACVLAN mode'));
536 o
.value('passthru', _('Pass-through (Mirror physical device to single MAC VLAN)', 'MACVLAN mode'));
537 o
.depends('type', 'macvlan');
540 o
= this.addOption(s
, gensection
, form
.Value
, 'name_complex', _('Device name'));
542 o
.datatype
= 'maxlength(15)';
544 o
.ucioption
= 'name';
545 o
.write
= o
.remove
= setIfActive
;
546 o
.validate = function(section_id
, value
) {
547 return deviceSectionExists(section_id
, value
) ? _('The device name "%s" is already taken').format(value
) : true;
549 o
.depends({ type
: '', '!reverse': true });
552 o
= this.addOption(s
, advsection
, form
.DynamicList
, 'ingress_qos_mapping', _('Ingress QoS mapping'), _('Defines a mapping of VLAN header priority to the Linux internal packet priority on incoming frames'));
554 o
.validate
= validateQoSMap
;
555 o
.depends('type', '8021q');
556 o
.depends('type', '8021ad');
558 o
= this.addOption(s
, advsection
, form
.DynamicList
, 'egress_qos_mapping', _('Egress QoS mapping'), _('Defines a mapping of Linux internal packet priority to VLAN header priority but for outgoing frames'));
560 o
.validate
= validateQoSMap
;
561 o
.depends('type', '8021q');
562 o
.depends('type', '8021ad');
564 o
= this.addOption(s
, gensection
, widgets
.DeviceSelect
, 'ifname_multi', _('Bridge ports'));
570 o
.ucioption
= 'ifname';
572 o
.write
= o
.remove = function() {};
573 o
.cfgvalue = function(section_id
) {
574 return (ifc
.getDevices() || L
.toArray(ifc
.getDevice())).map(function(dev
) { return dev
.getName() });
578 o
.write
= o
.remove
= setIfActive
;
579 o
.default = L
.toArray(dev
? dev
.getPorts() : null).filter(function(p
) { return p
.getType() != 'wifi' }).map(function(p
) { return p
.getName() });
580 o
.filter = function(section_id
, device_name
) {
581 var bridge_name
= uci
.get('network', section_id
, 'name'),
582 choice_dev
= network
.instantiateDevice(device_name
),
583 parent_dev
= choice_dev
.getParent();
585 /* only show wifi networks which are already present in "option ifname" */
586 if (choice_dev
.getType() == 'wifi') {
587 var ifnames
= L
.toArray(uci
.get('network', section_id
, 'ifname'));
589 for (var i
= 0; i
< ifnames
.length
; i
++)
590 if (ifnames
[i
] == device_name
)
596 return (!parent_dev
|| parent_dev
.getName() != bridge_name
);
598 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.')
600 o
.onchange = function(ev
, section_id
, values
) {
601 ss
.updatePorts(values
);
603 return ss
.parse().then(function() {
607 o
.depends('type', 'bridge');
609 o
= this.replaceOption(s
, gensection
, form
.Flag
, 'bridge_empty', _('Bring up empty bridge'), _('Bring up the bridge interface even if no ports are attached'));
610 o
.default = o
.disabled
;
611 o
.depends('type', 'bridge');
613 o
= this.replaceOption(s
, advsection
, form
.Value
, 'priority', _('Priority'));
614 o
.placeholder
= '32767';
615 o
.datatype
= 'range(0, 65535)';
616 o
.depends('type', 'bridge');
618 o
= this.replaceOption(s
, advsection
, form
.Value
, 'ageing_time', _('Ageing time'), _('Timeout in seconds for learned MAC addresses in the forwarding database'));
619 o
.placeholder
= '30';
620 o
.datatype
= 'uinteger';
621 o
.depends('type', 'bridge');
623 o
= this.replaceOption(s
, advsection
, form
.Flag
, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge'));
624 o
.default = o
.disabled
;
625 o
.depends('type', 'bridge');
627 o
= this.replaceOption(s
, advsection
, form
.Value
, 'hello_time', _('Hello interval'), _('Interval in seconds for STP hello packets'));
629 o
.datatype
= 'range(1, 10)';
630 o
.depends({ type
: 'bridge', stp
: '1' });
632 o
= this.replaceOption(s
, advsection
, form
.Value
, 'forward_delay', _('Forward delay'), _('Time in seconds to spend in listening and learning states'));
633 o
.placeholder
= '15';
634 o
.datatype
= 'range(2, 30)';
635 o
.depends({ type
: 'bridge', stp
: '1' });
637 o
= this.replaceOption(s
, advsection
, form
.Value
, 'max_age', _('Maximum age'), _('Timeout in seconds until topology updates on link loss'));
638 o
.placeholder
= '20';
639 o
.datatype
= 'range(6, 40)';
640 o
.depends({ type
: 'bridge', stp
: '1' });
643 o
= this.replaceOption(s
, advsection
, form
.Flag
, 'igmp_snooping', _('Enable <abbr title="Internet Group Management Protocol">IGMP</abbr> snooping'), _('Enables IGMP snooping on this bridge'));
644 o
.default = o
.disabled
;
645 o
.depends('type', 'bridge');
647 o
= this.replaceOption(s
, advsection
, form
.Value
, 'hash_max', _('Maximum snooping table size'));
648 o
.placeholder
= '512';
649 o
.datatype
= 'uinteger';
650 o
.depends({ type
: 'bridge', igmp_snooping
: '1' });
652 o
= this.replaceOption(s
, advsection
, form
.Flag
, 'multicast_querier', _('Enable multicast querier'));
653 o
.defaults
= { '1': [{'igmp_snooping': '1'}], '0': [{'igmp_snooping': '0'}] };
654 o
.depends('type', 'bridge');
656 o
= this.replaceOption(s
, advsection
, 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'));
658 o
.datatype
= 'min(1)';
659 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
661 o
= this.replaceOption(s
, advsection
, 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'));
662 o
.placeholder
= '12500';
663 o
.datatype
= 'uinteger';
664 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
666 o
= this.replaceOption(s
, advsection
, 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'));
667 o
.placeholder
= '1000';
668 o
.datatype
= 'uinteger';
669 o
.validate = function(section_id
, value
) {
670 var qiopt
= L
.toArray(this.map
.lookupOption('query_interval', section_id
))[0],
671 qival
= qiopt
? (qiopt
.formvalue(section_id
) || qiopt
.placeholder
) : '';
673 if (value
!= '' && qival
!= '' && +value
>= +qival
)
674 return _('The query response interval must be lower than the query interval value');
678 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
680 o
= this.replaceOption(s
, advsection
, 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'));
681 o
.placeholder
= '100';
682 o
.datatype
= 'uinteger';
683 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
685 o
= this.addOption(s
, gensection
, form
.Value
, 'mtu', _('MTU'));
686 o
.placeholder
= getDeviceValue(ifc
|| dev
, 'getMTU');
687 o
.datatype
= 'max(9200)';
688 o
.depends(simpledep
);
690 o
= this.addOption(s
, gensection
, form
.Value
, 'macaddr', _('MAC address'));
691 o
.placeholder
= getDeviceValue(ifc
|| dev
, 'getMAC');
692 o
.datatype
= 'macaddr';
693 o
.depends(simpledep
);
694 o
.depends('type', 'macvlan');
695 o
.depends('type', 'veth');
697 o
= this.addOption(s
, gensection
, form
.Value
, 'peer_name', _('Peer device name'));
699 o
.datatype
= 'maxlength(15)';
700 o
.depends('type', 'veth');
701 o
.load = function(section_id
) {
702 var sections
= uci
.sections('network', 'device'),
705 for (var i
= 0; i
< sections
.length
; i
++)
706 if (sections
[i
]['.name'] == section_id
)
708 else if (sections
[i
].type
== 'veth')
711 this.placeholder
= 'veth%d'.format(idx
);
713 return form
.Value
.prototype.load
.apply(this, arguments
);
716 o
= this.addOption(s
, gensection
, form
.Value
, 'peer_macaddr', _('Peer MAC address'));
718 o
.datatype
= 'macaddr';
719 o
.depends('type', 'veth');
721 o
= this.addOption(s
, gensection
, form
.Value
, 'txqueuelen', _('TX queue length'));
722 o
.placeholder
= dev
? dev
._devstate('qlen') : '';
723 o
.datatype
= 'uinteger';
724 o
.depends(simpledep
);
726 o
= this.addOption(s
, advsection
, form
.Flag
, 'promisc', _('Enable promiscious mode'));
727 o
.default = o
.disabled
;
728 o
.depends(simpledep
);
730 o
= this.addOption(s
, advsection
, form
.ListValue
, 'rpfilter', _('Reverse path filter'));
732 o
.value('', _('disabled'));
733 o
.value('loose', _('Loose filtering'));
734 o
.value('strict', _('Strict filtering'));
735 o
.cfgvalue = function(section_id
) {
736 var val
= form
.ListValue
.prototype.cfgvalue
.apply(this, [section_id
]);
751 o
.depends(simpledep
);
753 o
= this.addOption(s
, advsection
, form
.Flag
, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
754 o
.default = o
.disabled
;
755 o
.depends(simpledep
);
757 o
= this.addOption(s
, advsection
, form
.Flag
, 'sendredirects', _('Send ICMP redirects'));
758 o
.default = o
.enabled
;
759 o
.depends(simpledep
);
761 o
= this.addOption(s
, advsection
, form
.Value
, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds'));
762 o
.placeholder
= '30000';
763 o
.datatype
= 'uinteger';
764 o
.depends(simpledep
);
766 o
= this.addOption(s
, advsection
, form
.Value
, 'neighgcstaletime', _('Stale neighbour cache timeout'), _('Timeout in seconds'));
767 o
.placeholder
= '60';
768 o
.datatype
= 'uinteger';
769 o
.depends(simpledep
);
771 o
= this.addOption(s
, advsection
, form
.Value
, 'neighlocktime', _('Minimum ARP validity time'), _('Minimum required time in seconds before an ARP entry may be replaced. Prevents ARP cache thrashing.'));
773 o
.datatype
= 'uinteger';
774 o
.depends(simpledep
);
776 o
= this.addOption(s
, gensection
, form
.Flag
, 'ipv6', _('Enable IPv6'));
777 o
.default = o
.enabled
;
778 o
.depends(simpledep
);
780 o
= this.addOption(s
, gensection
, form
.Value
, 'mtu6', _('IPv6 MTU'));
781 o
.placeholder
= getDeviceValue(ifc
|| dev
, 'getMTU');
782 o
.datatype
= 'max(9200)';
783 o
.depends(Object
.assign({ ipv6
: '1' }, simpledep
));
785 o
= this.addOption(s
, gensection
, form
.Value
, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send'));
787 o
.datatype
= 'uinteger';
788 o
.depends(Object
.assign({ ipv6
: '1' }, simpledep
));
791 o
= this.addOption(s
, advsection
, form
.Flag
, 'multicast', _('Enable multicast support'));
792 o
.default = o
.enabled
;
793 o
.depends(simpledep
);
795 o
= this.addOption(s
, advsection
, form
.ListValue
, 'igmpversion', _('Force IGMP version'));
796 o
.value('', _('No enforcement'));
797 o
.value('1', _('Enforce IGMPv1'));
798 o
.value('2', _('Enforce IGMPv2'));
799 o
.value('3', _('Enforce IGMPv3'));
800 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
802 o
= this.addOption(s
, advsection
, form
.ListValue
, 'mldversion', _('Force MLD version'));
803 o
.value('', _('No enforcement'));
804 o
.value('1', _('Enforce MLD version 1'));
805 o
.value('2', _('Enforce MLD version 2'));
806 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
808 if (isBridgePort(dev
)) {
809 o
= this.addOption(s
, 'brport', form
.Flag
, 'learning', _('Enable MAC address learning'));
810 o
.default = o
.enabled
;
811 o
.depends(simpledep
);
813 o
= this.addOption(s
, 'brport', form
.Flag
, 'unicast_flood', _('Enable unicast flooding'));
814 o
.default = o
.enabled
;
815 o
.depends(simpledep
);
817 o
= this.addOption(s
, 'brport', form
.Flag
, 'isolated', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
818 o
.default = o
.disabled
;
819 o
.depends(simpledep
);
821 o
= this.addOption(s
, 'brport', form
.ListValue
, 'multicast_router', _('Multicast routing'));
822 o
.value('', _('Never'));
823 o
.value('1', _('Learn'));
824 o
.value('2', _('Always'));
825 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
827 o
= this.addOption(s
, 'brport', form
.Flag
, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
828 o
.default = o
.disabled
;
829 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
831 o
= this.addOption(s
, 'brport', form
.Flag
, 'multicast_fast_leave', _('Enable multicast fast leave'));
832 o
.default = o
.disabled
;
833 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
836 o
= this.addOption(s
, 'bridgevlan', form
.Flag
, 'vlan_filtering', _('Enable VLAN filterering'));
837 o
.depends('type', 'bridge');
838 o
.updateDefaultValue = function(section_id
) {
839 var device
= isIface
? 'br-%s'.format(s
.section
) : uci
.get('network', s
.section
, 'name'),
840 uielem
= this.getUIElement(section_id
),
843 uci
.sections('network', 'bridge-vlan', function(bvs
) {
844 has_vlans
= has_vlans
|| (bvs
.device
== device
);
847 this.default = has_vlans
? this.enabled
: this.disabled
;
849 if (uielem
&& !uielem
.isChanged())
850 uielem
.setValue(this.default);
853 o
= this.addOption(s
, 'bridgevlan', form
.SectionValue
, 'bridge-vlan', form
.TableSection
, 'bridge-vlan');
854 o
.depends('type', 'bridge');
855 o
.renderWidget = function(/* ... */) {
856 return form
.SectionValue
.prototype.renderWidget
.apply(this, arguments
).then(L
.bind(function(node
) {
857 node
.style
.overflowX
= 'auto';
858 node
.style
.overflowY
= 'visible';
859 node
.style
.paddingBottom
= '100px';
860 node
.style
.marginBottom
= '-100px';
870 ss
.renderHeaderRows = function(/* ... */) {
871 var node
= form
.TableSection
.prototype.renderHeaderRows
.apply(this, arguments
);
873 node
.querySelectorAll('.th').forEach(function(th
) {
874 th
.classList
.add('middle');
880 ss
.filter = function(section_id
) {
881 var devname
= isIface
? 'br-%s'.format(s
.section
) : uci
.get('network', s
.section
, 'name');
882 return (uci
.get('network', section_id
, 'device') == devname
);
885 ss
.render = function(/* ... */) {
886 return form
.TableSection
.prototype.render
.apply(this, arguments
).then(L
.bind(function(node
) {
888 this.node
.parentNode
.replaceChild(node
, this.node
);
896 ss
.redraw = function() {
897 return this.load().then(L
.bind(this.render
, this));
900 ss
.updatePorts = function(ports
) {
901 var devices
= ports
.map(function(port
) {
902 return network
.instantiateDevice(port
)
903 }).filter(function(dev
) {
904 return dev
.getType() != 'wifi' || dev
.isUp();
907 this.children
= this.children
.filter(function(opt
) { return !opt
.option
.match(/^port_/) });
909 for (var i
= 0; i
< devices
.length
; i
++) {
910 o
= ss
.option(cbiTagValue
, 'port_%s'.format(sfh(devices
[i
].getName())), renderDevBadge(devices
[i
]));
911 o
.port
= devices
[i
].getName();
914 var section_ids
= this.cfgsections(),
915 device_names
= devices
.reduce(function(names
, dev
) { names
[dev
.getName()] = true; return names
}, {});
917 for (var i
= 0; i
< section_ids
.length
; i
++) {
918 var old_spec
= L
.toArray(uci
.get('network', section_ids
[i
], 'ports')),
919 new_spec
= old_spec
.filter(function(spec
) { return device_names
[spec
.replace(/:[ut*]+$/, '')] });
921 if (old_spec
.length
!= new_spec
.length
)
922 uci
.set('network', section_ids
[i
], 'ports', new_spec
.length
? new_spec
: null);
926 ss
.handleAdd = function(ev
) {
927 return s
.parse().then(L
.bind(function() {
928 var device
= isIface
? 'br-%s'.format(s
.section
) : uci
.get('network', s
.section
, 'name'),
929 section_ids
= this.cfgsections(),
936 for (var i
= 0; i
< section_ids
.length
; i
++) {
937 var vid
= +uci
.get('network', section_ids
[i
], 'vlan');
939 if (vid
> max_vlan_id
)
943 section_id
= uci
.add('network', 'bridge-vlan');
944 uci
.set('network', section_id
, 'device', device
);
945 uci
.set('network', section_id
, 'vlan', max_vlan_id
+ 1);
947 s
.children
.forEach(function(opt
) {
948 switch (opt
.option
) {
951 var input
= opt
.map
.findElement('id', 'widget.%s'.format(opt
.cbid(s
.section
)));
953 input
.disabled
= true;
958 s
.getOption('vlan_filtering').updateDefaultValue(s
.section
);
960 return this.redraw();
964 o
= ss
.option(form
.Value
, 'vlan', _('VLAN ID'));
965 o
.datatype
= 'range(1, 4094)';
967 o
.renderWidget = function(/* ... */) {
968 var node
= form
.Value
.prototype.renderWidget
.apply(this, arguments
);
970 node
.style
.width
= '5em';
975 o
.validate = function(section_id
, value
) {
976 var section_ids
= this.section
.cfgsections();
978 for (var i
= 0; i
< section_ids
.length
; i
++) {
979 if (section_ids
[i
] == section_id
)
982 if (uci
.get('network', section_ids
[i
], 'vlan') == value
)
983 return _('The VLAN ID must be unique');
989 o
= ss
.option(form
.Flag
, 'local', _('Local'));
990 o
.default = o
.enabled
;
992 /* Do not touch bridge port state from interface config if legacy
993 * bridge config is disabled due to explicitely declared br-xxx
994 * device section... */
995 if (disableLegacyBridging
)
1001 Array
.prototype.push
.apply(ports
, L
.toArray(ifc
.getDevices() || ifc
.getDevice()).map(function(dev
) {
1002 return dev
.getName();
1006 var seen_ports
= {};
1008 L
.toArray(uci
.get('network', s
.section
, 'ifname')).forEach(function(ifname
) {
1009 seen_ports
[ifname
] = true;
1012 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1013 L
.toArray(bvs
.ports
).forEach(function(portspec
) {
1014 var m
= portspec
.match(/^([^:]+)(?::[ut*]+)?$/);
1017 seen_ports
[m
[1]] = true;
1021 for (var port_name
in seen_ports
)
1022 ports
.push(port_name
);
1027 ss
.updatePorts(ports
);