11 'require tools.widgets as widgets';
12 'require tools.network as nettools';
14 var isReadonlyView
= !L
.hasViewPermission() || null;
16 function count_changes(section_id
) {
17 var changes
= ui
.changes
.changes
, n
= 0;
19 if (!L
.isObject(changes
))
22 if (Array
.isArray(changes
.network
))
23 for (var i
= 0; i
< changes
.network
.length
; i
++)
24 n
+= (changes
.network
[i
][1] == section_id
);
26 if (Array
.isArray(changes
.dhcp
))
27 for (var i
= 0; i
< changes
.dhcp
.length
; i
++)
28 n
+= (changes
.dhcp
[i
][1] == section_id
);
33 function render_iface(dev
, alias
) {
34 var type
= dev
? dev
.getType() : 'ethernet',
35 up
= dev
? dev
.isUp() : false;
37 return E('span', { class: 'cbi-tooltip-container' }, [
38 E('img', { 'class' : 'middle', 'src': L
.resource('icons/%s%s.png').format(
39 alias
? 'alias' : type
,
40 up
? '' : '_disabled') }),
41 E('span', { 'class': 'cbi-tooltip ifacebadge large' }, [
42 E('img', { 'src': L
.resource('icons/%s%s.png').format(
43 type
, up
? '' : '_disabled') }),
44 L
.itemlist(E('span', { 'class': 'left' }), [
45 _('Type'), dev
? dev
.getTypeI18n() : null,
46 _('Device'), dev
? dev
.getName() : _('Not present'),
47 _('Connected'), up
? _('yes') : _('no'),
48 _('MAC'), dev
? dev
.getMAC() : null,
49 _('RX'), dev
? '%.2mB (%d %s)'.format(dev
.getRXBytes(), dev
.getRXPackets(), _('Pkts.')) : null,
50 _('TX'), dev
? '%.2mB (%d %s)'.format(dev
.getTXBytes(), dev
.getTXPackets(), _('Pkts.')) : null
56 function render_status(node
, ifc
, with_device
) {
57 var desc
= null, c
= [];
60 desc
= _('Virtual dynamic interface');
61 else if (ifc
.isAlias())
62 desc
= _('Alias Interface');
63 else if (!uci
.get('network', ifc
.getName()))
64 return L
.itemlist(node
, [
65 null, E('em', _('Interface is marked for deletion'))
68 var i18n
= ifc
.getI18n();
70 desc
= desc
? '%s (%s)'.format(desc
, i18n
) : i18n
;
72 var changecount
= with_device
? 0 : count_changes(ifc
.getName()),
73 ipaddrs
= changecount
? [] : ifc
.getIPAddrs(),
74 ip6addrs
= changecount
? [] : ifc
.getIP6Addrs(),
75 errors
= ifc
.getErrors(),
76 maindev
= ifc
.getL3Device() || ifc
.getDevice(),
77 macaddr
= maindev
? maindev
.getMAC() : null;
79 return L
.itemlist(node
, [
80 _('Protocol'), with_device
? null : (desc
|| '?'),
81 _('Device'), with_device
? (maindev
? maindev
.getShortName() : E('em', _('Not present'))) : null,
82 _('Uptime'), (!changecount
&& ifc
.isUp()) ? '%t'.format(ifc
.getUptime()) : null,
83 _('MAC'), (!changecount
&& !ifc
.isDynamic() && !ifc
.isAlias() && macaddr
) ? macaddr
: null,
84 _('RX'), (!changecount
&& !ifc
.isDynamic() && !ifc
.isAlias() && maindev
) ? '%.2mB (%d %s)'.format(maindev
.getRXBytes(), maindev
.getRXPackets(), _('Pkts.')) : null,
85 _('TX'), (!changecount
&& !ifc
.isDynamic() && !ifc
.isAlias() && maindev
) ? '%.2mB (%d %s)'.format(maindev
.getTXBytes(), maindev
.getTXPackets(), _('Pkts.')) : null,
86 _('IPv4'), ipaddrs
[0],
87 _('IPv4'), ipaddrs
[1],
88 _('IPv4'), ipaddrs
[2],
89 _('IPv4'), ipaddrs
[3],
90 _('IPv4'), ipaddrs
[4],
91 _('IPv6'), ip6addrs
[0],
92 _('IPv6'), ip6addrs
[1],
93 _('IPv6'), ip6addrs
[2],
94 _('IPv6'), ip6addrs
[3],
95 _('IPv6'), ip6addrs
[4],
96 _('IPv6'), ip6addrs
[5],
97 _('IPv6'), ip6addrs
[6],
98 _('IPv6'), ip6addrs
[7],
99 _('IPv6'), ip6addrs
[8],
100 _('IPv6'), ip6addrs
[9],
101 _('IPv6-PD'), changecount
? null : ifc
.getIP6Prefix(),
102 _('Information'), with_device
? null : (ifc
.get('auto') != '0' ? null : _('Not started on boot')),
103 _('Error'), errors
? errors
[0] : null,
104 _('Error'), errors
? errors
[1] : null,
105 _('Error'), errors
? errors
[2] : null,
106 _('Error'), errors
? errors
[3] : null,
107 _('Error'), errors
? errors
[4] : null,
108 null, changecount
? E('a', {
110 click
: L
.bind(ui
.changes
.displayChanges
, ui
.changes
)
111 }, _('Interface has %d pending changes').format(changecount
)) : null
115 function render_modal_status(node
, ifc
) {
116 var dev
= ifc
? (ifc
.getDevice() || ifc
.getL3Device() || ifc
.getL3Device()) : null;
120 'src': L
.resource('icons/%s%s.png').format(dev
? dev
.getType() : 'ethernet', (dev
&& dev
.isUp()) ? '' : '_disabled'),
121 'title': dev
? dev
.getTypeI18n() : _('Not present')
123 ifc
? render_status(E('span'), ifc
, true) : E('em', _('Interface not present or not connected yet.'))
129 function render_ifacebox_status(node
, ifc
) {
130 var dev
= ifc
.getL3Device() || ifc
.getDevice(),
131 subdevs
= dev
? dev
.getPorts() : null,
132 c
= [ render_iface(dev
, ifc
.isAlias()) ];
134 if (subdevs
&& subdevs
.length
) {
137 for (var j
= 0; j
< subdevs
.length
; j
++)
138 sifs
.push(render_iface(subdevs
[j
]));
142 c
.push(E('span', {}, sifs
));
146 c
.push(E('small', {}, ifc
.isAlias() ? _('Alias of "%s"').format(ifc
.isAlias())
147 : (dev
? dev
.getName() : E('em', _('Not present')))));
149 dom
.content(node
, c
);
151 return firewall
.getZoneByNetwork(ifc
.getName()).then(L
.bind(function(zone
) {
152 this.style
.backgroundColor
= zone
? zone
.getColor() : '#EEEEEE';
153 this.title
= zone
? _('Part of zone %q').format(zone
.getName()) : _('No zone assigned');
154 }, node
.previousElementSibling
));
157 function iface_updown(up
, id
, ev
, force
) {
158 var row
= document
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id
)),
159 dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
160 btns
= row
.querySelectorAll('.cbi-section-actions .reconnect, .cbi-section-actions .down');
163 btns
[+!up
].classList
.add('spinning');
165 btns
[0].disabled
= true;
166 btns
[1].disabled
= true;
169 L
.resolveDefault(fs
.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res
) {
170 var info
= null; try { info
= JSON
.parse(res
); } catch(e
) {}
172 if (L
.isObject(info
) &&
173 Array
.isArray(info
.inbound_interfaces
) &&
174 info
.inbound_interfaces
.filter(function(i
) { return i
== id
})[0]) {
176 ui
.showModal(_('Confirm disconnect'), [
177 E('p', _('You appear to be currently connected to the device via the "%h" interface. Do you really want to shut down the interface?').format(id
)),
178 E('div', { 'class': 'right' }, [
180 'class': 'cbi-button cbi-button-neutral',
181 'click': function(ev
) {
182 btns
[1].classList
.remove('spinning');
183 btns
[1].disabled
= false;
184 btns
[0].disabled
= false;
191 'class': 'cbi-button cbi-button-negative important',
192 'click': function(ev
) {
193 dsc
.setAttribute('disconnect', '');
194 dom
.content(dsc
, E('em', _('Interface is shutting down...')));
203 dsc
.setAttribute('disconnect', '');
204 dom
.content(dsc
, E('em', _('Interface is shutting down...')));
209 dsc
.setAttribute(up
? 'reconnect' : 'disconnect', force
? 'force' : '');
210 dom
.content(dsc
, E('em', up
? _('Interface is reconnecting...') : _('Interface is shutting down...')));
214 function get_netmask(s
, use_cfgvalue
) {
215 var readfn
= use_cfgvalue
? 'cfgvalue' : 'formvalue',
216 addrs
= L
.toArray(s
[readfn
](s
.section
, 'ipaddr')),
217 mask
= s
[readfn
](s
.section
, 'netmask'),
218 firstsubnet
= mask
? addrs
[0] + '/' + mask
: addrs
.filter(function(a
) { return a
.indexOf('/') > 0 })[0];
220 if (firstsubnet
== null)
223 var subnetmask
= firstsubnet
.split('/')[1];
225 if (!isNaN(subnetmask
))
226 subnetmask
= network
.prefixToMask(+subnetmask
);
231 function has_peerdns(proto
) {
248 var cbiRichListValue
= form
.ListValue
.extend({
249 renderWidget: function(section_id
, option_index
, cfgvalue
) {
250 var choices
= this.transformChoices();
251 var widget
= new ui
.Dropdown((cfgvalue
!= null) ? cfgvalue
: this.default, choices
, {
252 id
: this.cbid(section_id
),
255 select_placeholder
: this.select_placeholder
|| this.placeholder
,
256 custom_placeholder
: this.custom_placeholder
|| this.placeholder
,
257 validate
: L
.bind(this.validate
, this, section_id
),
258 disabled
: (this.readonly
!= null) ? this.readonly
: this.map
.readonly
261 return widget
.render();
264 value: function(value
, title
, description
) {
266 form
.ListValue
.prototype.value
.call(this, value
, E([], [
267 E('span', { 'class': 'hide-open' }, [ title
]),
268 E('div', { 'class': 'hide-close', 'style': 'min-width:25vw' }, [
269 E('strong', [ title
]),
271 E('span', { 'style': 'white-space:normal' }, description
)
276 form
.ListValue
.prototype.value
.call(this, value
, title
);
282 poll_status: function(map
, networks
) {
283 var resolveZone
= null;
285 for (var i
= 0; i
< networks
.length
; i
++) {
286 var ifc
= networks
[i
],
287 row
= map
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc
.getName()));
292 var dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
293 box
= row
.querySelector('[data-name="_ifacebox"] .ifacebox-body'),
294 btn1
= row
.querySelector('.cbi-section-actions .reconnect'),
295 btn2
= row
.querySelector('.cbi-section-actions .down'),
296 stat
= document
.querySelector('[id="%s-ifc-status"]'.format(ifc
.getName())),
297 resolveZone
= render_ifacebox_status(box
, ifc
),
298 disabled
= ifc
? !ifc
.isUp() : true,
299 dynamic
= ifc
? ifc
.isDynamic() : false;
301 if (dsc
.hasAttribute('reconnect')) {
302 dom
.content(dsc
, E('em', _('Interface is starting...')));
304 else if (dsc
.hasAttribute('disconnect')) {
305 dom
.content(dsc
, E('em', _('Interface is stopping...')));
307 else if (ifc
.getProtocol() || uci
.get('network', ifc
.getName()) == null) {
308 render_status(dsc
, ifc
, false);
310 else if (!ifc
.getProtocol()) {
311 var e
= map
.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc
.getName()));
312 if (e
) e
.disabled
= true;
314 var link
= L
.url('admin/system/opkg') + '?query=luci-proto';
316 E('em', _('Unsupported protocol type.')), E('br'),
317 E('a', { href
: link
}, _('Install protocol extensions...'))
321 dom
.content(dsc
, E('em', _('Interface not present or not connected yet.')));
325 var dev
= ifc
.getDevice();
328 'src': L
.resource('icons/%s%s.png').format(dev
? dev
.getType() : 'ethernet', (dev
&& dev
.isUp()) ? '' : '_disabled'),
329 'title': dev
? dev
.getTypeI18n() : _('Not present')
331 render_status(E('span'), ifc
, true)
335 btn1
.disabled
= isReadonlyView
|| btn1
.classList
.contains('spinning') || btn2
.classList
.contains('spinning') || dynamic
;
336 btn2
.disabled
= isReadonlyView
|| btn1
.classList
.contains('spinning') || btn2
.classList
.contains('spinning') || dynamic
|| disabled
;
339 document
.querySelectorAll('.port-status-device[data-device]').forEach(function(node
) {
340 nettools
.updateDevBadge(node
, network
.instantiateDevice(node
.getAttribute('data-device')));
343 document
.querySelectorAll('.port-status-link[data-device]').forEach(function(node
) {
344 nettools
.updatePortStatus(node
, network
.instantiateDevice(node
.getAttribute('data-device')));
347 return Promise
.all([ resolveZone
, network
.flushCache() ]);
352 network
.getDSLModemType(),
353 network
.getDevices(),
354 fs
.lines('/etc/iproute2/rt_tables'),
355 L
.resolveDefault(fs
.read('/usr/lib/opkg/info/netifd.control')),
360 interfaceBridgeWithIfnameSections: function() {
361 return uci
.sections('network', 'interface').filter(function(ns
) {
362 return ns
.type
== 'bridge' && !ns
.ports
&& ns
.ifname
;
366 deviceWithIfnameSections: function() {
367 return uci
.sections('network', 'device').filter(function(ns
) {
368 return ns
.type
== 'bridge' && !ns
.ports
&& ns
.ifname
;
372 interfaceWithIfnameSections: function() {
373 return uci
.sections('network', 'interface').filter(function(ns
) {
374 return !ns
.device
&& ns
.ifname
;
378 handleBridgeMigration: function(ev
) {
381 this.interfaceBridgeWithIfnameSections().forEach(function(ns
) {
382 var device_name
= 'br-' + ns
['.name'];
384 tasks
.push(uci
.callAdd('network', 'device', null, {
387 'ports': L
.toArray(ns
.ifname
),
389 'macaddr': ns
.macaddr
,
390 'igmp_snooping': ns
.igmp_snooping
393 tasks
.push(uci
.callSet('network', ns
['.name'], {
399 'device': device_name
403 return Promise
.all(tasks
)
404 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
405 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
408 renderBridgeMigration: function() {
409 ui
.showModal(_('Network bridge configuration migration'), [
410 E('p', _('The existing network configuration needs to be changed for LuCI to function properly.')),
411 E('p', _('Upon pressing "Continue", bridges configuration will be updated and the network will be restarted to apply the updated configuration.')),
412 E('div', { 'class': 'right' },
414 'class': 'btn cbi-button-action important',
415 'click': ui
.createHandlerFn(this, 'handleBridgeMigration')
420 handleIfnameMigration: function(ev
) {
423 this.deviceWithIfnameSections().forEach(function(ds
) {
424 tasks
.push(uci
.callSet('network', ds
['.name'], {
426 'ports': L
.toArray(ds
.ifname
)
430 this.interfaceWithIfnameSections().forEach(function(ns
) {
431 tasks
.push(uci
.callSet('network', ns
['.name'], {
437 return Promise
.all(tasks
)
438 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
439 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
442 renderIfnameMigration: function() {
443 ui
.showModal(_('Network ifname configuration migration'), [
444 E('p', _('The existing network configuration needs to be changed for LuCI to function properly.')),
445 E('p', _('Upon pressing "Continue", ifname options will get renamed and the network will be restarted to apply the updated configuration.')),
446 E('div', { 'class': 'right' },
448 'class': 'btn cbi-button-action important',
449 'click': ui
.createHandlerFn(this, 'handleIfnameMigration')
454 render: function(data
) {
455 var netifdVersion
= (data
[3] || '').match(/Version: ([^\n]+)/);
457 if (netifdVersion
&& netifdVersion
[1] >= "2021-05-26") {
458 if (this.interfaceBridgeWithIfnameSections().length
)
459 return this.renderBridgeMigration();
460 else if (this.deviceWithIfnameSections().length
|| this.interfaceWithIfnameSections().length
)
461 return this.renderIfnameMigration();
464 var dslModemType
= data
[0],
468 var rtTables
= data
[2].map(function(l
) {
469 var m
= l
.trim().match(/^(\d+)\s+(\S+)$/);
470 return m
? [ +m
[1], m
[2] ] : null;
471 }).filter(function(e
) {
472 return e
&& e
[0] > 0;
475 m
= new form
.Map('network');
479 s
= m
.section(form
.GridSection
, 'interface', _('Interfaces'));
482 s
.addbtntitle
= _('Add new interface...');
484 s
.load = function() {
486 network
.getNetworks(),
488 ]).then(L
.bind(function(data
) {
489 this.networks
= data
[0];
490 this.zones
= data
[1];
494 s
.tab('general', _('General Settings'));
495 s
.tab('advanced', _('Advanced Settings'));
496 s
.tab('physical', _('Physical Settings'));
497 s
.tab('brport', _('Bridge port specific options'));
498 s
.tab('bridgevlan', _('Bridge VLAN filtering'));
499 s
.tab('firewall', _('Firewall Settings'));
500 s
.tab('dhcp', _('DHCP Server'));
502 s
.cfgsections = function() {
503 return this.networks
.map(function(n
) { return n
.getName() })
504 .filter(function(n
) { return n
!= 'loopback' });
507 s
.modaltitle = function(section_id
) {
508 return _('Interfaces') + ' » ' + section_id
;
511 s
.renderRowActions = function(section_id
) {
512 var tdEl
= this.super('renderRowActions', [ section_id
, _('Edit') ]),
513 net
= this.networks
.filter(function(n
) { return n
.getName() == section_id
})[0],
514 disabled
= net
? !net
.isUp() : true,
515 dynamic
= net
? net
.isDynamic() : false;
517 dom
.content(tdEl
.lastChild
, [
519 'class': 'cbi-button cbi-button-neutral reconnect',
520 'click': iface_updown
.bind(this, true, section_id
),
521 'title': _('Reconnect this interface'),
522 'disabled': dynamic
? 'disabled' : null
525 'class': 'cbi-button cbi-button-neutral down',
526 'click': iface_updown
.bind(this, false, section_id
),
527 'title': _('Shutdown this interface'),
528 'disabled': (dynamic
|| disabled
) ? 'disabled' : null
530 tdEl
.lastChild
.firstChild
,
531 tdEl
.lastChild
.lastChild
534 if (!dynamic
&& net
&& !uci
.get('network', net
.getName())) {
535 tdEl
.lastChild
.childNodes
[0].disabled
= true;
536 tdEl
.lastChild
.childNodes
[2].disabled
= true;
537 tdEl
.lastChild
.childNodes
[3].disabled
= true;
543 s
.addModalOptions = function(s
) {
544 var protoval
= uci
.get('network', s
.section
, 'proto'),
545 protoclass
= protoval
? network
.getProtocol(protoval
) : null,
546 o
, proto_select
, proto_switch
, type
, stp
, igmp
, ss
, so
;
551 return network
.getNetwork(s
.section
).then(L
.bind(function(ifc
) {
552 var protocols
= network
.getProtocols();
554 protocols
.sort(function(a
, b
) {
555 return L
.naturalCompare(a
.getProtocol(), b
.getProtocol());
558 o
= s
.taboption('general', form
.DummyValue
, '_ifacestat_modal', _('Status'));
560 o
.cfgvalue
= L
.bind(function(section_id
) {
561 var net
= this.networks
.filter(function(n
) { return n
.getName() == section_id
})[0];
563 return render_modal_status(E('div', {
564 'id': '%s-ifc-status'.format(section_id
),
565 'class': 'ifacebadge large'
568 o
.write = function() {};
571 proto_select
= s
.taboption('general', form
.ListValue
, 'proto', _('Protocol'));
572 proto_select
.modalonly
= true;
574 proto_switch
= s
.taboption('general', form
.Button
, '_switch_proto');
575 proto_switch
.modalonly
= true;
576 proto_switch
.title
= _('Really switch protocol?');
577 proto_switch
.inputtitle
= _('Switch protocol');
578 proto_switch
.inputstyle
= 'apply';
579 proto_switch
.onclick
= L
.bind(function(ev
) {
581 .then(L
.bind(m
.load
, m
))
582 .then(L
.bind(m
.render
, m
))
583 .then(L
.bind(this.renderMoreOptionsModal
, this, s
.section
));
586 o
= s
.taboption('general', widgets
.DeviceSelect
, '_net_device', _('Device'));
587 o
.ucioption
= 'device';
590 o
.network
= ifc
.getName();
591 o
.exclude
= '@' + ifc
.getName();
593 o
= s
.taboption('general', form
.Flag
, 'auto', _('Bring up on boot'));
595 o
.default = o
.enabled
;
597 if (L
.hasSystemFeature('firewall')) {
598 o
= s
.taboption('firewall', widgets
.ZoneSelect
, '_zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>custom</em> field to define a new zone and attach the interface to it.'));
599 o
.network
= ifc
.getName();
602 o
.cfgvalue = function(section_id
) {
603 return firewall
.getZoneByNetwork(ifc
.getName()).then(function(zone
) {
604 return (zone
!= null ? zone
.getName() : null);
608 o
.write
= o
.remove = function(section_id
, value
) {
610 firewall
.getZoneByNetwork(ifc
.getName()),
611 (value
!= null) ? firewall
.getZone(value
) : null
612 ]).then(function(data
) {
613 var old_zone
= data
[0],
616 if (old_zone
== null && new_zone
== null && (value
== null || value
== ''))
619 if (old_zone
!= null && new_zone
!= null && old_zone
.getName() == new_zone
.getName())
622 if (old_zone
!= null)
623 old_zone
.deleteNetwork(ifc
.getName());
625 if (new_zone
!= null)
626 new_zone
.addNetwork(ifc
.getName());
627 else if (value
!= null)
628 return firewall
.addZone(value
).then(function(new_zone
) {
629 new_zone
.addNetwork(ifc
.getName());
635 for (var i
= 0; i
< protocols
.length
; i
++) {
636 proto_select
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
638 if (protocols
[i
].getProtocol() != uci
.get('network', s
.section
, 'proto'))
639 proto_switch
.depends('proto', protocols
[i
].getProtocol());
642 if (L
.hasSystemFeature('dnsmasq') || L
.hasSystemFeature('odhcpd')) {
643 o
= s
.taboption('dhcp', form
.SectionValue
, '_dhcp', form
.TypedSection
, 'dhcp');
646 ss
.uciconfig
= 'dhcp';
647 ss
.addremove
= false;
650 ss
.tab('general', _('General Setup'));
651 ss
.tab('advanced', _('Advanced Settings'));
652 ss
.tab('ipv6', _('IPv6 Settings'));
653 ss
.tab('ipv6-ra', _('IPv6 RA Settings'));
655 ss
.filter = function(section_id
) {
656 return (uci
.get('dhcp', section_id
, 'interface') == ifc
.getName());
659 ss
.renderSectionPlaceholder = function() {
660 return E('div', { 'class': 'cbi-section-create' }, [
661 E('p', _('No DHCP Server configured for this interface') + '   '),
663 'class': 'cbi-button cbi-button-add',
664 'title': _('Set up DHCP Server'),
665 'click': ui
.createHandlerFn(this, function(section_id
, ev
) {
666 this.map
.save(function() {
667 uci
.add('dhcp', 'dhcp', section_id
);
668 uci
.set('dhcp', section_id
, 'interface', section_id
);
670 if (protoval
== 'static') {
671 uci
.set('dhcp', section_id
, 'start', 100);
672 uci
.set('dhcp', section_id
, 'limit', 150);
673 uci
.set('dhcp', section_id
, 'leasetime', '12h');
676 uci
.set('dhcp', section_id
, 'ignore', 1);
680 }, _('Set up DHCP Server'))
684 ss
.taboption('general', form
.Flag
, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
686 if (protoval
== 'static') {
687 so
= ss
.taboption('general', form
.Value
, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
689 so
.datatype
= 'or(uinteger,ip4addr("nomask"))';
692 so
= ss
.taboption('general', form
.Value
, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
694 so
.datatype
= 'uinteger';
697 so
= ss
.taboption('general', form
.Value
, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
701 so
= ss
.taboption('advanced', form
.Flag
, 'dynamicdhcp', _('Dynamic <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>'), _('Dynamically allocate DHCP addresses for clients. If disabled, only clients having static leases will be served.'));
702 so
.default = so
.enabled
;
704 ss
.taboption('advanced', form
.Flag
, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
706 // XXX: is this actually useful?
707 //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
709 so
= ss
.taboption('advanced', form
.Value
, 'netmask', _('<abbr title="Internet Protocol Version 4">IPv4</abbr>-Netmask'), _('Override the netmask sent to clients. Normally it is calculated from the subnet that is served.'));
711 so
.datatype
= 'ip4addr';
713 so
.render = function(option_index
, section_id
, in_table
) {
714 this.placeholder
= get_netmask(s
, true);
715 return form
.Value
.prototype.render
.apply(this, [ option_index
, section_id
, in_table
]);
718 so
.validate = function(section_id
, value
) {
719 var uielem
= this.getUIElement(section_id
);
721 uielem
.setPlaceholder(get_netmask(s
, false));
722 return form
.Value
.prototype.validate
.apply(this, [ section_id
, value
]);
725 ss
.taboption('advanced', form
.DynamicList
, 'dhcp_option', _('DHCP-Options'), _('Define additional DHCP options, for example "<code>6,192.168.2.1,192.168.2.2</code>" which advertises different DNS servers to clients.'));
729 var has_other_master
= uci
.sections('dhcp', 'dhcp').filter(function(s
) {
730 return (s
.interface != ifc
.getName() && s
.master
== '1');
733 so
= ss
.taboption('ipv6', form
.Flag
, 'master', _('Designated master'));
734 so
.readonly
= has_other_master
? true : false;
735 so
.description
= has_other_master
736 ? _('Interface "%h" is already marked as designated master.').format(has_other_master
.interface || has_other_master
['.name'])
737 : _('Set this interface as master for RA and DHCPv6 relaying as well as NDP proxying.')
740 so
.validate = function(section_id
, value
) {
741 var hybrid_downstream_desc
= _('Operate in <em>relay mode</em> if a designated master interface is configured and active, otherwise fall back to <em>server mode</em>.'),
742 ndp_downstream_desc
= _('Operate in <em>relay mode</em> if a designated master interface is configured and active, otherwise disable <abbr title="Neighbour Discovery Protocol">NDP</abbr> proxying.'),
743 hybrid_master_desc
= _('Operate in <em>relay mode</em> if an upstream IPv6 prefix is present, otherwise disable service.'),
744 ra_server_allowed
= true,
745 checked
= this.formvalue(section_id
),
746 dhcpv6
= this.section
.getOption('dhcpv6').getUIElement(section_id
),
747 ndp
= this.section
.getOption('ndp').getUIElement(section_id
),
748 ra
= this.section
.getOption('ra').getUIElement(section_id
);
750 /* Assume that serving RAs by default is fine, but disallow it for certain
751 interface protocols such as DHCP, DHCPv6 or the various PPP flavors.
752 The intent is to only allow RA serving for interface protocols doing
753 some kind of static IP config over something resembling a layer 2
768 ra_server_allowed
= false;
772 if (checked
== '1' || !ra_server_allowed
) {
773 dhcpv6
.node
.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
775 if (dhcpv6
.getValue() == 'server')
776 dhcpv6
.setValue('hybrid');
778 ra
.node
.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
780 if (ra
.getValue() == 'server')
781 ra
.setValue('hybrid');
784 if (checked
== '1') {
785 dhcpv6
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
786 ra
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
787 ndp
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
790 if (ra_server_allowed
) {
791 dhcpv6
.node
.querySelector('li[data-value="server"]').removeAttribute('unselectable');
792 ra
.node
.querySelector('li[data-value="server"]').removeAttribute('unselectable');
795 dhcpv6
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_downstream_desc
;
796 ra
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_downstream_desc
;
797 ndp
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= ndp_downstream_desc
;
804 so
= ss
.taboption('ipv6', cbiRichListValue
, 'ra', _('<abbr title="Router Advertisement">RA</abbr>-Service'),
805 _('Configures the operation mode of the <abbr title="Router Advertisement">RA</abbr> service on this interface.'));
806 so
.value('', _('disabled'),
807 _('Do not send any <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages on this interface.'));
808 so
.value('server', _('server mode'),
809 _('Send <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages advertising this device as IPv6 router.'));
810 so
.value('relay', _('relay mode'),
811 _('Forward <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages received on the designated master interface to downstream interfaces.'));
812 so
.value('hybrid', _('hybrid mode'), ' ');
815 so
= ss
.taboption('ipv6-ra', cbiRichListValue
, 'ra_default', _('Default router'),
816 _('Configures the default router advertisement in <abbr title="Router Advertisement">RA</abbr> messages.'));
817 so
.value('', _('automatic'),
818 _('Announce this device as default router if a local IPv6 default route is present.'));
819 so
.value('1', _('on available prefix'),
820 _('Announce this device as default router if a public IPv6 prefix is available, regardless of local default route availability.'));
821 so
.value('2', _('forced'),
822 _('Announce this device as default router regardless of whether a prefix or default route is present.'));
823 so
.depends('ra', 'server');
824 so
.depends({ ra
: 'hybrid', master
: '0' });
826 so
= ss
.taboption('ipv6-ra', form
.Flag
, 'ra_slaac', _('Enable <abbr title="Stateless Address Auto Config">SLAAC</abbr>'),
827 _('Set the autonomous address-configuration flag in the prefix information options of sent <abbr title="Router Advertisement">RA</abbr> messages. When enabled, clients will perform stateless IPv6 address autoconfiguration.'));
828 so
.default = so
.enabled
;
829 so
.depends('ra', 'server');
830 so
.depends({ ra
: 'hybrid', master
: '0' });
832 so
= ss
.taboption('ipv6-ra', cbiRichListValue
, 'ra_flags', _('<abbr title="Router Advertisement">RA</abbr> Flags'),
833 _('Specifies the flags sent in <abbr title="Router Advertisement">RA</abbr> messages, for example to instruct clients to request further information via stateful DHCPv6.'));
834 so
.value('managed-config', _('managed config (M)'),
835 _('The <em>Managed address configuration</em> (M) flag indicates that IPv6 addresses are available via DHCPv6.'));
836 so
.value('other-config', _('other config (O)'),
837 _('The <em>Other configuration</em> (O) flag indicates that other information, such as DNS servers, is available via DHCPv6.'));
838 so
.value('home-agent', _('mobile home agent (H)'),
839 _('The <em>Mobile IPv6 Home Agent</em> (H) flag indicates that the device is also acting as Mobile IPv6 home agent on this link.'));
841 so
.select_placeholder
= _('none');
842 so
.depends('ra', 'server');
843 so
.depends({ ra
: 'hybrid', master
: '0' });
844 so
.cfgvalue = function(section_id
) {
845 var flags
= L
.toArray(uci
.get('dhcp', section_id
, 'ra_flags'));
846 return flags
.length
? flags
: [ 'other-config' ];
848 so
.remove = function(section_id
) {
849 var existing
= L
.toArray(uci
.get('dhcp', section_id
, 'ra_flags'));
850 if (this.isActive(section_id
)) {
851 if (existing
.length
!= 1 || existing
[0] != 'none')
852 uci
.set('dhcp', section_id
, 'ra_flags', [ 'none' ]);
854 else if (existing
.length
) {
855 uci
.unset('dhcp', section_id
, 'ra_flags');
859 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_pref64', _('NAT64 prefix'), _('Announce NAT64 prefix in <abbr title="Router Advertisement">RA</abbr> messages.'));
861 so
.datatype
= 'cidr6';
862 so
.placeholder
= '64:ff9b::/96';
863 so
.depends('ra', 'server');
864 so
.depends({ ra
: 'hybrid', master
: '0' });
866 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_maxinterval', _('Max <abbr title="Router Advertisement">RA</abbr> interval'), _('Maximum time allowed between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 600 seconds.'));
868 so
.datatype
= 'uinteger';
869 so
.placeholder
= '600';
870 so
.depends('ra', 'server');
871 so
.depends({ ra
: 'hybrid', master
: '0' });
873 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_mininterval', _('Min <abbr title="Router Advertisement">RA</abbr> interval'), _('Minimum time allowed between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 200 seconds.'));
875 so
.datatype
= 'uinteger';
876 so
.placeholder
= '200';
877 so
.depends('ra', 'server');
878 so
.depends({ ra
: 'hybrid', master
: '0' });
880 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_lifetime', _('<abbr title="Router Advertisement">RA</abbr> Lifetime'), _('Router Lifetime published in <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages. Maximum is 9000 seconds.'));
882 so
.datatype
= 'range(0, 9000)';
883 so
.placeholder
= '1800';
884 so
.depends('ra', 'server');
885 so
.depends({ ra
: 'hybrid', master
: '0' });
887 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_mtu', _('<abbr title="Router Advertisement">RA</abbr> MTU'), _('The <abbr title="Maximum Transmission Unit">MTU</abbr> to be published in <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages. Minimum is 1280 bytes.'));
889 so
.datatype
= 'range(1280, 65535)';
890 so
.depends('ra', 'server');
891 so
.depends({ ra
: 'hybrid', master
: '0' });
892 so
.load = function(section_id
) {
893 var dev
= ifc
.getL3Device(),
894 path
= dev
? "/proc/sys/net/ipv6/conf/%s/mtu".format(dev
.getName()) : null;
897 dev
? L
.resolveDefault(fs
.read(path
), dev
.getMTU()) : null,
898 this.super('load', [section_id
])
899 ]).then(L
.bind(function(res
) {
900 this.placeholder
= +res
[0];
906 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_hoplimit', _('<abbr title="Router Advertisement">RA</abbr> Hop Limit'), _('The maximum hops to be published in <abbr title="Router Advertisement">RA</abbr> messages. Maximum is 255 hops.'));
908 so
.datatype
= 'range(0, 255)';
909 so
.depends('ra', 'server');
910 so
.depends({ ra
: 'hybrid', master
: '0' });
911 so
.load = function(section_id
) {
912 var dev
= ifc
.getL3Device(),
913 path
= dev
? "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev
.getName()) : null;
916 dev
? L
.resolveDefault(fs
.read(path
), 64) : null,
917 this.super('load', [section_id
])
918 ]).then(L
.bind(function(res
) {
919 this.placeholder
= +res
[0];
926 so
= ss
.taboption('ipv6', cbiRichListValue
, 'dhcpv6', _('DHCPv6-Service'),
927 _('Configures the operation mode of the DHCPv6 service on this interface.'));
928 so
.value('', _('disabled'),
929 _('Do not offer DHCPv6 service on this interface.'));
930 so
.value('server', _('server mode'),
931 _('Provide a DHCPv6 server on this interface and reply to DHCPv6 solicitations and requests.'));
932 so
.value('relay', _('relay mode'),
933 _('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));
934 so
.value('hybrid', _('hybrid mode'), ' ');
936 so
= ss
.taboption('ipv6', form
.Value
, 'dhcpv6_pd_min_len', _('<abbr title="Prefix Delegation">PD</abbr> minimum length'),
937 _('Configures the minimum delegated prefix length assigned to a requesting downstream router, potentially overriding a requested prefix length. If left unspecified, the device will assign the smallest available prefix greater than or equal to the requested prefix.'));
938 so
.datatype
= 'range(1,62)';
939 so
.depends('dhcpv6', 'server');
941 so
= ss
.taboption('ipv6', form
.DynamicList
, 'dns', _('Announced IPv6 DNS servers'),
942 _('Specifies a fixed list of IPv6 DNS server addresses to announce via DHCPv6. If left unspecified, the device will announce itself as IPv6 DNS server unless the <em>Local IPv6 DNS server</em> option is disabled.'));
943 so
.datatype
= 'ip6addr("nomask")'; /* restrict to IPv6 only for now since dnsmasq (DHCPv4) does not honour this option */
944 so
.depends('ra', 'server');
945 so
.depends({ ra
: 'hybrid', master
: '0' });
946 so
.depends('dhcpv6', 'server');
947 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
949 so
= ss
.taboption('ipv6', form
.Flag
, 'dns_service', _('Local IPv6 DNS server'),
950 _('Announce this device as IPv6 DNS server.'));
951 so
.default = so
.enabled
;
952 so
.depends({ ra
: 'server', dns
: /^$/ });
953 so
.depends({ ra
: 'hybrid', dns
: /^$/, master
: '0' });
954 so
.depends({ dhcpv6
: 'server', dns
: /^$/ });
955 so
.depends({ dhcpv6
: 'hybrid', dns
: /^$/, master
: '0' });
957 so
= ss
.taboption('ipv6', form
.DynamicList
, 'domain', _('Announced DNS domains'),
958 _('Specifies a fixed list of DNS search domains to announce via DHCPv6. If left unspecified, the local device DNS search domain will be announced.'));
959 so
.datatype
= 'hostname';
960 so
.depends('ra', 'server');
961 so
.depends({ ra
: 'hybrid', master
: '0' });
962 so
.depends('dhcpv6', 'server');
963 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
966 so
= ss
.taboption('ipv6', cbiRichListValue
, 'ndp', _('<abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy'),
967 _('Configures the operation mode of the NDP proxy service on this interface.'));
968 so
.value('', _('disabled'),
969 _('Do not proxy any <abbr title="Neighbour Discovery Protocol">NDP</abbr> packets.'));
970 so
.value('relay', _('relay mode'),
971 _('Forward <abbr title="Neighbour Discovery Protocol">NDP</abbr> <abbr title="Neighbour Solicitation, Type 135">NS</abbr> and <abbr title="Neighbour Advertisement, Type 136">NA</abbr> messages between the designated master interface and downstream interfaces.'));
972 so
.value('hybrid', _('hybrid mode'), ' ');
975 so
= ss
.taboption('ipv6', form
.Flag
, 'ndproxy_routing', _('Learn routes'), _('Set up routes for proxied IPv6 neighbours.'));
976 so
.default = so
.enabled
;
977 so
.depends('ndp', 'relay');
978 so
.depends('ndp', 'hybrid');
980 so
= ss
.taboption('ipv6', form
.Flag
, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
981 so
.depends({ ndp
: 'relay', master
: '0' });
982 so
.depends({ ndp
: 'hybrid', master
: '0' });
984 so
= ss
.taboption('ipv6', form
.Value
, 'preferred_lifetime', _('IPv6 Prefix Lifetime'), _('Preferred lifetime for a prefix.'));
986 so
.placeholder
= '12h';
987 so
.value('5m', _('5m (5 minutes)'));
988 so
.value('3h', _('3h (3 hours)'));
989 so
.value('12h', _('12h (12 hours - default)'));
990 so
.value('7d', _('7d (7 days)'));
992 //This is a ra_* setting, but its placement is more logical/findable under IPv6 settings.
993 so
= ss
.taboption('ipv6', form
.Flag
, 'ra_useleasetime', _('Follow IPv4 Lifetime'), _('DHCPv4 <code>leasetime</code> is used as limit and preferred lifetime of the IPv6 prefix.'));
997 ifc
.renderFormOptions(s
);
999 // Common interface options
1000 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
1001 o
.default = o
.enabled
;
1003 if (has_peerdns(protoval
)) {
1004 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
1005 o
.default = o
.enabled
;
1008 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'dns', _('Use custom DNS servers'));
1009 if (has_peerdns(protoval
))
1010 o
.depends('peerdns', '0');
1011 o
.datatype
= 'ipaddr';
1013 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'dns_search', _('DNS search domains'));
1014 if (protoval
!= 'static')
1015 o
.depends('peerdns', '0');
1016 o
.datatype
= 'hostname';
1018 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'dns_metric', _('DNS weight'), _('The DNS server entries in the local resolv.conf are primarily sorted by the weight specified here'));
1019 o
.datatype
= 'uinteger';
1020 o
.placeholder
= '0';
1022 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'metric', _('Use gateway metric'));
1023 o
.datatype
= 'uinteger';
1024 o
.placeholder
= '0';
1026 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip4table', _('Override IPv4 routing table'));
1027 o
.datatype
= 'or(uinteger, string)';
1028 for (var i
= 0; i
< rtTables
.length
; i
++)
1029 o
.value(rtTables
[i
][1], '%s (%d)'.format(rtTables
[i
][1], rtTables
[i
][0]));
1031 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6table', _('Override IPv6 routing table'));
1032 o
.datatype
= 'or(uinteger, string)';
1033 for (var i
= 0; i
< rtTables
.length
; i
++)
1034 o
.value(rtTables
[i
][1], '%s (%d)'.format(rtTables
[i
][1], rtTables
[i
][0]));
1036 if (protoval
== 'dhcpv6') {
1037 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'sourcefilter', _('IPv6 source routing'), _('Automatically handle multiple uplink interfaces using source-based policy routing.'));
1038 o
.default = o
.enabled
;
1041 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
1042 o
.default = o
.enabled
;
1044 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6assign', _('IPv6 assignment length'), _('Assign a part of given length of every public IPv6-prefix to this interface'));
1045 o
.value('', _('disabled'));
1047 o
.datatype
= 'max(128)';
1049 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6hint', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
1050 o
.placeholder
= '0';
1051 o
.validate = function(section_id
, value
) {
1052 if (value
== null || value
== '')
1055 var n
= parseInt(value
, 16);
1057 if (!/^(0x)?[0-9a-fA-F]+$/.test(value
) || isNaN(n
) || n
>= 0xffffffff)
1058 return _('Expecting a hexadecimal assignment hint');
1062 for (var i
= 33; i
<= 64; i
++)
1063 o
.depends('ip6assign', String(i
));
1066 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'ip6class', _('IPv6 prefix filter'), _('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));
1067 o
.value('local', 'local (%s)'.format(_('Local ULA')));
1069 var prefixClasses
= {};
1071 this.networks
.forEach(function(net
) {
1072 var prefixes
= net
._ubus('ipv6-prefix');
1073 if (Array
.isArray(prefixes
)) {
1074 prefixes
.forEach(function(pfx
) {
1075 if (L
.isObject(pfx
) && typeof(pfx
['class']) == 'string') {
1076 prefixClasses
[pfx
['class']] = prefixClasses
[pfx
['class']] || {};
1077 prefixClasses
[pfx
['class']][net
.getName()] = true;
1083 Object
.keys(prefixClasses
).sort().forEach(function(c
) {
1084 var networks
= Object
.keys(prefixClasses
[c
]).sort().join(', ');
1085 o
.value(c
, (c
!= networks
) ? '%s (%s)'.format(c
, networks
) : c
);
1089 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6ifaceid', _('IPv6 suffix'), _("Optional. Allowed values: 'eui64', 'random', fixed value like '::1' or '::1:2'. When IPv6 prefix (like 'a:b:c:d::') is received from a delegating server, use the suffix (like '::1') to form the IPv6 address ('a:b:c:d::1') for the interface."));
1090 o
.datatype
= 'ip6hostid';
1091 o
.placeholder
= '::1';
1093 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6weight', _('IPv6 preference'), _('When delegating prefixes to multiple downstreams, interfaces with a higher preference value are considered first when allocating subnets.'));
1094 o
.datatype
= 'uinteger';
1095 o
.placeholder
= '0';
1097 for (var i
= 0; i
< s
.children
.length
; i
++) {
1105 case '_switch_proto':
1106 case '_ifacestat_modal':
1109 case 'igmp_snooping':
1114 for (var j
= 0; j
< protocols
.length
; j
++) {
1115 if (!protocols
[j
].isVirtual()) {
1117 for (var k
= 0; k
< o
.deps
.length
; k
++)
1118 deps
.push(Object
.assign({ proto
: protocols
[j
].getProtocol() }, o
.deps
[k
]));
1120 deps
.push({ proto
: protocols
[j
].getProtocol() });
1128 for (var j
= 0; j
< o
.deps
.length
; j
++)
1129 o
.deps
[j
].proto
= protoval
;
1131 o
.depends('proto', protoval
);
1135 this.activeSection
= s
.section
;
1139 s
.handleModalCancel = function(/* ... */) {
1140 var type
= uci
.get('network', this.activeSection
|| this.addedSection
, 'type'),
1141 device
= (type
== 'bridge') ? 'br-%s'.format(this.activeSection
|| this.addedSection
) : null;
1143 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1144 if (device
!= null && bvs
.device
== device
)
1145 uci
.remove('network', bvs
['.name']);
1148 return form
.GridSection
.prototype.handleModalCancel
.apply(this, arguments
);
1151 s
.handleAdd = function(ev
) {
1152 var m2
= new form
.Map('network'),
1153 s2
= m2
.section(form
.NamedSection
, '_new_'),
1154 protocols
= network
.getProtocols(),
1155 proto
, name
, device
;
1157 protocols
.sort(function(a
, b
) {
1158 return L
.naturalCompare(a
.getProtocol(), b
.getProtocol());
1161 s2
.render = function() {
1162 return Promise
.all([
1164 this.renderUCISection('_new_')
1165 ]).then(this.renderContents
.bind(this));
1168 name
= s2
.option(form
.Value
, 'name', _('Name'));
1169 name
.rmempty
= false;
1170 name
.datatype
= 'uciname';
1171 name
.placeholder
= _('New interface name…');
1172 name
.validate = function(section_id
, value
) {
1173 if (uci
.get('network', value
) != null)
1174 return _('The interface name is already used');
1176 var pr
= network
.getProtocol(proto
.formvalue(section_id
), value
),
1177 ifname
= pr
.isVirtual() ? '%s-%s'.format(pr
.getProtocol(), value
) : 'br-%s'.format(value
);
1179 if (value
.length
> 15)
1180 return _('The interface name is too long');
1185 proto
= s2
.option(form
.ListValue
, 'proto', _('Protocol'));
1186 proto
.validate
= name
.validate
;
1188 device
= s2
.option(widgets
.DeviceSelect
, 'device', _('Device'));
1189 device
.noaliases
= false;
1190 device
.optional
= false;
1192 for (var i
= 0; i
< protocols
.length
; i
++) {
1193 proto
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
1195 if (!protocols
[i
].isVirtual())
1196 device
.depends('proto', protocols
[i
].getProtocol());
1199 m2
.render().then(L
.bind(function(nodes
) {
1200 ui
.showModal(_('Add new interface...'), [
1202 E('div', { 'class': 'right' }, [
1205 'click': ui
.hideModal
1206 }, _('Cancel')), ' ',
1208 'class': 'cbi-button cbi-button-positive important',
1209 'click': ui
.createHandlerFn(this, function(ev
) {
1210 var nameval
= name
.isValid('_new_') ? name
.formvalue('_new_') : null,
1211 protoval
= proto
.isValid('_new_') ? proto
.formvalue('_new_') : null,
1212 protoclass
= protoval
? network
.getProtocol(protoval
, nameval
) : null;
1214 if (nameval
== null || protoval
== null || nameval
== '' || protoval
== '')
1217 return protoclass
.isCreateable(nameval
).then(function(checkval
) {
1218 if (checkval
!= null) {
1219 ui
.addNotification(null,
1220 E('p', _('New interface for "%s" can not be created: %s').format(protoclass
.getI18n(), checkval
)));
1225 return m
.save(function() {
1226 var section_id
= uci
.add('network', 'interface', nameval
);
1228 protoclass
.set('proto', protoval
);
1229 protoclass
.addDevice(device
.formvalue('_new_'));
1231 m
.children
[0].addedSection
= section_id
;
1234 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
1235 }).then(L
.bind(m
.children
[0].renderMoreOptionsModal
, m
.children
[0], nameval
));
1238 }, _('Create interface'))
1242 nodes
.querySelector('[id="%s"] input[type="text"]'.format(name
.cbid('_new_'))).focus();
1246 s
.handleRemove = function(section_id
, ev
) {
1247 return network
.deleteNetwork(section_id
).then(L
.bind(function(section_id
, ev
) {
1248 return form
.GridSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1249 }, this, section_id
, ev
));
1252 o
= s
.option(form
.DummyValue
, '_ifacebox');
1253 o
.modalonly
= false;
1254 o
.textvalue = function(section_id
) {
1255 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0],
1256 zone
= net
? this.section
.zones
.filter(function(z
) { return !!z
.getNetworks().filter(function(n
) { return n
== section_id
})[0] })[0] : null;
1261 var node
= E('div', { 'class': 'ifacebox' }, [
1263 'class': 'ifacebox-head',
1264 'style': firewall
.getZoneColorStyle(zone
),
1265 'title': zone
? _('Part of zone %q').format(zone
.getName()) : _('No zone assigned')
1266 }, E('strong', net
.getName())),
1268 'class': 'ifacebox-body',
1269 'id': '%s-ifc-devices'.format(section_id
),
1270 'data-network': section_id
1273 'src': L
.resource('icons/ethernet_disabled.png'),
1274 'style': 'width:16px; height:16px'
1276 E('br'), E('small', '?')
1280 render_ifacebox_status(node
.childNodes
[1], net
);
1285 o
= s
.option(form
.DummyValue
, '_ifacestat');
1286 o
.modalonly
= false;
1287 o
.textvalue = function(section_id
) {
1288 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0];
1293 var node
= E('div', { 'id': '%s-ifc-description'.format(section_id
) });
1295 render_status(node
, net
, false);
1300 o
= s
.taboption('advanced', form
.Flag
, 'delegate', _('Use builtin IPv6-management'));
1302 o
.default = o
.enabled
;
1304 o
= s
.taboption('advanced', form
.Flag
, 'force_link', _('Force link'), _('Set interface properties regardless of the link carrier (If set, carrier sense events do not invoke hotplug handlers).'));
1307 '1': [{ proto
: 'static' }],
1312 // Device configuration
1313 s
= m
.section(form
.GridSection
, 'device', _('Devices'));
1316 s
.addbtntitle
= _('Add device configuration…');
1318 s
.cfgsections = function() {
1319 var sections
= uci
.sections('network', 'device'),
1320 section_ids
= sections
.sort(function(a
, b
) { return L
.naturalCompare(a
.name
, b
.name
) }).map(function(s
) { return s
['.name'] });
1322 for (var i
= 0; i
< netDevs
.length
; i
++) {
1323 if (sections
.filter(function(s
) { return s
.name
== netDevs
[i
].getName() }).length
)
1326 if (netDevs
[i
].getType() == 'wifi' && !netDevs
[i
].isUp())
1329 /* Unless http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html is implemented,
1330 we cannot properly redefine bridges as devices, so filter them away for now... */
1332 var m
= netDevs
[i
].isBridge() ? netDevs
[i
].getName().match(/^br-([A-Za-z0-9_]+)$/) : null,
1333 s
= m
? uci
.get('network', m
[1]) : null;
1335 if (s
&& s
['.type'] == 'interface' && s
.type
== 'bridge')
1338 section_ids
.push('dev:%s'.format(netDevs
[i
].getName()));
1344 s
.renderMoreOptionsModal = function(section_id
, ev
) {
1345 var m
= section_id
.match(/^dev:(.+)$/);
1348 var devtype
= getDevType(section_id
);
1350 section_id
= uci
.add('network', 'device');
1352 uci
.set('network', section_id
, 'name', m
[1]);
1353 uci
.set('network', section_id
, 'type', (devtype
!= 'ethernet') ? devtype
: null);
1355 this.addedSection
= section_id
;
1358 return this.super('renderMoreOptionsModal', [section_id
, ev
]);
1361 s
.renderRowActions = function(section_id
) {
1362 var trEl
= this.super('renderRowActions', [ section_id
, _('Configure…') ]),
1363 deleteBtn
= trEl
.querySelector('button:last-child');
1365 deleteBtn
.firstChild
.data
= _('Unconfigure');
1366 deleteBtn
.setAttribute('title', _('Remove related device settings from the configuration'));
1367 deleteBtn
.disabled
= section_id
.match(/^dev:/) ? true : null;
1372 s
.modaltitle = function(section_id
) {
1373 var m
= section_id
.match(/^dev:(.+)$/),
1374 name
= m
? m
[1] : uci
.get('network', section_id
, 'name');
1376 return name
? '%s: %q'.format(getDevTypeDesc(section_id
), name
) : _('Add device configuration');
1379 s
.addModalOptions = function(s
) {
1380 var isNew
= (uci
.get('network', s
.section
, 'name') == null),
1381 dev
= getDevice(s
.section
);
1383 nettools
.addDeviceOptions(s
, dev
, isNew
);
1386 s
.handleModalCancel = function(map
/*, ... */) {
1387 var name
= uci
.get('network', this.addedSection
, 'name')
1389 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1390 if (name
!= null && bvs
.device
== name
)
1391 uci
.remove('network', bvs
['.name']);
1395 for (var i
= 0; i
< map
.addedVLANs
.length
; i
++)
1396 uci
.remove('network', map
.addedVLANs
[i
]);
1398 if (this.addedSection
)
1399 uci
.remove('network', this.addedSection
);
1401 return form
.GridSection
.prototype.handleModalCancel
.apply(this, arguments
);
1404 s
.handleRemove = function(section_id
/*, ... */) {
1405 var name
= uci
.get('network', section_id
, 'name'),
1406 type
= uci
.get('network', section_id
, 'type');
1408 if (name
!= null && type
== 'bridge') {
1409 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1410 if (bvs
.device
== name
)
1411 uci
.remove('network', bvs
['.name']);
1415 return form
.GridSection
.prototype.handleRemove
.apply(this, arguments
);
1418 function getDevice(section_id
) {
1419 var m
= section_id
.match(/^dev:(.+)$/),
1420 name
= m
? m
[1] : uci
.get('network', section_id
, 'name');
1422 return netDevs
.filter(function(d
) { return d
.getName() == name
})[0];
1425 function getDevType(section_id
) {
1426 var dev
= getDevice(section_id
),
1427 cfg
= uci
.get('network', section_id
),
1428 type
= cfg
? (uci
.get('network', section_id
, 'type') || 'ethernet') : (dev
? dev
.getType() : '');
1462 function getDevTypeDesc(section_id
) {
1463 switch (getDevType(section_id
) || '') {
1465 return E('em', [ _('Device not present') ]);
1468 return _('VLAN (802.1q)');
1471 return _('VLAN (802.1ad)');
1474 return _('Bridge device');
1477 return _('Tunnel device');
1480 return _('MAC VLAN');
1483 return _('Virtual Ethernet');
1486 return _('Network device');
1490 o
= s
.option(form
.DummyValue
, 'name', _('Device'));
1491 o
.modalonly
= false;
1492 o
.textvalue = function(section_id
) {
1493 var dev
= getDevice(section_id
),
1494 ext
= section_id
.match(/^dev:/),
1495 icon
= render_iface(dev
);
1498 icon
.querySelector('img').style
.opacity
= '.5';
1500 return E('span', { 'class': 'ifacebadge' }, [
1502 E('span', { 'style': ext
? 'opacity:.5' : null }, [
1503 dev
? dev
.getName() : (uci
.get('network', section_id
, 'name') || '?')
1508 o
= s
.option(form
.DummyValue
, 'type', _('Type'));
1509 o
.textvalue
= getDevTypeDesc
;
1510 o
.modalonly
= false;
1512 o
= s
.option(form
.DummyValue
, 'macaddr', _('MAC Address'));
1513 o
.modalonly
= false;
1514 o
.textvalue = function(section_id
) {
1515 var dev
= getDevice(section_id
),
1516 val
= uci
.get('network', section_id
, 'macaddr'),
1517 mac
= dev
? dev
.getMAC() : null;
1519 return val
? E('strong', {
1520 'data-tooltip': _('The value is overridden by configuration.')
1521 }, [ val
.toUpperCase() ]) : (mac
|| '-');
1524 o
= s
.option(form
.DummyValue
, 'mtu', _('MTU'));
1525 o
.modalonly
= false;
1526 o
.textvalue = function(section_id
) {
1527 var dev
= getDevice(section_id
),
1528 val
= uci
.get('network', section_id
, 'mtu'),
1529 mtu
= dev
? dev
.getMTU() : null;
1531 return val
? E('strong', {
1532 'data-tooltip': _('The value is overridden by configuration.')
1533 }, [ val
]) : (mtu
|| '-').toString();
1536 s
= m
.section(form
.TypedSection
, 'globals', _('Global network options'));
1537 s
.addremove
= false;
1540 o
= s
.option(form
.Value
, 'ula_prefix', _('IPv6 ULA-Prefix'), _('Unique Local Address - in the range <code>fc00::/7</code>. Typically only within the ‘local’ half <code>fd00::/8</code>. ULA for IPv6 is analogous to IPv4 private network addressing. This prefix is randomly generated at first install.'));
1541 o
.datatype
= 'cidr6';
1543 o
= s
.option(form
.Flag
, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
1547 if (dslModemType
!= null) {
1548 s
= m
.section(form
.TypedSection
, 'dsl', _('DSL'));
1551 o
= s
.option(form
.ListValue
, 'annex', _('Annex'));
1552 if (dslModemType
== 'vdsl') {
1553 o
.value('a', _('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));
1554 o
.value('b', _('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));
1555 o
.value('j', _('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));
1557 o
.value('a', _('ADSL (all variants) Annex A/L/M'));
1558 o
.value('b', _('ADSL (all variants) Annex B'));
1559 o
.value('j', _('ADSL (all variants) Annex B/J'));
1561 o
.value('m', _('ADSL (all variants) Annex M'));
1562 o
.value('at1', _('ANSI T1.413'));
1563 o
.value('admt', _('ADSL (G.992.1) Annex A'));
1564 o
.value('bdmt', _('ADSL (G.992.1) Annex B'));
1565 o
.value('alite', _('Splitterless ADSL (G.992.2) Annex A'));
1566 o
.value('a2', _('ADSL2 (G.992.3) Annex A'));
1567 o
.value('b2', _('ADSL2 (G.992.3) Annex B'));
1568 o
.value('l', _('ADSL2 (G.992.3) Annex L'));
1569 o
.value('m2', _('ADSL2 (G.992.3) Annex M'));
1570 o
.value('a2p', _('ADSL2+ (G.992.5) Annex A'));
1571 o
.value('b2p', _('ADSL2+ (G.992.5) Annex B'));
1572 o
.value('m2p', _('ADSL2+ (G.992.5) Annex M'));
1574 o
= s
.option(form
.ListValue
, 'tone', _('Tone'));
1575 o
.value('', _('auto'));
1576 o
.value('a', _('A43C + J43 + A43'));
1577 o
.value('av', _('A43C + J43 + A43 + V43'));
1578 o
.value('b', _('B43 + B43C'));
1579 o
.value('bv', _('B43 + B43C + V43'));
1581 if (dslModemType
== 'vdsl') {
1582 o
= s
.option(form
.ListValue
, 'xfer_mode', _('Encapsulation mode'));
1583 o
.value('', _('auto'));
1584 o
.value('atm', _('ATM (Asynchronous Transfer Mode)'));
1585 o
.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
1587 o
= s
.option(form
.ListValue
, 'line_mode', _('DSL line mode'));
1588 o
.value('', _('auto'));
1589 o
.value('adsl', _('ADSL'));
1590 o
.value('vdsl', _('VDSL'));
1592 o
= s
.option(form
.ListValue
, 'ds_snr_offset', _('Downstream SNR offset'));
1595 for (var i
= -100; i
<= 100; i
+= 5)
1596 o
.value(i
, _('%.1f dB').format(i
/ 10));
1599 s
.option(form
.Value
, 'firmware', _('Firmware File'));
1603 // Show ATM bridge section if we have the capabilities
1604 if (L
.hasSystemFeature('br2684ctl')) {
1605 s
= m
.section(form
.TypedSection
, 'atm-bridge', _('ATM Bridges'), _('ATM bridges expose encapsulated ethernet in AAL5 connections as virtual Linux network interfaces which can be used in conjunction with DHCP or PPP to dial into the provider network.'));
1609 s
.addbtntitle
= _('Add ATM Bridge');
1611 s
.handleAdd = function(ev
) {
1612 var sections
= uci
.sections('network', 'atm-bridge'),
1615 for (var i
= 0; i
< sections
.length
; i
++) {
1616 var unit
= +sections
[i
].unit
;
1618 if (!isNaN(unit
) && unit
> max_unit
)
1622 return this.map
.save(function() {
1623 var sid
= uci
.add('network', 'atm-bridge');
1625 uci
.set('network', sid
, 'unit', max_unit
+ 1);
1626 uci
.set('network', sid
, 'atmdev', 0);
1627 uci
.set('network', sid
, 'encaps', 'llc');
1628 uci
.set('network', sid
, 'payload', 'bridged');
1629 uci
.set('network', sid
, 'vci', 35);
1630 uci
.set('network', sid
, 'vpi', 8);
1634 s
.tab('general', _('General Setup'));
1635 s
.tab('advanced', _('Advanced Settings'));
1637 o
= s
.taboption('general', form
.Value
, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
1638 s
.taboption('general', form
.Value
, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
1640 o
= s
.taboption('general', form
.ListValue
, 'encaps', _('Encapsulation mode'));
1641 o
.value('llc', _('LLC'));
1642 o
.value('vc', _('VC-Mux'));
1644 s
.taboption('advanced', form
.Value
, 'atmdev', _('ATM device number'));
1645 s
.taboption('advanced', form
.Value
, 'unit', _('Bridge unit number'));
1647 o
= s
.taboption('advanced', form
.ListValue
, 'payload', _('Forwarding mode'));
1648 o
.value('bridged', _('bridged'));
1649 o
.value('routed', _('routed'));
1653 return m
.render().then(L
.bind(function(m
, nodes
) {
1654 poll
.add(L
.bind(function() {
1655 var section_ids
= m
.children
[0].cfgsections(),
1658 for (var i
= 0; i
< section_ids
.length
; i
++) {
1659 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
1660 dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
1661 btn1
= row
.querySelector('.cbi-section-actions .reconnect'),
1662 btn2
= row
.querySelector('.cbi-section-actions .down');
1664 if (dsc
.getAttribute('reconnect') == '') {
1665 dsc
.setAttribute('reconnect', '1');
1666 tasks
.push(fs
.exec('/sbin/ifup', [section_ids
[i
]]).catch(function(e
) {
1667 ui
.addNotification(null, E('p', e
.message
));
1670 else if (dsc
.getAttribute('disconnect') == '') {
1671 dsc
.setAttribute('disconnect', '1');
1672 tasks
.push(fs
.exec('/sbin/ifdown', [section_ids
[i
]]).catch(function(e
) {
1673 ui
.addNotification(null, E('p', e
.message
));
1676 else if (dsc
.getAttribute('reconnect') == '1') {
1677 dsc
.removeAttribute('reconnect');
1678 btn1
.classList
.remove('spinning');
1679 btn1
.disabled
= false;
1681 else if (dsc
.getAttribute('disconnect') == '1') {
1682 dsc
.removeAttribute('disconnect');
1683 btn2
.classList
.remove('spinning');
1684 btn2
.disabled
= false;
1688 return Promise
.all(tasks
)
1689 .then(L
.bind(network
.getNetworks
, network
))
1690 .then(L
.bind(this.poll_status
, this, nodes
));