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 checked
= this.formvalue(section_id
),
744 dhcpv6
= this.section
.getOption('dhcpv6').getUIElement(section_id
),
745 ndp
= this.section
.getOption('ndp').getUIElement(section_id
),
746 ra
= this.section
.getOption('ra').getUIElement(section_id
);
748 if (checked
== '1' || protoval
!= 'static') {
749 dhcpv6
.node
.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
751 if (dhcpv6
.getValue() == 'server')
752 dhcpv6
.setValue('hybrid');
754 ra
.node
.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
756 if (ra
.getValue() == 'server')
757 ra
.setValue('hybrid');
760 if (checked
== '1') {
761 dhcpv6
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
762 ra
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
763 ndp
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
766 if (protoval
== 'static') {
767 dhcpv6
.node
.querySelector('li[data-value="server"]').removeAttribute('unselectable');
768 ra
.node
.querySelector('li[data-value="server"]').removeAttribute('unselectable');
771 dhcpv6
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_downstream_desc
;
772 ra
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_downstream_desc
;
773 ndp
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= ndp_downstream_desc
;
780 so
= ss
.taboption('ipv6', cbiRichListValue
, 'ra', _('<abbr title="Router Advertisement">RA</abbr>-Service'),
781 _('Configures the operation mode of the <abbr title="Router Advertisement">RA</abbr> service on this interface.'));
782 so
.value('', _('disabled'),
783 _('Do not send any <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages on this interface.'));
784 so
.value('server', _('server mode'),
785 _('Send <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages advertising this device as IPv6 router.'));
786 so
.value('relay', _('relay mode'),
787 _('Forward <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages received on the designated master interface to downstream interfaces.'));
788 so
.value('hybrid', _('hybrid mode'), ' ');
791 so
= ss
.taboption('ipv6-ra', cbiRichListValue
, 'ra_default', _('Default router'),
792 _('Configures the default router advertisement in <abbr title="Router Advertisement">RA</abbr> messages.'));
793 so
.value('', _('automatic'),
794 _('Announce this device as default router if a local IPv6 default route is present.'));
795 so
.value('1', _('on available prefix'),
796 _('Announce this device as default router if a public IPv6 prefix is available, regardless of local default route availability.'));
797 so
.value('2', _('forced'),
798 _('Announce this device as default router regardless of whether a prefix or default route is present.'));
799 so
.depends('ra', 'server');
800 so
.depends({ ra
: 'hybrid', master
: '0' });
802 so
= ss
.taboption('ipv6-ra', form
.Flag
, 'ra_slaac', _('Enable <abbr title="Stateless Address Auto Config">SLAAC</abbr>'),
803 _('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.'));
804 so
.default = so
.enabled
;
805 so
.depends('ra', 'server');
806 so
.depends({ ra
: 'hybrid', master
: '0' });
808 so
= ss
.taboption('ipv6-ra', cbiRichListValue
, 'ra_flags', _('<abbr title="Router Advertisement">RA</abbr> Flags'),
809 _('Specifies the flags sent in <abbr title="Router Advertisement">RA</abbr> messages, for example to instruct clients to request further information via stateful DHCPv6.'));
810 so
.value('managed-config', _('managed config (M)'),
811 _('The <em>Managed address configuration</em> (M) flag indicates that IPv6 addresses are available via DHCPv6.'));
812 so
.value('other-config', _('other config (O)'),
813 _('The <em>Other configuration</em> (O) flag indicates that other information, such as DNS servers, is available via DHCPv6.'));
814 so
.value('home-agent', _('mobile home agent (H)'),
815 _('The <em>Mobile IPv6 Home Agent</em> (H) flag indicates that the device is also acting as Mobile IPv6 home agent on this link.'));
817 so
.select_placeholder
= _('none');
818 so
.depends('ra', 'server');
819 so
.depends({ ra
: 'hybrid', master
: '0' });
820 so
.cfgvalue = function(section_id
) {
821 var flags
= L
.toArray(uci
.get('dhcp', section_id
, 'ra_flags'));
822 return flags
.length
? flags
: [ 'other-config' ];
824 so
.remove = function(section_id
) {
825 var existing
= L
.toArray(uci
.get('dhcp', section_id
, 'ra_flags'));
826 if (this.isActive(section_id
)) {
827 if (existing
.length
!= 1 || existing
[0] != 'none')
828 uci
.set('dhcp', section_id
, 'ra_flags', [ 'none' ]);
830 else if (existing
.length
) {
831 uci
.unset('dhcp', section_id
, 'ra_flags');
835 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_pref64', _('NAT64 prefix'), _('Announce NAT64 prefix in <abbr title="Router Advertisement">RA</abbr> messages.'));
837 so
.datatype
= 'cidr6';
838 so
.placeholder
= '64:ff9b::/96';
839 so
.depends('ra', 'server');
840 so
.depends({ ra
: 'hybrid', master
: '0' });
842 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.'));
844 so
.datatype
= 'uinteger';
845 so
.placeholder
= '600';
846 so
.depends('ra', 'server');
847 so
.depends({ ra
: 'hybrid', master
: '0' });
849 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.'));
851 so
.datatype
= 'uinteger';
852 so
.placeholder
= '200';
853 so
.depends('ra', 'server');
854 so
.depends({ ra
: 'hybrid', master
: '0' });
856 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.'));
858 so
.datatype
= 'range(0, 9000)';
859 so
.placeholder
= '1800';
860 so
.depends('ra', 'server');
861 so
.depends({ ra
: 'hybrid', master
: '0' });
863 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.'));
865 so
.datatype
= 'range(1280, 65535)';
866 so
.depends('ra', 'server');
867 so
.depends({ ra
: 'hybrid', master
: '0' });
868 so
.load = function(section_id
) {
869 var dev
= ifc
.getL3Device(),
870 path
= dev
? "/proc/sys/net/ipv6/conf/%s/mtu".format(dev
.getName()) : null;
873 dev
? L
.resolveDefault(fs
.read(path
), dev
.getMTU()) : null,
874 this.super('load', [section_id
])
875 ]).then(L
.bind(function(res
) {
876 this.placeholder
= +res
[0];
882 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.'));
884 so
.datatype
= 'range(0, 255)';
885 so
.depends('ra', 'server');
886 so
.depends({ ra
: 'hybrid', master
: '0' });
887 so
.load = function(section_id
) {
888 var dev
= ifc
.getL3Device(),
889 path
= dev
? "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev
.getName()) : null;
892 dev
? L
.resolveDefault(fs
.read(path
), 64) : null,
893 this.super('load', [section_id
])
894 ]).then(L
.bind(function(res
) {
895 this.placeholder
= +res
[0];
902 so
= ss
.taboption('ipv6', cbiRichListValue
, 'dhcpv6', _('DHCPv6-Service'),
903 _('Configures the operation mode of the DHCPv6 service on this interface.'));
904 so
.value('', _('disabled'),
905 _('Do not offer DHCPv6 service on this interface.'));
906 so
.value('server', _('server mode'),
907 _('Provide a DHCPv6 server on this interface and reply to DHCPv6 solicitations and requests.'));
908 so
.value('relay', _('relay mode'),
909 _('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));
910 so
.value('hybrid', _('hybrid mode'), ' ');
912 so
= ss
.taboption('ipv6', form
.Value
, 'dhcpv6_pd_min_len', _('<abbr title="Prefix Delegation">PD</abbr> minimum length'),
913 _('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.'));
914 so
.datatype
= 'range(1,62)';
915 so
.depends('dhcpv6', 'server');
917 so
= ss
.taboption('ipv6', form
.DynamicList
, 'dns', _('Announced IPv6 DNS servers'),
918 _('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.'));
919 so
.datatype
= 'ip6addr("nomask")'; /* restrict to IPv6 only for now since dnsmasq (DHCPv4) does not honour this option */
920 so
.depends('ra', 'server');
921 so
.depends({ ra
: 'hybrid', master
: '0' });
922 so
.depends('dhcpv6', 'server');
923 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
925 so
= ss
.taboption('ipv6', form
.Flag
, 'dns_service', _('Local IPv6 DNS server'),
926 _('Announce this device as IPv6 DNS server.'));
927 so
.default = so
.enabled
;
928 so
.depends({ ra
: 'server', dns
: /^$/ });
929 so
.depends({ ra
: 'hybrid', dns
: /^$/, master
: '0' });
930 so
.depends({ dhcpv6
: 'server', dns
: /^$/ });
931 so
.depends({ dhcpv6
: 'hybrid', dns
: /^$/, master
: '0' });
933 so
= ss
.taboption('ipv6', form
.DynamicList
, 'domain', _('Announced DNS domains'),
934 _('Specifies a fixed list of DNS search domains to announce via DHCPv6. If left unspecified, the local device DNS search domain will be announced.'));
935 so
.datatype
= 'hostname';
936 so
.depends('ra', 'server');
937 so
.depends({ ra
: 'hybrid', master
: '0' });
938 so
.depends('dhcpv6', 'server');
939 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
942 so
= ss
.taboption('ipv6', cbiRichListValue
, 'ndp', _('<abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy'),
943 _('Configures the operation mode of the NDP proxy service on this interface.'));
944 so
.value('', _('disabled'),
945 _('Do not proxy any <abbr title="Neighbour Discovery Protocol">NDP</abbr> packets.'));
946 so
.value('relay', _('relay mode'),
947 _('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.'));
948 so
.value('hybrid', _('hybrid mode'), ' ');
951 so
= ss
.taboption('ipv6', form
.Flag
, 'ndproxy_routing', _('Learn routes'), _('Set up routes for proxied IPv6 neighbours.'));
952 so
.default = so
.enabled
;
953 so
.depends('ndp', 'relay');
954 so
.depends('ndp', 'hybrid');
956 so
= ss
.taboption('ipv6', form
.Flag
, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
957 so
.depends({ ndp
: 'relay', master
: '0' });
958 so
.depends({ ndp
: 'hybrid', master
: '0' });
960 so
= ss
.taboption('ipv6', form
.Value
, 'preferred_lifetime', _('IPv6 Prefix Lifetime'), _('Preferred lifetime for a prefix.'));
962 so
.placeholder
= '12h';
963 so
.value('5m', _('5m (5 minutes)'));
964 so
.value('3h', _('3h (3 hours)'));
965 so
.value('12h', _('12h (12 hours - default)'));
966 so
.value('7d', _('7d (7 days)'));
968 //This is a ra_* setting, but its placement is more logical/findable under IPv6 settings.
969 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.'));
973 ifc
.renderFormOptions(s
);
975 // Common interface options
976 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
977 o
.default = o
.enabled
;
979 if (has_peerdns(protoval
)) {
980 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
981 o
.default = o
.enabled
;
984 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'dns', _('Use custom DNS servers'));
985 if (has_peerdns(protoval
))
986 o
.depends('peerdns', '0');
987 o
.datatype
= 'ipaddr';
989 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'dns_search', _('DNS search domains'));
990 if (protoval
!= 'static')
991 o
.depends('peerdns', '0');
992 o
.datatype
= 'hostname';
994 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'));
995 o
.datatype
= 'uinteger';
998 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'metric', _('Use gateway metric'));
999 o
.datatype
= 'uinteger';
1000 o
.placeholder
= '0';
1002 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip4table', _('Override IPv4 routing table'));
1003 o
.datatype
= 'or(uinteger, string)';
1004 for (var i
= 0; i
< rtTables
.length
; i
++)
1005 o
.value(rtTables
[i
][1], '%s (%d)'.format(rtTables
[i
][1], rtTables
[i
][0]));
1007 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6table', _('Override IPv6 routing table'));
1008 o
.datatype
= 'or(uinteger, string)';
1009 for (var i
= 0; i
< rtTables
.length
; i
++)
1010 o
.value(rtTables
[i
][1], '%s (%d)'.format(rtTables
[i
][1], rtTables
[i
][0]));
1012 if (protoval
== 'dhcpv6') {
1013 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'sourcefilter', _('IPv6 source routing'), _('Automatically handle multiple uplink interfaces using source-based policy routing.'));
1014 o
.default = o
.enabled
;
1017 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
1018 o
.default = o
.enabled
;
1020 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'));
1021 o
.value('', _('disabled'));
1023 o
.datatype
= 'max(128)';
1025 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6hint', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
1026 o
.placeholder
= '0';
1027 o
.validate = function(section_id
, value
) {
1028 if (value
== null || value
== '')
1031 var n
= parseInt(value
, 16);
1033 if (!/^(0x)?[0-9a-fA-F]+$/.test(value
) || isNaN(n
) || n
>= 0xffffffff)
1034 return _('Expecting a hexadecimal assignment hint');
1038 for (var i
= 33; i
<= 64; i
++)
1039 o
.depends('ip6assign', String(i
));
1042 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'ip6class', _('IPv6 prefix filter'), _('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));
1043 o
.value('local', 'local (%s)'.format(_('Local ULA')));
1045 var prefixClasses
= {};
1047 this.networks
.forEach(function(net
) {
1048 var prefixes
= net
._ubus('ipv6-prefix');
1049 if (Array
.isArray(prefixes
)) {
1050 prefixes
.forEach(function(pfx
) {
1051 if (L
.isObject(pfx
) && typeof(pfx
['class']) == 'string') {
1052 prefixClasses
[pfx
['class']] = prefixClasses
[pfx
['class']] || {};
1053 prefixClasses
[pfx
['class']][net
.getName()] = true;
1059 Object
.keys(prefixClasses
).sort().forEach(function(c
) {
1060 var networks
= Object
.keys(prefixClasses
[c
]).sort().join(', ');
1061 o
.value(c
, (c
!= networks
) ? '%s (%s)'.format(c
, networks
) : c
);
1065 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."));
1066 o
.datatype
= 'ip6hostid';
1067 o
.placeholder
= '::1';
1069 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.'));
1070 o
.datatype
= 'uinteger';
1071 o
.placeholder
= '0';
1073 for (var i
= 0; i
< s
.children
.length
; i
++) {
1081 case '_switch_proto':
1082 case '_ifacestat_modal':
1085 case 'igmp_snooping':
1090 for (var j
= 0; j
< protocols
.length
; j
++) {
1091 if (!protocols
[j
].isVirtual()) {
1093 for (var k
= 0; k
< o
.deps
.length
; k
++)
1094 deps
.push(Object
.assign({ proto
: protocols
[j
].getProtocol() }, o
.deps
[k
]));
1096 deps
.push({ proto
: protocols
[j
].getProtocol() });
1104 for (var j
= 0; j
< o
.deps
.length
; j
++)
1105 o
.deps
[j
].proto
= protoval
;
1107 o
.depends('proto', protoval
);
1111 this.activeSection
= s
.section
;
1115 s
.handleModalCancel = function(/* ... */) {
1116 var type
= uci
.get('network', this.activeSection
|| this.addedSection
, 'type'),
1117 device
= (type
== 'bridge') ? 'br-%s'.format(this.activeSection
|| this.addedSection
) : null;
1119 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1120 if (device
!= null && bvs
.device
== device
)
1121 uci
.remove('network', bvs
['.name']);
1124 return form
.GridSection
.prototype.handleModalCancel
.apply(this, arguments
);
1127 s
.handleAdd = function(ev
) {
1128 var m2
= new form
.Map('network'),
1129 s2
= m2
.section(form
.NamedSection
, '_new_'),
1130 protocols
= network
.getProtocols(),
1131 proto
, name
, device
;
1133 protocols
.sort(function(a
, b
) {
1134 return L
.naturalCompare(a
.getProtocol(), b
.getProtocol());
1137 s2
.render = function() {
1138 return Promise
.all([
1140 this.renderUCISection('_new_')
1141 ]).then(this.renderContents
.bind(this));
1144 name
= s2
.option(form
.Value
, 'name', _('Name'));
1145 name
.rmempty
= false;
1146 name
.datatype
= 'uciname';
1147 name
.placeholder
= _('New interface name…');
1148 name
.validate = function(section_id
, value
) {
1149 if (uci
.get('network', value
) != null)
1150 return _('The interface name is already used');
1152 var pr
= network
.getProtocol(proto
.formvalue(section_id
), value
),
1153 ifname
= pr
.isVirtual() ? '%s-%s'.format(pr
.getProtocol(), value
) : 'br-%s'.format(value
);
1155 if (value
.length
> 15)
1156 return _('The interface name is too long');
1161 proto
= s2
.option(form
.ListValue
, 'proto', _('Protocol'));
1162 proto
.validate
= name
.validate
;
1164 device
= s2
.option(widgets
.DeviceSelect
, 'device', _('Device'));
1165 device
.noaliases
= false;
1166 device
.optional
= false;
1168 for (var i
= 0; i
< protocols
.length
; i
++) {
1169 proto
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
1171 if (!protocols
[i
].isVirtual())
1172 device
.depends('proto', protocols
[i
].getProtocol());
1175 m2
.render().then(L
.bind(function(nodes
) {
1176 ui
.showModal(_('Add new interface...'), [
1178 E('div', { 'class': 'right' }, [
1181 'click': ui
.hideModal
1182 }, _('Cancel')), ' ',
1184 'class': 'cbi-button cbi-button-positive important',
1185 'click': ui
.createHandlerFn(this, function(ev
) {
1186 var nameval
= name
.isValid('_new_') ? name
.formvalue('_new_') : null,
1187 protoval
= proto
.isValid('_new_') ? proto
.formvalue('_new_') : null,
1188 protoclass
= protoval
? network
.getProtocol(protoval
, nameval
) : null;
1190 if (nameval
== null || protoval
== null || nameval
== '' || protoval
== '')
1193 return protoclass
.isCreateable(nameval
).then(function(checkval
) {
1194 if (checkval
!= null) {
1195 ui
.addNotification(null,
1196 E('p', _('New interface for "%s" can not be created: %s').format(protoclass
.getI18n(), checkval
)));
1201 return m
.save(function() {
1202 var section_id
= uci
.add('network', 'interface', nameval
);
1204 protoclass
.set('proto', protoval
);
1205 protoclass
.addDevice(device
.formvalue('_new_'));
1207 m
.children
[0].addedSection
= section_id
;
1210 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
1211 }).then(L
.bind(m
.children
[0].renderMoreOptionsModal
, m
.children
[0], nameval
));
1214 }, _('Create interface'))
1218 nodes
.querySelector('[id="%s"] input[type="text"]'.format(name
.cbid('_new_'))).focus();
1222 s
.handleRemove = function(section_id
, ev
) {
1223 return network
.deleteNetwork(section_id
).then(L
.bind(function(section_id
, ev
) {
1224 return form
.GridSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1225 }, this, section_id
, ev
));
1228 o
= s
.option(form
.DummyValue
, '_ifacebox');
1229 o
.modalonly
= false;
1230 o
.textvalue = function(section_id
) {
1231 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0],
1232 zone
= net
? this.section
.zones
.filter(function(z
) { return !!z
.getNetworks().filter(function(n
) { return n
== section_id
})[0] })[0] : null;
1237 var node
= E('div', { 'class': 'ifacebox' }, [
1239 'class': 'ifacebox-head',
1240 'style': firewall
.getZoneColorStyle(zone
),
1241 'title': zone
? _('Part of zone %q').format(zone
.getName()) : _('No zone assigned')
1242 }, E('strong', net
.getName())),
1244 'class': 'ifacebox-body',
1245 'id': '%s-ifc-devices'.format(section_id
),
1246 'data-network': section_id
1249 'src': L
.resource('icons/ethernet_disabled.png'),
1250 'style': 'width:16px; height:16px'
1252 E('br'), E('small', '?')
1256 render_ifacebox_status(node
.childNodes
[1], net
);
1261 o
= s
.option(form
.DummyValue
, '_ifacestat');
1262 o
.modalonly
= false;
1263 o
.textvalue = function(section_id
) {
1264 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0];
1269 var node
= E('div', { 'id': '%s-ifc-description'.format(section_id
) });
1271 render_status(node
, net
, false);
1276 o
= s
.taboption('advanced', form
.Flag
, 'delegate', _('Use builtin IPv6-management'));
1278 o
.default = o
.enabled
;
1280 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).'));
1283 '1': [{ proto
: 'static' }],
1288 // Device configuration
1289 s
= m
.section(form
.GridSection
, 'device', _('Devices'));
1292 s
.addbtntitle
= _('Add device configuration…');
1294 s
.cfgsections = function() {
1295 var sections
= uci
.sections('network', 'device'),
1296 section_ids
= sections
.sort(function(a
, b
) { return L
.naturalCompare(a
.name
, b
.name
) }).map(function(s
) { return s
['.name'] });
1298 for (var i
= 0; i
< netDevs
.length
; i
++) {
1299 if (sections
.filter(function(s
) { return s
.name
== netDevs
[i
].getName() }).length
)
1302 if (netDevs
[i
].getType() == 'wifi' && !netDevs
[i
].isUp())
1305 /* Unless http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html is implemented,
1306 we cannot properly redefine bridges as devices, so filter them away for now... */
1308 var m
= netDevs
[i
].isBridge() ? netDevs
[i
].getName().match(/^br-([A-Za-z0-9_]+)$/) : null,
1309 s
= m
? uci
.get('network', m
[1]) : null;
1311 if (s
&& s
['.type'] == 'interface' && s
.type
== 'bridge')
1314 section_ids
.push('dev:%s'.format(netDevs
[i
].getName()));
1320 s
.renderMoreOptionsModal = function(section_id
, ev
) {
1321 var m
= section_id
.match(/^dev:(.+)$/);
1324 var devtype
= getDevType(section_id
);
1326 section_id
= uci
.add('network', 'device');
1328 uci
.set('network', section_id
, 'name', m
[1]);
1329 uci
.set('network', section_id
, 'type', (devtype
!= 'ethernet') ? devtype
: null);
1331 this.addedSection
= section_id
;
1334 return this.super('renderMoreOptionsModal', [section_id
, ev
]);
1337 s
.renderRowActions = function(section_id
) {
1338 var trEl
= this.super('renderRowActions', [ section_id
, _('Configure…') ]),
1339 deleteBtn
= trEl
.querySelector('button:last-child');
1341 deleteBtn
.firstChild
.data
= _('Unconfigure');
1342 deleteBtn
.setAttribute('title', _('Remove related device settings from the configuration'));
1343 deleteBtn
.disabled
= section_id
.match(/^dev:/) ? true : null;
1348 s
.modaltitle = function(section_id
) {
1349 var m
= section_id
.match(/^dev:(.+)$/),
1350 name
= m
? m
[1] : uci
.get('network', section_id
, 'name');
1352 return name
? '%s: %q'.format(getDevTypeDesc(section_id
), name
) : _('Add device configuration');
1355 s
.addModalOptions = function(s
) {
1356 var isNew
= (uci
.get('network', s
.section
, 'name') == null),
1357 dev
= getDevice(s
.section
);
1359 nettools
.addDeviceOptions(s
, dev
, isNew
);
1362 s
.handleModalCancel = function(map
/*, ... */) {
1363 var name
= uci
.get('network', this.addedSection
, 'name')
1365 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1366 if (name
!= null && bvs
.device
== name
)
1367 uci
.remove('network', bvs
['.name']);
1371 for (var i
= 0; i
< map
.addedVLANs
.length
; i
++)
1372 uci
.remove('network', map
.addedVLANs
[i
]);
1374 if (this.addedSection
)
1375 uci
.remove('network', this.addedSection
);
1377 return form
.GridSection
.prototype.handleModalCancel
.apply(this, arguments
);
1380 s
.handleRemove = function(section_id
/*, ... */) {
1381 var name
= uci
.get('network', section_id
, 'name'),
1382 type
= uci
.get('network', section_id
, 'type');
1384 if (name
!= null && type
== 'bridge') {
1385 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1386 if (bvs
.device
== name
)
1387 uci
.remove('network', bvs
['.name']);
1391 return form
.GridSection
.prototype.handleRemove
.apply(this, arguments
);
1394 function getDevice(section_id
) {
1395 var m
= section_id
.match(/^dev:(.+)$/),
1396 name
= m
? m
[1] : uci
.get('network', section_id
, 'name');
1398 return netDevs
.filter(function(d
) { return d
.getName() == name
})[0];
1401 function getDevType(section_id
) {
1402 var dev
= getDevice(section_id
),
1403 cfg
= uci
.get('network', section_id
),
1404 type
= cfg
? (uci
.get('network', section_id
, 'type') || 'ethernet') : (dev
? dev
.getType() : '');
1438 function getDevTypeDesc(section_id
) {
1439 switch (getDevType(section_id
) || '') {
1441 return E('em', [ _('Device not present') ]);
1444 return _('VLAN (802.1q)');
1447 return _('VLAN (802.1ad)');
1450 return _('Bridge device');
1453 return _('Tunnel device');
1456 return _('MAC VLAN');
1459 return _('Virtual Ethernet');
1462 return _('Network device');
1466 o
= s
.option(form
.DummyValue
, 'name', _('Device'));
1467 o
.modalonly
= false;
1468 o
.textvalue = function(section_id
) {
1469 var dev
= getDevice(section_id
),
1470 ext
= section_id
.match(/^dev:/),
1471 icon
= render_iface(dev
);
1474 icon
.querySelector('img').style
.opacity
= '.5';
1476 return E('span', { 'class': 'ifacebadge' }, [
1478 E('span', { 'style': ext
? 'opacity:.5' : null }, [
1479 dev
? dev
.getName() : (uci
.get('network', section_id
, 'name') || '?')
1484 o
= s
.option(form
.DummyValue
, 'type', _('Type'));
1485 o
.textvalue
= getDevTypeDesc
;
1486 o
.modalonly
= false;
1488 o
= s
.option(form
.DummyValue
, 'macaddr', _('MAC Address'));
1489 o
.modalonly
= false;
1490 o
.textvalue = function(section_id
) {
1491 var dev
= getDevice(section_id
),
1492 val
= uci
.get('network', section_id
, 'macaddr'),
1493 mac
= dev
? dev
.getMAC() : null;
1495 return val
? E('strong', {
1496 'data-tooltip': _('The value is overridden by configuration.')
1497 }, [ val
.toUpperCase() ]) : (mac
|| '-');
1500 o
= s
.option(form
.DummyValue
, 'mtu', _('MTU'));
1501 o
.modalonly
= false;
1502 o
.textvalue = function(section_id
) {
1503 var dev
= getDevice(section_id
),
1504 val
= uci
.get('network', section_id
, 'mtu'),
1505 mtu
= dev
? dev
.getMTU() : null;
1507 return val
? E('strong', {
1508 'data-tooltip': _('The value is overridden by configuration.')
1509 }, [ val
]) : (mtu
|| '-').toString();
1512 s
= m
.section(form
.TypedSection
, 'globals', _('Global network options'));
1513 s
.addremove
= false;
1516 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.'));
1517 o
.datatype
= 'cidr6';
1519 o
= s
.option(form
.Flag
, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
1523 if (dslModemType
!= null) {
1524 s
= m
.section(form
.TypedSection
, 'dsl', _('DSL'));
1527 o
= s
.option(form
.ListValue
, 'annex', _('Annex'));
1528 if (dslModemType
== 'vdsl') {
1529 o
.value('a', _('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));
1530 o
.value('b', _('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));
1531 o
.value('j', _('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));
1533 o
.value('a', _('ADSL (all variants) Annex A/L/M'));
1534 o
.value('b', _('ADSL (all variants) Annex B'));
1535 o
.value('j', _('ADSL (all variants) Annex B/J'));
1537 o
.value('m', _('ADSL (all variants) Annex M'));
1538 o
.value('at1', _('ANSI T1.413'));
1539 o
.value('admt', _('ADSL (G.992.1) Annex A'));
1540 o
.value('bdmt', _('ADSL (G.992.1) Annex B'));
1541 o
.value('alite', _('Splitterless ADSL (G.992.2) Annex A'));
1542 o
.value('a2', _('ADSL2 (G.992.3) Annex A'));
1543 o
.value('b2', _('ADSL2 (G.992.3) Annex B'));
1544 o
.value('l', _('ADSL2 (G.992.3) Annex L'));
1545 o
.value('m2', _('ADSL2 (G.992.3) Annex M'));
1546 o
.value('a2p', _('ADSL2+ (G.992.5) Annex A'));
1547 o
.value('b2p', _('ADSL2+ (G.992.5) Annex B'));
1548 o
.value('m2p', _('ADSL2+ (G.992.5) Annex M'));
1550 o
= s
.option(form
.ListValue
, 'tone', _('Tone'));
1551 o
.value('', _('auto'));
1552 o
.value('a', _('A43C + J43 + A43'));
1553 o
.value('av', _('A43C + J43 + A43 + V43'));
1554 o
.value('b', _('B43 + B43C'));
1555 o
.value('bv', _('B43 + B43C + V43'));
1557 if (dslModemType
== 'vdsl') {
1558 o
= s
.option(form
.ListValue
, 'xfer_mode', _('Encapsulation mode'));
1559 o
.value('', _('auto'));
1560 o
.value('atm', _('ATM (Asynchronous Transfer Mode)'));
1561 o
.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
1563 o
= s
.option(form
.ListValue
, 'line_mode', _('DSL line mode'));
1564 o
.value('', _('auto'));
1565 o
.value('adsl', _('ADSL'));
1566 o
.value('vdsl', _('VDSL'));
1568 o
= s
.option(form
.ListValue
, 'ds_snr_offset', _('Downstream SNR offset'));
1571 for (var i
= -100; i
<= 100; i
+= 5)
1572 o
.value(i
, _('%.1f dB').format(i
/ 10));
1575 s
.option(form
.Value
, 'firmware', _('Firmware File'));
1579 // Show ATM bridge section if we have the capabilities
1580 if (L
.hasSystemFeature('br2684ctl')) {
1581 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.'));
1585 s
.addbtntitle
= _('Add ATM Bridge');
1587 s
.handleAdd = function(ev
) {
1588 var sections
= uci
.sections('network', 'atm-bridge'),
1591 for (var i
= 0; i
< sections
.length
; i
++) {
1592 var unit
= +sections
[i
].unit
;
1594 if (!isNaN(unit
) && unit
> max_unit
)
1598 return this.map
.save(function() {
1599 var sid
= uci
.add('network', 'atm-bridge');
1601 uci
.set('network', sid
, 'unit', max_unit
+ 1);
1602 uci
.set('network', sid
, 'atmdev', 0);
1603 uci
.set('network', sid
, 'encaps', 'llc');
1604 uci
.set('network', sid
, 'payload', 'bridged');
1605 uci
.set('network', sid
, 'vci', 35);
1606 uci
.set('network', sid
, 'vpi', 8);
1610 s
.tab('general', _('General Setup'));
1611 s
.tab('advanced', _('Advanced Settings'));
1613 o
= s
.taboption('general', form
.Value
, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
1614 s
.taboption('general', form
.Value
, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
1616 o
= s
.taboption('general', form
.ListValue
, 'encaps', _('Encapsulation mode'));
1617 o
.value('llc', _('LLC'));
1618 o
.value('vc', _('VC-Mux'));
1620 s
.taboption('advanced', form
.Value
, 'atmdev', _('ATM device number'));
1621 s
.taboption('advanced', form
.Value
, 'unit', _('Bridge unit number'));
1623 o
= s
.taboption('advanced', form
.ListValue
, 'payload', _('Forwarding mode'));
1624 o
.value('bridged', _('bridged'));
1625 o
.value('routed', _('routed'));
1629 return m
.render().then(L
.bind(function(m
, nodes
) {
1630 poll
.add(L
.bind(function() {
1631 var section_ids
= m
.children
[0].cfgsections(),
1634 for (var i
= 0; i
< section_ids
.length
; i
++) {
1635 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
1636 dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
1637 btn1
= row
.querySelector('.cbi-section-actions .reconnect'),
1638 btn2
= row
.querySelector('.cbi-section-actions .down');
1640 if (dsc
.getAttribute('reconnect') == '') {
1641 dsc
.setAttribute('reconnect', '1');
1642 tasks
.push(fs
.exec('/sbin/ifup', [section_ids
[i
]]).catch(function(e
) {
1643 ui
.addNotification(null, E('p', e
.message
));
1646 else if (dsc
.getAttribute('disconnect') == '') {
1647 dsc
.setAttribute('disconnect', '1');
1648 tasks
.push(fs
.exec('/sbin/ifdown', [section_ids
[i
]]).catch(function(e
) {
1649 ui
.addNotification(null, E('p', e
.message
));
1652 else if (dsc
.getAttribute('reconnect') == '1') {
1653 dsc
.removeAttribute('reconnect');
1654 btn1
.classList
.remove('spinning');
1655 btn1
.disabled
= false;
1657 else if (dsc
.getAttribute('disconnect') == '1') {
1658 dsc
.removeAttribute('disconnect');
1659 btn2
.classList
.remove('spinning');
1660 btn2
.disabled
= false;
1664 return Promise
.all(tasks
)
1665 .then(L
.bind(network
.getNetworks
, network
))
1666 .then(L
.bind(this.poll_status
, this, nodes
));