a6d60360635cebc8eafdbd717b2a90801fa99fd0
10 'require tools.widgets as widgets';
12 function validateAddr(section_id
, value
) {
16 var ipv6
= /6$/.test(this.section
.formvalue(section_id
, 'mode')),
17 addr
= ipv6
? validation
.parseIPv6(value
) : validation
.parseIPv4(value
);
19 return addr
? true : (ipv6
? _('Expecting a valid IPv6 address') : _('Expecting a valid IPv4 address'));
22 function validateQoSMap(section_id
, value
) {
26 var m
= value
.match(/^(\d+):(\d+)$/);
28 if (!m
|| +m
[1] > 0xFFFFFFFF || +m
[2] > 0xFFFFFFFF)
29 return _('Expecting two priority values separated by a colon');
34 function deviceSectionExists(section_id
, devname
) {
37 uci
.sections('network', 'device', function(ss
) {
39 ss
['.name'] != section_id
&&
47 function isBridgePort(dev
) {
51 if (dev
.isBridgePort())
56 uci
.sections('network', null, function(s
) {
57 if (s
['.type'] != 'interface' && s
['.type'] != 'device')
60 if (s
.type
== 'bridge' && L
.toArray(s
.ifname
).indexOf(dev
.getName()) > -1)
67 function updateDevBadge(node
, dev
) {
68 var type
= dev
.getType(),
69 up
= dev
.getCarrier();
74 'src': L
.resource('icons/%s%s.png').format(type
, up
? '' : '_disabled')
82 function renderDevBadge(dev
) {
83 return updateDevBadge(E('span', {
84 'class': 'ifacebadge port-status-device',
85 'style': 'font-weight:normal',
86 'data-device': dev
.getName()
90 function updatePortStatus(node
, dev
) {
91 var carrier
= dev
.getCarrier(),
92 duplex
= dev
.getDuplex(),
93 speed
= dev
.getSpeed(),
96 if (carrier
&& speed
> 0 && duplex
!= null) {
97 desc
= '%d%s'.format(speed
, duplex
== 'full' ? 'FD' : 'HD');
98 title
= '%s, %d MBit/s, %s'.format(_('Connected'), speed
, duplex
== 'full' ? _('full-duplex') : _('half-duplex'));
101 desc
= _('Connected');
110 'src': L
.resource('icons/port_%s.png').format(carrier
? 'up' : 'down')
116 node
.setAttribute('data-tooltip', title
);
118 node
.removeAttribute('data-tooltip');
123 function renderPortStatus(dev
) {
124 return updatePortStatus(E('span', {
125 'class': 'ifacebadge port-status-link',
126 'data-device': dev
.getName()
130 function updatePlaceholders(opt
, section_id
) {
131 var dev
= network
.instantiateDevice(opt
.getUIElement(section_id
).getValue());
133 for (var i
= 0, co
; (co
= opt
.section
.children
[i
]) != null; i
++) {
138 co
.getUIElement(section_id
).setPlaceholder(dev
.getMTU());
142 co
.getUIElement(section_id
).setPlaceholder(dev
.getMAC());
146 co
.getUIElement(section_id
).setPlaceholder(dev
._devstate('qlen'));
153 var cbiFlagTristate
= form
.ListValue
.extend({
154 __init__: function(/* ... */) {
155 this.super('__init__', arguments
);
156 this.keylist
= [ '', '0!', '1!' ];
157 this.vallist
= [ _('automatic'), _('disabled'), _('enabled') ];
160 load: function(section_id
) {
161 var invert
= false, sysfs
= this.sysfs
;
164 if (sysfs
.charAt(0) == '!') {
166 sysfs
= sysfs
.substring(1);
169 return L
.resolveDefault(fs
.read(sysfs
), '').then(L
.bind(function(res
) {
170 res
= (res
|| '').trim();
173 this.sysfs_default
= invert
;
175 this.sysfs_default
= !invert
;
177 return this.super('load', [section_id
]);
181 return this.super('load', [section_id
]);
184 write: function(section_id
, formvalue
) {
185 if (formvalue
== '1!')
186 return this.super('write', [section_id
, '1']);
187 else if (formvalue
== '0!')
188 return this.super('write', [section_id
, '0']);
190 return this.super('remove', [section_id
]);
193 renderWidget: function(section_id
, option_index
, cfgvalue
) {
194 var sysdef
= this.sysfs_default
;
196 if (this.sysfs_default
!== null) {
197 this.keylist
[0] = sysdef
? '1' : '0';
198 this.vallist
[0] = sysdef
? _('automatic (enabled)') : _('automatic (disabled)');
201 return this.super('renderWidget', [section_id
, option_index
, cfgvalue
? cfgvalue
+ '!' : null]);
206 var cbiTagValue
= form
.Value
.extend({
207 renderWidget: function(section_id
, option_index
, cfgvalue
) {
208 var widget
= new ui
.Dropdown(cfgvalue
|| ['-'], {
210 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '—' ]),
211 E('span', { 'class': 'hide-close' }, [ _('Not Member', 'VLAN port state') ])
214 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'U' ]),
215 E('span', { 'class': 'hide-close' }, [ _('Untagged', 'VLAN port state') ])
218 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ 'T' ]),
219 E('span', { 'class': 'hide-close' }, [ _('Tagged', 'VLAN port state') ])
222 E('span', { 'class': 'hide-open', 'style': 'font-family:monospace' }, [ '*' ]),
223 E('span', { 'class': 'hide-close' }, [ _('Is Primary VLAN', 'VLAN port state') ])
226 id
: this.cbid(section_id
),
227 sort
: [ '-', 'u', 't', '*' ],
234 widget
.toggleItem = function(sb
, li
, force_state
) {
235 var lis
= li
.parentNode
.querySelectorAll('li'),
236 toggle
= ui
.Dropdown
.prototype.toggleItem
;
238 toggle
.apply(this, [sb
, li
, force_state
]);
240 if (force_state
!= null)
243 switch (li
.getAttribute('data-value'))
246 if (li
.hasAttribute('selected')) {
247 for (var i
= 0; i
< lis
.length
; i
++) {
248 switch (lis
[i
].getAttribute('data-value')) {
253 toggle
.apply(this, [sb
, lis
[i
], false]);
254 lis
[i
].setAttribute('unselectable', '');
258 toggle
.apply(this, [sb
, lis
[i
], false]);
266 if (li
.hasAttribute('selected')) {
267 for (var i
= 0; i
< lis
.length
; i
++) {
268 switch (lis
[i
].getAttribute('data-value')) {
269 case li
.getAttribute('data-value'):
273 lis
[i
].removeAttribute('unselectable');
277 toggle
.apply(this, [sb
, lis
[i
], false]);
282 toggle
.apply(this, [sb
, li
, true]);
287 if (li
.hasAttribute('selected')) {
288 var section_ids
= field
.section
.cfgsections();
290 for (var i
= 0; i
< section_ids
.length
; i
++) {
291 var other_widget
= field
.getUIElement(section_ids
[i
]),
292 other_value
= L
.toArray(other_widget
.getValue());
294 if (other_widget
=== this)
297 var new_value
= other_value
.filter(function(v
) { return v
!= '*' });
299 if (new_value
.length
== other_value
.length
)
302 other_widget
.setValue(new_value
);
309 var node
= widget
.render();
311 node
.style
.minWidth
= '4em';
314 node
.querySelector('li[data-value="*"]').setAttribute('unselectable', '');
316 return E('div', { 'style': 'display:inline-block' }, node
);
319 cfgvalue: function(section_id
) {
320 var ports
= L
.toArray(uci
.get('network', section_id
, 'ports'));
322 for (var i
= 0; i
< ports
.length
; i
++) {
323 var s
= ports
[i
].split(/:/);
325 if (s
[0] != this.port
)
328 var t
= /t
/.test(s
[1] || '') ? 't' : 'u';
330 return /\x2a/.test(s
[1] || '') ? [t
, '*'] : [t
];
336 write: function(section_id
, value
) {
339 for (var i
= 0; i
< this.section
.children
.length
; i
++) {
340 var opt
= this.section
.children
[i
];
343 var val
= L
.toArray(opt
.formvalue(section_id
)).join('');
350 ports
.push(opt
.port
);
354 ports
.push('%s:%s'.format(opt
.port
, val
));
360 uci
.set('network', section_id
, 'ports', ports
.length
? ports
: null);
363 remove: function() {}
366 return baseclass
.extend({
367 replaceOption: function(s
, tabName
, optionClass
, optionName
, optionTitle
, optionDescription
) {
368 var o
= s
.getOption(optionName
);
372 s
.tabs
[o
.tab
].children
= s
.tabs
[o
.tab
].children
.filter(function(opt
) {
373 return opt
.option
!= optionName
;
377 s
.children
= s
.children
.filter(function(opt
) {
378 return opt
.option
!= optionName
;
382 return s
.taboption(tabName
, optionClass
, optionName
, optionTitle
, optionDescription
);
385 addDeviceOptions: function(s
, dev
, isNew
) {
386 var parent_dev
= dev
? dev
.getParent() : null,
387 devname
= dev
? dev
.getName() : null,
390 s
.tab('devgeneral', _('General device options'));
391 s
.tab('devadvanced', _('Advanced device options'));
392 s
.tab('brport', _('Bridge port specific options'));
393 s
.tab('bridgevlan', _('Bridge VLAN filtering'));
395 o
= this.replaceOption(s
, 'devgeneral', form
.ListValue
, 'type', _('Device type'));
397 o
.value('', _('Network device'));
398 o
.value('bridge', _('Bridge device'));
399 o
.value('8021q', _('VLAN (802.1q)'));
400 o
.value('8021ad', _('VLAN (802.1ad)'));
401 o
.value('macvlan', _('MAC VLAN'));
402 o
.value('veth', _('Virtual Ethernet'));
403 o
.validate = function(section_id
, value
) {
404 if (value
== 'bridge' || value
== 'veth')
405 updatePlaceholders(this.section
.getOption('name_complex'), section_id
);
410 o
= this.replaceOption(s
, 'devgeneral', widgets
.DeviceSelect
, 'name_simple', _('Existing device'));
414 o
.default = (dev
? dev
.getName() : '');
415 o
.ucioption
= 'name';
416 o
.filter = function(section_id
, value
) {
417 var dev
= network
.instantiateDevice(value
);
418 return !deviceSectionExists(section_id
, value
) && (dev
.getType() != 'wifi' || dev
.isUp());
420 o
.validate = function(section_id
, value
) {
421 updatePlaceholders(this, section_id
);
423 return deviceSectionExists(section_id
, value
)
424 ? _('A configuration for the device "%s" already exists').format(value
) : true;
426 o
.onchange = function(ev
, section_id
, values
) {
427 updatePlaceholders(this, section_id
);
429 o
.depends('type', '');
431 o
= this.replaceOption(s
, 'devgeneral', widgets
.DeviceSelect
, 'ifname_single', _('Base device'));
435 o
.default = (dev
? dev
.getName() : '').match(/^.+\.\d+$/) ? dev
.getName().replace(/\.\d+$/, '') : '';
436 o
.ucioption
= 'ifname';
437 o
.filter = function(section_id
, value
) {
438 var dev
= network
.instantiateDevice(value
);
439 return (dev
.getType() != 'wifi' || dev
.isUp());
441 o
.validate = function(section_id
, value
) {
442 updatePlaceholders(this, section_id
);
445 var type
= this.section
.formvalue(section_id
, 'type'),
446 name
= this.section
.getUIElement(section_id
, 'name_complex');
448 if (type
== 'macvlan' && value
&& name
&& !name
.isChanged()) {
451 while (deviceSectionExists(section_id
, '%smac%d'.format(value
, i
)))
454 name
.setValue('%smac%d'.format(value
, i
));
455 name
.triggerValidation();
461 o
.onchange = function(ev
, section_id
, values
) {
462 updatePlaceholders(this, section_id
);
464 o
.depends('type', '8021q');
465 o
.depends('type', '8021ad');
466 o
.depends('type', 'macvlan');
468 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'vid', _('VLAN ID'));
470 o
.datatype
= 'range(1, 4094)';
472 o
.default = (dev
? dev
.getName() : '').match(/^.+\.\d+$/) ? dev
.getName().replace(/^.+\./, '') : '';
473 o
.validate = function(section_id
, value
) {
474 var base
= this.section
.formvalue(section_id
, 'ifname_single'),
475 vid
= this.section
.formvalue(section_id
, 'vid'),
476 name
= this.section
.getUIElement(section_id
, 'name_complex');
478 if (base
&& vid
&& name
&& !name
.isChanged() && isNew
) {
479 name
.setValue('%s.%d'.format(base
, vid
));
480 name
.triggerValidation();
485 o
.depends('type', '8021q');
486 o
.depends('type', '8021ad');
488 o
= this.replaceOption(s
, 'devgeneral', form
.ListValue
, 'mode', _('Mode'));
489 o
.value('vepa', _('VEPA (Virtual Ethernet Port Aggregator)', 'MACVLAN mode'));
490 o
.value('private', _('Private (Prevent communication between MAC VLANs)', 'MACVLAN mode'));
491 o
.value('bridge', _('Bridge (Support direct communication between MAC VLANs)', 'MACVLAN mode'));
492 o
.value('passthru', _('Pass-through (Mirror physical device to single MAC VLAN)', 'MACVLAN mode'));
493 o
.depends('type', 'macvlan');
495 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'name_complex', _('Device name'));
497 o
.datatype
= 'maxlength(15)';
499 o
.ucioption
= 'name';
500 o
.validate = function(section_id
, value
) {
501 var dev
= network
.instantiateDevice(value
);
503 if (deviceSectionExists(section_id
, value
) || (isNew
&& (dev
.dev
|| {}).idx
))
504 return _('The device name "%s" is already taken').format(value
);
508 o
.depends({ type
: '', '!reverse': true });
510 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'));
512 o
.validate
= validateQoSMap
;
513 o
.depends('type', '8021q');
514 o
.depends('type', '8021ad');
516 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'));
518 o
.validate
= validateQoSMap
;
519 o
.depends('type', '8021q');
520 o
.depends('type', '8021ad');
522 o
= this.replaceOption(s
, 'devgeneral', widgets
.DeviceSelect
, 'ifname_multi', _('Bridge ports'));
528 o
.ucioption
= 'ports';
529 o
.default = L
.toArray(dev
? dev
.getPorts() : null).filter(function(p
) { return p
.getType() != 'wifi' }).map(function(p
) { return p
.getName() });
530 o
.filter = function(section_id
, device_name
) {
531 var bridge_name
= uci
.get('network', section_id
, 'name'),
532 choice_dev
= network
.instantiateDevice(device_name
),
533 parent_dev
= choice_dev
.getParent();
535 /* only show wifi networks which are already present in "option ifname" */
536 if (choice_dev
.getType() == 'wifi') {
537 var ifnames
= L
.toArray(uci
.get('network', section_id
, 'ports'));
539 for (var i
= 0; i
< ifnames
.length
; i
++)
540 if (ifnames
[i
] == device_name
)
546 return (!parent_dev
|| parent_dev
.getName() != bridge_name
);
548 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.')
549 o
.onchange = function(ev
, section_id
, values
) {
550 ss
.updatePorts(values
);
552 return ss
.parse().then(function() {
556 o
.depends('type', 'bridge');
558 o
= this.replaceOption(s
, 'devgeneral', form
.Flag
, 'bridge_empty', _('Bring up empty bridge'), _('Bring up the bridge interface even if no ports are attached'));
559 o
.default = o
.disabled
;
560 o
.depends('type', 'bridge');
562 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'priority', _('Priority'));
563 o
.placeholder
= '32767';
564 o
.datatype
= 'range(0, 65535)';
565 o
.depends('type', 'bridge');
567 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'ageing_time', _('Ageing time'), _('Timeout in seconds for learned MAC addresses in the forwarding database'));
568 o
.placeholder
= '30';
569 o
.datatype
= 'uinteger';
570 o
.depends('type', 'bridge');
572 o
= this.replaceOption(s
, 'devadvanced', form
.Flag
, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge'));
573 o
.default = o
.disabled
;
574 o
.depends('type', 'bridge');
576 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'hello_time', _('Hello interval'), _('Interval in seconds for STP hello packets'));
578 o
.datatype
= 'range(1, 10)';
579 o
.depends({ type
: 'bridge', stp
: '1' });
581 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'forward_delay', _('Forward delay'), _('Time in seconds to spend in listening and learning states'));
582 o
.placeholder
= '15';
583 o
.datatype
= 'range(2, 30)';
584 o
.depends({ type
: 'bridge', stp
: '1' });
586 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'max_age', _('Maximum age'), _('Timeout in seconds until topology updates on link loss'));
587 o
.placeholder
= '20';
588 o
.datatype
= 'range(6, 40)';
589 o
.depends({ type
: 'bridge', stp
: '1' });
592 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'));
593 o
.default = o
.disabled
;
594 o
.depends('type', 'bridge');
596 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'hash_max', _('Maximum snooping table size'));
597 o
.placeholder
= '512';
598 o
.datatype
= 'uinteger';
599 o
.depends({ type
: 'bridge', igmp_snooping
: '1' });
601 o
= this.replaceOption(s
, 'devadvanced', form
.Flag
, 'multicast_querier', _('Enable multicast querier'));
602 o
.defaults
= { '1': [{'igmp_snooping': '1'}], '0': [{'igmp_snooping': '0'}] };
603 o
.depends('type', 'bridge');
605 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'));
607 o
.datatype
= 'min(1)';
608 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
610 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'));
611 o
.placeholder
= '12500';
612 o
.datatype
= 'uinteger';
613 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
615 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'));
616 o
.placeholder
= '1000';
617 o
.datatype
= 'uinteger';
618 o
.validate = function(section_id
, value
) {
619 var qiopt
= L
.toArray(this.map
.lookupOption('query_interval', section_id
))[0],
620 qival
= qiopt
? (qiopt
.formvalue(section_id
) || qiopt
.placeholder
) : '';
622 if (value
!= '' && qival
!= '' && +value
>= +qival
)
623 return _('The query response interval must be lower than the query interval value');
627 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
629 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'));
630 o
.placeholder
= '100';
631 o
.datatype
= 'uinteger';
632 o
.depends({ type
: 'bridge', multicast_querier
: '1' });
634 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'mtu', _('MTU'));
635 o
.datatype
= 'range(576, 9200)';
636 o
.validate = function(section_id
, value
) {
637 var parent_mtu
= (dev
&& dev
.getType() == 'vlan') ? (parent_dev
? parent_dev
.getMTU() : null) : null;
639 if (parent_mtu
!== null && +value
> parent_mtu
)
640 return _('The MTU must not exceed the parent device MTU of %d bytes').format(parent_mtu
);
645 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'macaddr', _('MAC address'));
646 o
.datatype
= 'macaddr';
648 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'peer_name', _('Peer device name'));
650 o
.datatype
= 'maxlength(15)';
651 o
.depends('type', 'veth');
652 o
.load = function(section_id
) {
653 var sections
= uci
.sections('network', 'device'),
656 for (var i
= 0; i
< sections
.length
; i
++)
657 if (sections
[i
]['.name'] == section_id
)
659 else if (sections
[i
].type
== 'veth')
662 this.placeholder
= 'veth%d'.format(idx
);
664 return form
.Value
.prototype.load
.apply(this, arguments
);
667 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'peer_macaddr', _('Peer MAC address'));
669 o
.datatype
= 'macaddr';
670 o
.depends('type', 'veth');
672 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'txqueuelen', _('TX queue length'));
673 o
.placeholder
= dev
? dev
._devstate('qlen') : '';
674 o
.datatype
= 'uinteger';
676 o
= this.replaceOption(s
, 'devadvanced', cbiFlagTristate
, 'promisc', _('Enable promiscuous mode'));
677 o
.sysfs_default
= (dev
&& dev
.dev
&& dev
.dev
.flags
) ? dev
.dev
.flags
.promisc
: null;
679 o
= this.replaceOption(s
, 'devadvanced', form
.ListValue
, 'rpfilter', _('Reverse path filter'));
681 o
.value('', _('disabled'));
682 o
.value('loose', _('Loose filtering'));
683 o
.value('strict', _('Strict filtering'));
684 o
.cfgvalue = function(/* ... */) {
685 var val
= form
.ListValue
.prototype.cfgvalue
.apply(this, arguments
);
701 o
= this.replaceOption(s
, 'devadvanced', cbiFlagTristate
, 'acceptlocal', _('Accept local'), _('Accept packets with local source addresses'));
702 o
.sysfs
= '/proc/sys/net/ipv4/conf/%s/accept_local'.format(devname
|| 'default');
704 o
= this.replaceOption(s
, 'devadvanced', cbiFlagTristate
, 'sendredirects', _('Send ICMP redirects'));
705 o
.sysfs
= '/proc/sys/net/ipv4/conf/%s/send_redirects'.format(devname
|| 'default');
707 o
= this.replaceOption(s
, 'devadvanced', cbiFlagTristate
, 'arp_accept', _('Honor gratuitous ARP'), _('When enabled, new ARP table entries are added from received gratuitous APR requests or replies, otherwise only preexisting table entries are updated, but no new hosts are learned.'));
708 o
.sysfs
= '/proc/sys/net/ipv4/conf/%s/arp_accept'.format(devname
|| 'default');
710 o
= this.replaceOption(s
, 'devadvanced', cbiFlagTristate
, 'drop_gratuitous_arp', _('Drop gratuitous ARP'), _('Drop all gratuitous ARP frames, for example if there’s a known good ARP proxy on the network and such frames need not be used or in the case of 802.11, must not be used to prevent attacks.'));
711 o
.sysfs
= '/proc/sys/net/ipv4/conf/%s/drop_gratuitous_arp'.format(devname
|| 'default');
713 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'neighreachabletime', _('Neighbour cache validity'), _('Time in milliseconds'));
714 o
.placeholder
= '30000';
715 o
.datatype
= 'uinteger';
717 o
= this.replaceOption(s
, 'devadvanced', form
.Value
, 'neighgcstaletime', _('Stale neighbour cache timeout'), _('Timeout in seconds'));
718 o
.placeholder
= '60';
719 o
.datatype
= 'uinteger';
721 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.'));
723 o
.datatype
= 'uinteger';
725 o
= this.replaceOption(s
, 'devgeneral', cbiFlagTristate
, 'ipv6', _('Enable IPv6'));
726 o
.sysfs
= '!/proc/sys/net/ipv6/conf/%s/disable_ipv6'.format(devname
|| 'default');
729 o
= this.replaceOption(s
, 'devadvanced', cbiFlagTristate
, 'ip6segmentrouting', _('Enable IPv6 segment routing'));
730 o
.sysfs
= '/proc/sys/net/ipv6/conf/%s/seg6_enabled'.format(devname
|| 'default');
731 o
.depends('ipv6', /1/);
733 o
= this.replaceOption(s
, 'devadvanced', cbiFlagTristate
, 'drop_unsolicited_na', _('Drop unsolicited NA'), _('Drop all unsolicited neighbor advertisements, for example if there’s a known good NA proxy on the network and such frames need not be used or in the case of 802.11, must not be used to prevent attacks.'));
734 o
.sysfs
= '/proc/sys/net/ipv6/conf/%s/drop_unsolicited_na'.format(devname
|| 'default');
735 o
.depends('ipv6', /1/);
737 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'mtu6', _('IPv6 MTU'));
738 o
.datatype
= 'max(9200)';
739 o
.depends('ipv6', /1/);
741 o
= this.replaceOption(s
, 'devgeneral', form
.Value
, 'dadtransmits', _('DAD transmits'), _('Amount of Duplicate Address Detection probes to send'));
743 o
.datatype
= 'uinteger';
744 o
.depends('ipv6', /1/);
747 o
= this.replaceOption(s
, 'devadvanced', cbiFlagTristate
, 'multicast', _('Enable multicast support'));
748 o
.sysfs_default
= (dev
&& dev
.dev
&& dev
.dev
.flags
) ? dev
.dev
.flags
.multicast
: null;
750 o
= this.replaceOption(s
, 'devadvanced', form
.ListValue
, 'igmpversion', _('Force IGMP version'));
751 o
.value('', _('No enforcement'));
752 o
.value('1', _('Enforce IGMPv1'));
753 o
.value('2', _('Enforce IGMPv2'));
754 o
.value('3', _('Enforce IGMPv3'));
755 o
.depends('multicast', /1/);
757 o
= this.replaceOption(s
, 'devadvanced', form
.ListValue
, 'mldversion', _('Force MLD version'));
758 o
.value('', _('No enforcement'));
759 o
.value('1', _('Enforce MLD version 1'));
760 o
.value('2', _('Enforce MLD version 2'));
761 o
.depends('multicast', /1/);
763 if (isBridgePort(dev
)) {
764 o
= this.replaceOption(s
, 'brport', cbiFlagTristate
, 'learning', _('Enable MAC address learning'));
765 o
.sysfs
= '/sys/class/net/%s/brport/learning'.format(devname
|| 'default');
767 o
= this.replaceOption(s
, 'brport', cbiFlagTristate
, 'unicast_flood', _('Enable unicast flooding'));
768 o
.sysfs
= '/sys/class/net/%s/brport/unicast_flood'.format(devname
|| 'default');
770 o
= this.replaceOption(s
, 'brport', cbiFlagTristate
, 'isolate', _('Port isolation'), _('Only allow communication with non-isolated bridge ports when enabled'));
771 o
.sysfs
= '/sys/class/net/%s/brport/isolated'.format(devname
|| 'default');
773 o
= this.replaceOption(s
, 'brport', form
.ListValue
, 'multicast_router', _('Multicast routing'));
774 o
.value('', _('Never'));
775 o
.value('1', _('Learn'));
776 o
.value('2', _('Always'));
777 o
.depends('multicast', /1/);
779 o
= this.replaceOption(s
, 'brport', cbiFlagTristate
, 'multicast_to_unicast', _('Multicast to unicast'), _('Forward multicast packets as unicast packets on this device.'));
780 o
.sysfs
= '/sys/class/net/%s/brport/multicast_to_unicast'.format(devname
|| 'default');
781 o
.depends('multicast', /1/);
783 o
= this.replaceOption(s
, 'brport', cbiFlagTristate
, 'multicast_fast_leave', _('Enable multicast fast leave'));
784 o
.sysfs
= '/sys/class/net/%s/brport/multicast_fast_leave'.format(devname
|| 'default');
785 o
.depends('multicast', /1/);
787 o
= this.replaceOption(s
, 'brport', cbiFlagTristate
, 'drop_v4_unicast_in_l2_multicast', _('Drop nested IPv4 unicast'), _('Drop layer 2 multicast frames containing IPv4 unicast packets.'));
788 o
.sysfs
= '/proc/sys/net/ipv4/conf/%s/drop_unicast_in_l2_multicast'.format(devname
|| 'default');
789 o
.depends('multicast', /1/);
791 o
= this.replaceOption(s
, 'brport', cbiFlagTristate
, 'drop_v6_unicast_in_l2_multicast', _('Drop nested IPv6 unicast'), _('Drop layer 2 multicast frames containing IPv6 unicast packets.'));
792 o
.sysfs
= '/proc/sys/net/ipv6/conf/%s/drop_unicast_in_l2_multicast'.format(devname
|| 'default');
793 o
.depends('multicast', /1/);
796 o
= this.replaceOption(s
, 'bridgevlan', form
.Flag
, 'vlan_filtering', _('Enable VLAN filtering'));
797 o
.depends('type', 'bridge');
798 o
.updateDefaultValue = function(section_id
) {
799 var device
= uci
.get('network', s
.section
, 'name'),
800 uielem
= this.getUIElement(section_id
),
803 uci
.sections('network', 'bridge-vlan', function(bvs
) {
804 has_vlans
= has_vlans
|| (bvs
.device
== device
);
807 this.default = has_vlans
? this.enabled
: this.disabled
;
809 if (uielem
&& !uielem
.isChanged())
810 uielem
.setValue(this.default);
813 o
= this.replaceOption(s
, 'bridgevlan', form
.SectionValue
, 'bridge-vlan', form
.TableSection
, 'bridge-vlan');
814 o
.depends('type', 'bridge');
820 ss
.renderHeaderRows = function(/* ... */) {
821 var node
= form
.TableSection
.prototype.renderHeaderRows
.apply(this, arguments
);
823 node
.querySelectorAll('.th').forEach(function(th
) {
824 th
.classList
.add('left');
825 th
.classList
.add('middle');
831 ss
.filter = function(section_id
) {
832 var devname
= uci
.get('network', s
.section
, 'name');
833 return (uci
.get('network', section_id
, 'device') == devname
);
836 ss
.render = function(/* ... */) {
837 return form
.TableSection
.prototype.render
.apply(this, arguments
).then(L
.bind(function(node
) {
838 node
.style
.overflow
= 'auto hidden';
839 node
.style
.paddingTop
= '1em';
842 this.node
.parentNode
.replaceChild(node
, this.node
);
850 ss
.redraw = function() {
851 return this.load().then(L
.bind(this.render
, this));
854 ss
.updatePorts = function(ports
) {
855 var devices
= ports
.map(function(port
) {
856 return network
.instantiateDevice(port
)
857 }).filter(function(dev
) {
858 return dev
.getType() != 'wifi' || dev
.isUp();
859 }).sort(function(a
, b
) {
860 return L
.naturalCompare(a
.getName(), b
.getName());
863 this.children
= this.children
.filter(function(opt
) { return !opt
.option
.match(/^port_/) });
865 for (var i
= 0; i
< devices
.length
; i
++) {
866 o
= ss
.option(cbiTagValue
, 'port_%s'.format(sfh(devices
[i
].getName())), renderDevBadge(devices
[i
]), renderPortStatus(devices
[i
]));
867 o
.port
= devices
[i
].getName();
870 var section_ids
= this.cfgsections(),
871 device_names
= devices
.reduce(function(names
, dev
) { names
[dev
.getName()] = true; return names
}, {});
873 for (var i
= 0; i
< section_ids
.length
; i
++) {
874 var old_spec
= L
.toArray(uci
.get('network', section_ids
[i
], 'ports')),
875 new_spec
= old_spec
.filter(function(spec
) { return device_names
[spec
.replace(/:[ut*]+$/, '')] });
877 if (old_spec
.length
!= new_spec
.length
)
878 uci
.set('network', section_ids
[i
], 'ports', new_spec
.length
? new_spec
: null);
882 ss
.handleAdd = function(ev
) {
883 return s
.parse().then(L
.bind(function() {
884 var device
= uci
.get('network', s
.section
, 'name'),
885 section_ids
= this.cfgsections(),
892 for (var i
= 0; i
< section_ids
.length
; i
++) {
893 var vid
= +uci
.get('network', section_ids
[i
], 'vlan');
895 if (vid
> max_vlan_id
)
899 section_id
= uci
.add('network', 'bridge-vlan');
900 uci
.set('network', section_id
, 'device', device
);
901 uci
.set('network', section_id
, 'vlan', max_vlan_id
+ 1);
903 s
.children
.forEach(function(opt
) {
904 switch (opt
.option
) {
907 var input
= opt
.map
.findElement('id', 'widget.%s'.format(opt
.cbid(s
.section
)));
909 input
.disabled
= true;
914 s
.getOption('vlan_filtering').updateDefaultValue(s
.section
);
916 s
.map
.addedVLANs
= s
.map
.addedVLANs
|| [];
917 s
.map
.addedVLANs
.push(section_id
);
919 return this.redraw();
923 o
= ss
.option(form
.Value
, 'vlan', _('VLAN ID'));
924 o
.datatype
= 'range(1, 4094)';
926 o
.renderWidget = function(/* ... */) {
927 var node
= form
.Value
.prototype.renderWidget
.apply(this, arguments
);
929 node
.style
.width
= '5em';
934 o
.validate = function(section_id
, value
) {
935 var section_ids
= this.section
.cfgsections();
937 for (var i
= 0; i
< section_ids
.length
; i
++) {
938 if (section_ids
[i
] == section_id
)
941 if (uci
.get('network', section_ids
[i
], 'vlan') == value
)
942 return _('The VLAN ID must be unique');
948 o
= ss
.option(form
.Flag
, 'local', _('Local'));
949 o
.default = o
.enabled
;
955 L
.toArray(uci
.get('network', s
.section
, 'ports')).forEach(function(port
) {
956 seen_ports
[port
] = true;
959 uci
.sections('network', 'bridge-vlan', function(bvs
) {
960 if (uci
.get('network', s
.section
, 'name') != bvs
.device
)
963 L
.toArray(bvs
.ports
).forEach(function(portspec
) {
964 var m
= portspec
.match(/^([^:]+)(?::[ut*]+)?$/);
967 seen_ports
[m
[1]] = true;
971 for (var port_name
in seen_ports
)
972 ports
.push(port_name
);
974 ss
.updatePorts(ports
);
977 updateDevBadge
: updateDevBadge
,
978 updatePortStatus
: updatePortStatus