4a9abea5a2310c3362efaaddda3fc06ee782b3fa
[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, 'auto', _('Bring up on boot'));
617 o.modalonly = true;
618 o.default = o.enabled;
619
620 if (L.hasSystemFeature('firewall')) {
621 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.'));
622 o.network = ifc.getName();
623 o.optional = true;
624
625 o.cfgvalue = function(section_id) {
626 return firewall.getZoneByNetwork(ifc.getName()).then(function(zone) {
627 return (zone != null ? zone.getName() : null);
628 });
629 };
630
631 o.write = o.remove = function(section_id, value) {
632 return Promise.all([
633 firewall.getZoneByNetwork(ifc.getName()),
634 (value != null) ? firewall.getZone(value) : null
635 ]).then(function(data) {
636 var old_zone = data[0],
637 new_zone = data[1];
638
639 if (old_zone == null && new_zone == null && (value == null || value == ''))
640 return;
641
642 if (old_zone != null && new_zone != null && old_zone.getName() == new_zone.getName())
643 return;
644
645 if (old_zone != null)
646 old_zone.deleteNetwork(ifc.getName());
647
648 if (new_zone != null)
649 new_zone.addNetwork(ifc.getName());
650 else if (value != null)
651 return firewall.addZone(value).then(function(new_zone) {
652 new_zone.addNetwork(ifc.getName());
653 });
654 });
655 };
656 }
657
658 for (var i = 0; i < protocols.length; i++) {
659 proto_select.value(protocols[i].getProtocol(), protocols[i].getI18n());
660
661 if (protocols[i].getProtocol() != uci.get('network', s.section, 'proto'))
662 proto_switch.depends('proto', protocols[i].getProtocol());
663 }
664
665 if (L.hasSystemFeature('dnsmasq') || L.hasSystemFeature('odhcpd')) {
666 o = s.taboption('dhcp', form.SectionValue, '_dhcp', form.TypedSection, 'dhcp');
667
668 ss = o.subsection;
669 ss.uciconfig = 'dhcp';
670 ss.addremove = false;
671 ss.anonymous = true;
672
673 ss.tab('general', _('General Setup'));
674 ss.tab('advanced', _('Advanced Settings'));
675 ss.tab('ipv6', _('IPv6 Settings'));
676 ss.tab('ipv6-ra', _('IPv6 RA Settings'));
677
678 ss.filter = function(section_id) {
679 return (uci.get('dhcp', section_id, 'interface') == ifc.getName());
680 };
681
682 ss.renderSectionPlaceholder = function() {
683 return E('div', { 'class': 'cbi-section-create' }, [
684 E('p', _('No DHCP Server configured for this interface') + ' &#160; '),
685 E('button', {
686 'class': 'cbi-button cbi-button-add',
687 'title': _('Set up DHCP Server'),
688 'click': ui.createHandlerFn(this, function(section_id, ev) {
689 this.map.save(function() {
690 uci.add('dhcp', 'dhcp', section_id);
691 uci.set('dhcp', section_id, 'interface', section_id);
692
693 if (protoval == 'static') {
694 uci.set('dhcp', section_id, 'start', 100);
695 uci.set('dhcp', section_id, 'limit', 150);
696 uci.set('dhcp', section_id, 'leasetime', '12h');
697 }
698 else {
699 uci.set('dhcp', section_id, 'ignore', 1);
700 }
701 });
702 }, ifc.getName())
703 }, _('Set up DHCP Server'))
704 ]);
705 };
706
707 ss.taboption('general', form.Flag, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
708
709 if (protoval == 'static') {
710 so = ss.taboption('general', form.Value, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
711 so.optional = true;
712 so.datatype = 'or(uinteger,ip4addr("nomask"))';
713 so.default = '100';
714
715 so = ss.taboption('general', form.Value, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
716 so.optional = true;
717 so.datatype = 'uinteger';
718 so.default = '150';
719
720 so = ss.taboption('general', form.Value, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
721 so.optional = true;
722 so.default = '12h';
723
724 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.'));
725 so.default = so.enabled;
726
727 ss.taboption('advanced', form.Flag, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
728
729 // XXX: is this actually useful?
730 //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
731
732 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.'));
733 so.optional = true;
734 so.datatype = 'ip4addr';
735
736 so.render = function(option_index, section_id, in_table) {
737 this.placeholder = get_netmask(s, true);
738 return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
739 };
740
741 so.validate = function(section_id, value) {
742 var uielem = this.getUIElement(section_id);
743 if (uielem)
744 uielem.setPlaceholder(get_netmask(s, false));
745 return form.Value.prototype.validate.apply(this, [ section_id, value ]);
746 };
747
748 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.'));
749 }
750
751
752 var has_other_master = uci.sections('dhcp', 'dhcp').filter(function(s) {
753 return (s.interface != ifc.getName() && s.master == '1');
754 })[0];
755
756 so = ss.taboption('ipv6', form.Flag , 'master', _('Designated master'));
757 so.readonly = has_other_master ? true : false;
758 so.description = has_other_master
759 ? _('Interface "%h" is already marked as designated master.').format(has_other_master.interface || has_other_master['.name'])
760 : _('Set this interface as master for RA and DHCPv6 relaying as well as NDP proxying.')
761 ;
762
763 so.validate = function(section_id, value) {
764 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>.'),
765 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.'),
766 hybrid_master_desc = _('Operate in <em>relay mode</em> if an upstream IPv6 prefix is present, otherwise disable service.'),
767 ra_server_allowed = true,
768 checked = this.formvalue(section_id),
769 dhcpv6 = this.section.getOption('dhcpv6').getUIElement(section_id),
770 ndp = this.section.getOption('ndp').getUIElement(section_id),
771 ra = this.section.getOption('ra').getUIElement(section_id);
772
773 /* Assume that serving RAs by default is fine, but disallow it for certain
774 interface protocols such as DHCP, DHCPv6 or the various PPP flavors.
775 The intent is to only allow RA serving for interface protocols doing
776 some kind of static IP config over something resembling a layer 2
777 ethernet device. */
778 switch (protoval) {
779 case 'dhcp':
780 case 'dhcpv6':
781 case '3g':
782 case 'l2tp':
783 case 'ppp':
784 case 'pppoa':
785 case 'pppoe':
786 case 'pptp':
787 case 'pppossh':
788 case 'ipip':
789 case 'gre':
790 case 'grev6':
791 ra_server_allowed = false;
792 break;
793 }
794
795 if (checked == '1' || !ra_server_allowed) {
796 dhcpv6.node.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
797
798 if (dhcpv6.getValue() == 'server')
799 dhcpv6.setValue('hybrid');
800
801 ra.node.querySelector('li[data-value="server"]').setAttribute('unselectable', '');
802
803 if (ra.getValue() == 'server')
804 ra.setValue('hybrid');
805 }
806
807 if (checked == '1') {
808 dhcpv6.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_master_desc;
809 ra.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_master_desc;
810 ndp.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_master_desc;
811 }
812 else {
813 if (ra_server_allowed) {
814 dhcpv6.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');
815 ra.node.querySelector('li[data-value="server"]').removeAttribute('unselectable');
816 }
817
818 dhcpv6.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_downstream_desc;
819 ra.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = hybrid_downstream_desc;
820 ndp.node.querySelector('li[data-value="hybrid"] > div > span').innerHTML = ndp_downstream_desc ;
821 }
822
823 return true;
824 };
825
826
827 so = ss.taboption('ipv6', cbiRichListValue, 'ra', _('<abbr title="Router Advertisement">RA</abbr>-Service'),
828 _('Configures the operation mode of the <abbr title="Router Advertisement">RA</abbr> service on this interface.'));
829 so.value('', _('disabled'),
830 _('Do not send any <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages on this interface.'));
831 so.value('server', _('server mode'),
832 _('Send <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages advertising this device as IPv6 router.'));
833 so.value('relay', _('relay mode'),
834 _('Forward <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages received on the designated master interface to downstream interfaces.'));
835 so.value('hybrid', _('hybrid mode'), ' ');
836
837
838 so = ss.taboption('ipv6-ra', cbiRichListValue, 'ra_default', _('Default router'),
839 _('Configures the default router advertisement in <abbr title="Router Advertisement">RA</abbr> messages.'));
840 so.value('', _('automatic'),
841 _('Announce this device as default router if a local IPv6 default route is present.'));
842 so.value('1', _('on available prefix'),
843 _('Announce this device as default router if a public IPv6 prefix is available, regardless of local default route availability.'));
844 so.value('2', _('forced'),
845 _('Announce this device as default router regardless of whether a prefix or default route is present.'));
846 so.depends('ra', 'server');
847 so.depends({ ra: 'hybrid', master: '0' });
848
849 so = ss.taboption('ipv6-ra', form.Flag, 'ra_slaac', _('Enable <abbr title="Stateless Address Auto Config">SLAAC</abbr>'),
850 _('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.'));
851 so.default = so.enabled;
852 so.depends('ra', 'server');
853 so.depends({ ra: 'hybrid', master: '0' });
854
855 so = ss.taboption('ipv6-ra', cbiRichListValue, 'ra_flags', _('<abbr title="Router Advertisement">RA</abbr> Flags'),
856 _('Specifies the flags sent in <abbr title="Router Advertisement">RA</abbr> messages, for example to instruct clients to request further information via stateful DHCPv6.'));
857 so.value('managed-config', _('managed config (M)'),
858 _('The <em>Managed address configuration</em> (M) flag indicates that IPv6 addresses are available via DHCPv6.'));
859 so.value('other-config', _('other config (O)'),
860 _('The <em>Other configuration</em> (O) flag indicates that other information, such as DNS servers, is available via DHCPv6.'));
861 so.value('home-agent', _('mobile home agent (H)'),
862 _('The <em>Mobile IPv6 Home Agent</em> (H) flag indicates that the device is also acting as Mobile IPv6 home agent on this link.'));
863 so.multiple = true;
864 so.select_placeholder = _('none');
865 so.depends('ra', 'server');
866 so.depends({ ra: 'hybrid', master: '0' });
867 so.cfgvalue = function(section_id) {
868 var flags = L.toArray(uci.get('dhcp', section_id, 'ra_flags'));
869 return flags.length ? flags : [ 'other-config' ];
870 };
871 so.remove = function(section_id) {
872 var existing = L.toArray(uci.get('dhcp', section_id, 'ra_flags'));
873 if (this.isActive(section_id)) {
874 if (existing.length != 1 || existing[0] != 'none')
875 uci.set('dhcp', section_id, 'ra_flags', [ 'none' ]);
876 }
877 else if (existing.length) {
878 uci.unset('dhcp', section_id, 'ra_flags');
879 }
880 };
881
882 so = ss.taboption('ipv6-ra', form.Value, 'ra_pref64', _('NAT64 prefix'), _('Announce NAT64 prefix in <abbr title="Router Advertisement">RA</abbr> messages.'));
883 so.optional = true;
884 so.datatype = 'cidr6';
885 so.placeholder = '64:ff9b::/96';
886 so.depends('ra', 'server');
887 so.depends({ ra: 'hybrid', master: '0' });
888
889 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.'));
890 so.optional = true;
891 so.datatype = 'uinteger';
892 so.placeholder = '600';
893 so.depends('ra', 'server');
894 so.depends({ ra: 'hybrid', master: '0' });
895
896 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.'));
897 so.optional = true;
898 so.datatype = 'uinteger';
899 so.placeholder = '200';
900 so.depends('ra', 'server');
901 so.depends({ ra: 'hybrid', master: '0' });
902
903 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.'));
904 so.optional = true;
905 so.datatype = 'range(0, 9000)';
906 so.placeholder = '1800';
907 so.depends('ra', 'server');
908 so.depends({ ra: 'hybrid', master: '0' });
909
910 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.'));
911 so.optional = true;
912 so.datatype = 'range(1280, 65535)';
913 so.depends('ra', 'server');
914 so.depends({ ra: 'hybrid', master: '0' });
915 so.load = function(section_id) {
916 var dev = ifc.getL3Device(),
917 path = dev ? "/proc/sys/net/ipv6/conf/%s/mtu".format(dev.getName()) : null;
918
919 return Promise.all([
920 dev ? L.resolveDefault(fs.read(path), dev.getMTU()) : null,
921 this.super('load', [section_id])
922 ]).then(L.bind(function(res) {
923 this.placeholder = +res[0];
924
925 return res[1];
926 }, this));
927 };
928
929 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.'));
930 so.optional = true;
931 so.datatype = 'range(0, 255)';
932 so.depends('ra', 'server');
933 so.depends({ ra: 'hybrid', master: '0' });
934 so.load = function(section_id) {
935 var dev = ifc.getL3Device(),
936 path = dev ? "/proc/sys/net/ipv6/conf/%s/hop_limit".format(dev.getName()) : null;
937
938 return Promise.all([
939 dev ? L.resolveDefault(fs.read(path), 64) : null,
940 this.super('load', [section_id])
941 ]).then(L.bind(function(res) {
942 this.placeholder = +res[0];
943
944 return res[1];
945 }, this));
946 };
947
948
949 so = ss.taboption('ipv6', cbiRichListValue, 'dhcpv6', _('DHCPv6-Service'),
950 _('Configures the operation mode of the DHCPv6 service on this interface.'));
951 so.value('', _('disabled'),
952 _('Do not offer DHCPv6 service on this interface.'));
953 so.value('server', _('server mode'),
954 _('Provide a DHCPv6 server on this interface and reply to DHCPv6 solicitations and requests.'));
955 so.value('relay', _('relay mode'),
956 _('Forward DHCPv6 messages between the designated master interface and downstream interfaces.'));
957 so.value('hybrid', _('hybrid mode'), ' ');
958
959 so = ss.taboption('ipv6', form.Value, 'dhcpv6_pd_min_len', _('<abbr title="Prefix Delegation">PD</abbr> minimum length'),
960 _('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.'));
961 so.datatype = 'range(1,62)';
962 so.depends('dhcpv6', 'server');
963
964 so = ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced IPv6 DNS servers'),
965 _('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.'));
966 so.datatype = 'ip6addr("nomask")'; /* restrict to IPv6 only for now since dnsmasq (DHCPv4) does not honour this option */
967 so.depends('ra', 'server');
968 so.depends({ ra: 'hybrid', master: '0' });
969 so.depends('dhcpv6', 'server');
970 so.depends({ dhcpv6: 'hybrid', master: '0' });
971
972 so = ss.taboption('ipv6', form.Flag, 'dns_service', _('Local IPv6 DNS server'),
973 _('Announce this device as IPv6 DNS server.'));
974 so.default = so.enabled;
975 so.depends({ ra: 'server', dns: /^$/ });
976 so.depends({ ra: 'hybrid', dns: /^$/, master: '0' });
977 so.depends({ dhcpv6: 'server', dns: /^$/ });
978 so.depends({ dhcpv6: 'hybrid', dns: /^$/, master: '0' });
979
980 so = ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains'),
981 _('Specifies a fixed list of DNS search domains to announce via DHCPv6. If left unspecified, the local device DNS search domain will be announced.'));
982 so.datatype = 'hostname';
983 so.depends('ra', 'server');
984 so.depends({ ra: 'hybrid', master: '0' });
985 so.depends('dhcpv6', 'server');
986 so.depends({ dhcpv6: 'hybrid', master: '0' });
987
988
989 so = ss.taboption('ipv6', cbiRichListValue, 'ndp', _('<abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy'),
990 _('Configures the operation mode of the NDP proxy service on this interface.'));
991 so.value('', _('disabled'),
992 _('Do not proxy any <abbr title="Neighbour Discovery Protocol">NDP</abbr> packets.'));
993 so.value('relay', _('relay mode'),
994 _('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.'));
995 so.value('hybrid', _('hybrid mode'), ' ');
996
997
998 so = ss.taboption('ipv6', form.Flag, 'ndproxy_routing', _('Learn routes'), _('Set up routes for proxied IPv6 neighbours.'));
999 so.default = so.enabled;
1000 so.depends('ndp', 'relay');
1001 so.depends('ndp', 'hybrid');
1002
1003 so = ss.taboption('ipv6', form.Flag, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
1004 so.depends({ ndp: 'relay', master: '0' });
1005 so.depends({ ndp: 'hybrid', master: '0' });
1006
1007 so = ss.taboption('ipv6', form.Value, 'preferred_lifetime', _('IPv6 Prefix Lifetime'), _('Preferred lifetime for a prefix.'));
1008 so.optional = true;
1009 so.placeholder = '12h';
1010 so.value('5m', _('5m (5 minutes)'));
1011 so.value('3h', _('3h (3 hours)'));
1012 so.value('12h', _('12h (12 hours - default)'));
1013 so.value('7d', _('7d (7 days)'));
1014
1015 //This is a ra_* setting, but its placement is more logical/findable under IPv6 settings.
1016 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.'));
1017 so.optional = true;
1018 }
1019
1020 ifc.renderFormOptions(s);
1021
1022 // Common interface options
1023 o = nettools.replaceOption(s, 'advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
1024 o.default = o.enabled;
1025
1026 if (has_peerdns(protoval)) {
1027 o = nettools.replaceOption(s, 'advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
1028 o.default = o.enabled;
1029 }
1030
1031 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
1032 if (has_peerdns(protoval))
1033 o.depends('peerdns', '0');
1034 o.datatype = 'ipaddr';
1035
1036 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns_search', _('DNS search domains'));
1037 if (protoval != 'static')
1038 o.depends('peerdns', '0');
1039 o.datatype = 'hostname';
1040
1041 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'));
1042 o.datatype = 'uinteger';
1043 o.placeholder = '0';
1044
1045 o = nettools.replaceOption(s, 'advanced', form.Value, 'metric', _('Use gateway metric'));
1046 o.datatype = 'uinteger';
1047 o.placeholder = '0';
1048
1049 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip4table', _('Override IPv4 routing table'));
1050 o.datatype = 'or(uinteger, string)';
1051 for (var i = 0; i < rtTables.length; i++)
1052 o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
1053
1054 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6table', _('Override IPv6 routing table'));
1055 o.datatype = 'or(uinteger, string)';
1056 for (var i = 0; i < rtTables.length; i++)
1057 o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
1058
1059 if (has_sourcefilter(protoval)) {
1060 o = nettools.replaceOption(s, 'advanced', form.Flag, 'sourcefilter', _('IPv6 source routing'), _('Automatically handle multiple uplink interfaces using source-based policy routing.'));
1061 o.default = o.enabled;
1062 }
1063
1064 o = nettools.replaceOption(s, 'advanced', form.Flag, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
1065 o.default = o.enabled;
1066
1067 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'));
1068 o.value('', _('disabled'));
1069 o.value('64');
1070 o.datatype = 'max(128)';
1071
1072 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6hint', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
1073 o.placeholder = '0';
1074 o.validate = function(section_id, value) {
1075 if (value == null || value == '')
1076 return true;
1077
1078 var n = parseInt(value, 16);
1079
1080 if (!/^(0x)?[0-9a-fA-F]+$/.test(value) || isNaN(n) || n >= 0xffffffff)
1081 return _('Expecting a hexadecimal assignment hint');
1082
1083 return true;
1084 };
1085 for (var i = 33; i <= 64; i++)
1086 o.depends('ip6assign', String(i));
1087
1088
1089 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'ip6class', _('IPv6 prefix filter'), _('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));
1090 o.value('local', 'local (%s)'.format(_('Local ULA')));
1091
1092 var prefixClasses = {};
1093
1094 this.networks.forEach(function(net) {
1095 var prefixes = net._ubus('ipv6-prefix');
1096 if (Array.isArray(prefixes)) {
1097 prefixes.forEach(function(pfx) {
1098 if (L.isObject(pfx) && typeof(pfx['class']) == 'string') {
1099 prefixClasses[pfx['class']] = prefixClasses[pfx['class']] || {};
1100 prefixClasses[pfx['class']][net.getName()] = true;
1101 }
1102 });
1103 }
1104 });
1105
1106 Object.keys(prefixClasses).sort().forEach(function(c) {
1107 var networks = Object.keys(prefixClasses[c]).sort().join(', ');
1108 o.value(c, (c != networks) ? '%s (%s)'.format(c, networks) : c);
1109 });
1110
1111
1112 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."));
1113 o.datatype = 'ip6hostid';
1114 o.placeholder = '::1';
1115
1116 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.'));
1117 o.datatype = 'uinteger';
1118 o.placeholder = '0';
1119
1120 for (var i = 0; i < s.children.length; i++) {
1121 o = s.children[i];
1122
1123 switch (o.option) {
1124 case 'proto':
1125 case 'auto':
1126 case '_dhcp':
1127 case '_zone':
1128 case '_switch_proto':
1129 case '_ifacestat_modal':
1130 continue;
1131
1132 case 'igmp_snooping':
1133 case 'stp':
1134 case 'type':
1135 case '_net_device':
1136 var deps = [];
1137 for (var j = 0; j < protocols.length; j++) {
1138 if (!protocols[j].isVirtual()) {
1139 if (o.deps.length)
1140 for (var k = 0; k < o.deps.length; k++)
1141 deps.push(Object.assign({ proto: protocols[j].getProtocol() }, o.deps[k]));
1142 else
1143 deps.push({ proto: protocols[j].getProtocol() });
1144 }
1145 }
1146 o.deps = deps;
1147 break;
1148
1149 default:
1150 if (o.deps.length)
1151 for (var j = 0; j < o.deps.length; j++)
1152 o.deps[j].proto = protoval;
1153 else
1154 o.depends('proto', protoval);
1155 }
1156 }
1157
1158 this.activeSection = s.section;
1159 }, this));
1160 };
1161
1162 s.handleModalCancel = function(/* ... */) {
1163 var type = uci.get('network', this.activeSection || this.addedSection, 'type'),
1164 device = (type == 'bridge') ? 'br-%s'.format(this.activeSection || this.addedSection) : null;
1165
1166 uci.sections('network', 'bridge-vlan', function(bvs) {
1167 if (device != null && bvs.device == device)
1168 uci.remove('network', bvs['.name']);
1169 });
1170
1171 return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
1172 };
1173
1174 s.handleAdd = function(ev) {
1175 var m2 = new form.Map('network'),
1176 s2 = m2.section(form.NamedSection, '_new_'),
1177 protocols = network.getProtocols(),
1178 proto, name, device;
1179
1180 protocols.sort(function(a, b) {
1181 return L.naturalCompare(a.getProtocol(), b.getProtocol());
1182 });
1183
1184 s2.render = function() {
1185 return Promise.all([
1186 {},
1187 this.renderUCISection('_new_')
1188 ]).then(this.renderContents.bind(this));
1189 };
1190
1191 name = s2.option(form.Value, 'name', _('Name'));
1192 name.rmempty = false;
1193 name.datatype = 'uciname';
1194 name.placeholder = _('New interface name…');
1195 name.validate = function(section_id, value) {
1196 if (uci.get('network', value) != null)
1197 return _('The interface name is already used');
1198
1199 var pr = network.getProtocol(proto.formvalue(section_id), value),
1200 ifname = pr.isVirtual() ? '%s-%s'.format(pr.getProtocol(), value) : 'br-%s'.format(value);
1201
1202 if (value.length > 15)
1203 return _('The interface name is too long');
1204
1205 return true;
1206 };
1207
1208 proto = s2.option(form.ListValue, 'proto', _('Protocol'));
1209 proto.validate = name.validate;
1210
1211 device = s2.option(widgets.DeviceSelect, 'device', _('Device'));
1212 device.noaliases = false;
1213 device.optional = false;
1214
1215 for (var i = 0; i < protocols.length; i++) {
1216 proto.value(protocols[i].getProtocol(), protocols[i].getI18n());
1217
1218 if (!protocols[i].isVirtual())
1219 device.depends('proto', protocols[i].getProtocol());
1220 }
1221
1222 m2.render().then(L.bind(function(nodes) {
1223 ui.showModal(_('Add new interface...'), [
1224 nodes,
1225 E('div', { 'class': 'right' }, [
1226 E('button', {
1227 'class': 'btn',
1228 'click': ui.hideModal
1229 }, _('Cancel')), ' ',
1230 E('button', {
1231 'class': 'cbi-button cbi-button-positive important',
1232 'click': ui.createHandlerFn(this, function(ev) {
1233 var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null,
1234 protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null,
1235 protoclass = protoval ? network.getProtocol(protoval, nameval) : null;
1236
1237 if (nameval == null || protoval == null || nameval == '' || protoval == '')
1238 return;
1239
1240 return protoclass.isCreateable(nameval).then(function(checkval) {
1241 if (checkval != null) {
1242 ui.addNotification(null,
1243 E('p', _('New interface for "%s" can not be created: %s').format(protoclass.getI18n(), checkval)));
1244 ui.hideModal();
1245 return;
1246 }
1247
1248 return m.save(function() {
1249 var section_id = uci.add('network', 'interface', nameval);
1250
1251 protoclass.set('proto', protoval);
1252 protoclass.addDevice(device.formvalue('_new_'));
1253
1254 m.children[0].addedSection = section_id;
1255
1256 ui.hideModal();
1257 ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ]));
1258 }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
1259 });
1260 })
1261 }, _('Create interface'))
1262 ])
1263 ], 'cbi-modal');
1264
1265 nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
1266 }, this));
1267 };
1268
1269 s.handleRemove = function(section_id, ev) {
1270 return network.deleteNetwork(section_id).then(L.bind(function(section_id, ev) {
1271 return form.GridSection.prototype.handleRemove.apply(this, [section_id, ev]);
1272 }, this, section_id, ev));
1273 };
1274
1275 o = s.option(form.DummyValue, '_ifacebox');
1276 o.modalonly = false;
1277 o.textvalue = function(section_id) {
1278 var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0],
1279 zone = net ? this.section.zones.filter(function(z) { return !!z.getNetworks().filter(function(n) { return n == section_id })[0] })[0] : null;
1280
1281 if (!net)
1282 return;
1283
1284 var node = E('div', { 'class': 'ifacebox' }, [
1285 E('div', {
1286 'class': 'ifacebox-head',
1287 'style': firewall.getZoneColorStyle(zone),
1288 'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned')
1289 }, E('strong', net.getName())),
1290 E('div', {
1291 'class': 'ifacebox-body',
1292 'id': '%s-ifc-devices'.format(section_id),
1293 'data-network': section_id
1294 }, [
1295 E('img', {
1296 'src': L.resource('icons/ethernet_disabled.png'),
1297 'style': 'width:16px; height:16px'
1298 }),
1299 E('br'), E('small', '?')
1300 ])
1301 ]);
1302
1303 render_ifacebox_status(node.childNodes[1], net);
1304
1305 return node;
1306 };
1307
1308 o = s.option(form.DummyValue, '_ifacestat');
1309 o.modalonly = false;
1310 o.textvalue = function(section_id) {
1311 var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0];
1312
1313 if (!net)
1314 return;
1315
1316 var node = E('div', { 'id': '%s-ifc-description'.format(section_id) });
1317
1318 render_status(node, net, false);
1319
1320 return node;
1321 };
1322
1323 o = s.taboption('advanced', form.Flag, 'delegate', _('Use builtin IPv6-management'));
1324 o.modalonly = true;
1325 o.default = o.enabled;
1326
1327 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).'));
1328 o.modalonly = true;
1329 o.defaults = {
1330 '1': [{ proto: 'static' }],
1331 '0': []
1332 };
1333
1334
1335 // Device configuration
1336 s = m.section(form.GridSection, 'device', _('Devices'));
1337 s.addremove = true;
1338 s.anonymous = true;
1339 s.addbtntitle = _('Add device configuration…');
1340
1341 s.cfgsections = function() {
1342 var sections = uci.sections('network', 'device'),
1343 section_ids = sections.sort(function(a, b) { return L.naturalCompare(a.name, b.name) }).map(function(s) { return s['.name'] });
1344
1345 for (var i = 0; i < netDevs.length; i++) {
1346 if (sections.filter(function(s) { return s.name == netDevs[i].getName() }).length)
1347 continue;
1348
1349 if (netDevs[i].getType() == 'wifi' && !netDevs[i].isUp())
1350 continue;
1351
1352 /* Unless http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html is implemented,
1353 we cannot properly redefine bridges as devices, so filter them away for now... */
1354
1355 var m = netDevs[i].isBridge() ? netDevs[i].getName().match(/^br-([A-Za-z0-9_]+)$/) : null,
1356 s = m ? uci.get('network', m[1]) : null;
1357
1358 if (s && s['.type'] == 'interface' && s.type == 'bridge')
1359 continue;
1360
1361 section_ids.push('dev:%s'.format(netDevs[i].getName()));
1362 }
1363
1364 return section_ids;
1365 };
1366
1367 s.renderMoreOptionsModal = function(section_id, ev) {
1368 var m = section_id.match(/^dev:(.+)$/);
1369
1370 if (m) {
1371 var devtype = getDevType(section_id);
1372
1373 section_id = uci.add('network', 'device');
1374
1375 uci.set('network', section_id, 'name', m[1]);
1376 uci.set('network', section_id, 'type', (devtype != 'ethernet') ? devtype : null);
1377
1378 this.addedSection = section_id;
1379 }
1380
1381 return this.super('renderMoreOptionsModal', [section_id, ev]);
1382 };
1383
1384 s.renderRowActions = function(section_id) {
1385 var trEl = this.super('renderRowActions', [ section_id, _('Configure…') ]),
1386 deleteBtn = trEl.querySelector('button:last-child');
1387
1388 deleteBtn.firstChild.data = _('Unconfigure');
1389 deleteBtn.setAttribute('title', _('Remove related device settings from the configuration'));
1390 deleteBtn.disabled = section_id.match(/^dev:/) ? true : null;
1391
1392 return trEl;
1393 };
1394
1395 s.modaltitle = function(section_id) {
1396 var m = section_id.match(/^dev:(.+)$/),
1397 name = m ? m[1] : uci.get('network', section_id, 'name');
1398
1399 return name ? '%s: %q'.format(getDevTypeDesc(section_id), name) : _('Add device configuration');
1400 };
1401
1402 s.addModalOptions = function(s) {
1403 var isNew = (uci.get('network', s.section, 'name') == null),
1404 dev = getDevice(s.section);
1405
1406 nettools.addDeviceOptions(s, dev, isNew);
1407 };
1408
1409 s.handleModalCancel = function(map /*, ... */) {
1410 var name = uci.get('network', this.addedSection, 'name')
1411
1412 uci.sections('network', 'bridge-vlan', function(bvs) {
1413 if (name != null && bvs.device == name)
1414 uci.remove('network', bvs['.name']);
1415 });
1416
1417 if (map.addedVLANs)
1418 for (var i = 0; i < map.addedVLANs.length; i++)
1419 uci.remove('network', map.addedVLANs[i]);
1420
1421 if (this.addedSection)
1422 uci.remove('network', this.addedSection);
1423
1424 return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
1425 };
1426
1427 s.handleRemove = function(section_id /*, ... */) {
1428 var name = uci.get('network', section_id, 'name'),
1429 type = uci.get('network', section_id, 'type');
1430
1431 if (name != null && type == 'bridge') {
1432 uci.sections('network', 'bridge-vlan', function(bvs) {
1433 if (bvs.device == name)
1434 uci.remove('network', bvs['.name']);
1435 });
1436 }
1437
1438 return form.GridSection.prototype.handleRemove.apply(this, arguments);
1439 };
1440
1441 function getDevice(section_id) {
1442 var m = section_id.match(/^dev:(.+)$/),
1443 name = m ? m[1] : uci.get('network', section_id, 'name');
1444
1445 return netDevs.filter(function(d) { return d.getName() == name })[0];
1446 }
1447
1448 function getDevType(section_id) {
1449 var dev = getDevice(section_id),
1450 cfg = uci.get('network', section_id),
1451 type = cfg ? (uci.get('network', section_id, 'type') || 'ethernet') : (dev ? dev.getType() : '');
1452
1453 switch (type) {
1454 case '':
1455 return null;
1456
1457 case 'vlan':
1458 case '8021q':
1459 return '8021q';
1460
1461 case '8021ad':
1462 return '8021ad';
1463
1464 case 'bridge':
1465 return 'bridge';
1466
1467 case 'tunnel':
1468 return 'tunnel';
1469
1470 case 'macvlan':
1471 return 'macvlan';
1472
1473 case 'veth':
1474 return 'veth';
1475
1476 case 'wifi':
1477 case 'alias':
1478 case 'switch':
1479 case 'ethernet':
1480 default:
1481 return 'ethernet';
1482 }
1483 }
1484
1485 function getDevTypeDesc(section_id) {
1486 switch (getDevType(section_id) || '') {
1487 case '':
1488 return E('em', [ _('Device not present') ]);
1489
1490 case '8021q':
1491 return _('VLAN (802.1q)');
1492
1493 case '8021ad':
1494 return _('VLAN (802.1ad)');
1495
1496 case 'bridge':
1497 return _('Bridge device');
1498
1499 case 'tunnel':
1500 return _('Tunnel device');
1501
1502 case 'macvlan':
1503 return _('MAC VLAN');
1504
1505 case 'veth':
1506 return _('Virtual Ethernet');
1507
1508 default:
1509 return _('Network device');
1510 }
1511 }
1512
1513 o = s.option(form.DummyValue, 'name', _('Device'));
1514 o.modalonly = false;
1515 o.textvalue = function(section_id) {
1516 var dev = getDevice(section_id),
1517 ext = section_id.match(/^dev:/),
1518 icon = render_iface(dev);
1519
1520 if (ext)
1521 icon.querySelector('img').style.opacity = '.5';
1522
1523 return E('span', { 'class': 'ifacebadge' }, [
1524 icon,
1525 E('span', { 'style': ext ? 'opacity:.5' : null }, [
1526 dev ? dev.getName() : (uci.get('network', section_id, 'name') || '?')
1527 ])
1528 ]);
1529 };
1530
1531 o = s.option(form.DummyValue, 'type', _('Type'));
1532 o.textvalue = getDevTypeDesc;
1533 o.modalonly = false;
1534
1535 o = s.option(form.DummyValue, 'macaddr', _('MAC Address'));
1536 o.modalonly = false;
1537 o.textvalue = function(section_id) {
1538 var dev = getDevice(section_id),
1539 val = uci.get('network', section_id, 'macaddr'),
1540 mac = dev ? dev.getMAC() : null;
1541
1542 return val ? E('strong', {
1543 'data-tooltip': _('The value is overridden by configuration.')
1544 }, [ val.toUpperCase() ]) : (mac || '-');
1545 };
1546
1547 o = s.option(form.DummyValue, 'mtu', _('MTU'));
1548 o.modalonly = false;
1549 o.textvalue = function(section_id) {
1550 var dev = getDevice(section_id),
1551 val = uci.get('network', section_id, 'mtu'),
1552 mtu = dev ? dev.getMTU() : null;
1553
1554 return val ? E('strong', {
1555 'data-tooltip': _('The value is overridden by configuration.')
1556 }, [ val ]) : (mtu || '-').toString();
1557 };
1558
1559 s = m.section(form.TypedSection, 'globals', _('Global network options'));
1560 s.addremove = false;
1561 s.anonymous = true;
1562
1563 o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'), _('Unique Local Address - in the range <code>fc00::/7</code>. Typically only within the &#8216;local&#8217; half <code>fd00::/8</code>. ULA for IPv6 is analogous to IPv4 private network addressing. This prefix is randomly generated at first install.'));
1564 o.datatype = 'cidr6';
1565
1566 o = s.option(form.Flag, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
1567 o.optional = true;
1568
1569
1570 if (dslModemType != null) {
1571 s = m.section(form.TypedSection, 'dsl', _('DSL'));
1572 s.anonymous = true;
1573
1574 o = s.option(form.ListValue, 'annex', _('Annex'));
1575 if (dslModemType == 'vdsl') {
1576 o.value('a', _('ADSL (all variants) Annex A/L/M + VDSL2 Annex A/B/C'));
1577 o.value('b', _('ADSL (all variants) Annex B + VDSL2 Annex A/B/C'));
1578 o.value('j', _('ADSL (all variants) Annex B/J + VDSL2 Annex A/B/C'));
1579 } else {
1580 o.value('a', _('ADSL (all variants) Annex A/L/M'));
1581 o.value('b', _('ADSL (all variants) Annex B'));
1582 o.value('j', _('ADSL (all variants) Annex B/J'));
1583 }
1584 o.value('m', _('ADSL (all variants) Annex M'));
1585 o.value('at1', _('ANSI T1.413'));
1586 o.value('admt', _('ADSL (G.992.1) Annex A'));
1587 o.value('bdmt', _('ADSL (G.992.1) Annex B'));
1588 o.value('alite', _('Splitterless ADSL (G.992.2) Annex A'));
1589 o.value('a2', _('ADSL2 (G.992.3) Annex A'));
1590 o.value('b2', _('ADSL2 (G.992.3) Annex B'));
1591 o.value('l', _('ADSL2 (G.992.3) Annex L'));
1592 o.value('m2', _('ADSL2 (G.992.3) Annex M'));
1593 o.value('a2p', _('ADSL2+ (G.992.5) Annex A'));
1594 o.value('b2p', _('ADSL2+ (G.992.5) Annex B'));
1595 o.value('m2p', _('ADSL2+ (G.992.5) Annex M'));
1596
1597 o = s.option(form.ListValue, 'tone', _('Tone'));
1598 o.value('', _('auto'));
1599 o.value('a', _('A43C + J43 + A43'));
1600 o.value('av', _('A43C + J43 + A43 + V43'));
1601 o.value('b', _('B43 + B43C'));
1602 o.value('bv', _('B43 + B43C + V43'));
1603
1604 if (dslModemType == 'vdsl') {
1605 o = s.option(form.ListValue, 'xfer_mode', _('Encapsulation mode'));
1606 o.value('', _('auto'));
1607 o.value('atm', _('ATM (Asynchronous Transfer Mode)'));
1608 o.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
1609
1610 o = s.option(form.ListValue, 'line_mode', _('DSL line mode'));
1611 o.value('', _('auto'));
1612 o.value('adsl', _('ADSL'));
1613 o.value('vdsl', _('VDSL'));
1614
1615 o = s.option(form.ListValue, 'ds_snr_offset', _('Downstream SNR offset'));
1616 o.default = '0';
1617
1618 for (var i = -100; i <= 100; i += 5)
1619 o.value(i, _('%.1f dB').format(i / 10));
1620 }
1621
1622 s.option(form.Value, 'firmware', _('Firmware File'));
1623 }
1624
1625
1626 // Show ATM bridge section if we have the capabilities
1627 if (L.hasSystemFeature('br2684ctl')) {
1628 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.'));
1629
1630 s.addremove = true;
1631 s.anonymous = true;
1632 s.addbtntitle = _('Add ATM Bridge');
1633
1634 s.handleAdd = function(ev) {
1635 var sections = uci.sections('network', 'atm-bridge'),
1636 max_unit = -1;
1637
1638 for (var i = 0; i < sections.length; i++) {
1639 var unit = +sections[i].unit;
1640
1641 if (!isNaN(unit) && unit > max_unit)
1642 max_unit = unit;
1643 }
1644
1645 return this.map.save(function() {
1646 var sid = uci.add('network', 'atm-bridge');
1647
1648 uci.set('network', sid, 'unit', max_unit + 1);
1649 uci.set('network', sid, 'atmdev', 0);
1650 uci.set('network', sid, 'encaps', 'llc');
1651 uci.set('network', sid, 'payload', 'bridged');
1652 uci.set('network', sid, 'vci', 35);
1653 uci.set('network', sid, 'vpi', 8);
1654 });
1655 };
1656
1657 s.tab('general', _('General Setup'));
1658 s.tab('advanced', _('Advanced Settings'));
1659
1660 o = s.taboption('general', form.Value, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
1661 s.taboption('general', form.Value, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
1662
1663 o = s.taboption('general', form.ListValue, 'encaps', _('Encapsulation mode'));
1664 o.value('llc', _('LLC'));
1665 o.value('vc', _('VC-Mux'));
1666
1667 s.taboption('advanced', form.Value, 'atmdev', _('ATM device number'));
1668 s.taboption('advanced', form.Value, 'unit', _('Bridge unit number'));
1669
1670 o = s.taboption('advanced', form.ListValue, 'payload', _('Forwarding mode'));
1671 o.value('bridged', _('bridged'));
1672 o.value('routed', _('routed'));
1673 }
1674
1675
1676 return m.render().then(L.bind(function(m, nodes) {
1677 poll.add(L.bind(function() {
1678 var section_ids = m.children[0].cfgsections(),
1679 tasks = [];
1680
1681 for (var i = 0; i < section_ids.length; i++) {
1682 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1683 dsc = row.querySelector('[data-name="_ifacestat"] > div'),
1684 btn1 = row.querySelector('.cbi-section-actions .reconnect'),
1685 btn2 = row.querySelector('.cbi-section-actions .down');
1686
1687 if (dsc.getAttribute('reconnect') == '') {
1688 dsc.setAttribute('reconnect', '1');
1689 tasks.push(fs.exec('/sbin/ifup', [section_ids[i]]).catch(function(e) {
1690 ui.addNotification(null, E('p', e.message));
1691 }));
1692 }
1693 else if (dsc.getAttribute('disconnect') == '') {
1694 dsc.setAttribute('disconnect', '1');
1695 tasks.push(fs.exec('/sbin/ifdown', [section_ids[i]]).catch(function(e) {
1696 ui.addNotification(null, E('p', e.message));
1697 }));
1698 }
1699 else if (dsc.getAttribute('reconnect') == '1') {
1700 dsc.removeAttribute('reconnect');
1701 btn1.classList.remove('spinning');
1702 btn1.disabled = false;
1703 }
1704 else if (dsc.getAttribute('disconnect') == '1') {
1705 dsc.removeAttribute('disconnect');
1706 btn2.classList.remove('spinning');
1707 btn2.disabled = false;
1708 }
1709 }
1710
1711 return Promise.all(tasks)
1712 .then(L.bind(network.getNetworks, network))
1713 .then(L.bind(this.poll_status, this, nodes));
1714 }, this), 5);
1715
1716 return nodes;
1717 }, this, m));
1718 }
1719 });