luci-mod-network: change ULA explanation
[project/luci.git] / modules / luci-mod-network / htdocs / luci-static / resources / view / network / interfaces.js
1 'use strict';
2 'require view';
3 'require dom';
4 'require poll';
5 'require fs';
6 'require ui';
7 'require uci';
8 'require form';
9 'require network';
10 'require firewall';
11 'require tools.widgets as widgets';
12 'require tools.network as nettools';
13
14 var isReadonlyView = !L.hasViewPermission() || null;
15
16 function count_changes(section_id) {
17 var changes = ui.changes.changes, n = 0;
18
19 if (!L.isObject(changes))
20 return n;
21
22 if (Array.isArray(changes.network))
23 for (var i = 0; i < changes.network.length; i++)
24 n += (changes.network[i][1] == section_id);
25
26 if (Array.isArray(changes.dhcp))
27 for (var i = 0; i < changes.dhcp.length; i++)
28 n += (changes.dhcp[i][1] == section_id);
29
30 return n;
31 }
32
33 function render_iface(dev, alias) {
34 var type = dev ? dev.getType() : 'ethernet',
35 up = dev ? dev.isUp() : false;
36
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
51 ])
52 ])
53 ]);
54 }
55
56 function render_status(node, ifc, with_device) {
57 var desc = null, c = [];
58
59 if (ifc.isDynamic())
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'))
66 ]);
67
68 var i18n = ifc.getI18n();
69 if (i18n)
70 desc = desc ? '%s (%s)'.format(desc, i18n) : i18n;
71
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;
78
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', {
109 href: '#',
110 click: L.bind(ui.changes.displayChanges, ui.changes)
111 }, _('Interface has %d pending changes').format(changecount)) : null
112 ]);
113 }
114
115 function render_modal_status(node, ifc) {
116 var dev = ifc ? (ifc.getDevice() || ifc.getL3Device() || ifc.getL3Device()) : null;
117
118 dom.content(node, [
119 E('img', {
120 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
121 'title': dev ? dev.getTypeI18n() : _('Not present')
122 }),
123 ifc ? render_status(E('span'), ifc, true) : E('em', _('Interface not present or not connected yet.'))
124 ]);
125
126 return node;
127 }
128
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()) ];
133
134 if (subdevs && subdevs.length) {
135 var sifs = [ ' (' ];
136
137 for (var j = 0; j < subdevs.length; j++)
138 sifs.push(render_iface(subdevs[j]));
139
140 sifs.push(')');
141
142 c.push(E('span', {}, sifs));
143 }
144
145 c.push(E('br'));
146 c.push(E('small', {}, ifc.isAlias() ? _('Alias of "%s"').format(ifc.isAlias())
147 : (dev ? dev.getName() : E('em', _('Not present')))));
148
149 dom.content(node, c);
150
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));
155 }
156
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');
161
162 btns[+!up].blur();
163 btns[+!up].classList.add('spinning');
164
165 btns[0].disabled = true;
166 btns[1].disabled = true;
167
168 if (!up) {
169 L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res) {
170 var info = null; try { info = JSON.parse(res); } catch(e) {}
171
172 if (L.isObject(info) &&
173 Array.isArray(info.inbound_interfaces) &&
174 info.inbound_interfaces.filter(function(i) { return i == id })[0]) {
175
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' }, [
179 E('button', {
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;
185
186 ui.hideModal();
187 }
188 }, _('Cancel')),
189 ' ',
190 E('button', {
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...')));
195
196 ui.hideModal();
197 }
198 }, _('Disconnect'))
199 ])
200 ]);
201 }
202 else {
203 dsc.setAttribute('disconnect', '');
204 dom.content(dsc, E('em', _('Interface is shutting down...')));
205 }
206 });
207 }
208 else {
209 dsc.setAttribute(up ? 'reconnect' : 'disconnect', force ? 'force' : '');
210 dom.content(dsc, E('em', up ? _('Interface is reconnecting...') : _('Interface is shutting down...')));
211 }
212 }
213
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];
219
220 if (firstsubnet == null)
221 return null;
222
223 var subnetmask = firstsubnet.split('/')[1];
224
225 if (!isNaN(subnetmask))
226 subnetmask = network.prefixToMask(+subnetmask);
227
228 return subnetmask;
229 }
230
231 function has_peerdns(proto) {
232 switch (proto) {
233 case 'dhcp':
234 case 'dhcpv6':
235 case 'qmi':
236 case 'ppp':
237 case 'pppoe':
238 case 'pppoa':
239 case 'pptp':
240 case 'openvpn':
241 case 'sstp':
242 return true;
243 }
244
245 return false;
246 }
247
248 function has_sourcefilter(proto) {
249 switch (proto) {
250 case '3g':
251 case 'dhcpv6':
252 case 'directip':
253 case 'mbim':
254 case 'ncm':
255 case 'ppp':
256 case 'pppoa':
257 case 'pppoe':
258 case 'pptp':
259 case 'qmi':
260 return true;
261 }
262
263 return false;
264 }
265
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),
271 sort: this.keylist,
272 optional: true,
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
277 });
278
279 return widget.render();
280 },
281
282 value: function(value, title, description) {
283 if (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 ]),
288 E('br'),
289 E('span', { 'style': 'white-space:normal' }, description)
290 ])
291 ]));
292 }
293 else {
294 form.ListValue.prototype.value.call(this, value, title);
295 }
296 }
297 });
298
299 return view.extend({
300 poll_status: function(map, networks) {
301 var resolveZone = null;
302
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()));
306
307 if (row == null)
308 continue;
309
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;
318
319 if (dsc.hasAttribute('reconnect')) {
320 dom.content(dsc, E('em', _('Interface is starting...')));
321 }
322 else if (dsc.hasAttribute('disconnect')) {
323 dom.content(dsc, E('em', _('Interface is stopping...')));
324 }
325 else if (ifc.getProtocol() || uci.get('network', ifc.getName()) == null) {
326 render_status(dsc, ifc, false);
327 }
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;
331
332 var link = L.url('admin/system/opkg') + '?query=luci-proto';
333 dom.content(dsc, [
334 E('em', _('Unsupported protocol type.')), E('br'),
335 E('a', { href: link }, _('Install protocol extensions...'))
336 ]);
337 }
338 else {
339 dom.content(dsc, E('em', _('Interface not present or not connected yet.')));
340 }
341
342 if (stat) {
343 var dev = ifc.getDevice();
344 dom.content(stat, [
345 E('img', {
346 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
347 'title': dev ? dev.getTypeI18n() : _('Not present')
348 }),
349 render_status(E('span'), ifc, true)
350 ]);
351 }
352
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;
355 }
356
357 document.querySelectorAll('.port-status-device[data-device]').forEach(function(node) {
358 nettools.updateDevBadge(node, network.instantiateDevice(node.getAttribute('data-device')));
359 });
360
361 document.querySelectorAll('.port-status-link[data-device]').forEach(function(node) {
362 nettools.updatePortStatus(node, network.instantiateDevice(node.getAttribute('data-device')));
363 });
364
365 return Promise.all([ resolveZone, network.flushCache() ]);
366 },
367
368 load: function() {
369 return Promise.all([
370 network.getDSLModemType(),
371 network.getDevices(),
372 fs.lines('/etc/iproute2/rt_tables'),
373 L.resolveDefault(fs.read('/usr/lib/opkg/info/netifd.control')),
374 uci.changes()
375 ]);
376 },
377
378 interfaceBridgeWithIfnameSections: function() {
379 return uci.sections('network', 'interface').filter(function(ns) {
380 return ns.type == 'bridge' && !ns.ports && ns.ifname;
381 });
382 },
383
384 deviceWithIfnameSections: function() {
385 return uci.sections('network', 'device').filter(function(ns) {
386 return ns.type == 'bridge' && !ns.ports && ns.ifname;
387 });
388 },
389
390 interfaceWithIfnameSections: function() {
391 return uci.sections('network', 'interface').filter(function(ns) {
392 return !ns.device && ns.ifname;
393 });
394 },
395
396 handleBridgeMigration: function(ev) {
397 var tasks = [];
398
399 this.interfaceBridgeWithIfnameSections().forEach(function(ns) {
400 var device_name = 'br-' + ns['.name'];
401
402 tasks.push(uci.callAdd('network', 'device', null, {
403 'name': device_name,
404 'type': 'bridge',
405 'ports': L.toArray(ns.ifname),
406 'mtu': ns.mtu,
407 'macaddr': ns.macaddr,
408 'igmp_snooping': ns.igmp_snooping
409 }));
410
411 tasks.push(uci.callSet('network', ns['.name'], {
412 'type': '',
413 'ifname': '',
414 'mtu': '',
415 'macaddr': '',
416 'igmp_snooping': '',
417 'device': device_name
418 }));
419 });
420
421 return Promise.all(tasks)
422 .then(L.bind(ui.changes.init, ui.changes))
423 .then(L.bind(ui.changes.apply, ui.changes));
424 },
425
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' },
431 E('button', {
432 'class': 'btn cbi-button-action important',
433 'click': ui.createHandlerFn(this, 'handleBridgeMigration')
434 }, _('Continue')))
435 ]);
436 },
437
438 handleIfnameMigration: function(ev) {
439 var tasks = [];
440
441 this.deviceWithIfnameSections().forEach(function(ds) {
442 tasks.push(uci.callSet('network', ds['.name'], {
443 'ifname': '',
444 'ports': L.toArray(ds.ifname)
445 }));
446 });
447
448 this.interfaceWithIfnameSections().forEach(function(ns) {
449 tasks.push(uci.callSet('network', ns['.name'], {
450 'ifname': '',
451 'device': ns.ifname
452 }));
453 });
454
455 return Promise.all(tasks)
456 .then(L.bind(ui.changes.init, ui.changes))
457 .then(L.bind(ui.changes.apply, ui.changes));
458 },
459
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' },
465 E('button', {
466 'class': 'btn cbi-button-action important',
467 'click': ui.createHandlerFn(this, 'handleIfnameMigration')
468 }, _('Continue')))
469 ]);
470 },
471
472 render: function(data) {
473 var netifdVersion = (data[3] || '').match(/Version: ([^\n]+)/);
474
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();
480 }
481
482 var dslModemType = data[0],
483 netDevs = data[1],
484 m, s, o;
485
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;
491 });
492
493 m = new form.Map('network');
494 m.tabbed = true;
495 m.chain('dhcp');
496
497 s = m.section(form.GridSection, 'interface', _('Interfaces'));
498 s.anonymous = true;
499 s.addremove = true;
500 s.addbtntitle = _('Add new interface...');
501
502 s.load = function() {
503 return Promise.all([
504 network.getNetworks(),
505 firewall.getZones()
506 ]).then(L.bind(function(data) {
507 this.networks = data[0];
508 this.zones = data[1];
509 }, this));
510 };
511
512 s.tab('general', _('General Settings'));
513 s.tab('advanced', _('Advanced Settings'));
514 s.tab('physical', _('Physical Settings'));
515 s.tab('brport', _('Bridge port specific options'));
516 s.tab('bridgevlan', _('Bridge VLAN filtering'));
517 s.tab('firewall', _('Firewall Settings'));
518 s.tab('dhcp', _('DHCP Server'));
519
520 s.cfgsections = function() {
521 return this.networks.map(function(n) { return n.getName() })
522 .filter(function(n) { return n != 'loopback' });
523 };
524
525 s.modaltitle = function(section_id) {
526 return _('Interfaces') + ' » ' + section_id;
527 };
528
529 s.renderRowActions = function(section_id) {
530 var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]),
531 net = this.networks.filter(function(n) { return n.getName() == section_id })[0],
532 disabled = net ? !net.isUp() : true,
533 dynamic = net ? net.isDynamic() : false;
534
535 dom.content(tdEl.lastChild, [
536 E('button', {
537 'class': 'cbi-button cbi-button-neutral reconnect',
538 'click': iface_updown.bind(this, true, section_id),
539 'title': _('Reconnect this interface'),
540 'disabled': dynamic ? 'disabled' : null
541 }, _('Restart')),
542 E('button', {
543 'class': 'cbi-button cbi-button-neutral down',
544 'click': iface_updown.bind(this, false, section_id),
545 'title': _('Shutdown this interface'),
546 'disabled': (dynamic || disabled) ? 'disabled' : null
547 }, _('Stop')),
548 tdEl.lastChild.firstChild,
549 tdEl.lastChild.lastChild
550 ]);
551
552 if (!dynamic && net && !uci.get('network', net.getName())) {
553 tdEl.lastChild.childNodes[0].disabled = true;
554 tdEl.lastChild.childNodes[2].disabled = true;
555 tdEl.lastChild.childNodes[3].disabled = true;
556 }
557
558 if (dynamic) {
559 //disable the 'Edit' button on dynamic interfaces
560 tdEl.lastChild.childNodes[2].disabled = true;
561 }
562
563 return tdEl;
564 };
565
566 s.addModalOptions = function(s) {
567 var protoval = uci.get('network', s.section, 'proto'),
568 protoclass = protoval ? network.getProtocol(protoval) : null,
569 o, proto_select, proto_switch, type, stp, igmp, ss, so;
570
571 if (!protoval)
572 return;
573
574 return network.getNetwork(s.section).then(L.bind(function(ifc) {
575 var protocols = network.getProtocols();
576
577 protocols.sort(function(a, b) {
578 return L.naturalCompare(a.getProtocol(), b.getProtocol());
579 });
580
581 o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status'));
582 o.modalonly = true;
583 o.cfgvalue = L.bind(function(section_id) {
584 var net = this.networks.filter(function(n) { return n.getName() == section_id })[0];
585
586 return render_modal_status(E('div', {
587 'id': '%s-ifc-status'.format(section_id),
588 'class': 'ifacebadge large'
589 }), net);
590 }, this);
591 o.write = function() {};
592
593
594 proto_select = s.taboption('general', form.ListValue, 'proto', _('Protocol'));
595 proto_select.modalonly = true;
596
597 proto_switch = s.taboption('general', form.Button, '_switch_proto');
598 proto_switch.modalonly = true;
599 proto_switch.title = _('Really switch protocol?');
600 proto_switch.inputtitle = _('Switch protocol');
601 proto_switch.inputstyle = 'apply';
602 proto_switch.onclick = L.bind(function(ev) {
603 s.map.save()
604 .then(L.bind(m.load, m))
605 .then(L.bind(m.render, m))
606 .then(L.bind(this.renderMoreOptionsModal, this, s.section));
607 }, this);
608
609 o = s.taboption('general', widgets.DeviceSelect, '_net_device', _('Device'));
610 o.ucioption = 'device';
611 o.nobridges = false;
612 o.optional = false;
613 o.network = ifc.getName();
614 o.exclude = '@' + ifc.getName();
615
616 o = s.taboption('general', form.Flag, 'disabled', _('Disable this interface'));
617 o.modalonly = true;
618
619 o = s.taboption('general', form.Flag, 'auto', _('Bring up on boot'));
620 o.modalonly = true;
621 o.default = o.enabled;
622
623 if (L.hasSystemFeature('firewall')) {
624 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.'));
625 o.network = ifc.getName();
626 o.optional = true;
627
628 o.cfgvalue = function(section_id) {
629 return firewall.getZoneByNetwork(ifc.getName()).then(function(zone) {
630 return (zone != null ? zone.getName() : null);
631 });
632 };
633
634 o.write = o.remove = function(section_id, value) {
635 return Promise.all([
636 firewall.getZoneByNetwork(ifc.getName()),
637 (value != null) ? firewall.getZone(value) : null
638 ]).then(function(data) {
639 var old_zone = data[0],
640 new_zone = data[1];
641
642 if (old_zone == null && new_zone == null && (value == null || value == ''))
643 return;
644
645 if (old_zone != null && new_zone != null && old_zone.getName() == new_zone.getName())
646 return;
647
648 if (old_zone != null)
649 old_zone.deleteNetwork(ifc.getName());
650
651 if (new_zone != null)
652 new_zone.addNetwork(ifc.getName());
653 else if (value != null)
654 return firewall.addZone(value).then(function(new_zone) {
655 new_zone.addNetwork(ifc.getName());
656 });
657 });
658 };
659 }
660
661 for (var i = 0; i < protocols.length; i++) {
662 proto_select.value(protocols[i].getProtocol(), protocols[i].getI18n());
663
664 if (protocols[i].getProtocol() != uci.get('network', s.section, 'proto'))
665 proto_switch.depends('proto', protocols[i].getProtocol());
666 }
667
668 if (L.hasSystemFeature('dnsmasq') || L.hasSystemFeature('odhcpd')) {
669 o = s.taboption('dhcp', form.SectionValue, '_dhcp', form.TypedSection, 'dhcp');
670
671 ss = o.subsection;
672 ss.uciconfig = 'dhcp';
673 ss.addremove = false;
674 ss.anonymous = true;
675
676 ss.tab('general', _('General Setup'));
677 ss.tab('advanced', _('Advanced Settings'));
678 ss.tab('ipv6', _('IPv6 Settings'));
679 ss.tab('ipv6-ra', _('IPv6 RA Settings'));
680
681 ss.filter = function(section_id) {
682 return (uci.get('dhcp', section_id, 'interface') == ifc.getName());
683 };
684
685 ss.renderSectionPlaceholder = function() {
686 return E('div', { 'class': 'cbi-section-create' }, [
687 E('p', _('No DHCP Server configured for this interface') + ' &#160; '),
688 E('button', {
689 'class': 'cbi-button cbi-button-add',
690 'title': _('Set up DHCP Server'),
691 'click': ui.createHandlerFn(this, function(section_id, ev) {
692 this.map.save(function() {
693 uci.add('dhcp', 'dhcp', section_id);
694 uci.set('dhcp', section_id, 'interface', section_id);
695
696 if (protoval == 'static') {
697 uci.set('dhcp', section_id, 'start', 100);
698 uci.set('dhcp', section_id, 'limit', 150);
699 uci.set('dhcp', section_id, 'leasetime', '12h');
700 }
701 else {
702 uci.set('dhcp', section_id, 'ignore', 1);
703 }
704 });
705 }, ifc.getName())
706 }, _('Set up DHCP Server'))
707 ]);
708 };
709
710 ss.taboption('general', form.Flag, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
711
712 if (protoval == 'static') {
713 so = ss.taboption('general', form.Value, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
714 so.optional = true;
715 so.datatype = 'or(uinteger,ip4addr("nomask"))';
716 so.default = '100';
717
718 so = ss.taboption('general', form.Value, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
719 so.optional = true;
720 so.datatype = 'uinteger';
721 so.default = '150';
722
723 so = ss.taboption('general', form.Value, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
724 so.optional = true;
725 so.default = '12h';
726
727 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.'));
728 so.default = so.enabled;
729
730 ss.taboption('advanced', form.Flag, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
731
732 // XXX: is this actually useful?
733 //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
734
735 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.'));
736 so.optional = true;
737 so.datatype = 'ip4addr';
738
739 so.render = function(option_index, section_id, in_table) {
740 this.placeholder = get_netmask(s, true);
741 return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
742 };
743
744 so.validate = function(section_id, value) {
745 var uielem = this.getUIElement(section_id);
746 if (uielem)
747 uielem.setPlaceholder(get_netmask(s, false));
748 return form.Value.prototype.validate.apply(this, [ section_id, value ]);
749 };
750
751 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.'));
752 }
753
754
755 var has_other_master = uci.sections('dhcp', 'dhcp').filter(function(s) {
756 return (s.interface != ifc.getName() && s.master == '1');
757 })[0];
758
759 so = ss.taboption('ipv6', form.Flag , 'master', _('Designated master'));
760 so.readonly = has_other_master ? true : false;
761 so.description = has_other_master
762 ? _('Interface "%h" is already marked as designated master.').format(has_other_master.interface || has_other_master['.name'])
763 : _('Set this interface as master for RA and DHCPv6 relaying as well as NDP proxying.')
764 ;
765
766 so.validate = function(section_id, value) {
767 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>.'),
768 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.'),
769 hybrid_master_desc = _('Operate in <em>relay mode</em> if an upstream IPv6 prefix is present, otherwise disable service.'),
770 ra_server_allowed = true,
771 checked = this.formvalue(section_id),
772 dhcpv6 = this.section.getOption('dhcpv6').getUIElement(section_id),
773 ndp = this.section.getOption('ndp').getUIElement(section_id),
774 ra = this.section.getOption('ra').getUIElement(section_id);
775
776 /* Assume that serving RAs by default is fine, but disallow it for certain
777 interface protocols such as DHCP, DHCPv6 or the various PPP flavors.
778 The intent is to only allow RA serving for interface protocols doing
779 some kind of static IP config over something resembling a layer 2
780 ethernet device. */
781 switch (protoval) {
782 case 'dhcp':
783 case 'dhcpv6':
784 case '3g':
785 case 'l2tp':
786 case 'ppp':
787 case 'pppoa':
788 case 'pppoe':
789 case 'pptp':
790 case 'pppossh':
791 case 'ipip':
792 case 'gre':
793 case 'grev6':
794 ra_server_allowed = false;
795 break;
796 }
797
798 if (checked == '1' || !ra_server_allowed) {
799 dhcpv6.node.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
800
801 if (dhcpv6.getValue() == 'server')
802 dhcpv6.setValue('hybrid');
803
804 ra.node.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
805
806 if (ra.getValue() == 'server')
807 ra.setValue('hybrid');
808 }
809
810 if (checked == '1') {
811 dhcpv6.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_master_desc;
812 ra.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_master_desc;
813 ndp.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_master_desc;
814 }
815 else {
816 if (ra_server_allowed) {
817 dhcpv6.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');
818 ra.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');
819 }
820
821 dhcpv6.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_downstream_desc;
822 ra.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_downstream_desc;
823 ndp.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = ndp_downstream_desc ;
824 }
825
826 return true;
827 };
828
829
830 so = ss.taboption('ipv6', cbiRichListValue, 'ra', _('<abbr title="Router Advertisement">RA</abbr>-Service'),
831 _('Configures the operation mode of the <abbr title="Router Advertisement">RA</abbr> service on this interface.'));
832 so.value('', _('disabled'),
833 _('Do not send any <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages on this interface.'));
834 so.value('server', _('server mode'),
835 _('Send <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages advertising this device as IPv6 router.'));
836 so.value('relay', _('relay mode'),
837 _('Forward <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages received on the designated master interface to downstream interfaces.'));
838 so.value('hybrid', _('hybrid mode'), ' ');
839
840
841 so = ss.taboption('ipv6-ra', cbiRichListValue, 'ra_default', _('Default router'),
842 _('Configures the default router advertisement in <abbr title="Router Advertisement">RA</abbr> messages.'));
843 so.value('', _('automatic'),
844 _('Announce this device as default router if a local IPv6 default route is present.'));
845 so.value('1', _('on available prefix'),
846 _('Announce this device as default router if a public IPv6 prefix is available, regardless of local default route availability.'));
847 so.value('2', _('forced'),
848 _('Announce this device as default router regardless of whether a prefix or default route is present.'));
849 so.depends('ra', 'server');
850 so.depends({ ra: 'hybrid', master: '0' });
851
852 so = ss.taboption('ipv6-ra', form.Flag, 'ra_slaac', _('Enable <abbr title="Stateless Address Auto Config">SLAAC</abbr>'),
853 _('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.'));
854 so.default = so.enabled;
855 so.depends('ra', 'server');
856 so.depends({ ra: 'hybrid', master: '0' });
857
858 so = ss.taboption('ipv6-ra', cbiRichListValue, 'ra_flags', _('<abbr title="Router Advertisement">RA</abbr> Flags'),
859 _('Specifies the flags sent in <abbr title="Router Advertisement">RA</abbr> messages, for example to instruct clients to request further information via stateful DHCPv6.'));
860 so.value('managed-config', _('managed config (M)'),
861 _('The <em>Managed address configuration</em> (M) flag indicates that IPv6 addresses are available via DHCPv6.'));
862 so.value('other-config', _('other config (O)'),
863 _('The <em>Other configuration</em> (O) flag indicates that other information, such as DNS servers, is available via DHCPv6.'));
864 so.value('home-agent', _('mobile home agent (H)'),
865 _('The <em>Mobile IPv6 Home Agent</em> (H) flag indicates that the device is also acting as Mobile IPv6 home agent on this link.'));
866 so.multiple = true;
867 so.select_placeholder = _('none');
868 so.depends('ra', 'server');
869 so.depends({ ra: 'hybrid', master: '0' });
870 so.cfgvalue = function(section_id) {
871 var flags = L.toArray(uci.get('dhcp', section_id, 'ra_flags'));
872 return flags.length ? flags : [ 'other-config' ];
873 };
874 so.remove = function(section_id) {
875 var existing = L.toArray(uci.get('dhcp', section_id, 'ra_flags'));
876 if (this.isActive(section_id)) {
877 if (existing.length != 1 || existing[0] != 'none')
878 uci.set('dhcp', section_id, 'ra_flags', [ 'none' ]);
879 }
880 else if (existing.length) {
881 uci.unset('dhcp', section_id, 'ra_flags');
882 }
883 };
884
885 so = ss.taboption('ipv6-ra', form.Value, 'ra_pref64', _('NAT64 prefix'), _('Announce NAT64 prefix in <abbr title="Router Advertisement">RA</abbr> messages.'));
886 so.optional = true;
887 so.datatype = 'cidr6';
888 so.placeholder = '64:ff9b::/96';
889 so.depends('ra', 'server');
890 so.depends({ ra: 'hybrid', master: '0' });
891
892 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.'));
893 so.optional = true;
894 so.datatype = 'uinteger';
895 so.placeholder = '600';
896 so.depends('ra', 'server');
897 so.depends({ ra: 'hybrid', master: '0' });
898
899 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.'));
900 so.optional = true;
901 so.datatype = 'uinteger';
902 so.placeholder = '200';
903 so.depends('ra', 'server');
904 so.depends({ ra: 'hybrid', master: '0' });
905
906 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.'));
907 so.optional = true;
908 so.datatype = 'range(0, 9000)';
909 so.placeholder = '1800';
910 so.depends('ra', 'server');
911 so.depends({ ra: 'hybrid', master: '0' });
912
913 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.'));
914 so.optional = true;
915 so.datatype = 'range(1280, 65535)';
916 so.depends('ra', 'server');
917 so.depends({ ra: 'hybrid', master: '0' });
918 so.load = function(section_id) {
919 var dev = ifc.getL3Device(),
920 path = dev ? "/proc/sys/net/ipv6/conf/%s/mtu".format(dev.getName()) : null;
921
922 return Promise.all([
923 dev ? L.resolveDefault(fs.read(path), dev.getMTU()) : null,
924 this.super('load', [section_id])
925 ]).then(L.bind(function(res) {
926 this.placeholder = +res[0];
927
928 return res[1];
929 }, this));
930 };
931
932 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.'));
933 so.optional = true;
934 so.datatype = 'range(0, 255)';
935 so.depends('ra', 'server');
936 so.depends({ ra: 'hybrid', master: '0' });
937 so.load = function(section_id) {
938 var dev = ifc.getL3Device(),
939 path = dev ? "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev.getName()) : null;
940
941 return Promise.all([
942 dev ? L.resolveDefault(fs.read(path), 64) : null,
943 this.super('load', [section_id])
944 ]).then(L.bind(function(res) {
945 this.placeholder = +res[0];
946
947 return res[1];
948 }, this));
949 };
950
951
952 so = ss.taboption('ipv6', cbiRichListValue, 'dhcpv6', _('DHCPv6-Service'),
953 _('Configures the operation mode of the DHCPv6 service on this interface.'));
954 so.value('', _('disabled'),
955 _('Do not offer DHCPv6 service on this interface.'));
956 so.value('server', _('server mode'),
957 _('Provide a DHCPv6 server on this interface and reply to DHCPv6 solicitations and requests.'));
958 so.value('relay', _('relay mode'),
959 _('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));
960 so.value('hybrid', _('hybrid mode'), ' ');
961
962 so = ss.taboption('ipv6', form.Value, 'dhcpv6_pd_min_len', _('<abbr title="Prefix Delegation">PD</abbr> minimum length'),
963 _('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.'));
964 so.datatype = 'range(1,62)';
965 so.depends('dhcpv6', 'server');
966
967 so = ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced IPv6 DNS servers'),
968 _('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.'));
969 so.datatype = 'ip6addr("nomask")'; /* restrict to IPv6 only for now since dnsmasq (DHCPv4) does not honour this option */
970 so.depends('ra', 'server');
971 so.depends({ ra: 'hybrid', master: '0' });
972 so.depends('dhcpv6', 'server');
973 so.depends({ dhcpv6: 'hybrid', master: '0' });
974
975 so = ss.taboption('ipv6', form.Flag, 'dns_service', _('Local IPv6 DNS server'),
976 _('Announce this device as IPv6 DNS server.'));
977 so.default = so.enabled;
978 so.depends({ ra: 'server', dns: /^$/ });
979 so.depends({ ra: 'hybrid', dns: /^$/, master: '0' });
980 so.depends({ dhcpv6: 'server', dns: /^$/ });
981 so.depends({ dhcpv6: 'hybrid', dns: /^$/, master: '0' });
982
983 so = ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains'),
984 _('Specifies a fixed list of DNS search domains to announce via DHCPv6. If left unspecified, the local device DNS search domain will be announced.'));
985 so.datatype = 'hostname';
986 so.depends('ra', 'server');
987 so.depends({ ra: 'hybrid', master: '0' });
988 so.depends('dhcpv6', 'server');
989 so.depends({ dhcpv6: 'hybrid', master: '0' });
990
991
992 so = ss.taboption('ipv6', cbiRichListValue, 'ndp', _('<abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy'),
993 _('Configures the operation mode of the NDP proxy service on this interface.'));
994 so.value('', _('disabled'),
995 _('Do not proxy any <abbr title="Neighbour Discovery Protocol">NDP</abbr> packets.'));
996 so.value('relay', _('relay mode'),
997 _('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.'));
998 so.value('hybrid', _('hybrid mode'), ' ');
999
1000
1001 so = ss.taboption('ipv6', form.Flag, 'ndproxy_routing', _('Learn routes'), _('Set up routes for proxied IPv6 neighbours.'));
1002 so.default = so.enabled;
1003 so.depends('ndp', 'relay');
1004 so.depends('ndp', 'hybrid');
1005
1006 so = ss.taboption('ipv6', form.Flag, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
1007 so.depends({ ndp: 'relay', master: '0' });
1008 so.depends({ ndp: 'hybrid', master: '0' });
1009
1010 so = ss.taboption('ipv6', form.Value, 'preferred_lifetime', _('IPv6 Prefix Lifetime'), _('Preferred lifetime for a prefix.'));
1011 so.optional = true;
1012 so.placeholder = '12h';
1013 so.value('5m', _('5m (5 minutes)'));
1014 so.value('3h', _('3h (3 hours)'));
1015 so.value('12h', _('12h (12 hours - default)'));
1016 so.value('7d', _('7d (7 days)'));
1017
1018 //This is a ra_* setting, but its placement is more logical/findable under IPv6 settings.
1019 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.'));
1020 so.optional = true;
1021 }
1022
1023 ifc.renderFormOptions(s);
1024
1025 // Common interface options
1026 o = nettools.replaceOption(s, 'advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
1027 o.default = o.enabled;
1028
1029 if (has_peerdns(protoval)) {
1030 o = nettools.replaceOption(s, 'advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
1031 o.default = o.enabled;
1032 }
1033
1034 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
1035 if (has_peerdns(protoval))
1036 o.depends('peerdns', '0');
1037 o.datatype = 'ipaddr';
1038
1039 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns_search', _('DNS search domains'));
1040 if (protoval != 'static')
1041 o.depends('peerdns', '0');
1042 o.datatype = 'hostname';
1043
1044 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'));
1045 o.datatype = 'uinteger';
1046 o.placeholder = '0';
1047
1048 o = nettools.replaceOption(s, 'advanced', form.Value, 'metric', _('Use gateway metric'));
1049 o.datatype = 'uinteger';
1050 o.placeholder = '0';
1051
1052 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip4table', _('Override IPv4 routing table'));
1053 o.datatype = 'or(uinteger, string)';
1054 for (var i = 0; i < rtTables.length; i++)
1055 o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
1056
1057 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6table', _('Override IPv6 routing table'));
1058 o.datatype = 'or(uinteger, string)';
1059 for (var i = 0; i < rtTables.length; i++)
1060 o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
1061
1062 if (has_sourcefilter(protoval)) {
1063 o = nettools.replaceOption(s, 'advanced', form.Flag, 'sourcefilter', _('IPv6 source routing'), _('Automatically handle multiple uplink interfaces using source-based policy routing.'));
1064 o.default = o.enabled;
1065 }
1066
1067 o = nettools.replaceOption(s, 'advanced', form.Flag, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
1068 o.default = o.enabled;
1069
1070 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'));
1071 o.value('', _('disabled'));
1072 o.value('64');
1073 o.datatype = 'max(128)';
1074
1075 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6hint', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
1076 o.placeholder = '0';
1077 o.validate = function(section_id, value) {
1078 if (value == null || value == '')
1079 return true;
1080
1081 var n = parseInt(value, 16);
1082
1083 if (!/^(0x)?[0-9a-fA-F]+$/.test(value) || isNaN(n) || n >= 0xffffffff)
1084 return _('Expecting a hexadecimal assignment hint');
1085
1086 return true;
1087 };
1088 for (var i = 33; i <= 64; i++)
1089 o.depends('ip6assign', String(i));
1090
1091
1092 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'ip6class', _('IPv6 prefix filter'), _('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));
1093 o.value('local', 'local (%s)'.format(_('Local ULA')));
1094
1095 var prefixClasses = {};
1096
1097 this.networks.forEach(function(net) {
1098 var prefixes = net._ubus('ipv6-prefix');
1099 if (Array.isArray(prefixes)) {
1100 prefixes.forEach(function(pfx) {
1101 if (L.isObject(pfx) && typeof(pfx['class']) == 'string') {
1102 prefixClasses[pfx['class']] = prefixClasses[pfx['class']] || {};
1103 prefixClasses[pfx['class']][net.getName()] = true;
1104 }
1105 });
1106 }
1107 });
1108
1109 Object.keys(prefixClasses).sort().forEach(function(c) {
1110 var networks = Object.keys(prefixClasses[c]).sort().join(', ');
1111 o.value(c, (c != networks) ? '%s (%s)'.format(c, networks) : c);
1112 });
1113
1114
1115 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."));
1116 o.datatype = 'ip6hostid';
1117 o.placeholder = '::1';
1118
1119 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.'));
1120 o.datatype = 'uinteger';
1121 o.placeholder = '0';
1122
1123 for (var i = 0; i < s.children.length; i++) {
1124 o = s.children[i];
1125
1126 switch (o.option) {
1127 case 'proto':
1128 case 'auto':
1129 case '_dhcp':
1130 case '_zone':
1131 case '_switch_proto':
1132 case '_ifacestat_modal':
1133 continue;
1134
1135 case 'igmp_snooping':
1136 case 'stp':
1137 case 'type':
1138 case '_net_device':
1139 var deps = [];
1140 for (var j = 0; j < protocols.length; j++) {
1141 if (!protocols[j].isVirtual()) {
1142 if (o.deps.length)
1143 for (var k = 0; k < o.deps.length; k++)
1144 deps.push(Object.assign({ proto: protocols[j].getProtocol() }, o.deps[k]));
1145 else
1146 deps.push({ proto: protocols[j].getProtocol() });
1147 }
1148 }
1149 o.deps = deps;
1150 break;
1151
1152 default:
1153 if (o.deps.length)
1154 for (var j = 0; j < o.deps.length; j++)
1155 o.deps[j].proto = protoval;
1156 else
1157 o.depends('proto', protoval);
1158 }
1159 }
1160
1161 this.activeSection = s.section;
1162 }, this));
1163 };
1164
1165 s.handleModalCancel = function(/* ... */) {
1166 var type = uci.get('network', this.activeSection || this.addedSection, 'type'),
1167 device = (type == 'bridge') ? 'br-%s'.format(this.activeSection || this.addedSection) : null;
1168
1169 uci.sections('network', 'bridge-vlan', function(bvs) {
1170 if (device != null && bvs.device == device)
1171 uci.remove('network', bvs['.name']);
1172 });
1173
1174 return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
1175 };
1176
1177 s.handleAdd = function(ev) {
1178 var m2 = new form.Map('network'),
1179 s2 = m2.section(form.NamedSection, '_new_'),
1180 protocols = network.getProtocols(),
1181 proto, name, device;
1182
1183 protocols.sort(function(a, b) {
1184 return L.naturalCompare(a.getProtocol(), b.getProtocol());
1185 });
1186
1187 s2.render = function() {
1188 return Promise.all([
1189 {},
1190 this.renderUCISection('_new_')
1191 ]).then(this.renderContents.bind(this));
1192 };
1193
1194 name = s2.option(form.Value, 'name', _('Name'));
1195 name.rmempty = false;
1196 name.datatype = 'uciname';
1197 name.placeholder = _('New interface name…');
1198 name.validate = function(section_id, value) {
1199 if (uci.get('network', value) != null)
1200 return _('The interface name is already used');
1201
1202 var pr = network.getProtocol(proto.formvalue(section_id), value),
1203 ifname = pr.isVirtual() ? '%s-%s'.format(pr.getProtocol(), value) : 'br-%s'.format(value);
1204
1205 if (value.length > 15)
1206 return _('The interface name is too long');
1207
1208 return true;
1209 };
1210
1211 proto = s2.option(form.ListValue, 'proto', _('Protocol'));
1212 proto.validate = name.validate;
1213
1214 device = s2.option(widgets.DeviceSelect, 'device', _('Device'));
1215 device.noaliases = false;
1216 device.optional = false;
1217
1218 for (var i = 0; i < protocols.length; i++) {
1219 proto.value(protocols[i].getProtocol(), protocols[i].getI18n());
1220
1221 if (!protocols[i].isVirtual())
1222 device.depends('proto', protocols[i].getProtocol());
1223 }
1224
1225 m2.render().then(L.bind(function(nodes) {
1226 ui.showModal(_('Add new interface...'), [
1227 nodes,
1228 E('div', { 'class': 'right' }, [
1229 E('button', {
1230 'class': 'btn',
1231 'click': ui.hideModal
1232 }, _('Cancel')), ' ',
1233 E('button', {
1234 'class': 'cbi-button cbi-button-positive important',
1235 'click': ui.createHandlerFn(this, function(ev) {
1236 var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null,
1237 protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null,
1238 protoclass = protoval ? network.getProtocol(protoval, nameval) : null;
1239
1240 if (nameval == null || protoval == null || nameval == '' || protoval == '')
1241 return;
1242
1243 return protoclass.isCreateable(nameval).then(function(checkval) {
1244 if (checkval != null) {
1245 ui.addNotification(null,
1246 E('p', _('New interface for "%s" can not be created: %s').format(protoclass.getI18n(), checkval)));
1247 ui.hideModal();
1248 return;
1249 }
1250
1251 return m.save(function() {
1252 var section_id = uci.add('network', 'interface', nameval);
1253
1254 protoclass.set('proto', protoval);
1255 protoclass.addDevice(device.formvalue('_new_'));
1256
1257 m.children[0].addedSection = section_id;
1258
1259 ui.hideModal();
1260 ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
1261 }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
1262 });
1263 })
1264 }, _('Create interface'))
1265 ])
1266 ], 'cbi-modal');
1267
1268 nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
1269 }, this));
1270 };
1271
1272 s.handleRemove = function(section_id, ev) {
1273 return network.deleteNetwork(section_id).then(L.bind(function(section_id, ev) {
1274 return form.GridSection.prototype.handleRemove.apply(this, [section_id, ev]);
1275 }, this, section_id, ev));
1276 };
1277
1278 o = s.option(form.DummyValue, '_ifacebox');
1279 o.modalonly = false;
1280 o.textvalue = function(section_id) {
1281 var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0],
1282 zone = net ? this.section.zones.filter(function(z) { return !!z.getNetworks().filter(function(n) { return n == section_id })[0] })[0] : null;
1283
1284 if (!net)
1285 return;
1286
1287 var node = E('div', { 'class': 'ifacebox' }, [
1288 E('div', {
1289 'class': 'ifacebox-head',
1290 'style': firewall.getZoneColorStyle(zone),
1291 'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned')
1292 }, E('strong', net.getName())),
1293 E('div', {
1294 'class': 'ifacebox-body',
1295 'id': '%s-ifc-devices'.format(section_id),
1296 'data-network': section_id
1297 }, [
1298 E('img', {
1299 'src': L.resource('icons/ethernet_disabled.png'),
1300 'style': 'width:16px; height:16px'
1301 }),
1302 E('br'), E('small', '?')
1303 ])
1304 ]);
1305
1306 render_ifacebox_status(node.childNodes[1], net);
1307
1308 return node;
1309 };
1310
1311 o = s.option(form.DummyValue, '_ifacestat');
1312 o.modalonly = false;
1313 o.textvalue = function(section_id) {
1314 var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0];
1315
1316 if (!net)
1317 return;
1318
1319 var node = E('div', { 'id': '%s-ifc-description'.format(section_id) });
1320
1321 render_status(node, net, false);
1322
1323 return node;
1324 };
1325
1326 o = s.taboption('advanced', form.Flag, 'delegate', _('Use builtin IPv6-management'));
1327 o.modalonly = true;
1328 o.default = o.enabled;
1329
1330 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).'));
1331 o.modalonly = true;
1332 o.defaults = {
1333 '1': [{ proto: 'static' }],
1334 '0': []
1335 };
1336
1337
1338 // Device configuration
1339 s = m.section(form.GridSection, 'device', _('Devices'));
1340 s.addremove = true;
1341 s.anonymous = true;
1342 s.addbtntitle = _('Add device configuration…');
1343
1344 s.cfgsections = function() {
1345 var sections = uci.sections('network', 'device'),
1346 section_ids = sections.sort(function(a, b) { return L.naturalCompare(a.name, b.name) }).map(function(s) { return s['.name'] });
1347
1348 for (var i = 0; i < netDevs.length; i++) {
1349 if (sections.filter(function(s) { return s.name == netDevs[i].getName() }).length)
1350 continue;
1351
1352 if (netDevs[i].getType() == 'wifi' && !netDevs[i].isUp())
1353 continue;
1354
1355 /* Unless http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html is implemented,
1356 we cannot properly redefine bridges as devices, so filter them away for now... */
1357
1358 var m = netDevs[i].isBridge() ? netDevs[i].getName().match(/^br-([A-Za-z0-9_]+)$/) : null,
1359 s = m ? uci.get('network', m[1]) : null;
1360
1361 if (s && s['.type'] == 'interface' && s.type == 'bridge')
1362 continue;
1363
1364 section_ids.push('dev:%s'.format(netDevs[i].getName()));
1365 }
1366
1367 return section_ids;
1368 };
1369
1370 s.renderMoreOptionsModal = function(section_id, ev) {
1371 var m = section_id.match(/^dev:(.+)$/);
1372
1373 if (m) {
1374 var devtype = getDevType(section_id);
1375
1376 section_id = uci.add('network', 'device');
1377
1378 uci.set('network', section_id, 'name', m[1]);
1379 uci.set('network', section_id, 'type', (devtype != 'ethernet') ? devtype : null);
1380
1381 this.addedSection = section_id;
1382 }
1383
1384 return this.super('renderMoreOptionsModal', [section_id, ev]);
1385 };
1386
1387 s.renderRowActions = function(section_id) {
1388 var trEl = this.super('renderRowActions', [ section_id, _('Configure…') ]),
1389 deleteBtn = trEl.querySelector('button:last-child');
1390
1391 deleteBtn.firstChild.data = _('Unconfigure');
1392 deleteBtn.setAttribute('title', _('Remove related device settings from the configuration'));
1393 deleteBtn.disabled = section_id.match(/^dev:/) ? true : null;
1394
1395 return trEl;
1396 };
1397
1398 s.modaltitle = function(section_id) {
1399 var m = section_id.match(/^dev:(.+)$/),
1400 name = m ? m[1] : uci.get('network', section_id, 'name');
1401
1402 return name ? '%s: %q'.format(getDevTypeDesc(section_id), name) : _('Add device configuration');
1403 };
1404
1405 s.addModalOptions = function(s) {
1406 var isNew = (uci.get('network', s.section, 'name') == null),
1407 dev = getDevice(s.section);
1408
1409 nettools.addDeviceOptions(s, dev, isNew);
1410 };
1411
1412 s.handleModalCancel = function(map /*, ... */) {
1413 var name = uci.get('network', this.addedSection, 'name')
1414
1415 uci.sections('network', 'bridge-vlan', function(bvs) {
1416 if (name != null && bvs.device == name)
1417 uci.remove('network', bvs['.name']);
1418 });
1419
1420 if (map.addedVLANs)
1421 for (var i = 0; i < map.addedVLANs.length; i++)
1422 uci.remove('network', map.addedVLANs[i]);
1423
1424 if (this.addedSection)
1425 uci.remove('network', this.addedSection);
1426
1427 return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
1428 };
1429
1430 s.handleRemove = function(section_id /*, ... */) {
1431 var name = uci.get('network', section_id, 'name'),
1432 type = uci.get('network', section_id, 'type');
1433
1434 if (name != null && type == 'bridge') {
1435 uci.sections('network', 'bridge-vlan', function(bvs) {
1436 if (bvs.device == name)
1437 uci.remove('network', bvs['.name']);
1438 });
1439 }
1440
1441 return form.GridSection.prototype.handleRemove.apply(this, arguments);
1442 };
1443
1444 function getDevice(section_id) {
1445 var m = section_id.match(/^dev:(.+)$/),
1446 name = m ? m[1] : uci.get('network', section_id, 'name');
1447
1448 return netDevs.filter(function(d) { return d.getName() == name })[0];
1449 }
1450
1451 function getDevType(section_id) {
1452 var dev = getDevice(section_id),
1453 cfg = uci.get('network', section_id),
1454 type = cfg ? (uci.get('network', section_id, 'type') || 'ethernet') : (dev ? dev.getType() : '');
1455
1456 switch (type) {
1457 case '':
1458 return null;
1459
1460 case 'vlan':
1461 case '8021q':
1462 return '8021q';
1463
1464 case '8021ad':
1465 return '8021ad';
1466
1467 case 'bridge':
1468 return 'bridge';
1469
1470 case 'tunnel':
1471 return 'tunnel';
1472
1473 case 'macvlan':
1474 return 'macvlan';
1475
1476 case 'veth':
1477 return 'veth';
1478
1479 case 'wifi':
1480 case 'alias':
1481 case 'switch':
1482 case 'ethernet':
1483 default:
1484 return 'ethernet';
1485 }
1486 }
1487
1488 function getDevTypeDesc(section_id) {
1489 switch (getDevType(section_id) || '') {
1490 case '':
1491 return E('em', [ _('Device not present') ]);
1492
1493 case '8021q':
1494 return _('VLAN (802.1q)');
1495
1496 case '8021ad':
1497 return _('VLAN (802.1ad)');
1498
1499 case 'bridge':
1500 return _('Bridge device');
1501
1502 case 'tunnel':
1503 return _('Tunnel device');
1504
1505 case 'macvlan':
1506 return _('MAC VLAN');
1507
1508 case 'veth':
1509 return _('Virtual Ethernet');
1510
1511 default:
1512 return _('Network device');
1513 }
1514 }
1515
1516 o = s.option(form.DummyValue, 'name', _('Device'));
1517 o.modalonly = false;
1518 o.textvalue = function(section_id) {
1519 var dev = getDevice(section_id),
1520 ext = section_id.match(/^dev:/),
1521 icon = render_iface(dev);
1522
1523 if (ext)
1524 icon.querySelector('img').style.opacity = '.5';
1525
1526 return E('span', { 'class': 'ifacebadge' }, [
1527 icon,
1528 E('span', { 'style': ext ? 'opacity:.5' : null }, [
1529 dev ? dev.getName() : (uci.get('network', section_id, 'name') || '?')
1530 ])
1531 ]);
1532 };
1533
1534 o = s.option(form.DummyValue, 'type', _('Type'));
1535 o.textvalue = getDevTypeDesc;
1536 o.modalonly = false;
1537
1538 o = s.option(form.DummyValue, 'macaddr', _('MAC Address'));
1539 o.modalonly = false;
1540 o.textvalue = function(section_id) {
1541 var dev = getDevice(section_id),
1542 val = uci.get('network', section_id, 'macaddr'),
1543 mac = dev ? dev.getMAC() : null;
1544
1545 return val ? E('strong', {
1546 'data-tooltip': _('The value is overridden by configuration.')
1547 }, [ val.toUpperCase() ]) : (mac || '-');
1548 };
1549
1550 o = s.option(form.DummyValue, 'mtu', _('MTU'));
1551 o.modalonly = false;
1552 o.textvalue = function(section_id) {
1553 var dev = getDevice(section_id),
1554 val = uci.get('network', section_id, 'mtu'),
1555 mtu = dev ? dev.getMTU() : null;
1556
1557 return val ? E('strong', {
1558 'data-tooltip': _('The value is overridden by configuration.')
1559 }, [ val ]) : (mtu || '-').toString();
1560 };
1561
1562 s = m.section(form.TypedSection, 'globals', _('Global network options'));
1563 s.addremove = false;
1564 s.anonymous = true;
1565
1566 o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'),
1567 _('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') + ' ' +
1568 _('ULA for IPv6 is analogous to IPv4 private network addressing.') + ' ' +
1569 _('This prefix is randomly generated at first install.'));
1570 o.datatype = 'cidr6';
1571
1572 o = s.option(form.Flag, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
1573 o.optional = true;
1574
1575
1576 if (dslModemType != null) {
1577 s = m.section(form.TypedSection, 'dsl', _('DSL'));
1578 s.anonymous = true;
1579
1580 o = s.option(form.ListValue, 'annex', _('Annex'));
1581 if (dslModemType == 'vdsl') {
1582 o.value('a', _('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));
1583 o.value('b', _('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));
1584 o.value('j', _('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));
1585 } else {
1586 o.value('a', _('ADSL (all variants) Annex A/L/M'));
1587 o.value('b', _('ADSL (all variants) Annex B'));
1588 o.value('j', _('ADSL (all variants) Annex B/J'));
1589 }
1590 o.value('m', _('ADSL (all variants) Annex M'));
1591 o.value('at1', _('ANSI T1.413'));
1592 o.value('admt', _('ADSL (G.992.1) Annex A'));
1593 o.value('bdmt', _('ADSL (G.992.1) Annex B'));
1594 o.value('alite', _('Splitterless ADSL (G.992.2) Annex A'));
1595 o.value('a2', _('ADSL2 (G.992.3) Annex A'));
1596 o.value('b2', _('ADSL2 (G.992.3) Annex B'));
1597 o.value('l', _('ADSL2 (G.992.3) Annex L'));
1598 o.value('m2', _('ADSL2 (G.992.3) Annex M'));
1599 o.value('a2p', _('ADSL2+ (G.992.5) Annex A'));
1600 o.value('b2p', _('ADSL2+ (G.992.5) Annex B'));
1601 o.value('m2p', _('ADSL2+ (G.992.5) Annex M'));
1602
1603 o = s.option(form.ListValue, 'tone', _('Tone'));
1604 o.value('', _('auto'));
1605 o.value('a', _('A43C + J43 + A43'));
1606 o.value('av', _('A43C + J43 + A43 + V43'));
1607 o.value('b', _('B43 + B43C'));
1608 o.value('bv', _('B43 + B43C + V43'));
1609
1610 if (dslModemType == 'vdsl') {
1611 o = s.option(form.ListValue, 'xfer_mode', _('Encapsulation mode'));
1612 o.value('', _('auto'));
1613 o.value('atm', _('ATM (Asynchronous Transfer Mode)'));
1614 o.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
1615
1616 o = s.option(form.ListValue, 'line_mode', _('DSL line mode'));
1617 o.value('', _('auto'));
1618 o.value('adsl', _('ADSL'));
1619 o.value('vdsl', _('VDSL'));
1620
1621 o = s.option(form.ListValue, 'ds_snr_offset', _('Downstream SNR offset'));
1622 o.default = '0';
1623
1624 for (var i = -100; i <= 100; i += 5)
1625 o.value(i, _('%.1f dB').format(i / 10));
1626 }
1627
1628 s.option(form.Value, 'firmware', _('Firmware File'));
1629 }
1630
1631
1632 // Show ATM bridge section if we have the capabilities
1633 if (L.hasSystemFeature('br2684ctl')) {
1634 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.'));
1635
1636 s.addremove = true;
1637 s.anonymous = true;
1638 s.addbtntitle = _('Add ATM Bridge');
1639
1640 s.handleAdd = function(ev) {
1641 var sections = uci.sections('network', 'atm-bridge'),
1642 max_unit = -1;
1643
1644 for (var i = 0; i < sections.length; i++) {
1645 var unit = +sections[i].unit;
1646
1647 if (!isNaN(unit) && unit > max_unit)
1648 max_unit = unit;
1649 }
1650
1651 return this.map.save(function() {
1652 var sid = uci.add('network', 'atm-bridge');
1653
1654 uci.set('network', sid, 'unit', max_unit + 1);
1655 uci.set('network', sid, 'atmdev', 0);
1656 uci.set('network', sid, 'encaps', 'llc');
1657 uci.set('network', sid, 'payload', 'bridged');
1658 uci.set('network', sid, 'vci', 35);
1659 uci.set('network', sid, 'vpi', 8);
1660 });
1661 };
1662
1663 s.tab('general', _('General Setup'));
1664 s.tab('advanced', _('Advanced Settings'));
1665
1666 o = s.taboption('general', form.Value, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
1667 s.taboption('general', form.Value, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
1668
1669 o = s.taboption('general', form.ListValue, 'encaps', _('Encapsulation mode'));
1670 o.value('llc', _('LLC'));
1671 o.value('vc', _('VC-Mux'));
1672
1673 s.taboption('advanced', form.Value, 'atmdev', _('ATM device number'));
1674 s.taboption('advanced', form.Value, 'unit', _('Bridge unit number'));
1675
1676 o = s.taboption('advanced', form.ListValue, 'payload', _('Forwarding mode'));
1677 o.value('bridged', _('bridged'));
1678 o.value('routed', _('routed'));
1679 }
1680
1681
1682 return m.render().then(L.bind(function(m, nodes) {
1683 poll.add(L.bind(function() {
1684 var section_ids = m.children[0].cfgsections(),
1685 tasks = [];
1686
1687 for (var i = 0; i < section_ids.length; i++) {
1688 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1689 dsc = row.querySelector('[data-name="_ifacestat"] > div'),
1690 btn1 = row.querySelector('.cbi-section-actions .reconnect'),
1691 btn2 = row.querySelector('.cbi-section-actions .down');
1692
1693 if (dsc.getAttribute('reconnect') == '') {
1694 dsc.setAttribute('reconnect', '1');
1695 tasks.push(fs.exec('/sbin/ifup', [section_ids[i]]).catch(function(e) {
1696 ui.addNotification(null, E('p', e.message));
1697 }));
1698 }
1699 else if (dsc.getAttribute('disconnect') == '') {
1700 dsc.setAttribute('disconnect', '1');
1701 tasks.push(fs.exec('/sbin/ifdown', [section_ids[i]]).catch(function(e) {
1702 ui.addNotification(null, E('p', e.message));
1703 }));
1704 }
1705 else if (dsc.getAttribute('reconnect') == '1') {
1706 dsc.removeAttribute('reconnect');
1707 btn1.classList.remove('spinning');
1708 btn1.disabled = false;
1709 }
1710 else if (dsc.getAttribute('disconnect') == '1') {
1711 dsc.removeAttribute('disconnect');
1712 btn2.classList.remove('spinning');
1713 btn2.disabled = false;
1714 }
1715 }
1716
1717 return Promise.all(tasks)
1718 .then(L.bind(network.getNetworks, network))
1719 .then(L.bind(this.poll_status, this, nodes));
1720 }, this), 5);
1721
1722 return nodes;
1723 }, this, m));
1724 }
1725 });