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