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();
592 o
= s
.taboption('general', form
.Flag
, 'auto', _('Bring up on boot'));
594 o
.default = o
.enabled
;
596 if (L
.hasSystemFeature('firewall')) {
597 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.'));
598 o
.network
= ifc
.getName();
601 o
.cfgvalue = function(section_id
) {
602 return firewall
.getZoneByNetwork(ifc
.getName()).then(function(zone
) {
603 return (zone
!= null ? zone
.getName() : null);
607 o
.write
= o
.remove = function(section_id
, value
) {
609 firewall
.getZoneByNetwork(ifc
.getName()),
610 (value
!= null) ? firewall
.getZone(value
) : null
611 ]).then(function(data
) {
612 var old_zone
= data
[0],
615 if (old_zone
== null && new_zone
== null && (value
== null || value
== ''))
618 if (old_zone
!= null && new_zone
!= null && old_zone
.getName() == new_zone
.getName())
621 if (old_zone
!= null)
622 old_zone
.deleteNetwork(ifc
.getName());
624 if (new_zone
!= null)
625 new_zone
.addNetwork(ifc
.getName());
626 else if (value
!= null)
627 return firewall
.addZone(value
).then(function(new_zone
) {
628 new_zone
.addNetwork(ifc
.getName());
634 for (var i
= 0; i
< protocols
.length
; i
++) {
635 proto_select
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
637 if (protocols
[i
].getProtocol() != uci
.get('network', s
.section
, 'proto'))
638 proto_switch
.depends('proto', protocols
[i
].getProtocol());
641 if (L
.hasSystemFeature('dnsmasq') || L
.hasSystemFeature('odhcpd')) {
642 o
= s
.taboption('dhcp', form
.SectionValue
, '_dhcp', form
.TypedSection
, 'dhcp');
645 ss
.uciconfig
= 'dhcp';
646 ss
.addremove
= false;
649 ss
.tab('general', _('General Setup'));
650 ss
.tab('advanced', _('Advanced Settings'));
651 ss
.tab('ipv6', _('IPv6 Settings'));
652 ss
.tab('ipv6-ra', _('IPv6 RA Settings'));
654 ss
.filter = function(section_id
) {
655 return (uci
.get('dhcp', section_id
, 'interface') == ifc
.getName());
658 ss
.renderSectionPlaceholder = function() {
659 return E('div', { 'class': 'cbi-section-create' }, [
660 E('p', _('No DHCP Server configured for this interface') + '   '),
662 'class': 'cbi-button cbi-button-add',
663 'title': _('Set up DHCP Server'),
664 'click': ui
.createHandlerFn(this, function(section_id
, ev
) {
665 this.map
.save(function() {
666 uci
.add('dhcp', 'dhcp', section_id
);
667 uci
.set('dhcp', section_id
, 'interface', section_id
);
669 if (protoval
== 'static') {
670 uci
.set('dhcp', section_id
, 'start', 100);
671 uci
.set('dhcp', section_id
, 'limit', 150);
672 uci
.set('dhcp', section_id
, 'leasetime', '12h');
675 uci
.set('dhcp', section_id
, 'ignore', 1);
679 }, _('Set up DHCP Server'))
683 ss
.taboption('general', form
.Flag
, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
685 if (protoval
== 'static') {
686 so
= ss
.taboption('general', form
.Value
, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
688 so
.datatype
= 'or(uinteger,ip4addr("nomask"))';
691 so
= ss
.taboption('general', form
.Value
, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
693 so
.datatype
= 'uinteger';
696 so
= ss
.taboption('general', form
.Value
, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
700 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.'));
701 so
.default = so
.enabled
;
703 ss
.taboption('advanced', form
.Flag
, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
705 // XXX: is this actually useful?
706 //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
708 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.'));
710 so
.datatype
= 'ip4addr';
712 so
.render = function(option_index
, section_id
, in_table
) {
713 this.placeholder
= get_netmask(s
, true);
714 return form
.Value
.prototype.render
.apply(this, [ option_index
, section_id
, in_table
]);
717 so
.validate = function(section_id
, value
) {
718 var uielem
= this.getUIElement(section_id
);
720 uielem
.setPlaceholder(get_netmask(s
, false));
721 return form
.Value
.prototype.validate
.apply(this, [ section_id
, value
]);
724 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.'));
728 var has_other_master
= uci
.sections('dhcp', 'dhcp').filter(function(s
) {
729 return (s
.interface != ifc
.getName() && s
.master
== '1');
732 so
= ss
.taboption('ipv6', form
.Flag
, 'master', _('Designated master'));
733 so
.readonly
= has_other_master
? true : false;
734 so
.description
= has_other_master
735 ? _('Interface "%h" is already marked as designated master.').format(has_other_master
.interface || has_other_master
['.name'])
736 : _('Set this interface as master for RA and DHCPv6 relaying as well as NDP proxying.')
739 so
.validate = function(section_id
, value
) {
740 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>.'),
741 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.'),
742 hybrid_master_desc
= _('Operate in <em>relay mode</em> if an upstream IPv6 prefix is present, otherwise disable service.'),
743 ra_server_allowed
= true,
744 checked
= this.formvalue(section_id
),
745 dhcpv6
= this.section
.getOption('dhcpv6').getUIElement(section_id
),
746 ndp
= this.section
.getOption('ndp').getUIElement(section_id
),
747 ra
= this.section
.getOption('ra').getUIElement(section_id
);
749 /* Assume that serving RAs by default is fine, but disallow it for certain
750 interface protocols such as DHCP, DHCPv6 or the various PPP flavors.
751 The intent is to only allow RA serving for interface protocols doing
752 some kind of static IP config over something resembling a layer 2
767 ra_server_allowed
= false;
771 if (checked
== '1' || !ra_server_allowed
) {
772 dhcpv6
.node
.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
774 if (dhcpv6
.getValue() == 'server')
775 dhcpv6
.setValue('hybrid');
777 ra
.node
.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
779 if (ra
.getValue() == 'server')
780 ra
.setValue('hybrid');
783 if (checked
== '1') {
784 dhcpv6
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
785 ra
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
786 ndp
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
789 if (ra_server_allowed
) {
790 dhcpv6
.node
.querySelector('li[data-value="server"]').removeAttribute('unselectable');
791 ra
.node
.querySelector('li[data-value="server"]').removeAttribute('unselectable');
794 dhcpv6
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_downstream_desc
;
795 ra
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_downstream_desc
;
796 ndp
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= ndp_downstream_desc
;
803 so
= ss
.taboption('ipv6', cbiRichListValue
, 'ra', _('<abbr title="Router Advertisement">RA</abbr>-Service'),
804 _('Configures the operation mode of the <abbr title="Router Advertisement">RA</abbr> service on this interface.'));
805 so
.value('', _('disabled'),
806 _('Do not send any <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages on this interface.'));
807 so
.value('server', _('server mode'),
808 _('Send <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages advertising this device as IPv6 router.'));
809 so
.value('relay', _('relay mode'),
810 _('Forward <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages received on the designated master interface to downstream interfaces.'));
811 so
.value('hybrid', _('hybrid mode'), ' ');
814 so
= ss
.taboption('ipv6-ra', cbiRichListValue
, 'ra_default', _('Default router'),
815 _('Configures the default router advertisement in <abbr title="Router Advertisement">RA</abbr> messages.'));
816 so
.value('', _('automatic'),
817 _('Announce this device as default router if a local IPv6 default route is present.'));
818 so
.value('1', _('on available prefix'),
819 _('Announce this device as default router if a public IPv6 prefix is available, regardless of local default route availability.'));
820 so
.value('2', _('forced'),
821 _('Announce this device as default router regardless of whether a prefix or default route is present.'));
822 so
.depends('ra', 'server');
823 so
.depends({ ra
: 'hybrid', master
: '0' });
825 so
= ss
.taboption('ipv6-ra', form
.Flag
, 'ra_slaac', _('Enable <abbr title="Stateless Address Auto Config">SLAAC</abbr>'),
826 _('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.'));
827 so
.default = so
.enabled
;
828 so
.depends('ra', 'server');
829 so
.depends({ ra
: 'hybrid', master
: '0' });
831 so
= ss
.taboption('ipv6-ra', cbiRichListValue
, 'ra_flags', _('<abbr title="Router Advertisement">RA</abbr> Flags'),
832 _('Specifies the flags sent in <abbr title="Router Advertisement">RA</abbr> messages, for example to instruct clients to request further information via stateful DHCPv6.'));
833 so
.value('managed-config', _('managed config (M)'),
834 _('The <em>Managed address configuration</em> (M) flag indicates that IPv6 addresses are available via DHCPv6.'));
835 so
.value('other-config', _('other config (O)'),
836 _('The <em>Other configuration</em> (O) flag indicates that other information, such as DNS servers, is available via DHCPv6.'));
837 so
.value('home-agent', _('mobile home agent (H)'),
838 _('The <em>Mobile IPv6 Home Agent</em> (H) flag indicates that the device is also acting as Mobile IPv6 home agent on this link.'));
840 so
.select_placeholder
= _('none');
841 so
.depends('ra', 'server');
842 so
.depends({ ra
: 'hybrid', master
: '0' });
843 so
.cfgvalue = function(section_id
) {
844 var flags
= L
.toArray(uci
.get('dhcp', section_id
, 'ra_flags'));
845 return flags
.length
? flags
: [ 'other-config' ];
847 so
.remove = function(section_id
) {
848 var existing
= L
.toArray(uci
.get('dhcp', section_id
, 'ra_flags'));
849 if (this.isActive(section_id
)) {
850 if (existing
.length
!= 1 || existing
[0] != 'none')
851 uci
.set('dhcp', section_id
, 'ra_flags', [ 'none' ]);
853 else if (existing
.length
) {
854 uci
.unset('dhcp', section_id
, 'ra_flags');
858 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_pref64', _('NAT64 prefix'), _('Announce NAT64 prefix in <abbr title="Router Advertisement">RA</abbr> messages.'));
860 so
.datatype
= 'cidr6';
861 so
.placeholder
= '64:ff9b::/96';
862 so
.depends('ra', 'server');
863 so
.depends({ ra
: 'hybrid', master
: '0' });
865 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.'));
867 so
.datatype
= 'uinteger';
868 so
.placeholder
= '600';
869 so
.depends('ra', 'server');
870 so
.depends({ ra
: 'hybrid', master
: '0' });
872 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.'));
874 so
.datatype
= 'uinteger';
875 so
.placeholder
= '200';
876 so
.depends('ra', 'server');
877 so
.depends({ ra
: 'hybrid', master
: '0' });
879 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.'));
881 so
.datatype
= 'range(0, 9000)';
882 so
.placeholder
= '1800';
883 so
.depends('ra', 'server');
884 so
.depends({ ra
: 'hybrid', master
: '0' });
886 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.'));
888 so
.datatype
= 'range(1280, 65535)';
889 so
.depends('ra', 'server');
890 so
.depends({ ra
: 'hybrid', master
: '0' });
891 so
.load = function(section_id
) {
892 var dev
= ifc
.getL3Device(),
893 path
= dev
? "/proc/sys/net/ipv6/conf/%s/mtu".format(dev
.getName()) : null;
896 dev
? L
.resolveDefault(fs
.read(path
), dev
.getMTU()) : null,
897 this.super('load', [section_id
])
898 ]).then(L
.bind(function(res
) {
899 this.placeholder
= +res
[0];
905 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.'));
907 so
.datatype
= 'range(0, 255)';
908 so
.depends('ra', 'server');
909 so
.depends({ ra
: 'hybrid', master
: '0' });
910 so
.load = function(section_id
) {
911 var dev
= ifc
.getL3Device(),
912 path
= dev
? "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev
.getName()) : null;
915 dev
? L
.resolveDefault(fs
.read(path
), 64) : null,
916 this.super('load', [section_id
])
917 ]).then(L
.bind(function(res
) {
918 this.placeholder
= +res
[0];
925 so
= ss
.taboption('ipv6', cbiRichListValue
, 'dhcpv6', _('DHCPv6-Service'),
926 _('Configures the operation mode of the DHCPv6 service on this interface.'));
927 so
.value('', _('disabled'),
928 _('Do not offer DHCPv6 service on this interface.'));
929 so
.value('server', _('server mode'),
930 _('Provide a DHCPv6 server on this interface and reply to DHCPv6 solicitations and requests.'));
931 so
.value('relay', _('relay mode'),
932 _('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));
933 so
.value('hybrid', _('hybrid mode'), ' ');
935 so
= ss
.taboption('ipv6', form
.Value
, 'dhcpv6_pd_min_len', _('<abbr title="Prefix Delegation">PD</abbr> minimum length'),
936 _('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.'));
937 so
.datatype
= 'range(1,62)';
938 so
.depends('dhcpv6', 'server');
940 so
= ss
.taboption('ipv6', form
.DynamicList
, 'dns', _('Announced IPv6 DNS servers'),
941 _('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.'));
942 so
.datatype
= 'ip6addr("nomask")'; /* restrict to IPv6 only for now since dnsmasq (DHCPv4) does not honour this option */
943 so
.depends('ra', 'server');
944 so
.depends({ ra
: 'hybrid', master
: '0' });
945 so
.depends('dhcpv6', 'server');
946 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
948 so
= ss
.taboption('ipv6', form
.Flag
, 'dns_service', _('Local IPv6 DNS server'),
949 _('Announce this device as IPv6 DNS server.'));
950 so
.default = so
.enabled
;
951 so
.depends({ ra
: 'server', dns
: /^$/ });
952 so
.depends({ ra
: 'hybrid', dns
: /^$/, master
: '0' });
953 so
.depends({ dhcpv6
: 'server', dns
: /^$/ });
954 so
.depends({ dhcpv6
: 'hybrid', dns
: /^$/, master
: '0' });
956 so
= ss
.taboption('ipv6', form
.DynamicList
, 'domain', _('Announced DNS domains'),
957 _('Specifies a fixed list of DNS search domains to announce via DHCPv6. If left unspecified, the local device DNS search domain will be announced.'));
958 so
.datatype
= 'hostname';
959 so
.depends('ra', 'server');
960 so
.depends({ ra
: 'hybrid', master
: '0' });
961 so
.depends('dhcpv6', 'server');
962 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
965 so
= ss
.taboption('ipv6', cbiRichListValue
, 'ndp', _('<abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy'),
966 _('Configures the operation mode of the NDP proxy service on this interface.'));
967 so
.value('', _('disabled'),
968 _('Do not proxy any <abbr title="Neighbour Discovery Protocol">NDP</abbr> packets.'));
969 so
.value('relay', _('relay mode'),
970 _('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.'));
971 so
.value('hybrid', _('hybrid mode'), ' ');
974 so
= ss
.taboption('ipv6', form
.Flag
, 'ndproxy_routing', _('Learn routes'), _('Set up routes for proxied IPv6 neighbours.'));
975 so
.default = so
.enabled
;
976 so
.depends('ndp', 'relay');
977 so
.depends('ndp', 'hybrid');
979 so
= ss
.taboption('ipv6', form
.Flag
, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
980 so
.depends({ ndp
: 'relay', master
: '0' });
981 so
.depends({ ndp
: 'hybrid', master
: '0' });
983 so
= ss
.taboption('ipv6', form
.Value
, 'preferred_lifetime', _('IPv6 Prefix Lifetime'), _('Preferred lifetime for a prefix.'));
985 so
.placeholder
= '12h';
986 so
.value('5m', _('5m (5 minutes)'));
987 so
.value('3h', _('3h (3 hours)'));
988 so
.value('12h', _('12h (12 hours - default)'));
989 so
.value('7d', _('7d (7 days)'));
991 //This is a ra_* setting, but its placement is more logical/findable under IPv6 settings.
992 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.'));
996 ifc
.renderFormOptions(s
);
998 // Common interface options
999 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
1000 o
.default = o
.enabled
;
1002 if (has_peerdns(protoval
)) {
1003 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
1004 o
.default = o
.enabled
;
1007 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'dns', _('Use custom DNS servers'));
1008 if (has_peerdns(protoval
))
1009 o
.depends('peerdns', '0');
1010 o
.datatype
= 'ipaddr';
1012 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'dns_search', _('DNS search domains'));
1013 if (protoval
!= 'static')
1014 o
.depends('peerdns', '0');
1015 o
.datatype
= 'hostname';
1017 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'));
1018 o
.datatype
= 'uinteger';
1019 o
.placeholder
= '0';
1021 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'metric', _('Use gateway metric'));
1022 o
.datatype
= 'uinteger';
1023 o
.placeholder
= '0';
1025 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip4table', _('Override IPv4 routing table'));
1026 o
.datatype
= 'or(uinteger, string)';
1027 for (var i
= 0; i
< rtTables
.length
; i
++)
1028 o
.value(rtTables
[i
][1], '%s (%d)'.format(rtTables
[i
][1], rtTables
[i
][0]));
1030 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6table', _('Override IPv6 routing table'));
1031 o
.datatype
= 'or(uinteger, string)';
1032 for (var i
= 0; i
< rtTables
.length
; i
++)
1033 o
.value(rtTables
[i
][1], '%s (%d)'.format(rtTables
[i
][1], rtTables
[i
][0]));
1035 if (protoval
== 'dhcpv6') {
1036 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'sourcefilter', _('IPv6 source routing'), _('Automatically handle multiple uplink interfaces using source-based policy routing.'));
1037 o
.default = o
.enabled
;
1040 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
1041 o
.default = o
.enabled
;
1043 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'));
1044 o
.value('', _('disabled'));
1046 o
.datatype
= 'max(128)';
1048 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6hint', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
1049 o
.placeholder
= '0';
1050 o
.validate = function(section_id
, value
) {
1051 if (value
== null || value
== '')
1054 var n
= parseInt(value
, 16);
1056 if (!/^(0x)?[0-9a-fA-F]+$/.test(value
) || isNaN(n
) || n
>= 0xffffffff)
1057 return _('Expecting a hexadecimal assignment hint');
1061 for (var i
= 33; i
<= 64; i
++)
1062 o
.depends('ip6assign', String(i
));
1065 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'ip6class', _('IPv6 prefix filter'), _('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));
1066 o
.value('local', 'local (%s)'.format(_('Local ULA')));
1068 var prefixClasses
= {};
1070 this.networks
.forEach(function(net
) {
1071 var prefixes
= net
._ubus('ipv6-prefix');
1072 if (Array
.isArray(prefixes
)) {
1073 prefixes
.forEach(function(pfx
) {
1074 if (L
.isObject(pfx
) && typeof(pfx
['class']) == 'string') {
1075 prefixClasses
[pfx
['class']] = prefixClasses
[pfx
['class']] || {};
1076 prefixClasses
[pfx
['class']][net
.getName()] = true;
1082 Object
.keys(prefixClasses
).sort().forEach(function(c
) {
1083 var networks
= Object
.keys(prefixClasses
[c
]).sort().join(', ');
1084 o
.value(c
, (c
!= networks
) ? '%s (%s)'.format(c
, networks
) : c
);
1088 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."));
1089 o
.datatype
= 'ip6hostid';
1090 o
.placeholder
= '::1';
1092 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.'));
1093 o
.datatype
= 'uinteger';
1094 o
.placeholder
= '0';
1096 for (var i
= 0; i
< s
.children
.length
; i
++) {
1104 case '_switch_proto':
1105 case '_ifacestat_modal':
1108 case 'igmp_snooping':
1113 for (var j
= 0; j
< protocols
.length
; j
++) {
1114 if (!protocols
[j
].isVirtual()) {
1116 for (var k
= 0; k
< o
.deps
.length
; k
++)
1117 deps
.push(Object
.assign({ proto
: protocols
[j
].getProtocol() }, o
.deps
[k
]));
1119 deps
.push({ proto
: protocols
[j
].getProtocol() });
1127 for (var j
= 0; j
< o
.deps
.length
; j
++)
1128 o
.deps
[j
].proto
= protoval
;
1130 o
.depends('proto', protoval
);
1134 this.activeSection
= s
.section
;
1138 s
.handleModalCancel = function(/* ... */) {
1139 var type
= uci
.get('network', this.activeSection
|| this.addedSection
, 'type'),
1140 device
= (type
== 'bridge') ? 'br-%s'.format(this.activeSection
|| this.addedSection
) : null;
1142 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1143 if (device
!= null && bvs
.device
== device
)
1144 uci
.remove('network', bvs
['.name']);
1147 return form
.GridSection
.prototype.handleModalCancel
.apply(this, arguments
);
1150 s
.handleAdd = function(ev
) {
1151 var m2
= new form
.Map('network'),
1152 s2
= m2
.section(form
.NamedSection
, '_new_'),
1153 protocols
= network
.getProtocols(),
1154 proto
, name
, device
;
1156 protocols
.sort(function(a
, b
) {
1157 return L
.naturalCompare(a
.getProtocol(), b
.getProtocol());
1160 s2
.render = function() {
1161 return Promise
.all([
1163 this.renderUCISection('_new_')
1164 ]).then(this.renderContents
.bind(this));
1167 name
= s2
.option(form
.Value
, 'name', _('Name'));
1168 name
.rmempty
= false;
1169 name
.datatype
= 'uciname';
1170 name
.placeholder
= _('New interface name…');
1171 name
.validate = function(section_id
, value
) {
1172 if (uci
.get('network', value
) != null)
1173 return _('The interface name is already used');
1175 var pr
= network
.getProtocol(proto
.formvalue(section_id
), value
),
1176 ifname
= pr
.isVirtual() ? '%s-%s'.format(pr
.getProtocol(), value
) : 'br-%s'.format(value
);
1178 if (value
.length
> 15)
1179 return _('The interface name is too long');
1184 proto
= s2
.option(form
.ListValue
, 'proto', _('Protocol'));
1185 proto
.validate
= name
.validate
;
1187 device
= s2
.option(widgets
.DeviceSelect
, 'device', _('Device'));
1188 device
.noaliases
= false;
1189 device
.optional
= false;
1191 for (var i
= 0; i
< protocols
.length
; i
++) {
1192 proto
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
1194 if (!protocols
[i
].isVirtual())
1195 device
.depends('proto', protocols
[i
].getProtocol());
1198 m2
.render().then(L
.bind(function(nodes
) {
1199 ui
.showModal(_('Add new interface...'), [
1201 E('div', { 'class': 'right' }, [
1204 'click': ui
.hideModal
1205 }, _('Cancel')), ' ',
1207 'class': 'cbi-button cbi-button-positive important',
1208 'click': ui
.createHandlerFn(this, function(ev
) {
1209 var nameval
= name
.isValid('_new_') ? name
.formvalue('_new_') : null,
1210 protoval
= proto
.isValid('_new_') ? proto
.formvalue('_new_') : null,
1211 protoclass
= protoval
? network
.getProtocol(protoval
, nameval
) : null;
1213 if (nameval
== null || protoval
== null || nameval
== '' || protoval
== '')
1216 return protoclass
.isCreateable(nameval
).then(function(checkval
) {
1217 if (checkval
!= null) {
1218 ui
.addNotification(null,
1219 E('p', _('New interface for "%s" can not be created: %s').format(protoclass
.getI18n(), checkval
)));
1224 return m
.save(function() {
1225 var section_id
= uci
.add('network', 'interface', nameval
);
1227 protoclass
.set('proto', protoval
);
1228 protoclass
.addDevice(device
.formvalue('_new_'));
1230 m
.children
[0].addedSection
= section_id
;
1233 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
1234 }).then(L
.bind(m
.children
[0].renderMoreOptionsModal
, m
.children
[0], nameval
));
1237 }, _('Create interface'))
1241 nodes
.querySelector('[id="%s"] input[type="text"]'.format(name
.cbid('_new_'))).focus();
1245 s
.handleRemove = function(section_id
, ev
) {
1246 return network
.deleteNetwork(section_id
).then(L
.bind(function(section_id
, ev
) {
1247 return form
.GridSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1248 }, this, section_id
, ev
));
1251 o
= s
.option(form
.DummyValue
, '_ifacebox');
1252 o
.modalonly
= false;
1253 o
.textvalue = function(section_id
) {
1254 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0],
1255 zone
= net
? this.section
.zones
.filter(function(z
) { return !!z
.getNetworks().filter(function(n
) { return n
== section_id
})[0] })[0] : null;
1260 var node
= E('div', { 'class': 'ifacebox' }, [
1262 'class': 'ifacebox-head',
1263 'style': firewall
.getZoneColorStyle(zone
),
1264 'title': zone
? _('Part of zone %q').format(zone
.getName()) : _('No zone assigned')
1265 }, E('strong', net
.getName())),
1267 'class': 'ifacebox-body',
1268 'id': '%s-ifc-devices'.format(section_id
),
1269 'data-network': section_id
1272 'src': L
.resource('icons/ethernet_disabled.png'),
1273 'style': 'width:16px; height:16px'
1275 E('br'), E('small', '?')
1279 render_ifacebox_status(node
.childNodes
[1], net
);
1284 o
= s
.option(form
.DummyValue
, '_ifacestat');
1285 o
.modalonly
= false;
1286 o
.textvalue = function(section_id
) {
1287 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0];
1292 var node
= E('div', { 'id': '%s-ifc-description'.format(section_id
) });
1294 render_status(node
, net
, false);
1299 o
= s
.taboption('advanced', form
.Flag
, 'delegate', _('Use builtin IPv6-management'));
1301 o
.default = o
.enabled
;
1303 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).'));
1306 '1': [{ proto
: 'static' }],
1311 // Device configuration
1312 s
= m
.section(form
.GridSection
, 'device', _('Devices'));
1315 s
.addbtntitle
= _('Add device configuration…');
1317 s
.cfgsections = function() {
1318 var sections
= uci
.sections('network', 'device'),
1319 section_ids
= sections
.sort(function(a
, b
) { return L
.naturalCompare(a
.name
, b
.name
) }).map(function(s
) { return s
['.name'] });
1321 for (var i
= 0; i
< netDevs
.length
; i
++) {
1322 if (sections
.filter(function(s
) { return s
.name
== netDevs
[i
].getName() }).length
)
1325 if (netDevs
[i
].getType() == 'wifi' && !netDevs
[i
].isUp())
1328 /* Unless http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html is implemented,
1329 we cannot properly redefine bridges as devices, so filter them away for now... */
1331 var m
= netDevs
[i
].isBridge() ? netDevs
[i
].getName().match(/^br-([A-Za-z0-9_]+)$/) : null,
1332 s
= m
? uci
.get('network', m
[1]) : null;
1334 if (s
&& s
['.type'] == 'interface' && s
.type
== 'bridge')
1337 section_ids
.push('dev:%s'.format(netDevs
[i
].getName()));
1343 s
.renderMoreOptionsModal = function(section_id
, ev
) {
1344 var m
= section_id
.match(/^dev:(.+)$/);
1347 var devtype
= getDevType(section_id
);
1349 section_id
= uci
.add('network', 'device');
1351 uci
.set('network', section_id
, 'name', m
[1]);
1352 uci
.set('network', section_id
, 'type', (devtype
!= 'ethernet') ? devtype
: null);
1354 this.addedSection
= section_id
;
1357 return this.super('renderMoreOptionsModal', [section_id
, ev
]);
1360 s
.renderRowActions = function(section_id
) {
1361 var trEl
= this.super('renderRowActions', [ section_id
, _('Configure…') ]),
1362 deleteBtn
= trEl
.querySelector('button:last-child');
1364 deleteBtn
.firstChild
.data
= _('Unconfigure');
1365 deleteBtn
.setAttribute('title', _('Remove related device settings from the configuration'));
1366 deleteBtn
.disabled
= section_id
.match(/^dev:/) ? true : null;
1371 s
.modaltitle = function(section_id
) {
1372 var m
= section_id
.match(/^dev:(.+)$/),
1373 name
= m
? m
[1] : uci
.get('network', section_id
, 'name');
1375 return name
? '%s: %q'.format(getDevTypeDesc(section_id
), name
) : _('Add device configuration');
1378 s
.addModalOptions = function(s
) {
1379 var isNew
= (uci
.get('network', s
.section
, 'name') == null),
1380 dev
= getDevice(s
.section
);
1382 nettools
.addDeviceOptions(s
, dev
, isNew
);
1385 s
.handleModalCancel = function(map
/*, ... */) {
1386 var name
= uci
.get('network', this.addedSection
, 'name')
1388 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1389 if (name
!= null && bvs
.device
== name
)
1390 uci
.remove('network', bvs
['.name']);
1394 for (var i
= 0; i
< map
.addedVLANs
.length
; i
++)
1395 uci
.remove('network', map
.addedVLANs
[i
]);
1397 if (this.addedSection
)
1398 uci
.remove('network', this.addedSection
);
1400 return form
.GridSection
.prototype.handleModalCancel
.apply(this, arguments
);
1403 s
.handleRemove = function(section_id
/*, ... */) {
1404 var name
= uci
.get('network', section_id
, 'name'),
1405 type
= uci
.get('network', section_id
, 'type');
1407 if (name
!= null && type
== 'bridge') {
1408 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1409 if (bvs
.device
== name
)
1410 uci
.remove('network', bvs
['.name']);
1414 return form
.GridSection
.prototype.handleRemove
.apply(this, arguments
);
1417 function getDevice(section_id
) {
1418 var m
= section_id
.match(/^dev:(.+)$/),
1419 name
= m
? m
[1] : uci
.get('network', section_id
, 'name');
1421 return netDevs
.filter(function(d
) { return d
.getName() == name
})[0];
1424 function getDevType(section_id
) {
1425 var dev
= getDevice(section_id
),
1426 cfg
= uci
.get('network', section_id
),
1427 type
= cfg
? (uci
.get('network', section_id
, 'type') || 'ethernet') : (dev
? dev
.getType() : '');
1461 function getDevTypeDesc(section_id
) {
1462 switch (getDevType(section_id
) || '') {
1464 return E('em', [ _('Device not present') ]);
1467 return _('VLAN (802.1q)');
1470 return _('VLAN (802.1ad)');
1473 return _('Bridge device');
1476 return _('Tunnel device');
1479 return _('MAC VLAN');
1482 return _('Virtual Ethernet');
1485 return _('Network device');
1489 o
= s
.option(form
.DummyValue
, 'name', _('Device'));
1490 o
.modalonly
= false;
1491 o
.textvalue = function(section_id
) {
1492 var dev
= getDevice(section_id
),
1493 ext
= section_id
.match(/^dev:/),
1494 icon
= render_iface(dev
);
1497 icon
.querySelector('img').style
.opacity
= '.5';
1499 return E('span', { 'class': 'ifacebadge' }, [
1501 E('span', { 'style': ext
? 'opacity:.5' : null }, [
1502 dev
? dev
.getName() : (uci
.get('network', section_id
, 'name') || '?')
1507 o
= s
.option(form
.DummyValue
, 'type', _('Type'));
1508 o
.textvalue
= getDevTypeDesc
;
1509 o
.modalonly
= false;
1511 o
= s
.option(form
.DummyValue
, 'macaddr', _('MAC Address'));
1512 o
.modalonly
= false;
1513 o
.textvalue = function(section_id
) {
1514 var dev
= getDevice(section_id
),
1515 val
= uci
.get('network', section_id
, 'macaddr'),
1516 mac
= dev
? dev
.getMAC() : null;
1518 return val
? E('strong', {
1519 'data-tooltip': _('The value is overridden by configuration.')
1520 }, [ val
.toUpperCase() ]) : (mac
|| '-');
1523 o
= s
.option(form
.DummyValue
, 'mtu', _('MTU'));
1524 o
.modalonly
= false;
1525 o
.textvalue = function(section_id
) {
1526 var dev
= getDevice(section_id
),
1527 val
= uci
.get('network', section_id
, 'mtu'),
1528 mtu
= dev
? dev
.getMTU() : null;
1530 return val
? E('strong', {
1531 'data-tooltip': _('The value is overridden by configuration.')
1532 }, [ val
]) : (mtu
|| '-').toString();
1535 s
= m
.section(form
.TypedSection
, 'globals', _('Global network options'));
1536 s
.addremove
= false;
1539 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.'));
1540 o
.datatype
= 'cidr6';
1542 o
= s
.option(form
.Flag
, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
1546 if (dslModemType
!= null) {
1547 s
= m
.section(form
.TypedSection
, 'dsl', _('DSL'));
1550 o
= s
.option(form
.ListValue
, 'annex', _('Annex'));
1551 if (dslModemType
== 'vdsl') {
1552 o
.value('a', _('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));
1553 o
.value('b', _('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));
1554 o
.value('j', _('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));
1556 o
.value('a', _('ADSL (all variants) Annex A/L/M'));
1557 o
.value('b', _('ADSL (all variants) Annex B'));
1558 o
.value('j', _('ADSL (all variants) Annex B/J'));
1560 o
.value('m', _('ADSL (all variants) Annex M'));
1561 o
.value('at1', _('ANSI T1.413'));
1562 o
.value('admt', _('ADSL (G.992.1) Annex A'));
1563 o
.value('bdmt', _('ADSL (G.992.1) Annex B'));
1564 o
.value('alite', _('Splitterless ADSL (G.992.2) Annex A'));
1565 o
.value('a2', _('ADSL2 (G.992.3) Annex A'));
1566 o
.value('b2', _('ADSL2 (G.992.3) Annex B'));
1567 o
.value('l', _('ADSL2 (G.992.3) Annex L'));
1568 o
.value('m2', _('ADSL2 (G.992.3) Annex M'));
1569 o
.value('a2p', _('ADSL2+ (G.992.5) Annex A'));
1570 o
.value('b2p', _('ADSL2+ (G.992.5) Annex B'));
1571 o
.value('m2p', _('ADSL2+ (G.992.5) Annex M'));
1573 o
= s
.option(form
.ListValue
, 'tone', _('Tone'));
1574 o
.value('', _('auto'));
1575 o
.value('a', _('A43C + J43 + A43'));
1576 o
.value('av', _('A43C + J43 + A43 + V43'));
1577 o
.value('b', _('B43 + B43C'));
1578 o
.value('bv', _('B43 + B43C + V43'));
1580 if (dslModemType
== 'vdsl') {
1581 o
= s
.option(form
.ListValue
, 'xfer_mode', _('Encapsulation mode'));
1582 o
.value('', _('auto'));
1583 o
.value('atm', _('ATM (Asynchronous Transfer Mode)'));
1584 o
.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
1586 o
= s
.option(form
.ListValue
, 'line_mode', _('DSL line mode'));
1587 o
.value('', _('auto'));
1588 o
.value('adsl', _('ADSL'));
1589 o
.value('vdsl', _('VDSL'));
1591 o
= s
.option(form
.ListValue
, 'ds_snr_offset', _('Downstream SNR offset'));
1594 for (var i
= -100; i
<= 100; i
+= 5)
1595 o
.value(i
, _('%.1f dB').format(i
/ 10));
1598 s
.option(form
.Value
, 'firmware', _('Firmware File'));
1602 // Show ATM bridge section if we have the capabilities
1603 if (L
.hasSystemFeature('br2684ctl')) {
1604 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.'));
1608 s
.addbtntitle
= _('Add ATM Bridge');
1610 s
.handleAdd = function(ev
) {
1611 var sections
= uci
.sections('network', 'atm-bridge'),
1614 for (var i
= 0; i
< sections
.length
; i
++) {
1615 var unit
= +sections
[i
].unit
;
1617 if (!isNaN(unit
) && unit
> max_unit
)
1621 return this.map
.save(function() {
1622 var sid
= uci
.add('network', 'atm-bridge');
1624 uci
.set('network', sid
, 'unit', max_unit
+ 1);
1625 uci
.set('network', sid
, 'atmdev', 0);
1626 uci
.set('network', sid
, 'encaps', 'llc');
1627 uci
.set('network', sid
, 'payload', 'bridged');
1628 uci
.set('network', sid
, 'vci', 35);
1629 uci
.set('network', sid
, 'vpi', 8);
1633 s
.tab('general', _('General Setup'));
1634 s
.tab('advanced', _('Advanced Settings'));
1636 o
= s
.taboption('general', form
.Value
, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
1637 s
.taboption('general', form
.Value
, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
1639 o
= s
.taboption('general', form
.ListValue
, 'encaps', _('Encapsulation mode'));
1640 o
.value('llc', _('LLC'));
1641 o
.value('vc', _('VC-Mux'));
1643 s
.taboption('advanced', form
.Value
, 'atmdev', _('ATM device number'));
1644 s
.taboption('advanced', form
.Value
, 'unit', _('Bridge unit number'));
1646 o
= s
.taboption('advanced', form
.ListValue
, 'payload', _('Forwarding mode'));
1647 o
.value('bridged', _('bridged'));
1648 o
.value('routed', _('routed'));
1652 return m
.render().then(L
.bind(function(m
, nodes
) {
1653 poll
.add(L
.bind(function() {
1654 var section_ids
= m
.children
[0].cfgsections(),
1657 for (var i
= 0; i
< section_ids
.length
; i
++) {
1658 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
1659 dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
1660 btn1
= row
.querySelector('.cbi-section-actions .reconnect'),
1661 btn2
= row
.querySelector('.cbi-section-actions .down');
1663 if (dsc
.getAttribute('reconnect') == '') {
1664 dsc
.setAttribute('reconnect', '1');
1665 tasks
.push(fs
.exec('/sbin/ifup', [section_ids
[i
]]).catch(function(e
) {
1666 ui
.addNotification(null, E('p', e
.message
));
1669 else if (dsc
.getAttribute('disconnect') == '') {
1670 dsc
.setAttribute('disconnect', '1');
1671 tasks
.push(fs
.exec('/sbin/ifdown', [section_ids
[i
]]).catch(function(e
) {
1672 ui
.addNotification(null, E('p', e
.message
));
1675 else if (dsc
.getAttribute('reconnect') == '1') {
1676 dsc
.removeAttribute('reconnect');
1677 btn1
.classList
.remove('spinning');
1678 btn1
.disabled
= false;
1680 else if (dsc
.getAttribute('disconnect') == '1') {
1681 dsc
.removeAttribute('disconnect');
1682 btn2
.classList
.remove('spinning');
1683 btn2
.disabled
= false;
1687 return Promise
.all(tasks
)
1688 .then(L
.bind(network
.getNetworks
, network
))
1689 .then(L
.bind(this.poll_status
, this, nodes
));