9 'require tools.widgets as widgets';
11 function validateAddr(section_id
, value
) {
15 var ipv6
= /6$/.test(this.section
.formvalue(section_id
, 'mode')),
16 addr
= ipv6
? validation
.parseIPv6(value
) : validation
.parseIPv4(value
);
18 return addr
? true : (ipv6
? _('Expecting a valid IPv6 address') : _('Expecting a valid IPv4 address'));
21 function setIfActive(section_id
, value
) {
22 if (this.isActive(section_id
)) {
23 uci
.set('network', section_id
, this.ucioption
, value
);
25 /* Requires http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html */
26 if (false && this.option == 'ifname_multi') {
27 var devname = this.section.formvalue(section_id, 'name_complex'),
28 m = devname ? devname.match(/^br-([A-Za-z0-9_]+)$/) : null;
30 if (m && uci.get('network', m[1], 'type') == 'bridge') {
31 uci.set('network', m[1], 'ifname', devname);
32 uci.unset('network', m[1], 'type');
38 function validateQoSMap(section_id, value) {
42 var m = value.match(/^(\d+):(\d+)$/);
44 if (!m || +m[1] > 0xFFFFFFFF || +m[2] > 0xFFFFFFFF)
45 return _('Expecting two priority values separated by a colon');
50 function deviceSectionExists(section_id, devname, ignore_type_match) {
53 uci.sections('network', 'device', function(ss) {
55 ss['.name'] != section_id &&
57 (!ignore_type_match || !ignore_type_match.test(ss.type || ''))
64 function isBridgePort(dev) {
68 if (dev.isBridgePort())
73 uci.sections('network', null, function(s) {
74 if (s['.type'] != 'interface' && s['.type'] != 'device')
77 if (s.type == 'bridge' && L.toArray(s.ifname).indexOf(dev.getName()) > -1)
84 function updateDevBadge(node, dev) {
85 var type = dev.getType(),
86 up = dev.getCarrier();
91 'src': L.resource('icons/%s%s.png').format(type, up ? '' : '_disabled')
99 function renderDevBadge(dev) {
100 return updateDevBadge(E('span', {
101 'class': 'ifacebadge port-status-device',
102 'style': 'font-weight:normal',
103 'data-device': dev.getName()
107 function updatePortStatus(node, dev) {
108 var carrier = dev.getCarrier(),
109 duplex = dev.getDuplex(),
110 speed = dev.getSpeed(),
113 if (carrier && speed > 0 && duplex != null)
115 'title': '%d MBit/s, %s'.format(speed, duplex == 'full' ? _('full-duplex') : _('half-duplex'))
116 }, [ '%d%s'.format(speed, duplex == 'full' ? 'FD' : 'HD') ]);
118 desc = document.createTextNode(_('Connected'));
120 desc = document.createTextNode(_('no link'));
122 dom.content(node, [ desc ]);
127 function renderPortStatus(dev) {
128 return updatePortStatus(E('span', {
129 'class': 'port-status-link',
130 'data-device': dev.getName()
134 function lookupDevName(s, section_id) {
135 var typeui = s.getUIElement(section_id, 'type'),
136 typeval = typeui ? typeui.getValue() : s.cfgvalue(section_id, 'type'),
137 ifnameui = s.getUIElement(section_id, 'ifname_single'),
138 ifnameval = ifnameui ? ifnameui.getValue() : s.cfgvalue(section_id, 'ifname_single');
140 return (typeval == 'bridge') ? 'br-%s'.format(section_id) : ifnameval;
143 function lookupDevSection(s, section_id, autocreate) {
144 var devname = lookupDevName(s, section_id),
147 uci.sections('network', 'device', function(ds) {
148 if (ds.name == devname)
149 devsection = ds['.name'];
152 if (autocreate && !devsection) {
153 devsection = uci.add('network', 'device');
154 uci.set('network', devsection, 'name', devname);
160 function getDeviceValue(dev, method) {
161 if (dev && dev.getL3Device)
162 dev = dev.getL3Device();
164 if (dev && typeof(dev[method]) == 'function')
165 return dev[method].apply(dev);
170 function deviceCfgValue(section_id) {
171 if (arguments.length == 2)
174 var ds = lookupDevSection(this.section, section_id, false);
176 return (ds ? uci.get('network', ds, this.option) : null) ||
177 (this.migrate ? uci.get('network', section_id, this.option) : null) ||
181 function deviceWrite(section_id, formvalue) {
182 var ds = lookupDevSection(this.section, section_id, true);
184 uci.set('network', ds, this.option, formvalue);
187 uci.unset('network', section_id, this.option);
190 function deviceRemove(section_id) {
191 var ds = lookupDevSection(this.section, section_id, false);
193 uci.unset('network', ds, this.option);
196 uci.unset('network', section_id, this.option);
199 function deviceRefresh(section_id) {
200 var dev = network.instantiateDevice(lookupDevName(this.section, section_id)),
201 uielem = this.getUIElement(section_id);
204 switch (this.option) {
207 uielem.setPlaceholder(dev.getMTU());
211 uielem.setPlaceholder(dev.getMAC());
215 uielem.setValue(this.cfgvalue(section_id));
219 function sectionParse() {
220 var ds = lookupDevSection(this, this.section, false);
222 return form.NamedSection.prototype.parse.apply(this).then(function() {
223 var sv = ds ? uci.get('network', ds) : null;
228 for (var opt in sv) {
229 if (opt.charAt(0) == '.' || opt == 'name')
236 uci.remove('network', ds);
242 var cbiTagValue = form.Value.extend({
243 renderWidget: function(section_id, option_index, cfgvalue) {
244 var widget = new ui.Dropdown(cfgvalue || ['-'], {
246 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '—' ]),
247 E('span', { 'class': 'hide-close' }, [ _('Do not participate', 'VLAN port state') ])
250 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'u' ]),
251 E('span', { 'class': 'hide-close' }, [ _('Egress untagged', 'VLAN port state') ])
254 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 't' ]),
255 E('span', { 'class': 'hide-close' }, [ _('Egress tagged', 'VLAN port state') ])
258 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '*' ]),
259 E('span', { 'class': 'hide-close' }, [ _('Primary VLAN ID', 'VLAN port state') ])
262 id: this.cbid(section_id),
263 sort: [ '-', 'u', 't', '*' ],
270 widget.toggleItem = function(sb, li, force_state) {
271 var lis = li.parentNode.querySelectorAll('li'),
272 toggle = ui.Dropdown.prototype.toggleItem;
274 toggle.apply(this, [sb, li, force_state]);
276 if (force_state != null)
279 switch (li.getAttribute('data-value'))
282 if (li.hasAttribute('selected')) {
283 for (var i = 0; i < lis.length; i++) {
284 switch (lis[i].getAttribute('data-value')) {
289 toggle.apply(this, [sb, lis[i], false]);
290 lis[i].setAttribute('unselectable', '');
294 toggle.apply(this, [sb, lis[i], false]);
302 if (li.hasAttribute('selected')) {
303 for (var i = 0; i < lis.length; i++) {
304 switch (lis[i].getAttribute('data-value')) {
305 case li.getAttribute('data-value'):
309 lis[i].removeAttribute('unselectable');
313 toggle.apply(this, [sb, lis[i], false]);
318 toggle.apply(this, [sb, li, true]);
323 if (li.hasAttribute('selected')) {
324 var section_ids = field.section.cfgsections();
326 for (var i = 0; i < section_ids.length; i++) {
327 var other_widget = field.getUIElement(section_ids[i]),
328 other_value = L.toArray(other_widget.getValue());
330 if (other_widget === this)
333 var new_value = other_value.filter(function(v) { return v != '*' });
335 if (new_value.length == other_value.length)
338 other_widget.setValue(new_value);
345 var node = widget.render();
347 node.style.minWidth = '4em';
350 node.querySelector('li[data-value="*"]').setAttribute('unselectable', '');
352 return E('div', { 'style': 'display:inline-block' }, node);
355 cfgvalue: function(section_id) {
356 var pname = this.port,
357 spec = L.toArray(uci.get('network', section_id, 'ports')).filter(function(p) { return p.replace(/:[ut*]+$/, '') == pname })[0];
359 if (spec && spec.match(/t/))
360 return spec.match(/\*/) ? ['t', '*'] : ['t'];
362 return spec.match(/\*/) ? ['u', '*'] : ['u'];
367 write: function(section_id, value) {
370 for (var i = 0; i < this.section.children.length; i++) {
371 var opt = this.section.children[i];
374 var val = L.toArray(opt.formvalue(section_id)).join('');
381 ports.push(opt.port);
385 ports.push('%s:%s'.format(opt.port, val));
391 uci.set('network', section_id, 'ports', ports);
394 remove: function() {}
397 return baseclass.extend({
398 replaceOption: function(s, tabName, optionClass, optionName, optionTitle, optionDescription) {
399 var o = s.getOption(optionName);
403 s.tabs[o.tab].children = s.tabs[o.tab].children.filter(function(opt) {
404 return opt.option != optionName;
408 s.children = s.children.filter(function(opt) {
409 return opt.option != optionName;
413 return s.taboption(tabName, optionClass, optionName, optionTitle, optionDescription);
416 addOption: function(s, tabName, optionClass, optionName, optionTitle, optionDescription) {
417 var o = this.replaceOption(s, tabName, optionClass, optionName, optionTitle, optionDescription);
419 if (s.sectiontype == 'interface' && optionName != 'type' && optionName != 'vlan_filtering') {
421 o.cfgvalue = deviceCfgValue;
422 o.write = deviceWrite;
423 o.remove = deviceRemove;
424 o.refresh = deviceRefresh;
430 addDeviceOptions: function(s, dev, isNew) {
433 s.tab('devgeneral', _('General device options'));
434 s.tab('devadvanced', _('Advanced device options'));
435 s.tab('brport', _('Bridge port specific options'));
436 s.tab('bridgevlan', _('Bridge VLAN filtering'));
438 o = this.addOption(s, 'devgeneral', form.ListValue, 'type', _('Device type'));
440 o.value('', _('Network device'));
441 o.value('bridge', _('Bridge device'));
442 o.value('8021q', _('VLAN (802.1q)'));
443 o.value('8021ad', _('VLAN (802.1ad)'));
444 o.value('macvlan', _('MAC VLAN'));
445 o.value('veth', _('Virtual Ethernet'));
447 o = this.addOption(s, 'devgeneral', widgets.DeviceSelect, 'name_simple', _('Existing device'));
451 o.default = (dev ? dev.getName() : '');
452 o.ucioption = 'name';
453 o.write = o.remove = setIfActive;
454 o.filter = function(section_id, value) {
455 return !deviceSectionExists(section_id, value, /^(?:bridge|8021q|8021ad|macvlan|veth)$/);
457 o.validate = function(section_id, value) {
458 return deviceSectionExists(section_id, value, /^(?:bridge|8021q|8021ad|macvlan|veth)$/)
459 ? _('A configuration for the device "%s" already exists').format(value) : true;
461 o.depends('type', '');
463 o = this.addOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_single', _('Base device'));
467 o.default = (dev ? dev.getName() : '').match(/^.+\.\d+$/) ? dev.getName().replace(/\.\d+$/, '') : '';
468 o.ucioption = 'ifname';
469 o.validate = function(section_id, value) {
471 var type = this.section.formvalue(section_id, 'type'),
472 name = this.section.getUIElement(section_id, 'name_complex');
474 if (type == 'macvlan' && value && name && !name.isChanged()) {
477 while (deviceSectionExists(section_id, '%smac%d'.format(value, i)))
480 name.setValue('%smac%d'.format(value, i));
481 name.triggerValidation();
487 o.write = o.remove = setIfActive;
488 o.depends('type', '8021q');
489 o.depends('type', '8021ad');
490 o.depends('type', 'macvlan');
492 o = this.addOption(s, 'devgeneral', form.Value, 'vid', _('VLAN ID'));
494 o.datatype = 'range(1, 4094)';
496 o.default = (dev ? dev.getName() : '').match(/^.+\.\d+$/) ? dev.getName().replace(/^.+\./, '') : '';
497 o.validate = function(section_id, value) {
498 var base = this.section.formvalue(section_id, 'ifname_single'),
499 vid = this.section.formvalue(section_id, 'vid'),
500 name = this.section.getUIElement(section_id, 'name_complex');
502 if (base && vid && name && !name.isChanged()) {
503 name.setValue('%s.%d'.format(base, vid));
504 name.triggerValidation();
509 o.depends('type', '8021q');
510 o.depends('type', '8021ad');
512 o = this.addOption(s, 'devgeneral', form.ListValue, 'mode', _('Mode'));
513 o.value('vepa', _('VEPA (Virtual Ethernet Port Aggregator)', 'MACVLAN mode'));
514 o.value('private', _('Private (Prevent communication between MAC VLANs)', 'MACVLAN mode'));
515 o.value('bridge', _('Bridge (Support direct communication between MAC VLANs)', 'MACVLAN mode'));
516 o.value('passthru', _('Pass-through (Mirror physical device to single MAC VLAN)', 'MACVLAN mode'));
517 o.depends('type', 'macvlan');
519 o = this.addOption(s, 'devgeneral', form.Value, 'name_complex', _('Device name'));
521 o.datatype = 'maxlength(15)';
523 o.ucioption = 'name';
524 o.write = o.remove = setIfActive;
525 o.validate = function(section_id, value) {
526 return deviceSectionExists(section_id, value, /^$/) ? _('The device name "%s" is already taken').format(value) : true;
528 o.depends({ type: '', '!reverse': true });
530 o = this.addOption(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'));
532 o.validate = validateQoSMap;
533 o.depends('type', '8021q');
534 o.depends('type', '8021ad');
536 o = this.addOption(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'));
538 o.validate = validateQoSMap;
539 o.depends('type', '8021q');
540 o.depends('type', '8021ad');
542 o = this.addOption(s, 'devgeneral', widgets.DeviceSelect, 'ifname_multi', _('Bridge ports'));
548 o.ucioption = 'ports';
549 o.write = o.remove = setIfActive;
550 o.default = L.toArray(dev ? dev.getPorts() : null).filter(function(p) { return p.getType() != 'wifi' }).map(function(p) { return p.getName() });
551 o.filter = function(section_id, device_name) {
552 var bridge_name = uci.get('network', section_id, 'name'),
553 choice_dev = network.instantiateDevice(device_name),
554 parent_dev = choice_dev.getParent();
556 /* only show wifi networks which are already present in "option ifname" */
557 if (choice_dev
.getType() == 'wifi') {
558 var ifnames
= L
.toArray(uci
.get('network', section_id
, 'ports'));
560 for (var i
= 0; i
< ifnames
.length
; i
++)
561 if (ifnames
[i
] == device_name
)
567 return (!parent_dev
|| parent_dev
.getName() != bridge_name
);
569 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.')
570 o
.onchange = function(ev
, section_id
, values
) {
571 ss
.updatePorts(values
);
573 return ss
.parse().then(function() {
577 o
.depends('type', 'bridge');
579 o
= this.replaceOption(s
, 'devgeneral', form
.Flag
, 'bridge_empty', _('Bring up empty bridge'), _('Bring up the bridge interface even if no ports are attached'));
580 o
.default = o
.disabled
;
581 o
.depends('type', 'bridge');
583 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'priority', _('Priority'));
584 o
.placeholder
= '32767';
585 o
.datatype
= 'range(0, 65535)';
586 o
.depends('type', 'bridge');
588 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'ageing_time', _('Ageing time'), _('Timeout in seconds for learned MAC addresses in the forwarding database'));
589 o
.placeholder
= '30';
590 o
.datatype
= 'uinteger';
591 o
.depends('type', 'bridge');
593 o
= this.replaceOption(s
, 'devadvanced', form
.Flag
, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge'));
594 o
.default = o
.disabled
;
595 o
.depends('type', 'bridge');
597 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'hello_time', _('Hello interval'), _('Interval in seconds for STP hello packets'));
599 o
.datatype
= 'range(1, 10)';
600 o
.depends({ type
: 'bridge', stp
: '1' });
602 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'forward_delay', _('Forward delay'), _('Time in seconds to spend in listening and learning states'));
603 o
.placeholder
= '15';
604 o
.datatype
= 'range(2, 30)';
605 o
.depends({ type
: 'bridge', stp
: '1' });
607 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'max_age', _('Maximum age'), _('Timeout in seconds until topology updates on link loss'));
608 o
.placeholder
= '20';
609 o
.datatype
= 'range(6, 40)';
610 o
.depends({ type
: 'bridge', stp
: '1' });
613 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'));
614 o
.default = o
.disabled
;
615 o
.depends('type', 'bridge');
617 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'hash_max', _('Maximum snooping table size'));
618 o
.placeholder
= '512';
619 o
.datatype
= 'uinteger';
620 o
.depends({ type
: 'bridge', igmp_snooping
: '1' });
622 o
= this.replaceOption(s
, 'devadvanced', form
.Flag
, 'multicast_querier', _('Enable multicast querier'));
623 o
.defaults
= { '1': [{'igmp_snooping': '1'}], '0': [{'igmp_snooping': '0'}] };
624 o
.depends('type', 'bridge');
626 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'));
628 o
.datatype
= 'min(1)';
629 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
631 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'));
632 o
.placeholder
= '12500';
633 o
.datatype
= 'uinteger';
634 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
636 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'));
637 o
.placeholder
= '1000';
638 o
.datatype
= 'uinteger';
639 o
.validate = function(section_id
, value
) {
640 var qiopt
= L
.toArray(this.map
.lookupOption('query_interval', section_id
))[0],
641 qival
= qiopt
? (qiopt
.formvalue(section_id
) || qiopt
.placeholder
) : '';
643 if (value
!= '' && qival
!= '' && +value
>= +qival
)
644 return _('The query response interval must be lower than the query interval value');
648 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
650 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'));
651 o
.placeholder
= '100';
652 o
.datatype
= 'uinteger';
653 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
655 o
= this.addOption(s
, 'devgeneral', form
.Value
, 'mtu', _('MTU'));
656 o
.placeholder
= getDeviceValue(dev
, 'getMTU');
657 o
.datatype
= 'max(9200)';
658 o
.depends('type', '');
659 o
.depends('type', 'bridge');
661 o
= this.addOption(s
, 'devgeneral', form
.Value
, 'macaddr', _('MAC address'));
662 o
.placeholder
= getDeviceValue(dev
, 'getMAC');
663 o
.datatype
= 'macaddr';
664 o
.depends('type', '');
665 o
.depends('type', 'bridge');
666 o
.depends('type', 'macvlan');
667 o
.depends('type', 'veth');
669 o
= this.addOption(s
, 'devgeneral', form
.Value
, 'peer_name', _('Peer device name'));
671 o
.datatype
= 'maxlength(15)';
672 o
.depends('type', 'veth');
673 o
.load = function(section_id
) {
674 var sections
= uci
.sections('network', 'device'),
677 for (var i
= 0; i
< sections
.length
; i
++)
678 if (sections
[i
]['.name'] == section_id
)
680 else if (sections
[i
].type
== 'veth')
683 this.placeholder
= 'veth%d'.format(idx
);
685 return form
.Value
.prototype.load
.apply(this, arguments
);
688 o
= this.addOption(s
, 'devgeneral', form
.Value
, 'peer_macaddr', _('Peer MAC address'));
690 o
.datatype
= 'macaddr';
691 o
.depends('type', 'veth');
693 o
= this.addOption(s
, 'devgeneral', form
.Value
, 'txqueuelen', _('TX queue length'));
694 o
.placeholder
= dev
? dev
._devstate('qlen') : '';
695 o
.datatype
= 'uinteger';
696 o
.depends('type', '');
698 o
= this.addOption(s
, 'devadvanced', form
.Flag
, 'promisc', _('Enable promiscuous mode'));
699 o
.default = o
.disabled
;
700 o
.depends('type', '');
702 o
= this.addOption(s
, 'devadvanced', form
.ListValue
, 'rpfilter', _('Reverse path filter'));
704 o
.value('', _('disabled'));
705 o
.value('loose', _('Loose filtering'));
706 o
.value('strict', _('Strict filtering'));
707 o
.cfgvalue = function(section_id
) {
708 var val
= form
.ListValue
.prototype.cfgvalue
.apply(this, [section_id
]);
723 o
.depends('type', '');
725 o
= this.addOption(s
, 'devadvanced', form
.Flag
, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
726 o
.default = o
.disabled
;
727 o
.depends('type', '');
729 o
= this.addOption(s
, 'devadvanced', form
.Flag
, 'sendredirects', _('Send ICMP redirects'));
730 o
.default = o
.enabled
;
731 o
.depends('type', '');
733 o
= this.addOption(s
, 'devadvanced', form
.Value
, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds'));
734 o
.placeholder
= '30000';
735 o
.datatype
= 'uinteger';
736 o
.depends('type', '');
738 o
= this.addOption(s
, 'devadvanced', form
.Value
, 'neighgcstaletime', _('Stale neighbour cache timeout'), _('Timeout in seconds'));
739 o
.placeholder
= '60';
740 o
.datatype
= 'uinteger';
741 o
.depends('type', '');
743 o
= this.addOption(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.'));
745 o
.datatype
= 'uinteger';
746 o
.depends('type', '');
748 o
= this.addOption(s
, 'devgeneral', form
.Flag
, 'ipv6', _('Enable IPv6'));
750 o
.default = o
.enabled
;
751 o
.depends('type', '');
753 o
= this.addOption(s
, 'devgeneral', form
.Value
, 'mtu6', _('IPv6 MTU'));
754 o
.placeholder
= getDeviceValue(dev
, 'getMTU');
755 o
.datatype
= 'max(9200)';
756 o
.depends(Object
.assign({ ipv6
: '1' }, 'type', ''));
758 o
= this.addOption(s
, 'devgeneral', form
.Value
, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send'));
760 o
.datatype
= 'uinteger';
761 o
.depends(Object
.assign({ ipv6
: '1' }, 'type', ''));
764 o
= this.addOption(s
, 'devadvanced', form
.Flag
, 'multicast', _('Enable multicast support'));
765 o
.default = o
.enabled
;
766 o
.depends('type', '');
768 o
= this.addOption(s
, 'devadvanced', form
.ListValue
, 'igmpversion', _('Force IGMP version'));
769 o
.value('', _('No enforcement'));
770 o
.value('1', _('Enforce IGMPv1'));
771 o
.value('2', _('Enforce IGMPv2'));
772 o
.value('3', _('Enforce IGMPv3'));
773 o
.depends(Object
.assign({ multicast
: '1' }, 'type', ''));
775 o
= this.addOption(s
, 'devadvanced', form
.ListValue
, 'mldversion', _('Force MLD version'));
776 o
.value('', _('No enforcement'));
777 o
.value('1', _('Enforce MLD version 1'));
778 o
.value('2', _('Enforce MLD version 2'));
779 o
.depends(Object
.assign({ multicast
: '1' }, 'type', ''));
781 if (isBridgePort(dev
)) {
782 o
= this.addOption(s
, 'brport', form
.Flag
, 'learning', _('Enable MAC address learning'));
783 o
.default = o
.enabled
;
784 o
.depends('type', '');
786 o
= this.addOption(s
, 'brport', form
.Flag
, 'unicast_flood', _('Enable unicast flooding'));
787 o
.default = o
.enabled
;
788 o
.depends('type', '');
790 o
= this.addOption(s
, 'brport', form
.Flag
, 'isolated', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
791 o
.default = o
.disabled
;
792 o
.depends('type', '');
794 o
= this.addOption(s
, 'brport', form
.ListValue
, 'multicast_router', _('Multicast routing'));
795 o
.value('', _('Never'));
796 o
.value('1', _('Learn'));
797 o
.value('2', _('Always'));
798 o
.depends(Object
.assign({ multicast
: '1' }, 'type', ''));
800 o
= this.addOption(s
, 'brport', form
.Flag
, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
801 o
.default = o
.disabled
;
802 o
.depends(Object
.assign({ multicast
: '1' }, 'type', ''));
804 o
= this.addOption(s
, 'brport', form
.Flag
, 'multicast_fast_leave', _('Enable multicast fast leave'));
805 o
.default = o
.disabled
;
806 o
.depends(Object
.assign({ multicast
: '1' }, 'type', ''));
809 o
= this.addOption(s
, 'bridgevlan', form
.Flag
, 'vlan_filtering', _('Enable VLAN filterering'));
810 o
.depends('type', 'bridge');
811 o
.updateDefaultValue = function(section_id
) {
812 var device
= uci
.get('network', s
.section
, 'name'),
813 uielem
= this.getUIElement(section_id
),
816 uci
.sections('network', 'bridge-vlan', function(bvs
) {
817 has_vlans
= has_vlans
|| (bvs
.device
== device
);
820 this.default = has_vlans
? this.enabled
: this.disabled
;
822 if (uielem
&& !uielem
.isChanged())
823 uielem
.setValue(this.default);
826 o
= this.addOption(s
, 'bridgevlan', form
.SectionValue
, 'bridge-vlan', form
.TableSection
, 'bridge-vlan');
827 o
.depends('type', 'bridge');
833 ss
.renderHeaderRows = function(/* ... */) {
834 var node
= form
.TableSection
.prototype.renderHeaderRows
.apply(this, arguments
);
836 node
.querySelectorAll('.th').forEach(function(th
) {
837 th
.classList
.add('middle');
843 ss
.filter = function(section_id
) {
844 var devname
= uci
.get('network', s
.section
, 'name');
845 return (uci
.get('network', section_id
, 'device') == devname
);
848 ss
.render = function(/* ... */) {
849 return form
.TableSection
.prototype.render
.apply(this, arguments
).then(L
.bind(function(node
) {
850 node
.style
.overflow
= 'auto hidden';
853 this.node
.parentNode
.replaceChild(node
, this.node
);
861 ss
.redraw = function() {
862 return this.load().then(L
.bind(this.render
, this));
865 ss
.updatePorts = function(ports
) {
866 var devices
= ports
.map(function(port
) {
867 return network
.instantiateDevice(port
)
868 }).filter(function(dev
) {
869 return dev
.getType() != 'wifi' || dev
.isUp();
872 this.children
= this.children
.filter(function(opt
) { return !opt
.option
.match(/^port_/) });
874 for (var i
= 0; i
< devices
.length
; i
++) {
875 o
= ss
.option(cbiTagValue
, 'port_%s'.format(sfh(devices
[i
].getName())), renderDevBadge(devices
[i
]), renderPortStatus(devices
[i
]));
876 o
.port
= devices
[i
].getName();
879 var section_ids
= this.cfgsections(),
880 device_names
= devices
.reduce(function(names
, dev
) { names
[dev
.getName()] = true; return names
}, {});
882 for (var i
= 0; i
< section_ids
.length
; i
++) {
883 var old_spec
= L
.toArray(uci
.get('network', section_ids
[i
], 'ports')),
884 new_spec
= old_spec
.filter(function(spec
) { return device_names
[spec
.replace(/:[ut*]+$/, '')] });
886 if (old_spec
.length
!= new_spec
.length
)
887 uci
.set('network', section_ids
[i
], 'ports', new_spec
.length
? new_spec
: null);
891 ss
.handleAdd = function(ev
) {
892 return s
.parse().then(L
.bind(function() {
893 var device
= uci
.get('network', s
.section
, 'name'),
894 section_ids
= this.cfgsections(),
901 for (var i
= 0; i
< section_ids
.length
; i
++) {
902 var vid
= +uci
.get('network', section_ids
[i
], 'vlan');
904 if (vid
> max_vlan_id
)
908 section_id
= uci
.add('network', 'bridge-vlan');
909 uci
.set('network', section_id
, 'device', device
);
910 uci
.set('network', section_id
, 'vlan', max_vlan_id
+ 1);
912 s
.children
.forEach(function(opt
) {
913 switch (opt
.option
) {
916 var input
= opt
.map
.findElement('id', 'widget.%s'.format(opt
.cbid(s
.section
)));
918 input
.disabled
= true;
923 s
.getOption('vlan_filtering').updateDefaultValue(s
.section
);
925 s
.map
.addedVLANs
= s
.map
.addedVLANs
|| [];
926 s
.map
.addedVLANs
.push(section_id
);
928 return this.redraw();
932 o
= ss
.option(form
.Value
, 'vlan', _('VLAN ID'));
933 o
.datatype
= 'range(1, 4094)';
935 o
.renderWidget = function(/* ... */) {
936 var node
= form
.Value
.prototype.renderWidget
.apply(this, arguments
);
938 node
.style
.width
= '5em';
943 o
.validate = function(section_id
, value
) {
944 var section_ids
= this.section
.cfgsections();
946 for (var i
= 0; i
< section_ids
.length
; i
++) {
947 if (section_ids
[i
] == section_id
)
950 if (uci
.get('network', section_ids
[i
], 'vlan') == value
)
951 return _('The VLAN ID must be unique');
957 o
= ss
.option(form
.Flag
, 'local', _('Local'));
958 o
.default = o
.enabled
;
964 L
.toArray(uci
.get('network', s
.section
, 'ports')).forEach(function(port
) {
965 seen_ports
[port
] = true;
968 uci
.sections('network', 'bridge-vlan', function(bvs
) {
969 L
.toArray(bvs
.ports
).forEach(function(portspec
) {
970 var m
= portspec
.match(/^([^:]+)(?::[ut*]+)?$/);
973 seen_ports
[m
[1]] = true;
977 for (var port_name
in seen_ports
)
978 ports
.push(port_name
);
980 ports
.sort(function(a
, b
) {
981 var m1
= a
.match(/^(.+?)([0-9]*)$/),
982 m2
= b
.match(/^(.+?)([0-9]*)$/);
986 else if (m1
[1] > m2
[1])
989 return +(m1
[2] || 0) - +(m2
[2] || 0);
992 ss
.updatePorts(ports
);
995 updateDevBadge
: updateDevBadge
,
996 updatePortStatus
: updatePortStatus