luci-mod-network: Introduce new RA and NDP params with help-text.
[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 = ifc.getDevices(),
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 return view.extend({
232 poll_status: function(map, networks) {
233 var resolveZone = null;
234
235 for (var i = 0; i < networks.length; i++) {
236 var ifc = networks[i],
237 row = map.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc.getName()));
238
239 if (row == null)
240 continue;
241
242 var dsc = row.querySelector('[data-name="_ifacestat"] > div'),
243 box = row.querySelector('[data-name="_ifacebox"] .ifacebox-body'),
244 btn1 = row.querySelector('.cbi-section-actions .reconnect'),
245 btn2 = row.querySelector('.cbi-section-actions .down'),
246 stat = document.querySelector('[id="%s-ifc-status"]'.format(ifc.getName())),
247 resolveZone = render_ifacebox_status(box, ifc),
248 disabled = ifc ? !ifc.isUp() : true,
249 dynamic = ifc ? ifc.isDynamic() : false;
250
251 if (dsc.hasAttribute('reconnect')) {
252 dom.content(dsc, E('em', _('Interface is starting...')));
253 }
254 else if (dsc.hasAttribute('disconnect')) {
255 dom.content(dsc, E('em', _('Interface is stopping...')));
256 }
257 else if (ifc.getProtocol() || uci.get('network', ifc.getName()) == null) {
258 render_status(dsc, ifc, false);
259 }
260 else if (!ifc.getProtocol()) {
261 var e = map.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc.getName()));
262 if (e) e.disabled = true;
263
264 var link = L.url('admin/system/opkg') + '?query=luci-proto';
265 dom.content(dsc, [
266 E('em', _('Unsupported protocol type.')), E('br'),
267 E('a', { href: link }, _('Install protocol extensions...'))
268 ]);
269 }
270 else {
271 dom.content(dsc, E('em', _('Interface not present or not connected yet.')));
272 }
273
274 if (stat) {
275 var dev = ifc.getDevice();
276 dom.content(stat, [
277 E('img', {
278 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
279 'title': dev ? dev.getTypeI18n() : _('Not present')
280 }),
281 render_status(E('span'), ifc, true)
282 ]);
283 }
284
285 btn1.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic;
286 btn2.disabled = isReadonlyView || btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic || disabled;
287 }
288
289 return Promise.all([ resolveZone, network.flushCache() ]);
290 },
291
292 load: function() {
293 return Promise.all([
294 network.getDSLModemType(),
295 network.getDevices(),
296 fs.lines('/etc/iproute2/rt_tables'),
297 uci.changes()
298 ]);
299 },
300
301 render: function(data) {
302 var dslModemType = data[0],
303 netDevs = data[1],
304 m, s, o;
305
306 var rtTables = data[2].map(function(l) {
307 var m = l.trim().match(/^(\d+)\s+(\S+)$/);
308 return m ? [ +m[1], m[2] ] : null;
309 }).filter(function(e) {
310 return e && e[0] > 0;
311 });
312
313 m = new form.Map('network');
314 m.tabbed = true;
315 m.chain('dhcp');
316
317 s = m.section(form.GridSection, 'interface', _('Interfaces'));
318 s.anonymous = true;
319 s.addremove = true;
320 s.addbtntitle = _('Add new interface...');
321
322 s.load = function() {
323 return Promise.all([
324 network.getNetworks(),
325 firewall.getZones()
326 ]).then(L.bind(function(data) {
327 this.networks = data[0];
328 this.zones = data[1];
329 }, this));
330 };
331
332 s.tab('general', _('General Settings'));
333 s.tab('advanced', _('Advanced Settings'));
334 s.tab('physical', _('Physical Settings'));
335 s.tab('brport', _('Bridge port specific options'));
336 s.tab('bridgevlan', _('Bridge VLAN filtering'));
337 s.tab('firewall', _('Firewall Settings'));
338 s.tab('dhcp', _('DHCP Server'));
339
340 s.cfgsections = function() {
341 return this.networks.map(function(n) { return n.getName() })
342 .filter(function(n) { return n != 'loopback' });
343 };
344
345 s.modaltitle = function(section_id) {
346 return _('Interfaces') + ' » ' + section_id.toUpperCase();
347 };
348
349 s.renderRowActions = function(section_id) {
350 var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]),
351 net = this.networks.filter(function(n) { return n.getName() == section_id })[0],
352 disabled = net ? !net.isUp() : true,
353 dynamic = net ? net.isDynamic() : false;
354
355 dom.content(tdEl.lastChild, [
356 E('button', {
357 'class': 'cbi-button cbi-button-neutral reconnect',
358 'click': iface_updown.bind(this, true, section_id),
359 'title': _('Reconnect this interface'),
360 'disabled': dynamic ? 'disabled' : null
361 }, _('Restart')),
362 E('button', {
363 'class': 'cbi-button cbi-button-neutral down',
364 'click': iface_updown.bind(this, false, section_id),
365 'title': _('Shutdown this interface'),
366 'disabled': (dynamic || disabled) ? 'disabled' : null
367 }, _('Stop')),
368 tdEl.lastChild.firstChild,
369 tdEl.lastChild.lastChild
370 ]);
371
372 if (!dynamic && net && !uci.get('network', net.getName())) {
373 tdEl.lastChild.childNodes[0].disabled = true;
374 tdEl.lastChild.childNodes[2].disabled = true;
375 tdEl.lastChild.childNodes[3].disabled = true;
376 }
377
378 return tdEl;
379 };
380
381 s.addModalOptions = function(s) {
382 var protoval = uci.get('network', s.section, 'proto'),
383 protoclass = protoval ? network.getProtocol(protoval) : null,
384 o, ifname_single, ifname_multi, proto_select, proto_switch, type, stp, igmp, ss, so;
385
386 if (!protoval)
387 return;
388
389 return network.getNetwork(s.section).then(L.bind(function(ifc) {
390 var protocols = network.getProtocols();
391
392 protocols.sort(function(a, b) {
393 return a.getProtocol() > b.getProtocol();
394 });
395
396 o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status'));
397 o.modalonly = true;
398 o.cfgvalue = L.bind(function(section_id) {
399 var net = this.networks.filter(function(n) { return n.getName() == section_id })[0];
400
401 return render_modal_status(E('div', {
402 'id': '%s-ifc-status'.format(section_id),
403 'class': 'ifacebadge large'
404 }), net);
405 }, this);
406 o.write = function() {};
407
408 proto_select = s.taboption('general', form.ListValue, 'proto', _('Protocol'));
409 proto_select.modalonly = true;
410
411 proto_switch = s.taboption('general', form.Button, '_switch_proto');
412 proto_switch.modalonly = true;
413 proto_switch.title = _('Really switch protocol?');
414 proto_switch.inputtitle = _('Switch protocol');
415 proto_switch.inputstyle = 'apply';
416 proto_switch.onclick = L.bind(function(ev) {
417 s.map.save()
418 .then(L.bind(m.load, m))
419 .then(L.bind(m.render, m))
420 .then(L.bind(this.renderMoreOptionsModal, this, s.section));
421 }, this);
422
423 o = s.taboption('general', form.Flag, 'auto', _('Bring up on boot'));
424 o.modalonly = true;
425 o.default = o.enabled;
426
427 if (L.hasSystemFeature('firewall')) {
428 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.'));
429 o.network = ifc.getName();
430 o.optional = true;
431
432 o.cfgvalue = function(section_id) {
433 return firewall.getZoneByNetwork(ifc.getName()).then(function(zone) {
434 return (zone != null ? zone.getName() : null);
435 });
436 };
437
438 o.write = o.remove = function(section_id, value) {
439 return Promise.all([
440 firewall.getZoneByNetwork(ifc.getName()),
441 (value != null) ? firewall.getZone(value) : null
442 ]).then(function(data) {
443 var old_zone = data[0],
444 new_zone = data[1];
445
446 if (old_zone == null && new_zone == null && (value == null || value == ''))
447 return;
448
449 if (old_zone != null && new_zone != null && old_zone.getName() == new_zone.getName())
450 return;
451
452 if (old_zone != null)
453 old_zone.deleteNetwork(ifc.getName());
454
455 if (new_zone != null)
456 new_zone.addNetwork(ifc.getName());
457 else if (value != null)
458 return firewall.addZone(value).then(function(new_zone) {
459 new_zone.addNetwork(ifc.getName());
460 });
461 });
462 };
463 }
464
465 for (var i = 0; i < protocols.length; i++) {
466 proto_select.value(protocols[i].getProtocol(), protocols[i].getI18n());
467
468 if (protocols[i].getProtocol() != uci.get('network', s.section, 'proto'))
469 proto_switch.depends('proto', protocols[i].getProtocol());
470 }
471
472 if (L.hasSystemFeature('dnsmasq') || L.hasSystemFeature('odhcpd')) {
473 o = s.taboption('dhcp', form.SectionValue, '_dhcp', form.TypedSection, 'dhcp');
474 o.depends('proto', 'static');
475
476 ss = o.subsection;
477 ss.uciconfig = 'dhcp';
478 ss.addremove = false;
479 ss.anonymous = true;
480
481 ss.tab('general', _('General Setup'));
482 ss.tab('advanced', _('Advanced Settings'));
483 ss.tab('ipv6', _('IPv6 Settings'));
484
485 ss.filter = function(section_id) {
486 return (uci.get('dhcp', section_id, 'interface') == ifc.getName());
487 };
488
489 ss.renderSectionPlaceholder = function() {
490 return E('div', { 'class': 'cbi-section-create' }, [
491 E('p', _('No DHCP Server configured for this interface') + ' &#160; '),
492 E('button', {
493 'class': 'cbi-button cbi-button-add',
494 'title': _('Setup DHCP Server'),
495 'click': ui.createHandlerFn(this, function(section_id, ev) {
496 this.map.save(function() {
497 uci.add('dhcp', 'dhcp', section_id);
498 uci.set('dhcp', section_id, 'interface', section_id);
499 uci.set('dhcp', section_id, 'start', 100);
500 uci.set('dhcp', section_id, 'limit', 150);
501 uci.set('dhcp', section_id, 'leasetime', '12h');
502 });
503 }, ifc.getName())
504 }, _('Setup DHCP Server'))
505 ]);
506 };
507
508 ss.taboption('general', form.Flag, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
509
510 so = ss.taboption('general', form.Value, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
511 so.optional = true;
512 so.datatype = 'or(uinteger,ip4addr("nomask"))';
513 so.default = '100';
514
515 so = ss.taboption('general', form.Value, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
516 so.optional = true;
517 so.datatype = 'uinteger';
518 so.default = '150';
519
520 so = ss.taboption('general', form.Value, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
521 so.optional = true;
522 so.default = '12h';
523
524 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.'));
525 so.default = so.enabled;
526
527 ss.taboption('advanced', form.Flag, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
528
529 // XXX: is this actually useful?
530 //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
531
532 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.'));
533 so.optional = true;
534 so.datatype = 'ip4addr';
535
536 so.render = function(option_index, section_id, in_table) {
537 this.placeholder = get_netmask(s, true);
538 return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
539 };
540
541 so.validate = function(section_id, value) {
542 var uielem = this.getUIElement(section_id);
543 if (uielem)
544 uielem.setPlaceholder(get_netmask(s, false));
545 return form.Value.prototype.validate.apply(this, [ section_id, value ]);
546 };
547
548 ss.taboption('advanced', form.DynamicList, 'dhcp_option', _('DHCP-Options'), _('Define additional DHCP options, \
549 for example "<code>6,192.168.2.1,192.168.2.2</code>" which advertises different DNS servers to clients.'));
550
551 for (var i = 0; i < ss.children.length; i++)
552 if (ss.children[i].option != 'ignore')
553 ss.children[i].depends('ignore', '0');
554
555 so = ss.taboption('ipv6', form.ListValue, 'ra', _('<abbr title="Router Advertisement">RA</abbr>-Service'), _('<ul style="list-style-type:none;">\
556 <li><strong>server mode</strong>: Router advertises itself as the default IPv6 gateway \
557 via <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages \
558 (to <code>ff02::1</code>) and provides <abbr title="Prefix Delegation">PD</abbr> to downstream devices.</li>\
559 <li><strong>relay mode</strong>: Router relays <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> from upstream, \
560 and extends upstream (e.g. WAN) interface config and prefix to downstream (e.g. LAN) interfaces.</li>\
561 <li><strong>hybrid mode</strong>: Router does both server+relay; extends upstream config and prefix downstream, and \
562 uses <abbr title="Prefix Delegation">PD</abbr> locally.</li></ul>'));
563 so.value('', _('disabled'));
564 so.value('server', _('server mode'));
565 so.value('relay', _('relay mode'));
566 so.value('hybrid', _('hybrid mode'));
567
568 so = ss.taboption('ipv6', form.Value, 'ra_maxinterval', _('Max <abbr title="Router Advertisement">RA</abbr> interval'), _('Maximum time allowed \
569 between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 600 seconds (<code>600</code>).'));
570 so.optional = true;
571 so.default = '600';
572 so.depends('ra', 'server');
573 so.depends('ra', 'hybrid');
574 so.depends('ra', 'relay');
575
576
577 so = ss.taboption('ipv6', form.Value, 'ra_mininterval', _('Min <abbr title="Router Advertisement">RA</abbr> interval'), _('Minimum time allowed \
578 between sending unsolicited <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr>. Default is 200 seconds (<code>200</code>).'));
579 so.optional = true;
580 so.default = '200';
581 so.depends('ra', 'server');
582 so.depends('ra', 'hybrid');
583 so.depends('ra', 'relay');
584
585 so = ss.taboption('ipv6', form.Value, 'ra_lifetime', _('<abbr title="Router Advertisement">RA</abbr> Lifetime'), _('Router Lifetime published \
586 in <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages. Default is 1800 seconds (<code>1800</code>). \
587 Max 9000 seconds.'));
588 so.optional = true;
589 so.default = '1800';
590 so.depends('ra', 'server');
591 so.depends('ra', 'hybrid');
592 so.depends('ra', 'relay');
593
594 so = ss.taboption('ipv6', form.Value, 'ra_mtu', _('<abbr title="Router Advertisement">RA</abbr> MTU'), _('The <abbr title="Maximum Transmission Unit">MTU</abbr> \
595 to be published in <abbr title="Router Advertisement, ICMPv6 Type 134">RA</abbr> messages. Default is 0 (<code>0</code>).\
596 Min 1280.'));
597 so.optional = true;
598 so.default = '0';
599 so.depends('ra', 'server');
600 so.depends('ra', 'hybrid');
601 so.depends('ra', 'relay');
602
603 so = ss.taboption('ipv6', form.Value, 'ra_hoplimit', _('<abbr title="Router Advertisement">RA</abbr> Hop Limit'), _('The maximum hops \
604 to be published in <abbr title="Router Advertisement">RA</abbr> messages.<br>Default is 0 (<code>0</code>), meaning unspecified.\
605 Max 255.'));
606 so.optional = true;
607 so.default = '0';
608 so.depends('ra', 'server');
609 so.depends('ra', 'hybrid');
610 so.depends('ra', 'relay');
611
612 so = ss.taboption('ipv6', form.ListValue, 'ra_management', _('DHCPv6-Mode'), _('Default is stateless + stateful<br />\
613 <ul style="list-style-type:none;">\
614 <li><strong>stateless</strong>: Router advertises prefixes, host uses <abbr title="Stateless Address Auto Config">SLAAC</abbr> \
615 to self assign its own address. No DHCPv6.</li>\
616 <li><strong>stateless + stateful</strong>: SLAAC. In addition, router assigns an IPv6 address to a host via DHCPv6.</li>\
617 <li><strong>stateful-only</strong>: No SLAAC. Router assigns an IPv6 address to a host via DHCPv6.</li><ul>'));
618 so.value('0', _('stateless'));
619 so.value('1', _('stateless + stateful'));
620 so.value('2', _('stateful-only'));
621 so.depends('dhcpv6', 'server');
622 so.depends('dhcpv6', 'hybrid');
623 so.default = '1';
624
625 so = ss.taboption('ipv6', form.ListValue, 'dhcpv6', _('DHCPv6-Service'), _('<ul style="list-style-type:none;">\
626 <li><strong>server mode</strong>: Router assigns IPs and delegates prefixes \
627 (<abbr title="Prefix Delegation">PD</abbr>) to downstream interfaces.</li>\
628 <li><strong>relay mode</strong>: Router relays WAN interface config downstream. Helps support upstream \
629 links that lack <abbr title="Prefix Delegation">PD</abbr>.</li>\
630 <li><strong>hybrid mode</strong>: Router does combination of server+relay.</li></ul>'));
631 so.value('', _('disabled'));
632 so.value('server', _('server mode'));
633 so.value('relay', _('relay mode'));
634 so.value('hybrid', _('hybrid mode'));
635
636 so = ss.taboption('ipv6', form.ListValue, 'ndp', _('<abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy'), _('Reverts to \
637 disabled internally if there are no interfaces with boolean <code>ndproxy_slave</code> set to 1. Think of \
638 <abbr title="Neighbour Discovery Protocol">NDP</abbr> Proxy as Proxy ARP for IPv6: unify hosts on different physical \
639 hardware segments into the same IP subnet. Consists of <abbr title="Neighbour Solicitation, Type 135">NS</abbr> and \
640 <abbr title="Neighbour Advertisement, Type 136">NA</abbr> messages. <abbr title="Neighbour Discovery Protocol">NDP</abbr>-Proxy \
641 listens for <abbr title="Neighbour Solicitation, Type 135">NS</abbr> on an interface marked with boolean \
642 <code>master</code> as 1 (i.e. upstream), then queries the slave/internal interfaces for that target IP before finally \
643 sending an <abbr title="Neighbour Advertisement, Type 136">NA</abbr> message. \
644 <abbr title="Neighbour Discovery Protocol">NDP</abbr> is effectively ARP for IPv6. \
645 <abbr title="Neighbour Solicitation, Type 135">NS</abbr> and <abbr title="Neighbour Advertisement, Type 136">NA</abbr> \
646 detect reachability and duplicate addresses on a link, themselves also a prerequisite for SLAAC autoconfig.<br />\
647 <ul style="list-style-type:none;">\
648 <li><strong>disabled</strong>: No <abbr title="Neighbour Discovery Protocol">NDP</abbr> messages are proxied through to \
649 <code>ndproxy_slave</code> true interfaces.</li> \
650 <li><strong>relay mode</strong>: Proxies <abbr title="Neighbour Discovery Protocol">NDP</abbr> messages from <code>master</code> to \
651 <code>ndproxy_slave</code> true interfaces. Helps to support provider links without \
652 <abbr title="Prefix Delegation">PD</abbr>, and to firewall proxied hosts.</li>\
653 <li><strong>hybrid mode</strong>: Relay mode is disabled unless the interface boolean <code>master</code> is 1.</li></ul>'));
654 so.value('', _('disabled'));
655 so.value('relay', _('relay mode'));
656 so.value('hybrid', _('hybrid mode'));
657
658 so = ss.taboption('ipv6', form.Flag, 'ndproxy_routing', _('Learn routes from NDP'), _('Default is on.'));
659 so.default = '1';
660
661 so = ss.taboption('ipv6', form.Flag, 'ndproxy_slave', _('NDP-Proxy slave'), _('Set interface as NDP-Proxy external slave. Default is off.'));
662
663 so = ss.taboption('ipv6', form.DynamicList, 'ndproxy_static', _('Static NDP-Proxy prefixes'));
664
665 so = ss.taboption('ipv6', form.Flag , 'master', _('Master'), _('Set this interface as master for the dhcpv6 relay.'));
666 so.depends('dhcpv6', 'relay');
667 so.depends('dhcpv6', 'hybrid');
668
669 so = ss.taboption('ipv6', form.Flag , 'master', _('Master'), _('Set this interface as master for the dhcpv6 relay.'));
670 so.depends('dhcpv6', 'relay');
671 so.depends('dhcpv6', 'hybrid');
672
673 so = ss.taboption('ipv6', form.Flag, 'ra_default', _('Announce as default router'), _('Always, even if no public prefix is available.'));
674 so.depends('ra', 'server');
675 so.depends('ra', 'hybrid');
676
677 ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced DNS servers'));
678 ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains'));
679 }
680
681 ifc.renderFormOptions(s);
682 nettools.addDeviceOptions(s, null, true);
683
684 // Common interface options
685 o = nettools.replaceOption(s, 'advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
686 o.default = o.enabled;
687
688 if (protoval != 'static') {
689 o = nettools.replaceOption(s, 'advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
690 o.default = o.enabled;
691 }
692
693 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
694 if (protoval != 'static')
695 o.depends('peerdns', '0');
696 o.datatype = 'ipaddr';
697
698 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'dns_search', _('DNS search domains'));
699 if (protoval != 'static')
700 o.depends('peerdns', '0');
701 o.datatype = 'hostname';
702
703 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'));
704 o.datatype = 'uinteger';
705 o.placeholder = '0';
706
707 o = nettools.replaceOption(s, 'advanced', form.Value, 'metric', _('Use gateway metric'));
708 o.datatype = 'uinteger';
709 o.placeholder = '0';
710
711 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip4table', _('Override IPv4 routing table'));
712 o.datatype = 'or(uinteger, string)';
713 for (var i = 0; i < rtTables.length; i++)
714 o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][1], rtTables[i][0]));
715
716 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6table', _('Override IPv6 routing table'));
717 o.datatype = 'or(uinteger, string)';
718 for (var i = 0; i < rtTables.length; i++)
719 o.value(rtTables[i][1], '%s (%d)'.format(rtTables[i][0], rtTables[i][1]));
720
721 o = nettools.replaceOption(s, 'advanced', form.Flag, 'delegate', _('Delegate IPv6 prefixes'), _('Enable downstream delegation of IPv6 prefixes available on this interface'));
722 o.default = o.enabled;
723
724 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'));
725 o.value('', _('disabled'));
726 o.value('64');
727 o.datatype = 'max(128)';
728
729 o = nettools.replaceOption(s, 'advanced', form.Value, 'ip6hint', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
730 o.placeholder = '0';
731 o.validate = function(section_id, value) {
732 if (value == null || value == '')
733 return true;
734
735 var n = parseInt(value, 16);
736
737 if (!/^(0x)?[0-9a-fA-F]+$/.test(value) || isNaN(n) || n >= 0xffffffff)
738 return _('Expecting a hexadecimal assignment hint');
739
740 return true;
741 };
742 for (var i = 33; i <= 64; i++)
743 o.depends('ip6assign', String(i));
744
745
746 o = nettools.replaceOption(s, 'advanced', form.DynamicList, 'ip6class', _('IPv6 prefix filter'), _('If set, downstream subnets are only allocated from the given IPv6 prefix classes.'));
747 o.value('local', 'local (%s)'.format(_('Local ULA')));
748
749 var prefixClasses = {};
750
751 this.networks.forEach(function(net) {
752 var prefixes = net._ubus('ipv6-prefix');
753 if (Array.isArray(prefixes)) {
754 prefixes.forEach(function(pfx) {
755 if (L.isObject(pfx) && typeof(pfx['class']) == 'string') {
756 prefixClasses[pfx['class']] = prefixClasses[pfx['class']] || {};
757 prefixClasses[pfx['class']][net.getName()] = true;
758 }
759 });
760 }
761 });
762
763 Object.keys(prefixClasses).sort().forEach(function(c) {
764 var networks = Object.keys(prefixClasses[c]).sort().join(', ');
765 o.value(c, (c != networks) ? '%s (%s)'.format(c, networks) : c);
766 });
767
768
769 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."));
770 o.datatype = 'ip6hostid';
771 o.placeholder = '::1';
772
773 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.'));
774 o.datatype = 'uinteger';
775 o.placeholder = '0';
776
777 for (var i = 0; i < s.children.length; i++) {
778 o = s.children[i];
779
780 switch (o.option) {
781 case 'proto':
782 case 'auto':
783 case '_dhcp':
784 case '_zone':
785 case '_switch_proto':
786 case '_ifacestat_modal':
787 continue;
788
789 case 'ifname_multi':
790 case 'ifname_single':
791 case 'igmp_snooping':
792 case 'stp':
793 case 'type':
794 var deps = [];
795 for (var j = 0; j < protocols.length; j++) {
796 if (!protocols[j].isVirtual()) {
797 if (o.deps.length)
798 for (var k = 0; k < o.deps.length; k++)
799 deps.push(Object.assign({ proto: protocols[j].getProtocol() }, o.deps[k]));
800 else
801 deps.push({ proto: protocols[j].getProtocol() });
802 }
803 }
804 o.deps = deps;
805 break;
806
807 default:
808 if (o.deps.length)
809 for (var j = 0; j < o.deps.length; j++)
810 o.deps[j].proto = protoval;
811 else
812 o.depends('proto', protoval);
813 }
814 }
815
816 this.activeSection = s.section;
817 }, this));
818 };
819
820 s.handleModalCancel = function(/* ... */) {
821 var type = uci.get('network', this.activeSection || this.addedSection, 'type'),
822 ifname = (type == 'bridge') ? 'br-%s'.format(this.activeSection || this.addedSection) : null;
823
824 uci.sections('network', 'bridge-vlan', function(bvs) {
825 if (ifname != null && bvs.device == ifname)
826 uci.remove('network', bvs['.name']);
827 });
828
829 return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
830 };
831
832 s.handleAdd = function(ev) {
833 var m2 = new form.Map('network'),
834 s2 = m2.section(form.NamedSection, '_new_'),
835 protocols = network.getProtocols(),
836 proto, name, bridge, ifname_single, ifname_multi;
837
838 protocols.sort(function(a, b) {
839 return a.getProtocol() > b.getProtocol();
840 });
841
842 s2.render = function() {
843 return Promise.all([
844 {},
845 this.renderUCISection('_new_')
846 ]).then(this.renderContents.bind(this));
847 };
848
849 name = s2.option(form.Value, 'name', _('Name'));
850 name.rmempty = false;
851 name.datatype = 'uciname';
852 name.placeholder = _('New interface name…');
853 name.validate = function(section_id, value) {
854 if (uci.get('network', value) != null)
855 return _('The interface name is already used');
856
857 var pr = network.getProtocol(proto.formvalue(section_id), value),
858 ifname = pr.isVirtual() ? '%s-%s'.format(pr.getProtocol(), value) : 'br-%s'.format(value);
859
860 if (value.length > 15)
861 return _('The interface name is too long');
862
863 return true;
864 };
865
866 proto = s2.option(form.ListValue, 'proto', _('Protocol'));
867 proto.validate = name.validate;
868
869 bridge = s2.option(form.Flag, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
870 bridge.modalonly = true;
871 bridge.disabled = '';
872 bridge.enabled = 'bridge';
873
874 ifname_single = s2.option(widgets.DeviceSelect, 'ifname_single', _('Interface'));
875 ifname_single.noaliases = false;
876 ifname_single.optional = false;
877
878 ifname_multi = s2.option(widgets.DeviceSelect, 'ifname_multi', _('Interface'));
879 ifname_multi.nobridges = true;
880 ifname_multi.noaliases = true;
881 ifname_multi.multiple = true;
882 ifname_multi.optional = true;
883 ifname_multi.display_size = 6;
884
885 for (var i = 0; i < protocols.length; i++) {
886 proto.value(protocols[i].getProtocol(), protocols[i].getI18n());
887
888 if (!protocols[i].isVirtual()) {
889 bridge.depends({ proto: protocols[i].getProtocol() });
890 ifname_single.depends({ type: '', proto: protocols[i].getProtocol() });
891 ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
892 }
893 }
894
895 m2.render().then(L.bind(function(nodes) {
896 ui.showModal(_('Add new interface...'), [
897 nodes,
898 E('div', { 'class': 'right' }, [
899 E('button', {
900 'class': 'btn',
901 'click': ui.hideModal
902 }, _('Cancel')), ' ',
903 E('button', {
904 'class': 'cbi-button cbi-button-positive important',
905 'click': ui.createHandlerFn(this, function(ev) {
906 var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null,
907 protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null,
908 protoclass = protoval ? network.getProtocol(protoval, nameval) : null;
909
910 if (nameval == null || protoval == null || nameval == '' || protoval == '')
911 return;
912
913 return protoclass.isCreateable(nameval).then(function(checkval) {
914 if (checkval != null) {
915 ui.addNotification(null,
916 E('p', _('New interface for "%s" can not be created: %s').format(protoclass.getI18n(), checkval)));
917 ui.hideModal();
918 return;
919 }
920
921 return m.save(function() {
922 var section_id = uci.add('network', 'interface', nameval);
923
924 protoclass.set('proto', protoval);
925
926 if (ifname_single.isActive('_new_')) {
927 protoclass.addDevice(ifname_single.formvalue('_new_'));
928 }
929 else if (ifname_multi.isActive('_new_')) {
930 protoclass.set('type', 'bridge');
931 L.toArray(ifname_multi.formvalue('_new_')).map(function(dev) {
932 protoclass.addDevice(dev);
933 });
934 }
935
936 m.children[0].addedSection = section_id;
937 }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
938 });
939 })
940 }, _('Create interface'))
941 ])
942 ], 'cbi-modal');
943
944 nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
945 }, this));
946 };
947
948 s.handleRemove = function(section_id, ev) {
949 return network.deleteNetwork(section_id).then(L.bind(function(section_id, ev) {
950 return form.GridSection.prototype.handleRemove.apply(this, [section_id, ev]);
951 }, this, section_id, ev));
952 };
953
954 o = s.option(form.DummyValue, '_ifacebox');
955 o.modalonly = false;
956 o.textvalue = function(section_id) {
957 var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0],
958 zone = net ? this.section.zones.filter(function(z) { return !!z.getNetworks().filter(function(n) { return n == section_id })[0] })[0] : null;
959
960 if (!net)
961 return;
962
963 var node = E('div', { 'class': 'ifacebox' }, [
964 E('div', {
965 'class': 'ifacebox-head',
966 'style': 'background-color:%s'.format(zone ? zone.getColor() : '#EEEEEE'),
967 'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned')
968 }, E('strong', net.getName().toUpperCase())),
969 E('div', {
970 'class': 'ifacebox-body',
971 'id': '%s-ifc-devices'.format(section_id),
972 'data-network': section_id
973 }, [
974 E('img', {
975 'src': L.resource('icons/ethernet_disabled.png'),
976 'style': 'width:16px; height:16px'
977 }),
978 E('br'), E('small', '?')
979 ])
980 ]);
981
982 render_ifacebox_status(node.childNodes[1], net);
983
984 return node;
985 };
986
987 o = s.option(form.DummyValue, '_ifacestat');
988 o.modalonly = false;
989 o.textvalue = function(section_id) {
990 var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0];
991
992 if (!net)
993 return;
994
995 var node = E('div', { 'id': '%s-ifc-description'.format(section_id) });
996
997 render_status(node, net, false);
998
999 return node;
1000 };
1001
1002 o = s.taboption('advanced', form.Flag, 'delegate', _('Use builtin IPv6-management'));
1003 o.modalonly = true;
1004 o.default = o.enabled;
1005
1006 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).'));
1007 o.modalonly = true;
1008 o.defaults = {
1009 '1': [{ proto: 'static' }],
1010 '0': []
1011 };
1012
1013
1014 // Device configuration
1015 s = m.section(form.GridSection, 'device', _('Devices'));
1016 s.addremove = true;
1017 s.anonymous = true;
1018 s.addbtntitle = _('Add device configuration…');
1019
1020 s.cfgsections = function() {
1021 var sections = uci.sections('network', 'device'),
1022 section_ids = sections.sort(function(a, b) { return a.name > b.name }).map(function(s) { return s['.name'] });
1023
1024 for (var i = 0; i < netDevs.length; i++) {
1025 if (sections.filter(function(s) { return s.name == netDevs[i].getName() }).length)
1026 continue;
1027
1028 if (netDevs[i].getType() == 'wifi' && !netDevs[i].isUp())
1029 continue;
1030
1031 /* Unless http://lists.openwrt.org/pipermail/openwrt-devel/2020-July/030397.html is implemented,
1032 we cannot properly redefine bridges as devices, so filter them away for now... */
1033
1034 var m = netDevs[i].isBridge() ? netDevs[i].getName().match(/^br-([A-Za-z0-9_]+)$/) : null,
1035 s = m ? uci.get('network', m[1]) : null;
1036
1037 if (s && s['.type'] == 'interface' && s.type == 'bridge')
1038 continue;
1039
1040 section_ids.push('dev:%s'.format(netDevs[i].getName()));
1041 }
1042
1043 return section_ids;
1044 };
1045
1046 s.renderMoreOptionsModal = function(section_id, ev) {
1047 var m = section_id.match(/^dev:(.+)$/);
1048
1049 if (m) {
1050 var devtype = getDevType(section_id);
1051
1052 section_id = uci.add('network', 'device');
1053
1054 uci.set('network', section_id, 'name', m[1]);
1055 uci.set('network', section_id, 'type', (devtype != 'ethernet') ? devtype : null);
1056
1057 this.addedSection = section_id;
1058 }
1059
1060 return this.super('renderMoreOptionsModal', [section_id, ev]);
1061 };
1062
1063 s.renderRowActions = function(section_id) {
1064 var trEl = this.super('renderRowActions', [ section_id, _('Configure…') ]),
1065 deleteBtn = trEl.querySelector('button:last-child');
1066
1067 deleteBtn.firstChild.data = _('Reset');
1068 deleteBtn.disabled = section_id.match(/^dev:/) ? true : null;
1069
1070 return trEl;
1071 };
1072
1073 s.modaltitle = function(section_id) {
1074 var m = section_id.match(/^dev:(.+)$/),
1075 name = m ? m[1] : uci.get('network', section_id, 'name');
1076
1077 return name ? '%s: %q'.format(getDevTypeDesc(section_id), name) : _('Add device configuration');
1078 };
1079
1080 s.addModalOptions = function(s) {
1081 var isNew = (uci.get('network', s.section, 'name') == null),
1082 dev = getDevice(s.section);
1083
1084 nettools.addDeviceOptions(s, dev, isNew);
1085 };
1086
1087 s.handleModalCancel = function(/* ... */) {
1088 var name = uci.get('network', this.addedSection, 'name')
1089
1090 uci.sections('network', 'bridge-vlan', function(bvs) {
1091 if (name != null && bvs.device == name)
1092 uci.remove('network', bvs['.name']);
1093 });
1094
1095 return form.GridSection.prototype.handleModalCancel.apply(this, arguments);
1096 };
1097
1098 function getDevice(section_id) {
1099 var m = section_id.match(/^dev:(.+)$/),
1100 name = m ? m[1] : uci.get('network', section_id, 'name');
1101
1102 return netDevs.filter(function(d) { return d.getName() == name })[0];
1103 }
1104
1105 function getDevType(section_id) {
1106 var cfgtype = uci.get('network', section_id, 'type'),
1107 dev = getDevice(section_id);
1108
1109 switch (cfgtype || (dev ? dev.getType() : '')) {
1110 case '':
1111 return null;
1112
1113 case 'vlan':
1114 case '8021q':
1115 return '8021q';
1116
1117 case '8021ad':
1118 return '8021ad';
1119
1120 case 'bridge':
1121 return 'bridge';
1122
1123 case 'tunnel':
1124 return 'tunnel';
1125
1126 case 'macvlan':
1127 return 'macvlan';
1128
1129 case 'veth':
1130 return 'veth';
1131
1132 case 'wifi':
1133 case 'alias':
1134 case 'switch':
1135 case 'ethernet':
1136 default:
1137 return 'ethernet';
1138 }
1139 }
1140
1141 function getDevTypeDesc(section_id) {
1142 switch (getDevType(section_id) || '') {
1143 case '':
1144 return E('em', [ _('Device not present') ]);
1145
1146 case '8021q':
1147 return _('VLAN (802.1q)');
1148
1149 case '8021ad':
1150 return _('VLAN (802.1ad)');
1151
1152 case 'bridge':
1153 return _('Bridge device');
1154
1155 case 'tunnel':
1156 return _('Tunnel device');
1157
1158 case 'macvlan':
1159 return _('MAC VLAN');
1160
1161 case 'veth':
1162 return _('Virtual Ethernet');
1163
1164 default:
1165 return _('Network device');
1166 }
1167 }
1168
1169 o = s.option(form.DummyValue, 'name', _('Device'));
1170 o.modalonly = false;
1171 o.textvalue = function(section_id) {
1172 var dev = getDevice(section_id),
1173 ext = section_id.match(/^dev:/),
1174 icon = render_iface(dev);
1175
1176 if (ext)
1177 icon.querySelector('img').style.opacity = '.5';
1178
1179 return E('span', { 'class': 'ifacebadge' }, [
1180 icon,
1181 E('span', { 'style': ext ? 'opacity:.5' : null }, [
1182 dev ? dev.getName() : (uci.get('network', section_id, 'name') || '?')
1183 ])
1184 ]);
1185 };
1186
1187 o = s.option(form.DummyValue, 'type', _('Type'));
1188 o.textvalue = getDevTypeDesc;
1189 o.modalonly = false;
1190
1191 o = s.option(form.DummyValue, 'macaddr', _('MAC Address'));
1192 o.modalonly = false;
1193 o.textvalue = function(section_id) {
1194 var dev = getDevice(section_id),
1195 val = uci.get('network', section_id, 'macaddr'),
1196 mac = dev ? dev.getMAC() : null;
1197
1198 return val ? E('strong', {
1199 'data-tooltip': _('The value is overridden by configuration. Original: %s').format(mac || _('unknown'))
1200 }, [ val.toUpperCase() ]) : (mac || '-');
1201 };
1202
1203 o = s.option(form.DummyValue, 'mtu', _('MTU'));
1204 o.modalonly = false;
1205 o.textvalue = function(section_id) {
1206 var dev = getDevice(section_id),
1207 val = uci.get('network', section_id, 'mtu'),
1208 mtu = dev ? dev.getMTU() : null;
1209
1210 return val ? E('strong', {
1211 'data-tooltip': _('The value is overridden by configuration. Original: %s').format(mtu || _('unknown'))
1212 }, [ val ]) : (mtu || '-').toString();
1213 };
1214
1215 s = m.section(form.TypedSection, 'globals', _('Global network options'));
1216 s.addremove = false;
1217 s.anonymous = true;
1218
1219 o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'), _('Unique Local Address - in the range <code>fc00::/7</code>. \
1220 Typically only within the &#8216;local&#8217; half <code>fd00::/8</code>. ULA for IPv6 is analogous to IPv4 private network addressing.\
1221 This prefix is randomly generated at first install.'));
1222 o.datatype = 'cidr6';
1223
1224 o = s.option(form.Flag, 'packet_steering', _('Packet Steering'), _('Enable packet steering across all CPUs. May help or hinder network speed.'));
1225 o.optional = true;
1226
1227
1228 if (dslModemType != null) {
1229 s = m.section(form.TypedSection, 'dsl', _('DSL'));
1230 s.anonymous = true;
1231
1232 o = s.option(form.ListValue, 'annex', _('Annex'));
1233 o.value('a', _('Annex A + L + M (all)'));
1234 o.value('b', _('Annex B (all)'));
1235 o.value('j', _('Annex J (all)'));
1236 o.value('m', _('Annex M (all)'));
1237 o.value('bdmt', _('Annex B G.992.1'));
1238 o.value('b2', _('Annex B G.992.3'));
1239 o.value('b2p', _('Annex B G.992.5'));
1240 o.value('at1', _('ANSI T1.413'));
1241 o.value('admt', _('Annex A G.992.1'));
1242 o.value('alite', _('Annex A G.992.2'));
1243 o.value('a2', _('Annex A G.992.3'));
1244 o.value('a2p', _('Annex A G.992.5'));
1245 o.value('l', _('Annex L G.992.3 POTS 1'));
1246 o.value('m2', _('Annex M G.992.3'));
1247 o.value('m2p', _('Annex M G.992.5'));
1248
1249 o = s.option(form.ListValue, 'tone', _('Tone'));
1250 o.value('', _('auto'));
1251 o.value('a', _('A43C + J43 + A43'));
1252 o.value('av', _('A43C + J43 + A43 + V43'));
1253 o.value('b', _('B43 + B43C'));
1254 o.value('bv', _('B43 + B43C + V43'));
1255
1256 if (dslModemType == 'vdsl') {
1257 o = s.option(form.ListValue, 'xfer_mode', _('Encapsulation mode'));
1258 o.value('', _('auto'));
1259 o.value('atm', _('ATM (Asynchronous Transfer Mode)'));
1260 o.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
1261
1262 o = s.option(form.ListValue, 'line_mode', _('DSL line mode'));
1263 o.value('', _('auto'));
1264 o.value('adsl', _('ADSL'));
1265 o.value('vdsl', _('VDSL'));
1266
1267 o = s.option(form.ListValue, 'ds_snr_offset', _('Downstream SNR offset'));
1268 o.default = '0';
1269
1270 for (var i = -100; i <= 100; i += 5)
1271 o.value(i, _('%.1f dB').format(i / 10));
1272 }
1273
1274 s.option(form.Value, 'firmware', _('Firmware File'));
1275 }
1276
1277
1278 // Show ATM bridge section if we have the capabilities
1279 if (L.hasSystemFeature('br2684ctl')) {
1280 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.'));
1281
1282 s.addremove = true;
1283 s.anonymous = true;
1284 s.addbtntitle = _('Add ATM Bridge');
1285
1286 s.handleAdd = function(ev) {
1287 var sections = uci.sections('network', 'atm-bridge'),
1288 max_unit = -1;
1289
1290 for (var i = 0; i < sections.length; i++) {
1291 var unit = +sections[i].unit;
1292
1293 if (!isNaN(unit) && unit > max_unit)
1294 max_unit = unit;
1295 }
1296
1297 return this.map.save(function() {
1298 var sid = uci.add('network', 'atm-bridge');
1299
1300 uci.set('network', sid, 'unit', max_unit + 1);
1301 uci.set('network', sid, 'atmdev', 0);
1302 uci.set('network', sid, 'encaps', 'llc');
1303 uci.set('network', sid, 'payload', 'bridged');
1304 uci.set('network', sid, 'vci', 35);
1305 uci.set('network', sid, 'vpi', 8);
1306 });
1307 };
1308
1309 s.tab('general', _('General Setup'));
1310 s.tab('advanced', _('Advanced Settings'));
1311
1312 o = s.taboption('general', form.Value, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
1313 s.taboption('general', form.Value, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
1314
1315 o = s.taboption('general', form.ListValue, 'encaps', _('Encapsulation mode'));
1316 o.value('llc', _('LLC'));
1317 o.value('vc', _('VC-Mux'));
1318
1319 s.taboption('advanced', form.Value, 'atmdev', _('ATM device number'));
1320 s.taboption('advanced', form.Value, 'unit', _('Bridge unit number'));
1321
1322 o = s.taboption('advanced', form.ListValue, 'payload', _('Forwarding mode'));
1323 o.value('bridged', _('bridged'));
1324 o.value('routed', _('routed'));
1325 }
1326
1327
1328 return m.render().then(L.bind(function(m, nodes) {
1329 poll.add(L.bind(function() {
1330 var section_ids = m.children[0].cfgsections(),
1331 tasks = [];
1332
1333 for (var i = 0; i < section_ids.length; i++) {
1334 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
1335 dsc = row.querySelector('[data-name="_ifacestat"] > div'),
1336 btn1 = row.querySelector('.cbi-section-actions .reconnect'),
1337 btn2 = row.querySelector('.cbi-section-actions .down');
1338
1339 if (dsc.getAttribute('reconnect') == '') {
1340 dsc.setAttribute('reconnect', '1');
1341 tasks.push(fs.exec('/sbin/ifup', [section_ids[i]]).catch(function(e) {
1342 ui.addNotification(null, E('p', e.message));
1343 }));
1344 }
1345 else if (dsc.getAttribute('disconnect') == '') {
1346 dsc.setAttribute('disconnect', '1');
1347 tasks.push(fs.exec('/sbin/ifdown', [section_ids[i]]).catch(function(e) {
1348 ui.addNotification(null, E('p', e.message));
1349 }));
1350 }
1351 else if (dsc.getAttribute('reconnect') == '1') {
1352 dsc.removeAttribute('reconnect');
1353 btn1.classList.remove('spinning');
1354 btn1.disabled = false;
1355 }
1356 else if (dsc.getAttribute('disconnect') == '1') {
1357 dsc.removeAttribute('disconnect');
1358 btn2.classList.remove('spinning');
1359 btn2.disabled = false;
1360 }
1361 }
1362
1363 return Promise.all(tasks)
1364 .then(L.bind(network.getNetworks, network))
1365 .then(L.bind(this.poll_status, this, nodes));
1366 }, this), 5);
1367
1368 return nodes;
1369 }, this, m));
1370 }
1371 });