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 validateQoSMap(section_id
, value
) {
25 var m
= value
.match(/^(\d+):(\d+)$/);
27 if (!m
|| +m
[1] > 0xFFFFFFFF || +m
[2] > 0xFFFFFFFF)
28 return _('Expecting two priority values separated by a colon');
33 function deviceSectionExists(section_id
, devname
) {
36 uci
.sections('network', 'device', function(ss
) {
38 ss
['.name'] != section_id
&&
46 function isBridgePort(dev
) {
50 if (dev
.isBridgePort())
55 uci
.sections('network', null, function(s
) {
56 if (s
['.type'] != 'interface' && s
['.type'] != 'device')
59 if (s
.type
== 'bridge' && L
.toArray(s
.ifname
).indexOf(dev
.getName()) > -1)
66 function updateDevBadge(node
, dev
) {
67 var type
= dev
.getType(),
68 up
= dev
.getCarrier();
73 'src': L
.resource('icons/%s%s.png').format(type
, up
? '' : '_disabled')
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()
89 function updatePortStatus(node
, dev
) {
90 var carrier
= dev
.getCarrier(),
91 duplex
= dev
.getDuplex(),
92 speed
= dev
.getSpeed(),
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'));
100 desc
= _('Connected');
109 'src': L
.resource('icons/port_%s.png').format(carrier
? 'up' : 'down')
115 node
.setAttribute('data-tooltip', title
);
117 node
.removeAttribute('data-tooltip');
122 function renderPortStatus(dev
) {
123 return updatePortStatus(E('span', {
124 'class': 'ifacebadge port-status-link',
125 'data-device': dev
.getName()
129 function updatePlaceholders(opt
, section_id
) {
130 var dev
= network
.instantiateDevice(opt
.getUIElement(section_id
).getValue());
132 for (var i
= 0, co
; (co
= opt
.section
.children
[i
]) != null; i
++) {
137 co
.getUIElement(section_id
).setPlaceholder(dev
.getMTU());
141 co
.getUIElement(section_id
).setPlaceholder(dev
.getMAC());
145 co
.getUIElement(section_id
).setPlaceholder(dev
._devstate('qlen'));
153 var cbiTagValue
= form
.Value
.extend({
154 renderWidget: function(section_id
, option_index
, cfgvalue
) {
155 var widget
= new ui
.Dropdown(cfgvalue
|| ['-'], {
157 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '—' ]),
158 E('span', { 'class': 'hide-close' }, [ _('Do not participate', 'VLAN port state') ])
161 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'u' ]),
162 E('span', { 'class': 'hide-close' }, [ _('Egress untagged', 'VLAN port state') ])
165 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 't' ]),
166 E('span', { 'class': 'hide-close' }, [ _('Egress tagged', 'VLAN port state') ])
169 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '*' ]),
170 E('span', { 'class': 'hide-close' }, [ _('Primary VLAN ID', 'VLAN port state') ])
173 id
: this.cbid(section_id
),
174 sort
: [ '-', 'u', 't', '*' ],
181 widget
.toggleItem = function(sb
, li
, force_state
) {
182 var lis
= li
.parentNode
.querySelectorAll('li'),
183 toggle
= ui
.Dropdown
.prototype.toggleItem
;
185 toggle
.apply(this, [sb
, li
, force_state
]);
187 if (force_state
!= null)
190 switch (li
.getAttribute('data-value'))
193 if (li
.hasAttribute('selected')) {
194 for (var i
= 0; i
< lis
.length
; i
++) {
195 switch (lis
[i
].getAttribute('data-value')) {
200 toggle
.apply(this, [sb
, lis
[i
], false]);
201 lis
[i
].setAttribute('unselectable', '');
205 toggle
.apply(this, [sb
, lis
[i
], false]);
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'):
220 lis
[i
].removeAttribute('unselectable');
224 toggle
.apply(this, [sb
, lis
[i
], false]);
229 toggle
.apply(this, [sb
, li
, true]);
234 if (li
.hasAttribute('selected')) {
235 var section_ids
= field
.section
.cfgsections();
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());
241 if (other_widget
=== this)
244 var new_value
= other_value
.filter(function(v
) { return v
!= '*' });
246 if (new_value
.length
== other_value
.length
)
249 other_widget
.setValue(new_value
);
256 var node
= widget
.render();
258 node
.style
.minWidth
= '4em';
261 node
.querySelector('li[data-value="*"]').setAttribute('unselectable', '');
263 return E('div', { 'style': 'display:inline-block' }, node
);
266 cfgvalue: function(section_id
) {
267 var ports
= L
.toArray(uci
.get('network', section_id
, 'ports'));
269 for (var i
= 0; i
< ports
.length
; i
++) {
270 var s
= ports
[i
].split(/:/);
272 if (s
[0] != this.port
)
275 var t
= /t
/.test(s
[1] || '') ? 't' : 'u';
277 return /\*/.test(s
[1] || '') ? [t
, '*'] : [t
];
283 write: function(section_id
, value
) {
286 for (var i
= 0; i
< this.section
.children
.length
; i
++) {
287 var opt
= this.section
.children
[i
];
290 var val
= L
.toArray(opt
.formvalue(section_id
)).join('');
297 ports
.push(opt
.port
);
301 ports
.push('%s:%s'.format(opt
.port
, val
));
307 uci
.set('network', section_id
, 'ports', ports
);
310 remove: function() {}
313 return baseclass
.extend({
314 replaceOption: function(s
, tabName
, optionClass
, optionName
, optionTitle
, optionDescription
) {
315 var o
= s
.getOption(optionName
);
319 s
.tabs
[o
.tab
].children
= s
.tabs
[o
.tab
].children
.filter(function(opt
) {
320 return opt
.option
!= optionName
;
324 s
.children
= s
.children
.filter(function(opt
) {
325 return opt
.option
!= optionName
;
329 return s
.taboption(tabName
, optionClass
, optionName
, optionTitle
, optionDescription
);
332 addDeviceOptions: function(s
, dev
, isNew
) {
333 var parent_dev
= dev
? dev
.getParent() : null,
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'));
341 o
= this.replaceOption(s
, 'devgeneral', form
.ListValue
, 'type', _('Device type'));
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
);
356 o
= this.replaceOption(s
, 'devgeneral', widgets
.DeviceSelect
, 'name_simple', _('Existing device'));
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());
366 o
.validate = function(section_id
, value
) {
367 updatePlaceholders(this, section_id
);
369 return deviceSectionExists(section_id
, value
)
370 ? _('A configuration for the device "%s" already exists').format(value
) : true;
372 o
.onchange = function(ev
, section_id
, values
) {
373 updatePlaceholders(this, section_id
);
375 o
.depends('type', '');
377 o
= this.replaceOption(s
, 'devgeneral', widgets
.DeviceSelect
, 'ifname_single', _('Base device'));
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());
387 o
.validate = function(section_id
, value
) {
388 updatePlaceholders(this, section_id
);
391 var type
= this.section
.formvalue(section_id
, 'type'),
392 name
= this.section
.getUIElement(section_id
, 'name_complex');
394 if (type
== 'macvlan' && value
&& name
&& !name
.isChanged()) {
397 while (deviceSectionExists(section_id
, '%smac%d'.format(value
, i
)))
400 name
.setValue('%smac%d'.format(value
, i
));
401 name
.triggerValidation();
407 o
.onchange = function(ev
, section_id
, values
) {
408 updatePlaceholders(this, section_id
);
410 o
.depends('type', '8021q');
411 o
.depends('type', '8021ad');
412 o
.depends('type', 'macvlan');
414 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'vid', _('VLAN ID'));
416 o
.datatype
= 'range(1, 4094)';
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');
424 if (base
&& vid
&& name
&& !name
.isChanged()) {
425 name
.setValue('%s.%d'.format(base
, vid
));
426 name
.triggerValidation();
431 o
.depends('type', '8021q');
432 o
.depends('type', '8021ad');
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');
441 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'name_complex', _('Device name'));
443 o
.datatype
= 'maxlength(15)';
445 o
.ucioption
= 'name';
446 o
.validate = function(section_id
, value
) {
447 var dev
= network
.instantiateDevice(value
);
449 if (deviceSectionExists(section_id
, value
) || (isNew
&& (dev
.dev
|| {}).idx
))
450 return _('The device name "%s" is already taken').format(value
);
454 o
.depends({ type
: '', '!reverse': true });
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'));
458 o
.validate
= validateQoSMap
;
459 o
.depends('type', '8021q');
460 o
.depends('type', '8021ad');
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'));
464 o
.validate
= validateQoSMap
;
465 o
.depends('type', '8021q');
466 o
.depends('type', '8021ad');
468 o
= this.replaceOption(s
, 'devgeneral', widgets
.DeviceSelect
, 'ifname_multi', _('Bridge ports'));
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();
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'));
485 for (var i
= 0; i
< ifnames
.length
; i
++)
486 if (ifnames
[i
] == device_name
)
492 return (!parent_dev
|| parent_dev
.getName() != bridge_name
);
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
);
498 return ss
.parse().then(function() {
502 o
.depends('type', 'bridge');
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');
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');
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');
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');
522 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'hello_time', _('Hello interval'), _('Interval in seconds for STP hello packets'));
524 o
.datatype
= 'range(1, 10)';
525 o
.depends({ type
: 'bridge', stp
: '1' });
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' });
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' });
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');
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' });
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');
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'));
553 o
.datatype
= 'min(1)';
554 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
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' });
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
) : '';
568 if (value
!= '' && qival
!= '' && +value
>= +qival
)
569 return _('The query response interval must be lower than the query interval value');
573 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
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' });
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;
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
);
591 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'macaddr', _('MAC address'));
592 o
.datatype
= 'macaddr';
594 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'peer_name', _('Peer device name'));
596 o
.datatype
= 'maxlength(15)';
597 o
.depends('type', 'veth');
598 o
.load = function(section_id
) {
599 var sections
= uci
.sections('network', 'device'),
602 for (var i
= 0; i
< sections
.length
; i
++)
603 if (sections
[i
]['.name'] == section_id
)
605 else if (sections
[i
].type
== 'veth')
608 this.placeholder
= 'veth%d'.format(idx
);
610 return form
.Value
.prototype.load
.apply(this, arguments
);
613 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'peer_macaddr', _('Peer MAC address'));
615 o
.datatype
= 'macaddr';
616 o
.depends('type', 'veth');
618 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'txqueuelen', _('TX queue length'));
619 o
.placeholder
= dev
? dev
._devstate('qlen') : '';
620 o
.datatype
= 'uinteger';
622 o
= this.replaceOption(s
, 'devadvanced', form
.Flag
, 'promisc', _('Enable promiscuous mode'));
623 o
.default = o
.disabled
;
625 o
= this.replaceOption(s
, 'devadvanced', form
.ListValue
, 'rpfilter', _('Reverse path filter'));
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
]);
647 o
= this.replaceOption(s
, 'devadvanced', form
.Flag
, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
648 o
.default = o
.disabled
;
650 o
= this.replaceOption(s
, 'devadvanced', form
.Flag
, 'sendredirects', _('Send ICMP redirects'));
651 o
.default = o
.enabled
;
653 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds'));
654 o
.placeholder
= '30000';
655 o
.datatype
= 'uinteger';
657 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'neighgcstaletime', _('Stale neighbour cache timeout'), _('Timeout in seconds'));
658 o
.placeholder
= '60';
659 o
.datatype
= 'uinteger';
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.'));
663 o
.datatype
= 'uinteger';
665 o
= this.replaceOption(s
, 'devgeneral', form
.Flag
, 'ipv6', _('Enable IPv6'));
667 o
.default = o
.enabled
;
669 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'mtu6', _('IPv6 MTU'));
670 o
.datatype
= 'max(9200)';
671 o
.depends('ipv6', '1');
673 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send'));
675 o
.datatype
= 'uinteger';
676 o
.depends('ipv6', '1');
679 o
= this.replaceOption(s
, 'devadvanced', form
.Flag
, 'multicast', _('Enable multicast support'));
680 o
.default = o
.enabled
;
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');
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');
695 if (isBridgePort(dev
)) {
696 o
= this.replaceOption(s
, 'brport', form
.Flag
, 'learning', _('Enable MAC address learning'));
697 o
.default = o
.enabled
;
699 o
= this.replaceOption(s
, 'brport', form
.Flag
, 'unicast_flood', _('Enable unicast flooding'));
700 o
.default = o
.enabled
;
702 o
= this.replaceOption(s
, 'brport', form
.Flag
, 'isolated', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
703 o
.default = o
.disabled
;
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');
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');
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');
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
),
727 uci
.sections('network', 'bridge-vlan', function(bvs
) {
728 has_vlans
= has_vlans
|| (bvs
.device
== device
);
731 this.default = has_vlans
? this.enabled
: this.disabled
;
733 if (uielem
&& !uielem
.isChanged())
734 uielem
.setValue(this.default);
737 o
= this.replaceOption(s
, 'bridgevlan', form
.SectionValue
, 'bridge-vlan', form
.TableSection
, 'bridge-vlan');
738 o
.depends('type', 'bridge');
744 ss
.renderHeaderRows = function(/* ... */) {
745 var node
= form
.TableSection
.prototype.renderHeaderRows
.apply(this, arguments
);
747 node
.querySelectorAll('.th').forEach(function(th
) {
748 th
.classList
.add('left');
749 th
.classList
.add('middle');
755 ss
.filter = function(section_id
) {
756 var devname
= uci
.get('network', s
.section
, 'name');
757 return (uci
.get('network', section_id
, 'device') == devname
);
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';
766 this.node
.parentNode
.replaceChild(node
, this.node
);
774 ss
.redraw = function() {
775 return this.load().then(L
.bind(this.render
, this));
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();
785 this.children
= this.children
.filter(function(opt
) { return !opt
.option
.match(/^port_/) });
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();
792 var section_ids
= this.cfgsections(),
793 device_names
= devices
.reduce(function(names
, dev
) { names
[dev
.getName()] = true; return names
}, {});
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*]+$/, '')] });
799 if (old_spec
.length
!= new_spec
.length
)
800 uci
.set('network', section_ids
[i
], 'ports', new_spec
.length
? new_spec
: null);
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(),
814 for (var i
= 0; i
< section_ids
.length
; i
++) {
815 var vid
= +uci
.get('network', section_ids
[i
], 'vlan');
817 if (vid
> max_vlan_id
)
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);
825 s
.children
.forEach(function(opt
) {
826 switch (opt
.option
) {
829 var input
= opt
.map
.findElement('id', 'widget.%s'.format(opt
.cbid(s
.section
)));
831 input
.disabled
= true;
836 s
.getOption('vlan_filtering').updateDefaultValue(s
.section
);
838 s
.map
.addedVLANs
= s
.map
.addedVLANs
|| [];
839 s
.map
.addedVLANs
.push(section_id
);
841 return this.redraw();
845 o
= ss
.option(form
.Value
, 'vlan', _('VLAN ID'));
846 o
.datatype
= 'range(1, 4094)';
848 o
.renderWidget = function(/* ... */) {
849 var node
= form
.Value
.prototype.renderWidget
.apply(this, arguments
);
851 node
.style
.width
= '5em';
856 o
.validate = function(section_id
, value
) {
857 var section_ids
= this.section
.cfgsections();
859 for (var i
= 0; i
< section_ids
.length
; i
++) {
860 if (section_ids
[i
] == section_id
)
863 if (uci
.get('network', section_ids
[i
], 'vlan') == value
)
864 return _('The VLAN ID must be unique');
870 o
= ss
.option(form
.Flag
, 'local', _('Local'));
871 o
.default = o
.enabled
;
877 L
.toArray(uci
.get('network', s
.section
, 'ports')).forEach(function(port
) {
878 seen_ports
[port
] = true;
881 uci
.sections('network', 'bridge-vlan', function(bvs
) {
882 if (uci
.get('network', s
.section
, 'name') != bvs
.device
)
885 L
.toArray(bvs
.ports
).forEach(function(portspec
) {
886 var m
= portspec
.match(/^([^:]+)(?::[ut*]+)?$/);
889 seen_ports
[m
[1]] = true;
893 for (var port_name
in seen_ports
)
894 ports
.push(port_name
);
896 ports
.sort(function(a
, b
) {
897 var m1
= a
.match(/^(.+?)([0-9]*)$/),
898 m2
= b
.match(/^(.+?)([0-9]*)$/);
902 else if (m1
[1] > m2
[1])
905 return +(m1
[2] || 0) - +(m2
[2] || 0);
908 ss
.updatePorts(ports
);
911 updateDevBadge
: updateDevBadge
,
912 updatePortStatus
: updatePortStatus