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) {
52 uci.sections('network', 'device', function(ss) {
53 exists = exists || (ss['.name'] != section_id && ss.name == devname /* && !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
);
163 uci
.unset('network', section_id
, this.option
);
166 function deviceRefresh(section_id
) {
167 var dev
= network
.instantiateDevice(lookupDevName(this.section
, section_id
)),
168 uielem
= this.getUIElement(section_id
);
171 switch (this.option
) {
174 uielem
.setPlaceholder(dev
.getMTU());
178 uielem
.setPlaceholder(dev
.getMAC());
182 uielem
.setValue(this.cfgvalue(section_id
));
187 var cbiTagValue
= form
.Value
.extend({
188 renderWidget: function(section_id
, option_index
, cfgvalue
) {
189 var widget
= new ui
.Dropdown(cfgvalue
|| ['-'], {
191 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '—' ]),
192 E('span', { 'class': 'hide-close' }, [ _('Do not participate', 'VLAN port state') ])
195 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'u' ]),
196 E('span', { 'class': 'hide-close' }, [ _('Egress untagged', 'VLAN port state') ])
199 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 't' ]),
200 E('span', { 'class': 'hide-close' }, [ _('Egress tagged', 'VLAN port state') ])
203 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '*' ]),
204 E('span', { 'class': 'hide-close' }, [ _('Primary VLAN ID', 'VLAN port state') ])
207 id
: this.cbid(section_id
),
208 sort
: [ '-', 'u', 't', '*' ],
215 widget
.toggleItem = function(sb
, li
, force_state
) {
216 var lis
= li
.parentNode
.querySelectorAll('li'),
217 toggle
= ui
.Dropdown
.prototype.toggleItem
;
219 toggle
.apply(this, [sb
, li
, force_state
]);
221 if (force_state
!= null)
224 switch (li
.getAttribute('data-value'))
227 if (li
.hasAttribute('selected')) {
228 for (var i
= 0; i
< lis
.length
; i
++) {
229 switch (lis
[i
].getAttribute('data-value')) {
234 toggle
.apply(this, [sb
, lis
[i
], false]);
235 lis
[i
].setAttribute('unselectable', '');
239 toggle
.apply(this, [sb
, lis
[i
], false]);
247 if (li
.hasAttribute('selected')) {
248 for (var i
= 0; i
< lis
.length
; i
++) {
249 switch (lis
[i
].getAttribute('data-value')) {
250 case li
.getAttribute('data-value'):
254 lis
[i
].removeAttribute('unselectable');
258 toggle
.apply(this, [sb
, lis
[i
], false]);
263 toggle
.apply(this, [sb
, li
, true]);
268 if (li
.hasAttribute('selected')) {
269 var section_ids
= field
.section
.cfgsections();
271 for (var i
= 0; i
< section_ids
.length
; i
++) {
272 var other_widget
= field
.getUIElement(section_ids
[i
]),
273 other_value
= L
.toArray(other_widget
.getValue());
275 if (other_widget
=== this)
278 var new_value
= other_value
.filter(function(v
) { return v
!= '*' });
280 if (new_value
.length
== other_value
.length
)
283 other_widget
.setValue(new_value
);
290 var node
= widget
.render();
292 node
.style
.minWidth
= '4em';
295 node
.querySelector('li[data-value="*"]').setAttribute('unselectable', '');
297 return E('div', { 'style': 'display:inline-block' }, node
);
300 cfgvalue: function(section_id
) {
301 var pname
= this.port
,
302 spec
= L
.toArray(uci
.get('network', section_id
, 'ports')).filter(function(p
) { return p
.replace(/:[ut*]+$/, '') == pname
})[0];
304 if (spec
&& spec
.match(/t
/))
305 return spec
.match(/\*/) ? ['t', '*'] : ['t'];
307 return spec
.match(/\*/) ? ['u', '*'] : ['u'];
312 write: function(section_id
, value
) {
315 for (var i
= 0; i
< this.section
.children
.length
; i
++) {
316 var opt
= this.section
.children
[i
];
319 var val
= L
.toArray(opt
.formvalue(section_id
)).join('');
326 ports
.push(opt
.port
);
330 ports
.push('%s:%s'.format(opt
.port
, val
));
336 uci
.set('network', section_id
, 'ports', ports
);
339 remove: function() {}
342 return baseclass
.extend({
343 replaceOption: function(s
, tabName
, optionClass
, optionName
, optionTitle
, optionDescription
) {
344 var o
= s
.getOption(optionName
);
348 s
.tabs
[o
.tab
].children
= s
.tabs
[o
.tab
].children
.filter(function(opt
) {
349 return opt
.option
!= optionName
;
353 s
.children
= s
.children
.filter(function(opt
) {
354 return opt
.option
!= optionName
;
358 return s
.taboption(tabName
, optionClass
, optionName
, optionTitle
, optionDescription
);
361 addOption: function(s
, tabName
, optionClass
, optionName
, optionTitle
, optionDescription
) {
362 var o
= this.replaceOption(s
, tabName
, optionClass
, optionName
, optionTitle
, optionDescription
);
364 if (s
.sectiontype
== 'interface' && optionName
!= 'type' && optionName
!= 'vlan_filtering') {
365 o
.cfgvalue
= deviceCfgValue
;
366 o
.write
= deviceWrite
;
367 o
.remove
= deviceRemove
;
368 o
.refresh
= deviceRefresh
;
374 addDeviceOptions: function(s
, dev
, isNew
) {
375 var isIface
= (s
.sectiontype
== 'interface'),
376 ifc
= isIface
? network
.instantiateNetwork(s
.section
) : null,
377 gensection
= ifc
? 'physical' : 'devgeneral',
378 advsection
= ifc
? 'physical' : 'devadvanced',
379 simpledep
= ifc
? { type
: '', ifname_single
: /^[^@]/ } : { type
: '' },
380 disableLegacyBridging
= isIface
&& deviceSectionExists(null, 'br-%s'.format(ifc
.getName())),
383 /* If an externally configured br-xxx interface already exists,
384 * then disable legacy bridge configuration */
385 if (disableLegacyBridging
) {
386 o
= this.addOption(s
, gensection
, form
.HiddenValue
, 'type');
387 o
.cfgvalue = function() { return '' };
392 type
= this.addOption(s
, gensection
, form
.Flag
, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
393 type
.modalonly
= true;
395 type
.enabled
= 'bridge';
396 type
.write
= type
.remove = function(section_id
, value
) {
397 var protoname
= this.section
.formvalue(section_id
, 'proto'),
398 protocol
= network
.getProtocol(protoname
),
399 new_ifnames
= this.isActive(section_id
) ? L
.toArray(this.section
.formvalue(section_id
, value
? 'ifname_multi' : 'ifname_single')) : [];
401 if (!protocol
.isVirtual() && !this.isActive(section_id
))
404 var old_ifnames
= [],
405 devs
= ifc
.getDevices() || L
.toArray(ifc
.getDevice());
407 for (var i
= 0; i
< devs
.length
; i
++)
408 old_ifnames
.push(devs
[i
].getName());
411 new_ifnames
.length
= Math
.max(new_ifnames
.length
, 1);
416 for (var i
= 0; i
< Math
.max(old_ifnames
.length
, new_ifnames
.length
); i
++) {
417 if (old_ifnames
[i
] != new_ifnames
[i
]) {
419 for (var j
= 0; j
< old_ifnames
.length
; j
++)
420 ifc
.deleteDevice(old_ifnames
[j
]);
422 for (var j
= 0; j
< new_ifnames
.length
; j
++)
423 ifc
.addDevice(new_ifnames
[j
]);
430 uci
.set('network', section_id
, 'type', 'bridge');
432 uci
.unset('network', section_id
, 'type');
436 s
.tab('devgeneral', _('General device options'));
437 s
.tab('devadvanced', _('Advanced device options'));
438 s
.tab('brport', _('Bridge port specific options'));
439 s
.tab('bridgevlan', _('Bridge VLAN filtering'));
441 o
= this.addOption(s
, gensection
, form
.ListValue
, 'type', _('Device type'));
443 o
.value('', _('Network device'));
444 o
.value('bridge', _('Bridge device'));
445 o
.value('8021q', _('VLAN (802.1q)'));
446 o
.value('8021ad', _('VLAN (802.1ad)'));
447 o
.value('macvlan', _('MAC VLAN'));
448 o
.value('veth', _('Virtual Ethernet'));
450 o
= this.addOption(s
, gensection
, widgets
.DeviceSelect
, 'name_simple', _('Existing device'));
454 o
.default = (dev
? dev
.getName() : '');
455 o
.ucioption
= 'name';
456 o
.write
= o
.remove
= setIfActive
;
457 o
.filter = function(section_id
, value
) {
458 return !deviceSectionExists(section_id
, value
);
460 o
.validate = function(section_id
, value
) {
461 return deviceSectionExists(section_id
, value
) ? _('A configuration for the device "%s" already exists').format(value
) : true;
463 o
.depends('type', '');
466 o
= this.addOption(s
, gensection
, widgets
.DeviceSelect
, 'ifname_single', isIface
? _('Interface') : _('Base device'));
469 o
.noaliases
= !isIface
;
470 o
.default = (dev
? dev
.getName() : '').match(/^.+\.\d+$/) ? dev
.getName().replace(/\.\d+$/, '') : '';
471 o
.ucioption
= 'ifname';
472 o
.validate = function(section_id
, value
) {
473 var type
= this.section
.formvalue(section_id
, 'type'),
474 name
= this.section
.getUIElement(section_id
, 'name_complex');
476 if (type
== 'macvlan' && value
&& name
&& !name
.isChanged()) {
479 while (deviceSectionExists(section_id
, '%smac%d'.format(value
, i
)))
482 name
.setValue('%smac%d'.format(value
, i
));
483 name
.triggerValidation();
489 o
.write
= o
.remove = function() {};
490 o
.cfgvalue = function(section_id
) {
491 return (ifc
.getDevices() || L
.toArray(ifc
.getDevice())).map(function(dev
) {
492 return dev
.getName();
495 o
.onchange = function(ev
, section_id
, values
) {
496 for (var i
= 0, co
; (co
= this.section
.children
[i
]) != null; i
++)
497 if (co
!== this && co
.refresh
)
498 co
.refresh(section_id
);
501 o
.depends('type', '');
504 o
.write
= o
.remove
= setIfActive
;
505 o
.depends('type', '8021q');
506 o
.depends('type', '8021ad');
507 o
.depends('type', 'macvlan');
510 o
= this.addOption(s
, gensection
, form
.Value
, 'vid', _('VLAN ID'));
512 o
.datatype
= 'range(1, 4094)';
514 o
.default = (dev
? dev
.getName() : '').match(/^.+\.\d+$/) ? dev
.getName().replace(/^.+\./, '') : '';
515 o
.validate = function(section_id
, value
) {
516 var base
= this.section
.formvalue(section_id
, 'ifname_single'),
517 vid
= this.section
.formvalue(section_id
, 'vid'),
518 name
= this.section
.getUIElement(section_id
, 'name_complex');
520 if (base
&& vid
&& name
&& !name
.isChanged()) {
521 name
.setValue('%s.%d'.format(base
, vid
));
522 name
.triggerValidation();
527 o
.depends('type', '8021q');
528 o
.depends('type', '8021ad');
530 o
= this.addOption(s
, gensection
, form
.ListValue
, 'mode', _('Mode'));
531 o
.value('vepa', _('VEPA (Virtual Ethernet Port Aggregator)', 'MACVLAN mode'));
532 o
.value('private', _('Private (Prevent communication between MAC VLANs)', 'MACVLAN mode'));
533 o
.value('bridge', _('Bridge (Support direct communication between MAC VLANs)', 'MACVLAN mode'));
534 o
.value('passthru', _('Pass-through (Mirror physical device to single MAC VLAN)', 'MACVLAN mode'));
535 o
.depends('type', 'macvlan');
538 o
= this.addOption(s
, gensection
, form
.Value
, 'name_complex', _('Device name'));
540 o
.datatype
= 'maxlength(15)';
542 o
.ucioption
= 'name';
543 o
.write
= o
.remove
= setIfActive
;
544 o
.validate = function(section_id
, value
) {
545 return deviceSectionExists(section_id
, value
) ? _('The device name "%s" is already taken').format(value
) : true;
547 o
.depends({ type
: '', '!reverse': true });
550 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'));
552 o
.validate
= validateQoSMap
;
553 o
.depends('type', '8021q');
554 o
.depends('type', '8021ad');
556 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'));
558 o
.validate
= validateQoSMap
;
559 o
.depends('type', '8021q');
560 o
.depends('type', '8021ad');
562 o
= this.addOption(s
, gensection
, widgets
.DeviceSelect
, 'ifname_multi', _('Bridge ports'));
568 o
.ucioption
= 'ifname';
570 o
.write
= o
.remove = function() {};
571 o
.cfgvalue = function(section_id
) {
572 return (ifc
.getDevices() || L
.toArray(ifc
.getDevice())).map(function(dev
) { return dev
.getName() });
576 o
.write
= o
.remove
= setIfActive
;
577 o
.default = L
.toArray(dev
? dev
.getPorts() : null).filter(function(p
) { return p
.getType() != 'wifi' }).map(function(p
) { return p
.getName() });
578 o
.filter = function(section_id
, device_name
) {
579 var bridge_name
= uci
.get('network', section_id
, 'name'),
580 choice_dev
= network
.instantiateDevice(device_name
),
581 parent_dev
= choice_dev
.getParent();
583 /* only show wifi networks which are already present in "option ifname" */
584 if (choice_dev
.getType() == 'wifi') {
585 var ifnames
= L
.toArray(uci
.get('network', section_id
, 'ifname'));
587 for (var i
= 0; i
< ifnames
.length
; i
++)
588 if (ifnames
[i
] == device_name
)
594 return (!parent_dev
|| parent_dev
.getName() != bridge_name
);
596 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.')
598 o
.onchange = function(ev
, section_id
, values
) {
599 ss
.updatePorts(values
);
601 return ss
.parse().then(function() {
605 o
.depends('type', 'bridge');
607 o
= this.replaceOption(s
, gensection
, form
.Flag
, 'bridge_empty', _('Bring up empty bridge'), _('Bring up the bridge interface even if no ports are attached'));
608 o
.default = o
.disabled
;
609 o
.depends('type', 'bridge');
611 o
= this.replaceOption(s
, advsection
, form
.Value
, 'priority', _('Priority'));
612 o
.placeholder
= '32767';
613 o
.datatype
= 'range(0, 65535)';
614 o
.depends('type', 'bridge');
616 o
= this.replaceOption(s
, advsection
, form
.Value
, 'ageing_time', _('Ageing time'), _('Timeout in seconds for learned MAC addresses in the forwarding database'));
617 o
.placeholder
= '30';
618 o
.datatype
= 'uinteger';
619 o
.depends('type', 'bridge');
621 o
= this.replaceOption(s
, advsection
, form
.Flag
, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge'));
622 o
.default = o
.disabled
;
623 o
.depends('type', 'bridge');
625 o
= this.replaceOption(s
, advsection
, form
.Value
, 'hello_time', _('Hello interval'), _('Interval in seconds for STP hello packets'));
627 o
.datatype
= 'range(1, 10)';
628 o
.depends({ type
: 'bridge', stp
: '1' });
630 o
= this.replaceOption(s
, advsection
, form
.Value
, 'forward_delay', _('Forward delay'), _('Time in seconds to spend in listening and learning states'));
631 o
.placeholder
= '15';
632 o
.datatype
= 'range(2, 30)';
633 o
.depends({ type
: 'bridge', stp
: '1' });
635 o
= this.replaceOption(s
, advsection
, form
.Value
, 'max_age', _('Maximum age'), _('Timeout in seconds until topology updates on link loss'));
636 o
.placeholder
= '20';
637 o
.datatype
= 'range(6, 40)';
638 o
.depends({ type
: 'bridge', stp
: '1' });
641 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'));
642 o
.default = o
.disabled
;
643 o
.depends('type', 'bridge');
645 o
= this.replaceOption(s
, advsection
, form
.Value
, 'hash_max', _('Maximum snooping table size'));
646 o
.placeholder
= '512';
647 o
.datatype
= 'uinteger';
648 o
.depends({ type
: 'bridge', igmp_snooping
: '1' });
650 o
= this.replaceOption(s
, advsection
, form
.Flag
, 'multicast_querier', _('Enable multicast querier'));
651 o
.defaults
= { '1': [{'igmp_snooping': '1'}], '0': [{'igmp_snooping': '0'}] };
652 o
.depends('type', 'bridge');
654 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'));
656 o
.datatype
= 'min(1)';
657 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
659 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'));
660 o
.placeholder
= '12500';
661 o
.datatype
= 'uinteger';
662 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
664 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'));
665 o
.placeholder
= '1000';
666 o
.datatype
= 'uinteger';
667 o
.validate = function(section_id
, value
) {
668 var qiopt
= L
.toArray(this.map
.lookupOption('query_interval', section_id
))[0],
669 qival
= qiopt
? (qiopt
.formvalue(section_id
) || qiopt
.placeholder
) : '';
671 if (value
!= '' && qival
!= '' && +value
>= +qival
)
672 return _('The query response interval must be lower than the query interval value');
676 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
678 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'));
679 o
.placeholder
= '100';
680 o
.datatype
= 'uinteger';
681 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
683 o
= this.addOption(s
, gensection
, form
.Value
, 'mtu', _('MTU'));
684 o
.placeholder
= getDeviceValue(ifc
|| dev
, 'getMTU');
685 o
.datatype
= 'max(9200)';
686 o
.depends(simpledep
);
688 o
= this.addOption(s
, gensection
, form
.Value
, 'macaddr', _('MAC address'));
689 o
.placeholder
= getDeviceValue(ifc
|| dev
, 'getMAC');
690 o
.datatype
= 'macaddr';
691 o
.depends(simpledep
);
692 o
.depends('type', 'macvlan');
693 o
.depends('type', 'veth');
695 o
= this.addOption(s
, gensection
, form
.Value
, 'peer_name', _('Peer device name'));
697 o
.datatype
= 'maxlength(15)';
698 o
.depends('type', 'veth');
699 o
.load = function(section_id
) {
700 var sections
= uci
.sections('network', 'device'),
703 for (var i
= 0; i
< sections
.length
; i
++)
704 if (sections
[i
]['.name'] == section_id
)
706 else if (sections
[i
].type
== 'veth')
709 this.placeholder
= 'veth%d'.format(idx
);
711 return form
.Value
.prototype.load
.apply(this, arguments
);
714 o
= this.addOption(s
, gensection
, form
.Value
, 'peer_macaddr', _('Peer MAC address'));
716 o
.datatype
= 'macaddr';
717 o
.depends('type', 'veth');
719 o
= this.addOption(s
, gensection
, form
.Value
, 'txqueuelen', _('TX queue length'));
720 o
.placeholder
= dev
? dev
._devstate('qlen') : '';
721 o
.datatype
= 'uinteger';
722 o
.depends(simpledep
);
724 o
= this.addOption(s
, advsection
, form
.Flag
, 'promisc', _('Enable promiscious mode'));
725 o
.default = o
.disabled
;
726 o
.depends(simpledep
);
728 o
= this.addOption(s
, advsection
, form
.ListValue
, 'rpfilter', _('Reverse path filter'));
730 o
.value('', _('disabled'));
731 o
.value('loose', _('Loose filtering'));
732 o
.value('strict', _('Strict filtering'));
733 o
.cfgvalue = function(section_id
) {
734 var val
= form
.ListValue
.prototype.cfgvalue
.apply(this, [section_id
]);
749 o
.depends(simpledep
);
751 o
= this.addOption(s
, advsection
, form
.Flag
, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
752 o
.default = o
.disabled
;
753 o
.depends(simpledep
);
755 o
= this.addOption(s
, advsection
, form
.Flag
, 'sendredirects', _('Send ICMP redirects'));
756 o
.default = o
.enabled
;
757 o
.depends(simpledep
);
759 o
= this.addOption(s
, advsection
, form
.Value
, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds'));
760 o
.placeholder
= '30000';
761 o
.datatype
= 'uinteger';
762 o
.depends(simpledep
);
764 o
= this.addOption(s
, advsection
, form
.Value
, 'neighgcstaletime', _('Stale neighbour cache timeout'), _('Timeout in seconds'));
765 o
.placeholder
= '60';
766 o
.datatype
= 'uinteger';
767 o
.depends(simpledep
);
769 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.'));
771 o
.datatype
= 'uinteger';
772 o
.depends(simpledep
);
774 o
= this.addOption(s
, gensection
, form
.Flag
, 'ipv6', _('Enable IPv6'));
775 o
.default = o
.enabled
;
776 o
.depends(simpledep
);
778 o
= this.addOption(s
, gensection
, form
.Value
, 'mtu6', _('IPv6 MTU'));
779 o
.placeholder
= getDeviceValue(ifc
|| dev
, 'getMTU');
780 o
.datatype
= 'max(9200)';
781 o
.depends(Object
.assign({ ipv6
: '1' }, simpledep
));
783 o
= this.addOption(s
, gensection
, form
.Value
, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send'));
785 o
.datatype
= 'uinteger';
786 o
.depends(Object
.assign({ ipv6
: '1' }, simpledep
));
789 o
= this.addOption(s
, advsection
, form
.Flag
, 'multicast', _('Enable multicast support'));
790 o
.default = o
.enabled
;
791 o
.depends(simpledep
);
793 o
= this.addOption(s
, advsection
, form
.ListValue
, 'igmpversion', _('Force IGMP version'));
794 o
.value('', _('No enforcement'));
795 o
.value('1', _('Enforce IGMPv1'));
796 o
.value('2', _('Enforce IGMPv2'));
797 o
.value('3', _('Enforce IGMPv3'));
798 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
800 o
= this.addOption(s
, advsection
, form
.ListValue
, 'mldversion', _('Force MLD version'));
801 o
.value('', _('No enforcement'));
802 o
.value('1', _('Enforce MLD version 1'));
803 o
.value('2', _('Enforce MLD version 2'));
804 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
806 if (isBridgePort(dev
)) {
807 o
= this.addOption(s
, 'brport', form
.Flag
, 'learning', _('Enable MAC address learning'));
808 o
.default = o
.enabled
;
809 o
.depends(simpledep
);
811 o
= this.addOption(s
, 'brport', form
.Flag
, 'unicast_flood', _('Enable unicast flooding'));
812 o
.default = o
.enabled
;
813 o
.depends(simpledep
);
815 o
= this.addOption(s
, 'brport', form
.Flag
, 'isolated', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
816 o
.default = o
.disabled
;
817 o
.depends(simpledep
);
819 o
= this.addOption(s
, 'brport', form
.ListValue
, 'multicast_router', _('Multicast routing'));
820 o
.value('', _('Never'));
821 o
.value('1', _('Learn'));
822 o
.value('2', _('Always'));
823 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
825 o
= this.addOption(s
, 'brport', form
.Flag
, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
826 o
.default = o
.disabled
;
827 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
829 o
= this.addOption(s
, 'brport', form
.Flag
, 'multicast_fast_leave', _('Enable multicast fast leave'));
830 o
.default = o
.disabled
;
831 o
.depends(Object
.assign({ multicast
: '1' }, simpledep
));
834 o
= this.addOption(s
, 'bridgevlan', form
.Flag
, 'vlan_filtering', _('Enable VLAN filterering'));
835 o
.depends('type', 'bridge');
836 o
.updateDefaultValue = function(section_id
) {
837 var device
= isIface
? 'br-%s'.format(s
.section
) : uci
.get('network', s
.section
, 'name'),
838 uielem
= this.getUIElement(section_id
),
841 uci
.sections('network', 'bridge-vlan', function(bvs
) {
842 has_vlans
= has_vlans
|| (bvs
.device
== device
);
845 this.default = has_vlans
? this.enabled
: this.disabled
;
847 if (uielem
&& !uielem
.isChanged())
848 uielem
.setValue(this.default);
851 o
= this.addOption(s
, 'bridgevlan', form
.SectionValue
, 'bridge-vlan', form
.TableSection
, 'bridge-vlan');
852 o
.depends('type', 'bridge');
853 o
.renderWidget = function(/* ... */) {
854 return form
.SectionValue
.prototype.renderWidget
.apply(this, arguments
).then(L
.bind(function(node
) {
855 node
.style
.overflowX
= 'auto';
856 node
.style
.overflowY
= 'visible';
857 node
.style
.paddingBottom
= '100px';
858 node
.style
.marginBottom
= '-100px';
868 ss
.renderHeaderRows = function(/* ... */) {
869 var node
= form
.TableSection
.prototype.renderHeaderRows
.apply(this, arguments
);
871 node
.querySelectorAll('.th').forEach(function(th
) {
872 th
.classList
.add('middle');
878 ss
.filter = function(section_id
) {
879 var devname
= isIface
? 'br-%s'.format(s
.section
) : uci
.get('network', s
.section
, 'name');
880 return (uci
.get('network', section_id
, 'device') == devname
);
883 ss
.render = function(/* ... */) {
884 return form
.TableSection
.prototype.render
.apply(this, arguments
).then(L
.bind(function(node
) {
886 this.node
.parentNode
.replaceChild(node
, this.node
);
894 ss
.redraw = function() {
895 return this.load().then(L
.bind(this.render
, this));
898 ss
.updatePorts = function(ports
) {
899 var devices
= ports
.map(function(port
) {
900 return network
.instantiateDevice(port
)
901 }).filter(function(dev
) {
902 return dev
.getType() != 'wifi' || dev
.isUp();
905 this.children
= this.children
.filter(function(opt
) { return !opt
.option
.match(/^port_/) });
907 for (var i
= 0; i
< devices
.length
; i
++) {
908 o
= ss
.option(cbiTagValue
, 'port_%s'.format(sfh(devices
[i
].getName())), renderDevBadge(devices
[i
]));
909 o
.port
= devices
[i
].getName();
912 var section_ids
= this.cfgsections(),
913 device_names
= devices
.reduce(function(names
, dev
) { names
[dev
.getName()] = true; return names
}, {});
915 for (var i
= 0; i
< section_ids
.length
; i
++) {
916 var old_spec
= L
.toArray(uci
.get('network', section_ids
[i
], 'ports')),
917 new_spec
= old_spec
.filter(function(spec
) { return device_names
[spec
.replace(/:[ut*]+$/, '')] });
919 if (old_spec
.length
!= new_spec
.length
)
920 uci
.set('network', section_ids
[i
], 'ports', new_spec
.length
? new_spec
: null);
924 ss
.handleAdd = function(ev
) {
925 return s
.parse().then(L
.bind(function() {
926 var device
= isIface
? 'br-%s'.format(s
.section
) : uci
.get('network', s
.section
, 'name'),
927 section_ids
= this.cfgsections(),
934 for (var i
= 0; i
< section_ids
.length
; i
++) {
935 var vid
= +uci
.get('network', section_ids
[i
], 'vlan');
937 if (vid
> max_vlan_id
)
941 section_id
= uci
.add('network', 'bridge-vlan');
942 uci
.set('network', section_id
, 'device', device
);
943 uci
.set('network', section_id
, 'vlan', max_vlan_id
+ 1);
945 s
.children
.forEach(function(opt
) {
946 switch (opt
.option
) {
949 var input
= opt
.map
.findElement('id', 'widget.%s'.format(opt
.cbid(s
.section
)));
951 input
.disabled
= true;
956 s
.getOption('vlan_filtering').updateDefaultValue(s
.section
);
958 return this.redraw();
962 o
= ss
.option(form
.Value
, 'vlan', _('VLAN ID'));
963 o
.datatype
= 'range(1, 4094)';
965 o
.renderWidget = function(/* ... */) {
966 var node
= form
.Value
.prototype.renderWidget
.apply(this, arguments
);
968 node
.style
.width
= '5em';
973 o
.validate = function(section_id
, value
) {
974 var section_ids
= this.section
.cfgsections();
976 for (var i
= 0; i
< section_ids
.length
; i
++) {
977 if (section_ids
[i
] == section_id
)
980 if (uci
.get('network', section_ids
[i
], 'vlan') == value
)
981 return _('The VLAN ID must be unique');
987 o
= ss
.option(form
.Flag
, 'local', _('Local'));
988 o
.default = o
.enabled
;
990 /* Do not touch bridge port state from interface config if legacy
991 * bridge config is disabled due to explicitely declared br-xxx
992 * device section... */
993 if (disableLegacyBridging
)
999 Array
.prototype.push
.apply(ports
, L
.toArray(ifc
.getDevices() || ifc
.getDevice()).map(function(dev
) {
1000 return dev
.getName();
1004 var seen_ports
= {};
1006 L
.toArray(uci
.get('network', s
.section
, 'ifname')).forEach(function(ifname
) {
1007 seen_ports
[ifname
] = true;
1010 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1011 L
.toArray(bvs
.ports
).forEach(function(portspec
) {
1012 var m
= portspec
.match(/^([^:]+)(?::[ut*]+)?$/);
1015 seen_ports
[m
[1]] = true;
1019 for (var port_name
in seen_ports
)
1020 ports
.push(port_name
);
1025 ss
.updatePorts(ports
);