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 function has_sourcefilter(proto
) {
266 var cbiRichListValue
= form
.ListValue
.extend({
267 renderWidget: function(section_id
, option_index
, cfgvalue
) {
268 var choices
= this.transformChoices();
269 var widget
= new ui
.Dropdown((cfgvalue
!= null) ? cfgvalue
: this.default, choices
, {
270 id
: this.cbid(section_id
),
273 select_placeholder
: this.select_placeholder
|| this.placeholder
,
274 custom_placeholder
: this.custom_placeholder
|| this.placeholder
,
275 validate
: L
.bind(this.validate
, this, section_id
),
276 disabled
: (this.readonly
!= null) ? this.readonly
: this.map
.readonly
279 return widget
.render();
282 value: function(value
, title
, description
) {
284 form
.ListValue
.prototype.value
.call(this, value
, E([], [
285 E('span', { 'class': 'hide-open' }, [ title
]),
286 E('div', { 'class': 'hide-close', 'style': 'min-width:25vw' }, [
287 E('strong', [ title
]),
289 E('span', { 'style': 'white-space:normal' }, description
)
294 form
.ListValue
.prototype.value
.call(this, value
, title
);
300 poll_status: function(map
, networks
) {
301 var resolveZone
= null;
303 for (var i
= 0; i
< networks
.length
; i
++) {
304 var ifc
= networks
[i
],
305 row
= map
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc
.getName()));
310 var dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
311 box
= row
.querySelector('[data-name="_ifacebox"] .ifacebox-body'),
312 btn1
= row
.querySelector('.cbi-section-actions .reconnect'),
313 btn2
= row
.querySelector('.cbi-section-actions .down'),
314 stat
= document
.querySelector('[id="%s-ifc-status"]'.format(ifc
.getName())),
315 resolveZone
= render_ifacebox_status(box
, ifc
),
316 disabled
= ifc
? !ifc
.isUp() : true,
317 dynamic
= ifc
? ifc
.isDynamic() : false;
319 if (dsc
.hasAttribute('reconnect')) {
320 dom
.content(dsc
, E('em', _('Interface is starting...')));
322 else if (dsc
.hasAttribute('disconnect')) {
323 dom
.content(dsc
, E('em', _('Interface is stopping...')));
325 else if (ifc
.getProtocol() || uci
.get('network', ifc
.getName()) == null) {
326 render_status(dsc
, ifc
, false);
328 else if (!ifc
.getProtocol()) {
329 var e
= map
.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc
.getName()));
330 if (e
) e
.disabled
= true;
332 var link
= L
.url('admin/system/opkg') + '?query=luci-proto';
334 E('em', _('Unsupported protocol type.')), E('br'),
335 E('a', { href
: link
}, _('Install protocol extensions...'))
339 dom
.content(dsc
, E('em', _('Interface not present or not connected yet.')));
343 var dev
= ifc
.getDevice();
346 'src': L
.resource('icons/%s%s.png').format(dev
? dev
.getType() : 'ethernet', (dev
&& dev
.isUp()) ? '' : '_disabled'),
347 'title': dev
? dev
.getTypeI18n() : _('Not present')
349 render_status(E('span'), ifc
, true)
353 btn1
.disabled
= isReadonlyView
|| btn1
.classList
.contains('spinning') || btn2
.classList
.contains('spinning') || dynamic
;
354 btn2
.disabled
= isReadonlyView
|| btn1
.classList
.contains('spinning') || btn2
.classList
.contains('spinning') || dynamic
|| disabled
;
357 document
.querySelectorAll('.port-status-device[data-device]').forEach(function(node
) {
358 nettools
.updateDevBadge(node
, network
.instantiateDevice(node
.getAttribute('data-device')));
361 document
.querySelectorAll('.port-status-link[data-device]').forEach(function(node
) {
362 nettools
.updatePortStatus(node
, network
.instantiateDevice(node
.getAttribute('data-device')));
365 return Promise
.all([ resolveZone
, network
.flushCache() ]);
370 network
.getDSLModemType(),
371 network
.getDevices(),
372 fs
.lines('/etc/iproute2/rt_tables'),
373 L
.resolveDefault(fs
.read('/usr/lib/opkg/info/netifd.control')),
378 interfaceBridgeWithIfnameSections: function() {
379 return uci
.sections('network', 'interface').filter(function(ns
) {
380 return ns
.type
== 'bridge' && !ns
.ports
&& ns
.ifname
;
384 deviceWithIfnameSections: function() {
385 return uci
.sections('network', 'device').filter(function(ns
) {
386 return ns
.type
== 'bridge' && !ns
.ports
&& ns
.ifname
;
390 interfaceWithIfnameSections: function() {
391 return uci
.sections('network', 'interface').filter(function(ns
) {
392 return !ns
.device
&& ns
.ifname
;
396 handleBridgeMigration: function(ev
) {
399 this.interfaceBridgeWithIfnameSections().forEach(function(ns
) {
400 var device_name
= 'br-' + ns
['.name'];
402 tasks
.push(uci
.callAdd('network', 'device', null, {
405 'ports': L
.toArray(ns
.ifname
),
407 'macaddr': ns
.macaddr
,
408 'igmp_snooping': ns
.igmp_snooping
411 tasks
.push(uci
.callSet('network', ns
['.name'], {
417 'device': device_name
421 return Promise
.all(tasks
)
422 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
423 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
426 renderBridgeMigration: function() {
427 ui
.showModal(_('Network bridge configuration migration'), [
428 E('p', _('The existing network configuration needs to be changed for LuCI to function properly.')),
429 E('p', _('Upon pressing "Continue", bridges configuration will be updated and the network will be restarted to apply the updated configuration.')),
430 E('div', { 'class': 'right' },
432 'class': 'btn cbi-button-action important',
433 'click': ui
.createHandlerFn(this, 'handleBridgeMigration')
438 handleIfnameMigration: function(ev
) {
441 this.deviceWithIfnameSections().forEach(function(ds
) {
442 tasks
.push(uci
.callSet('network', ds
['.name'], {
444 'ports': L
.toArray(ds
.ifname
)
448 this.interfaceWithIfnameSections().forEach(function(ns
) {
449 tasks
.push(uci
.callSet('network', ns
['.name'], {
455 return Promise
.all(tasks
)
456 .then(L
.bind(ui
.changes
.init
, ui
.changes
))
457 .then(L
.bind(ui
.changes
.apply
, ui
.changes
));
460 renderIfnameMigration: function() {
461 ui
.showModal(_('Network ifname configuration migration'), [
462 E('p', _('The existing network configuration needs to be changed for LuCI to function properly.')),
463 E('p', _('Upon pressing "Continue", ifname options will get renamed and the network will be restarted to apply the updated configuration.')),
464 E('div', { 'class': 'right' },
466 'class': 'btn cbi-button-action important',
467 'click': ui
.createHandlerFn(this, 'handleIfnameMigration')
472 render: function(data
) {
473 var netifdVersion
= (data
[3] || '').match(/Version: ([^\n]+)/);
475 if (netifdVersion
&& netifdVersion
[1] >= "2021-05-26") {
476 if (this.interfaceBridgeWithIfnameSections().length
)
477 return this.renderBridgeMigration();
478 else if (this.deviceWithIfnameSections().length
|| this.interfaceWithIfnameSections().length
)
479 return this.renderIfnameMigration();
482 var dslModemType
= data
[0],
486 var rtTables
= data
[2].map(function(l
) {
487 var m
= l
.trim().match(/^(\d+)\s+(\S+)$/);
488 return m
? [ +m
[1], m
[2] ] : null;
489 }).filter(function(e
) {
490 return e
&& e
[0] > 0;
493 m
= new form
.Map('network');
497 s
= m
.section(form
.GridSection
, 'interface', _('Interfaces'));
500 s
.addbtntitle
= _('Add new interface...');
502 s
.load = function() {
504 network
.getNetworks(),
507 ]).then(L
.bind(function(data
) {
508 this.networks
= data
[0];
509 this.zones
= data
[1];
513 s
.tab('general', _('General Settings'));
514 s
.tab('advanced', _('Advanced Settings'));
515 s
.tab('physical', _('Physical Settings'));
516 s
.tab('brport', _('Bridge port specific options'));
517 s
.tab('bridgevlan', _('Bridge VLAN filtering'));
518 s
.tab('firewall', _('Firewall Settings'));
519 s
.tab('dhcp', _('DHCP Server'));
521 s
.cfgsections = function() {
522 return this.networks
.map(function(n
) { return n
.getName() })
523 .filter(function(n
) { return n
!= 'loopback' });
526 s
.modaltitle = function(section_id
) {
527 return _('Interfaces') + ' » ' + section_id
;
530 s
.renderRowActions = function(section_id
) {
531 var tdEl
= this.super('renderRowActions', [ section_id
, _('Edit') ]),
532 net
= this.networks
.filter(function(n
) { return n
.getName() == section_id
})[0],
533 disabled
= net
? !net
.isUp() : true,
534 dynamic
= net
? net
.isDynamic() : false;
536 dom
.content(tdEl
.lastChild
, [
538 'class': 'cbi-button cbi-button-neutral reconnect',
539 'click': iface_updown
.bind(this, true, section_id
),
540 'title': _('Reconnect this interface'),
541 'disabled': dynamic
? 'disabled' : null
544 'class': 'cbi-button cbi-button-neutral down',
545 'click': iface_updown
.bind(this, false, section_id
),
546 'title': _('Shutdown this interface'),
547 'disabled': (dynamic
|| disabled
) ? 'disabled' : null
549 tdEl
.lastChild
.firstChild
,
550 tdEl
.lastChild
.lastChild
553 if (!dynamic
&& net
&& !uci
.get('network', net
.getName())) {
554 tdEl
.lastChild
.childNodes
[0].disabled
= true;
555 tdEl
.lastChild
.childNodes
[2].disabled
= true;
556 tdEl
.lastChild
.childNodes
[3].disabled
= true;
560 //disable the 'Edit' button on dynamic interfaces
561 tdEl
.lastChild
.childNodes
[2].disabled
= true;
567 s
.addModalOptions = function(s
) {
568 var protoval
= uci
.get('network', s
.section
, 'proto'),
569 protoclass
= protoval
? network
.getProtocol(protoval
) : null,
570 o
, proto_select
, proto_switch
, type
, stp
, igmp
, ss
, so
;
575 return network
.getNetwork(s
.section
).then(L
.bind(function(ifc
) {
576 var protocols
= network
.getProtocols();
578 protocols
.sort(function(a
, b
) {
579 return L
.naturalCompare(a
.getProtocol(), b
.getProtocol());
582 o
= s
.taboption('general', form
.DummyValue
, '_ifacestat_modal', _('Status'));
584 o
.cfgvalue
= L
.bind(function(section_id
) {
585 var net
= this.networks
.filter(function(n
) { return n
.getName() == section_id
})[0];
587 return render_modal_status(E('div', {
588 'id': '%s-ifc-status'.format(section_id
),
589 'class': 'ifacebadge large'
592 o
.write = function() {};
595 proto_select
= s
.taboption('general', form
.ListValue
, 'proto', _('Protocol'));
596 proto_select
.modalonly
= true;
598 proto_switch
= s
.taboption('general', form
.Button
, '_switch_proto');
599 proto_switch
.modalonly
= true;
600 proto_switch
.title
= _('Really switch protocol?');
601 proto_switch
.inputtitle
= _('Switch protocol');
602 proto_switch
.inputstyle
= 'apply';
603 proto_switch
.onclick
= L
.bind(function(ev
) {
605 .then(L
.bind(m
.load
, m
))
606 .then(L
.bind(m
.render
, m
))
607 .then(L
.bind(this.renderMoreOptionsModal
, this, s
.section
));
610 o
= s
.taboption('general', widgets
.DeviceSelect
, '_net_device', _('Device'));
611 o
.ucioption
= 'device';
614 o
.network
= ifc
.getName();
615 o
.exclude
= '@' + ifc
.getName();
617 o
= s
.taboption('general', form
.Flag
, 'disabled', _('Disable this interface'));
620 o
= s
.taboption('general', form
.Flag
, 'auto', _('Bring up on boot'));
622 o
.default = o
.enabled
;
624 if (L
.hasSystemFeature('firewall')) {
625 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.'));
626 o
.network
= ifc
.getName();
629 o
.cfgvalue = function(section_id
) {
630 return firewall
.getZoneByNetwork(ifc
.getName()).then(function(zone
) {
631 return (zone
!= null ? zone
.getName() : null);
635 o
.write
= o
.remove = function(section_id
, value
) {
637 firewall
.getZoneByNetwork(ifc
.getName()),
638 (value
!= null) ? firewall
.getZone(value
) : null
639 ]).then(function(data
) {
640 var old_zone
= data
[0],
643 if (old_zone
== null && new_zone
== null && (value
== null || value
== ''))
646 if (old_zone
!= null && new_zone
!= null && old_zone
.getName() == new_zone
.getName())
649 if (old_zone
!= null)
650 old_zone
.deleteNetwork(ifc
.getName());
652 if (new_zone
!= null)
653 new_zone
.addNetwork(ifc
.getName());
654 else if (value
!= null)
655 return firewall
.addZone(value
).then(function(new_zone
) {
656 new_zone
.addNetwork(ifc
.getName());
662 for (var i
= 0; i
< protocols
.length
; i
++) {
663 proto_select
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
665 if (protocols
[i
].getProtocol() != uci
.get('network', s
.section
, 'proto'))
666 proto_switch
.depends('proto', protocols
[i
].getProtocol());
669 if (L
.hasSystemFeature('dnsmasq') || L
.hasSystemFeature('odhcpd')) {
670 o
= s
.taboption('dhcp', form
.SectionValue
, '_dhcp', form
.TypedSection
, 'dhcp');
673 ss
.uciconfig
= 'dhcp';
674 ss
.addremove
= false;
677 ss
.tab('general', _('General Setup'));
678 ss
.tab('advanced', _('Advanced Settings'));
679 ss
.tab('ipv6', _('IPv6 Settings'));
680 ss
.tab('ipv6-ra', _('IPv6 RA Settings'));
682 ss
.filter = function(section_id
) {
683 return (uci
.get('dhcp', section_id
, 'interface') == ifc
.getName());
686 ss
.renderSectionPlaceholder = function() {
687 return E('div', { 'class': 'cbi-section-create' }, [
688 E('p', _('No DHCP Server configured for this interface') + '   '),
690 'class': 'cbi-button cbi-button-add',
691 'title': _('Set up DHCP Server'),
692 'click': ui
.createHandlerFn(this, function(section_id
, ev
) {
693 this.map
.save(function() {
694 uci
.add('dhcp', 'dhcp', section_id
);
695 uci
.set('dhcp', section_id
, 'interface', section_id
);
697 if (protoval
== 'static') {
698 uci
.set('dhcp', section_id
, 'start', 100);
699 uci
.set('dhcp', section_id
, 'limit', 150);
700 uci
.set('dhcp', section_id
, 'leasetime', '12h');
703 uci
.set('dhcp', section_id
, 'ignore', 1);
707 }, _('Set up DHCP Server'))
711 ss
.taboption('general', form
.Flag
, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
713 if (protoval
== 'static') {
714 so
= ss
.taboption('general', form
.Value
, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
716 so
.datatype
= 'or(uinteger,ip4addr("nomask"))';
719 so
= ss
.taboption('general', form
.Value
, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
721 so
.datatype
= 'uinteger';
724 so
= ss
.taboption('general', form
.Value
, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
728 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.'));
729 so
.default = so
.enabled
;
731 ss
.taboption('advanced', form
.Flag
, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
733 // XXX: is this actually useful?
734 //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
736 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.'));
738 so
.datatype
= 'ip4addr';
740 so
.render = function(option_index
, section_id
, in_table
) {
741 this.placeholder
= get_netmask(s
, true);
742 return form
.Value
.prototype.render
.apply(this, [ option_index
, section_id
, in_table
]);
745 so
.validate = function(section_id
, value
) {
746 var uielem
= this.getUIElement(section_id
);
748 uielem
.setPlaceholder(get_netmask(s
, false));
749 return form
.Value
.prototype.validate
.apply(this, [ section_id
, value
]);
752 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.'));
756 var has_other_master
= uci
.sections('dhcp', 'dhcp').filter(function(s
) {
757 return (s
.interface != ifc
.getName() && s
.master
== '1');
760 so
= ss
.taboption('ipv6', form
.Flag
, 'master', _('Designated master'));
761 so
.readonly
= has_other_master
? true : false;
762 so
.description
= has_other_master
763 ? _('Interface "%h" is already marked as designated master.').format(has_other_master
.interface || has_other_master
['.name'])
764 : _('Set this interface as master for RA and DHCPv6 relaying as well as NDP proxying.')
767 so
.validate = function(section_id
, value
) {
768 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>.'),
769 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.'),
770 hybrid_master_desc
= _('Operate in <em>relay mode</em> if an upstream IPv6 prefix is present, otherwise disable service.'),
771 ra_server_allowed
= true,
772 checked
= this.formvalue(section_id
),
773 dhcpv6
= this.section
.getOption('dhcpv6').getUIElement(section_id
),
774 ndp
= this.section
.getOption('ndp').getUIElement(section_id
),
775 ra
= this.section
.getOption('ra').getUIElement(section_id
);
777 /* Assume that serving RAs by default is fine, but disallow it for certain
778 interface protocols such as DHCP, DHCPv6 or the various PPP flavors.
779 The intent is to only allow RA serving for interface protocols doing
780 some kind of static IP config over something resembling a layer 2
795 ra_server_allowed
= false;
799 if (checked
== '1' || !ra_server_allowed
) {
800 dhcpv6
.node
.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
802 if (dhcpv6
.getValue() == 'server')
803 dhcpv6
.setValue('hybrid');
805 ra
.node
.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
807 if (ra
.getValue() == 'server')
808 ra
.setValue('hybrid');
811 if (checked
== '1') {
812 dhcpv6
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
813 ra
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
814 ndp
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_master_desc
;
817 if (ra_server_allowed
) {
818 dhcpv6
.node
.querySelector('li[data-value="server"]').removeAttribute('unselectable');
819 ra
.node
.querySelector('li[data-value="server"]').removeAttribute('unselectable');
822 dhcpv6
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_downstream_desc
;
823 ra
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= hybrid_downstream_desc
;
824 ndp
.node
.querySelector('li[data-value="hybrid"] > div > span').innerHTML
= ndp_downstream_desc
;
831 so
= ss
.taboption('ipv6', cbiRichListValue
, 'ra', _('<abbr title="Router Advertisement">RA</abbr>-Service'),
832 _('Configures the operation mode of the <abbr title="Router Advertisement">RA</abbr> service on this interface.'));
833 so
.value('', _('disabled'),
834 _('Do not send any <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages on this interface.'));
835 so
.value('server', _('server mode'),
836 _('Send <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages advertising this device as IPv6 router.'));
837 so
.value('relay', _('relay mode'),
838 _('Forward <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages received on the designated master interface to downstream interfaces.'));
839 so
.value('hybrid', _('hybrid mode'), ' ');
842 so
= ss
.taboption('ipv6-ra', cbiRichListValue
, 'ra_default', _('Default router'),
843 _('Configures the default router advertisement in <abbr title="Router Advertisement">RA</abbr> messages.'));
844 so
.value('', _('automatic'),
845 _('Announce this device as default router if a local IPv6 default route is present.'));
846 so
.value('1', _('on available prefix'),
847 _('Announce this device as default router if a public IPv6 prefix is available, regardless of local default route availability.'));
848 so
.value('2', _('forced'),
849 _('Announce this device as default router regardless of whether a prefix or default route is present.'));
850 so
.depends('ra', 'server');
851 so
.depends({ ra
: 'hybrid', master
: '0' });
853 so
= ss
.taboption('ipv6-ra', form
.Flag
, 'ra_slaac', _('Enable <abbr title="Stateless Address Auto Config">SLAAC</abbr>'),
854 _('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.'));
855 so
.default = so
.enabled
;
856 so
.depends('ra', 'server');
857 so
.depends({ ra
: 'hybrid', master
: '0' });
859 so
= ss
.taboption('ipv6-ra', cbiRichListValue
, 'ra_flags', _('<abbr title="Router Advertisement">RA</abbr> Flags'),
860 _('Specifies the flags sent in <abbr title="Router Advertisement">RA</abbr> messages, for example to instruct clients to request further information via stateful DHCPv6.'));
861 so
.value('managed-config', _('managed config (M)'),
862 _('The <em>Managed address configuration</em> (M) flag indicates that IPv6 addresses are available via DHCPv6.'));
863 so
.value('other-config', _('other config (O)'),
864 _('The <em>Other configuration</em> (O) flag indicates that other information, such as DNS servers, is available via DHCPv6.'));
865 so
.value('home-agent', _('mobile home agent (H)'),
866 _('The <em>Mobile IPv6 Home Agent</em> (H) flag indicates that the device is also acting as Mobile IPv6 home agent on this link.'));
868 so
.select_placeholder
= _('none');
869 so
.depends('ra', 'server');
870 so
.depends({ ra
: 'hybrid', master
: '0' });
871 so
.cfgvalue = function(section_id
) {
872 var flags
= L
.toArray(uci
.get('dhcp', section_id
, 'ra_flags'));
873 return flags
.length
? flags
: [ 'other-config' ];
875 so
.remove = function(section_id
) {
876 var existing
= L
.toArray(uci
.get('dhcp', section_id
, 'ra_flags'));
877 if (this.isActive(section_id
)) {
878 if (existing
.length
!= 1 || existing
[0] != 'none')
879 uci
.set('dhcp', section_id
, 'ra_flags', [ 'none' ]);
881 else if (existing
.length
) {
882 uci
.unset('dhcp', section_id
, 'ra_flags');
886 so
= ss
.taboption('ipv6-ra', form
.Value
, 'ra_pref64', _('NAT64 prefix'), _('Announce NAT64 prefix in <abbr title="Router Advertisement">RA</abbr> messages.'));
888 so
.datatype
= 'cidr6';
889 so
.placeholder
= '64:ff9b::/96';
890 so
.depends('ra', 'server');
891 so
.depends({ ra
: 'hybrid', master
: '0' });
893 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.'));
895 so
.datatype
= 'uinteger';
896 so
.placeholder
= '600';
897 so
.depends('ra', 'server');
898 so
.depends({ ra
: 'hybrid', master
: '0' });
900 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.'));
902 so
.datatype
= 'uinteger';
903 so
.placeholder
= '200';
904 so
.depends('ra', 'server');
905 so
.depends({ ra
: 'hybrid', master
: '0' });
907 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.'));
909 so
.datatype
= 'range(0, 9000)';
910 so
.placeholder
= '1800';
911 so
.depends('ra', 'server');
912 so
.depends({ ra
: 'hybrid', master
: '0' });
914 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.'));
916 so
.datatype
= 'range(1280, 65535)';
917 so
.depends('ra', 'server');
918 so
.depends({ ra
: 'hybrid', master
: '0' });
919 so
.load = function(section_id
) {
920 var dev
= ifc
.getL3Device(),
921 path
= dev
? "/proc/sys/net/ipv6/conf/%s/mtu".format(dev
.getName()) : null;
924 dev
? L
.resolveDefault(fs
.read(path
), dev
.getMTU()) : null,
925 this.super('load', [section_id
])
926 ]).then(L
.bind(function(res
) {
927 this.placeholder
= +res
[0];
933 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.'));
935 so
.datatype
= 'range(0, 255)';
936 so
.depends('ra', 'server');
937 so
.depends({ ra
: 'hybrid', master
: '0' });
938 so
.load = function(section_id
) {
939 var dev
= ifc
.getL3Device(),
940 path
= dev
? "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev
.getName()) : null;
943 dev
? L
.resolveDefault(fs
.read(path
), 64) : null,
944 this.super('load', [section_id
])
945 ]).then(L
.bind(function(res
) {
946 this.placeholder
= +res
[0];
953 so
= ss
.taboption('ipv6', cbiRichListValue
, 'dhcpv6', _('DHCPv6-Service'),
954 _('Configures the operation mode of the DHCPv6 service on this interface.'));
955 so
.value('', _('disabled'),
956 _('Do not offer DHCPv6 service on this interface.'));
957 so
.value('server', _('server mode'),
958 _('Provide a DHCPv6 server on this interface and reply to DHCPv6 solicitations and requests.'));
959 so
.value('relay', _('relay mode'),
960 _('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));
961 so
.value('hybrid', _('hybrid mode'), ' ');
963 so
= ss
.taboption('ipv6', form
.Value
, 'dhcpv6_pd_min_len', _('<abbr title="Prefix Delegation">PD</abbr> minimum length'),
964 _('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.'));
965 so
.datatype
= 'range(1,62)';
966 so
.depends('dhcpv6', 'server');
968 so
= ss
.taboption('ipv6', form
.DynamicList
, 'dns', _('Announced IPv6 DNS servers'),
969 _('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.'));
970 so
.datatype
= 'ip6addr("nomask")'; /* restrict to IPv6 only for now since dnsmasq (DHCPv4) does not honour this option */
971 so
.depends('ra', 'server');
972 so
.depends({ ra
: 'hybrid', master
: '0' });
973 so
.depends('dhcpv6', 'server');
974 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
976 so
= ss
.taboption('ipv6', form
.Flag
, 'dns_service', _('Local IPv6 DNS server'),
977 _('Announce this device as IPv6 DNS server.'));
978 so
.default = so
.enabled
;
979 so
.depends({ ra
: 'server', dns
: /^$/ });
980 so
.depends({ ra
: 'hybrid', dns
: /^$/, master
: '0' });
981 so
.depends({ dhcpv6
: 'server', dns
: /^$/ });
982 so
.depends({ dhcpv6
: 'hybrid', dns
: /^$/, master
: '0' });
984 so
= ss
.taboption('ipv6', form
.DynamicList
, 'domain', _('Announced DNS domains'),
985 _('Specifies a fixed list of DNS search domains to announce via DHCPv6. If left unspecified, the local device DNS search domain will be announced.'));
986 so
.datatype
= 'hostname';
987 so
.depends('ra', 'server');
988 so
.depends({ ra
: 'hybrid', master
: '0' });
989 so
.depends('dhcpv6', 'server');
990 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
992 //This is a DHCPv6 specific odhcpd setting
993 so
= ss
.taboption('ipv6', form
.DynamicList
, 'ntp', _('NTP Servers'),
994 _('DHCPv6 option 56. %s.', 'DHCPv6 option 56. RFC5908 link').format('<a href="%s" target="_blank">RFC5908</a>').format('https://www.rfc-editor.org/rfc/rfc5908#section-4'));
995 so
.datatype
= 'host(0)';
996 for(var x
of uci
.get('system', 'ntp', 'server') || '') {
999 var lan_net
= this.networks
.filter(function(n
) { return n
.getName() == 'lan' })[0];
1000 // If ntpd is set up, suggest our IP(v6) also
1001 if(uci
.get('system', 'ntp', 'enable_server')) {
1002 lan_net
.getIPAddrs().forEach(function(i4
) {
1003 so
.value(i4
.split('/')[0]);
1005 lan_net
.getIP6Addrs().forEach(function(i6
) {
1006 so
.value(i6
.split('/')[0]);
1011 so
.depends('dhcpv6', 'server');
1012 so
.depends({ dhcpv6
: 'hybrid', master
: '0' });
1014 so
= ss
.taboption('ipv6', cbiRichListValue
, 'ndp', _('<abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy'),
1015 _('Configures the operation mode of the NDP proxy service on this interface.'));
1016 so
.value('', _('disabled'),
1017 _('Do not proxy any <abbr title="Neighbour Discovery Protocol">NDP</abbr> packets.'));
1018 so
.value('relay', _('relay mode'),
1019 _('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.'));
1020 so
.value('hybrid', _('hybrid mode'), ' ');
1023 so
= ss
.taboption('ipv6', form
.Flag
, 'ndproxy_routing', _('Learn routes'), _('Set up routes for proxied IPv6 neighbours.'));
1024 so
.default = so
.enabled
;
1025 so
.depends('ndp', 'relay');
1026 so
.depends('ndp', 'hybrid');
1028 so
= ss
.taboption('ipv6', form
.Flag
, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
1029 so
.depends({ ndp
: 'relay', master
: '0' });
1030 so
.depends({ ndp
: 'hybrid', master
: '0' });
1032 so
= ss
.taboption('ipv6', form
.Value
, 'preferred_lifetime', _('IPv6 Prefix Lifetime'), _('Preferred lifetime for a prefix.'));
1034 so
.placeholder
= '12h';
1035 so
.value('5m', _('5m (5 minutes)'));
1036 so
.value('3h', _('3h (3 hours)'));
1037 so
.value('12h', _('12h (12 hours - default)'));
1038 so
.value('7d', _('7d (7 days)'));
1040 //This is a ra_* setting, but its placement is more logical/findable under IPv6 settings.
1041 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.'));
1045 ifc
.renderFormOptions(s
);
1047 // Common interface options
1048 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
1049 o
.default = o
.enabled
;
1051 if (has_peerdns(protoval
)) {
1052 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
1053 o
.default = o
.enabled
;
1056 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'dns', _('Use custom DNS servers'));
1057 if (has_peerdns(protoval
))
1058 o
.depends('peerdns', '0');
1059 o
.datatype
= 'ipaddr';
1061 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'dns_search', _('DNS search domains'));
1062 if (protoval
!= 'static')
1063 o
.depends('peerdns', '0');
1064 o
.datatype
= 'hostname';
1066 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'));
1067 o
.datatype
= 'uinteger';
1068 o
.placeholder
= '0';
1070 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'metric', _('Use gateway metric'));
1071 o
.datatype
= 'uinteger';
1072 o
.placeholder
= '0';
1074 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip4table', _('Override IPv4 routing table'));
1075 o
.datatype
= 'or(uinteger, string)';
1076 for (var i
= 0; i
< rtTables
.length
; i
++)
1077 o
.value(rtTables
[i
][1], '%s (%d)'.format(rtTables
[i
][1], rtTables
[i
][0]));
1079 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6table', _('Override IPv6 routing table'));
1080 o
.datatype
= 'or(uinteger, string)';
1081 for (var i
= 0; i
< rtTables
.length
; i
++)
1082 o
.value(rtTables
[i
][1], '%s (%d)'.format(rtTables
[i
][1], rtTables
[i
][0]));
1084 if (has_sourcefilter(protoval
)) {
1085 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'sourcefilter', _('IPv6 source routing'), _('Automatically handle multiple uplink interfaces using source-based policy routing.'));
1086 o
.default = o
.enabled
;
1089 o
= nettools
.replaceOption(s
, 'advanced', form
.Flag
, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
1090 o
.default = o
.enabled
;
1092 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'));
1093 o
.value('', _('disabled'));
1095 o
.datatype
= 'max(128)';
1097 o
= nettools
.replaceOption(s
, 'advanced', form
.Value
, 'ip6hint', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
1098 o
.placeholder
= '0';
1099 o
.validate = function(section_id
, value
) {
1100 if (value
== null || value
== '')
1103 var n
= parseInt(value
, 16);
1105 if (!/^(0x)?[0-9a-fA-F]+$/.test(value
) || isNaN(n
) || n
>= 0xffffffff)
1106 return _('Expecting a hexadecimal assignment hint');
1110 for (var i
= 33; i
<= 64; i
++)
1111 o
.depends('ip6assign', String(i
));
1114 o
= nettools
.replaceOption(s
, 'advanced', form
.DynamicList
, 'ip6class', _('IPv6 prefix filter'), _('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));
1115 o
.value('local', 'local (%s)'.format(_('Local ULA')));
1117 var prefixClasses
= {};
1119 this.networks
.forEach(function(net
) {
1120 var prefixes
= net
._ubus('ipv6-prefix');
1121 if (Array
.isArray(prefixes
)) {
1122 prefixes
.forEach(function(pfx
) {
1123 if (L
.isObject(pfx
) && typeof(pfx
['class']) == 'string') {
1124 prefixClasses
[pfx
['class']] = prefixClasses
[pfx
['class']] || {};
1125 prefixClasses
[pfx
['class']][net
.getName()] = true;
1131 Object
.keys(prefixClasses
).sort().forEach(function(c
) {
1132 var networks
= Object
.keys(prefixClasses
[c
]).sort().join(', ');
1133 o
.value(c
, (c
!= networks
) ? '%s (%s)'.format(c
, networks
) : c
);
1137 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."));
1138 o
.datatype
= 'ip6hostid';
1139 o
.placeholder
= '::1';
1141 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.'));
1142 o
.datatype
= 'uinteger';
1143 o
.placeholder
= '0';
1145 for (var i
= 0; i
< s
.children
.length
; i
++) {
1153 case '_switch_proto':
1154 case '_ifacestat_modal':
1157 case 'igmp_snooping':
1162 for (var j
= 0; j
< protocols
.length
; j
++) {
1163 if (!protocols
[j
].isVirtual()) {
1165 for (var k
= 0; k
< o
.deps
.length
; k
++)
1166 deps
.push(Object
.assign({ proto
: protocols
[j
].getProtocol() }, o
.deps
[k
]));
1168 deps
.push({ proto
: protocols
[j
].getProtocol() });
1176 for (var j
= 0; j
< o
.deps
.length
; j
++)
1177 o
.deps
[j
].proto
= protoval
;
1179 o
.depends('proto', protoval
);
1183 this.activeSection
= s
.section
;
1187 s
.handleModalCancel = function(/* ... */) {
1188 var type
= uci
.get('network', this.activeSection
|| this.addedSection
, 'type'),
1189 device
= (type
== 'bridge') ? 'br-%s'.format(this.activeSection
|| this.addedSection
) : null;
1191 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1192 if (device
!= null && bvs
.device
== device
)
1193 uci
.remove('network', bvs
['.name']);
1196 return form
.GridSection
.prototype.handleModalCancel
.apply(this, arguments
);
1199 s
.handleAdd = function(ev
) {
1200 var m2
= new form
.Map('network'),
1201 s2
= m2
.section(form
.NamedSection
, '_new_'),
1202 protocols
= network
.getProtocols(),
1203 proto
, name
, device
;
1205 protocols
.sort(function(a
, b
) {
1206 return L
.naturalCompare(a
.getProtocol(), b
.getProtocol());
1209 s2
.render = function() {
1210 return Promise
.all([
1212 this.renderUCISection('_new_')
1213 ]).then(this.renderContents
.bind(this));
1216 name
= s2
.option(form
.Value
, 'name', _('Name'));
1217 name
.rmempty
= false;
1218 name
.datatype
= 'uciname';
1219 name
.placeholder
= _('New interface name…');
1220 name
.validate = function(section_id
, value
) {
1221 if (uci
.get('network', value
) != null)
1222 return _('The interface name is already used');
1224 var pr
= network
.getProtocol(proto
.formvalue(section_id
), value
),
1225 ifname
= pr
.isVirtual() ? '%s-%s'.format(pr
.getProtocol(), value
) : 'br-%s'.format(value
);
1227 if (value
.length
> 15)
1228 return _('The interface name is too long');
1233 proto
= s2
.option(form
.ListValue
, 'proto', _('Protocol'));
1234 proto
.validate
= name
.validate
;
1236 device
= s2
.option(widgets
.DeviceSelect
, 'device', _('Device'));
1237 device
.noaliases
= false;
1238 device
.optional
= false;
1240 for (var i
= 0; i
< protocols
.length
; i
++) {
1241 proto
.value(protocols
[i
].getProtocol(), protocols
[i
].getI18n());
1243 if (!protocols
[i
].isVirtual())
1244 device
.depends('proto', protocols
[i
].getProtocol());
1247 m2
.render().then(L
.bind(function(nodes
) {
1248 ui
.showModal(_('Add new interface...'), [
1250 E('div', { 'class': 'right' }, [
1253 'click': ui
.hideModal
1254 }, _('Cancel')), ' ',
1256 'class': 'cbi-button cbi-button-positive important',
1257 'click': ui
.createHandlerFn(this, function(ev
) {
1258 var nameval
= name
.isValid('_new_') ? name
.formvalue('_new_') : null,
1259 protoval
= proto
.isValid('_new_') ? proto
.formvalue('_new_') : null,
1260 protoclass
= protoval
? network
.getProtocol(protoval
, nameval
) : null;
1262 if (nameval
== null || protoval
== null || nameval
== '' || protoval
== '')
1265 return protoclass
.isCreateable(nameval
).then(function(checkval
) {
1266 if (checkval
!= null) {
1267 ui
.addNotification(null,
1268 E('p', _('New interface for "%s" can not be created: %s').format(protoclass
.getI18n(), checkval
)));
1273 return m
.save(function() {
1274 var section_id
= uci
.add('network', 'interface', nameval
);
1276 protoclass
.set('proto', protoval
);
1277 protoclass
.addDevice(device
.formvalue('_new_'));
1279 m
.children
[0].addedSection
= section_id
;
1282 ui
.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
1283 }).then(L
.bind(m
.children
[0].renderMoreOptionsModal
, m
.children
[0], nameval
));
1286 }, _('Create interface'))
1290 nodes
.querySelector('[id="%s"] input[type="text"]'.format(name
.cbid('_new_'))).focus();
1294 s
.handleRemove = function(section_id
, ev
) {
1295 return network
.deleteNetwork(section_id
).then(L
.bind(function(section_id
, ev
) {
1296 return form
.GridSection
.prototype.handleRemove
.apply(this, [section_id
, ev
]);
1297 }, this, section_id
, ev
));
1300 o
= s
.option(form
.DummyValue
, '_ifacebox');
1301 o
.modalonly
= false;
1302 o
.textvalue = function(section_id
) {
1303 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0],
1304 zone
= net
? this.section
.zones
.filter(function(z
) { return !!z
.getNetworks().filter(function(n
) { return n
== section_id
})[0] })[0] : null;
1309 var node
= E('div', { 'class': 'ifacebox' }, [
1311 'class': 'ifacebox-head',
1312 'style': firewall
.getZoneColorStyle(zone
),
1313 'title': zone
? _('Part of zone %q').format(zone
.getName()) : _('No zone assigned')
1314 }, E('strong', net
.getName())),
1316 'class': 'ifacebox-body',
1317 'id': '%s-ifc-devices'.format(section_id
),
1318 'data-network': section_id
1321 'src': L
.resource('icons/ethernet_disabled.png'),
1322 'style': 'width:16px; height:16px'
1324 E('br'), E('small', '?')
1328 render_ifacebox_status(node
.childNodes
[1], net
);
1333 o
= s
.option(form
.DummyValue
, '_ifacestat');
1334 o
.modalonly
= false;
1335 o
.textvalue = function(section_id
) {
1336 var net
= this.section
.networks
.filter(function(n
) { return n
.getName() == section_id
})[0];
1341 var node
= E('div', { 'id': '%s-ifc-description'.format(section_id
) });
1343 render_status(node
, net
, false);
1348 o
= s
.taboption('advanced', form
.Flag
, 'delegate', _('Use builtin IPv6-management'));
1350 o
.default = o
.enabled
;
1352 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).'));
1355 '1': [{ proto
: 'static' }],
1360 // Device configuration
1361 s
= m
.section(form
.GridSection
, 'device', _('Devices'));
1364 s
.addbtntitle
= _('Add device configuration…');
1366 s
.cfgsections = function() {
1367 var sections
= uci
.sections('network', 'device'),
1368 section_ids
= sections
.sort(function(a
, b
) { return L
.naturalCompare(a
.name
, b
.name
) }).map(function(s
) { return s
['.name'] });
1370 for (var i
= 0; i
< netDevs
.length
; i
++) {
1371 if (sections
.filter(function(s
) { return s
.name
== netDevs
[i
].getName() }).length
)
1374 if (netDevs
[i
].getType() == 'wifi' && !netDevs
[i
].isUp())
1377 /* Unless http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html is implemented,
1378 we cannot properly redefine bridges as devices, so filter them away for now... */
1380 var m
= netDevs
[i
].isBridge() ? netDevs
[i
].getName().match(/^br-([A-Za-z0-9_]+)$/) : null,
1381 s
= m
? uci
.get('network', m
[1]) : null;
1383 if (s
&& s
['.type'] == 'interface' && s
.type
== 'bridge')
1386 section_ids
.push('dev:%s'.format(netDevs
[i
].getName()));
1392 s
.renderMoreOptionsModal = function(section_id
, ev
) {
1393 var m
= section_id
.match(/^dev:(.+)$/);
1396 var devtype
= getDevType(section_id
);
1398 section_id
= uci
.add('network', 'device');
1400 uci
.set('network', section_id
, 'name', m
[1]);
1401 uci
.set('network', section_id
, 'type', (devtype
!= 'ethernet') ? devtype
: null);
1403 this.addedSection
= section_id
;
1406 return this.super('renderMoreOptionsModal', [section_id
, ev
]);
1409 s
.renderRowActions = function(section_id
) {
1410 var trEl
= this.super('renderRowActions', [ section_id
, _('Configure…') ]),
1411 deleteBtn
= trEl
.querySelector('button:last-child');
1413 deleteBtn
.firstChild
.data
= _('Unconfigure');
1414 deleteBtn
.setAttribute('title', _('Remove related device settings from the configuration'));
1415 deleteBtn
.disabled
= section_id
.match(/^dev:/) ? true : null;
1420 s
.modaltitle = function(section_id
) {
1421 var m
= section_id
.match(/^dev:(.+)$/),
1422 name
= m
? m
[1] : uci
.get('network', section_id
, 'name');
1424 return name
? '%s: %q'.format(getDevTypeDesc(section_id
), name
) : _('Add device configuration');
1427 s
.addModalOptions = function(s
) {
1428 var isNew
= (uci
.get('network', s
.section
, 'name') == null),
1429 dev
= getDevice(s
.section
);
1431 nettools
.addDeviceOptions(s
, dev
, isNew
);
1434 s
.handleModalCancel = function(map
/*, ... */) {
1435 var name
= uci
.get('network', this.addedSection
, 'name')
1437 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1438 if (name
!= null && bvs
.device
== name
)
1439 uci
.remove('network', bvs
['.name']);
1443 for (var i
= 0; i
< map
.addedVLANs
.length
; i
++)
1444 uci
.remove('network', map
.addedVLANs
[i
]);
1446 if (this.addedSection
)
1447 uci
.remove('network', this.addedSection
);
1449 return form
.GridSection
.prototype.handleModalCancel
.apply(this, arguments
);
1452 s
.handleRemove = function(section_id
/*, ... */) {
1453 var name
= uci
.get('network', section_id
, 'name'),
1454 type
= uci
.get('network', section_id
, 'type');
1456 if (name
!= null && type
== 'bridge') {
1457 uci
.sections('network', 'bridge-vlan', function(bvs
) {
1458 if (bvs
.device
== name
)
1459 uci
.remove('network', bvs
['.name']);
1463 return form
.GridSection
.prototype.handleRemove
.apply(this, arguments
);
1466 function getDevice(section_id
) {
1467 var m
= section_id
.match(/^dev:(.+)$/),
1468 name
= m
? m
[1] : uci
.get('network', section_id
, 'name');
1470 return netDevs
.filter(function(d
) { return d
.getName() == name
})[0];
1473 function getDevType(section_id
) {
1474 var dev
= getDevice(section_id
),
1475 cfg
= uci
.get('network', section_id
),
1476 type
= cfg
? (uci
.get('network', section_id
, 'type') || 'ethernet') : (dev
? dev
.getType() : '');
1510 function getDevTypeDesc(section_id
) {
1511 switch (getDevType(section_id
) || '') {
1513 return E('em', [ _('Device not present') ]);
1516 return _('VLAN (802.1q)');
1519 return _('VLAN (802.1ad)');
1522 return _('Bridge device');
1525 return _('Tunnel device');
1528 return _('MAC VLAN');
1531 return _('Virtual Ethernet');
1534 return _('Network device');
1538 o
= s
.option(form
.DummyValue
, 'name', _('Device'));
1539 o
.modalonly
= false;
1540 o
.textvalue = function(section_id
) {
1541 var dev
= getDevice(section_id
),
1542 ext
= section_id
.match(/^dev:/),
1543 icon
= render_iface(dev
);
1546 icon
.querySelector('img').style
.opacity
= '.5';
1548 return E('span', { 'class': 'ifacebadge' }, [
1550 E('span', { 'style': ext
? 'opacity:.5' : null }, [
1551 dev
? dev
.getName() : (uci
.get('network', section_id
, 'name') || '?')
1556 o
= s
.option(form
.DummyValue
, 'type', _('Type'));
1557 o
.textvalue
= getDevTypeDesc
;
1558 o
.modalonly
= false;
1560 o
= s
.option(form
.DummyValue
, 'macaddr', _('MAC Address'));
1561 o
.modalonly
= false;
1562 o
.textvalue = function(section_id
) {
1563 var dev
= getDevice(section_id
),
1564 val
= uci
.get('network', section_id
, 'macaddr'),
1565 mac
= dev
? dev
.getMAC() : null;
1567 return val
? E('strong', {
1568 'data-tooltip': _('The value is overridden by configuration.')
1569 }, [ val
.toUpperCase() ]) : (mac
|| '-');
1572 o
= s
.option(form
.DummyValue
, 'mtu', _('MTU'));
1573 o
.modalonly
= false;
1574 o
.textvalue = function(section_id
) {
1575 var dev
= getDevice(section_id
),
1576 val
= uci
.get('network', section_id
, 'mtu'),
1577 mtu
= dev
? dev
.getMTU() : null;
1579 return val
? E('strong', {
1580 'data-tooltip': _('The value is overridden by configuration.')
1581 }, [ val
]) : (mtu
|| '-').toString();
1584 s
= m
.section(form
.TypedSection
, 'globals', _('Global network options'));
1585 s
.addremove
= false;
1588 o
= s
.option(form
.Value
, 'ula_prefix', _('IPv6 ULA-Prefix'),
1589 _('Unique Local Address (%s) - prefix <code>fd00::/8</code> (the L bit is always 1).').format('<a href="%s" target="_blank">RFC4193</a>').format('https://datatracker.ietf.org/doc/html/rfc4193#section-3') + ' ' +
1590 _('ULA for IPv6 is analogous to IPv4 private network addressing.') + ' ' +
1591 _('This prefix is randomly generated at first install.'));
1592 o
.datatype
= 'cidr6';
1594 o
= s
.option(form
.Flag
, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
1598 if (dslModemType
!= null) {
1599 s
= m
.section(form
.TypedSection
, 'dsl', _('DSL'));
1602 o
= s
.option(form
.ListValue
, 'annex', _('Annex'));
1603 if (dslModemType
== 'vdsl') {
1604 o
.value('a', _('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));
1605 o
.value('b', _('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));
1606 o
.value('j', _('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));
1608 o
.value('a', _('ADSL (all variants) Annex A/L/M'));
1609 o
.value('b', _('ADSL (all variants) Annex B'));
1610 o
.value('j', _('ADSL (all variants) Annex B/J'));
1612 o
.value('m', _('ADSL (all variants) Annex M'));
1613 o
.value('at1', _('ANSI T1.413'));
1614 o
.value('admt', _('ADSL (G.992.1) Annex A'));
1615 o
.value('bdmt', _('ADSL (G.992.1) Annex B'));
1616 o
.value('alite', _('Splitterless ADSL (G.992.2) Annex A'));
1617 o
.value('a2', _('ADSL2 (G.992.3) Annex A'));
1618 o
.value('b2', _('ADSL2 (G.992.3) Annex B'));
1619 o
.value('l', _('ADSL2 (G.992.3) Annex L'));
1620 o
.value('m2', _('ADSL2 (G.992.3) Annex M'));
1621 o
.value('a2p', _('ADSL2+ (G.992.5) Annex A'));
1622 o
.value('b2p', _('ADSL2+ (G.992.5) Annex B'));
1623 o
.value('m2p', _('ADSL2+ (G.992.5) Annex M'));
1625 o
= s
.option(form
.ListValue
, 'tone', _('Tone'));
1626 o
.value('', _('auto'));
1627 o
.value('a', _('A43C + J43 + A43'));
1628 o
.value('av', _('A43C + J43 + A43 + V43'));
1629 o
.value('b', _('B43 + B43C'));
1630 o
.value('bv', _('B43 + B43C + V43'));
1632 if (dslModemType
== 'vdsl') {
1633 o
= s
.option(form
.ListValue
, 'xfer_mode', _('Encapsulation mode'));
1634 o
.value('', _('auto'));
1635 o
.value('atm', _('ATM (Asynchronous Transfer Mode)'));
1636 o
.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
1638 o
= s
.option(form
.ListValue
, 'line_mode', _('DSL line mode'));
1639 o
.value('', _('auto'));
1640 o
.value('adsl', _('ADSL'));
1641 o
.value('vdsl', _('VDSL'));
1643 o
= s
.option(form
.ListValue
, 'ds_snr_offset', _('Downstream SNR offset'));
1646 for (var i
= -100; i
<= 100; i
+= 5)
1647 o
.value(i
, _('%.1f dB').format(i
/ 10));
1650 s
.option(form
.Value
, 'firmware', _('Firmware File'));
1654 // Show ATM bridge section if we have the capabilities
1655 if (L
.hasSystemFeature('br2684ctl')) {
1656 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.'));
1660 s
.addbtntitle
= _('Add ATM Bridge');
1662 s
.handleAdd = function(ev
) {
1663 var sections
= uci
.sections('network', 'atm-bridge'),
1666 for (var i
= 0; i
< sections
.length
; i
++) {
1667 var unit
= +sections
[i
].unit
;
1669 if (!isNaN(unit
) && unit
> max_unit
)
1673 return this.map
.save(function() {
1674 var sid
= uci
.add('network', 'atm-bridge');
1676 uci
.set('network', sid
, 'unit', max_unit
+ 1);
1677 uci
.set('network', sid
, 'atmdev', 0);
1678 uci
.set('network', sid
, 'encaps', 'llc');
1679 uci
.set('network', sid
, 'payload', 'bridged');
1680 uci
.set('network', sid
, 'vci', 35);
1681 uci
.set('network', sid
, 'vpi', 8);
1685 s
.tab('general', _('General Setup'));
1686 s
.tab('advanced', _('Advanced Settings'));
1688 o
= s
.taboption('general', form
.Value
, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
1689 s
.taboption('general', form
.Value
, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
1691 o
= s
.taboption('general', form
.ListValue
, 'encaps', _('Encapsulation mode'));
1692 o
.value('llc', _('LLC'));
1693 o
.value('vc', _('VC-Mux'));
1695 s
.taboption('advanced', form
.Value
, 'atmdev', _('ATM device number'));
1696 s
.taboption('advanced', form
.Value
, 'unit', _('Bridge unit number'));
1698 o
= s
.taboption('advanced', form
.ListValue
, 'payload', _('Forwarding mode'));
1699 o
.value('bridged', _('bridged'));
1700 o
.value('routed', _('routed'));
1704 return m
.render().then(L
.bind(function(m
, nodes
) {
1705 poll
.add(L
.bind(function() {
1706 var section_ids
= m
.children
[0].cfgsections(),
1709 for (var i
= 0; i
< section_ids
.length
; i
++) {
1710 var row
= nodes
.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids
[i
])),
1711 dsc
= row
.querySelector('[data-name="_ifacestat"] > div'),
1712 btn1
= row
.querySelector('.cbi-section-actions .reconnect'),
1713 btn2
= row
.querySelector('.cbi-section-actions .down');
1715 if (dsc
.getAttribute('reconnect') == '') {
1716 dsc
.setAttribute('reconnect', '1');
1717 tasks
.push(fs
.exec('/sbin/ifup', [section_ids
[i
]]).catch(function(e
) {
1718 ui
.addNotification(null, E('p', e
.message
));
1721 else if (dsc
.getAttribute('disconnect') == '') {
1722 dsc
.setAttribute('disconnect', '1');
1723 tasks
.push(fs
.exec('/sbin/ifdown', [section_ids
[i
]]).catch(function(e
) {
1724 ui
.addNotification(null, E('p', e
.message
));
1727 else if (dsc
.getAttribute('reconnect') == '1') {
1728 dsc
.removeAttribute('reconnect');
1729 btn1
.classList
.remove('spinning');
1730 btn1
.disabled
= false;
1732 else if (dsc
.getAttribute('disconnect') == '1') {
1733 dsc
.removeAttribute('disconnect');
1734 btn2
.classList
.remove('spinning');
1735 btn2
.disabled
= false;
1739 return Promise
.all(tasks
)
1740 .then(L
.bind(network
.getNetworks
, network
))
1741 .then(L
.bind(this.poll_status
, this, nodes
));