luci-mod-network: fix typo
[project/luci.git] / modules / luci-mod-network / htdocs / luci-static / resources / view / network / interfaces.js
1 'use strict';
2 'require fs';
3 'require ui';
4 'require uci';
5 'require form';
6 'require network';
7 'require firewall';
8 'require tools.widgets as widgets';
9
10 function count_changes(section_id) {
11 var changes = ui.changes.changes, n = 0;
12
13 if (!L.isObject(changes))
14 return n;
15
16 if (Array.isArray(changes.network))
17 for (var i = 0; i < changes.network.length; i++)
18 n += (changes.network[i][1] == section_id);
19
20 if (Array.isArray(changes.dhcp))
21 for (var i = 0; i < changes.dhcp.length; i++)
22 n += (changes.dhcp[i][1] == section_id);
23
24 return n;
25 }
26
27 function render_iface(dev, alias) {
28 var type = dev ? dev.getType() : 'ethernet',
29 up = dev ? dev.isUp() : false;
30
31 return E('span', { class: 'cbi-tooltip-container' }, [
32 E('img', { 'class' : 'middle', 'src': L.resource('icons/%s%s.png').format(
33 alias ? 'alias' : type,
34 up ? '' : '_disabled') }),
35 E('span', { 'class': 'cbi-tooltip ifacebadge large' }, [
36 E('img', { 'src': L.resource('icons/%s%s.png').format(
37 type, up ? '' : '_disabled') }),
38 L.itemlist(E('span', { 'class': 'left' }), [
39 _('Type'), dev ? dev.getTypeI18n() : null,
40 _('Device'), dev ? dev.getName() : _('Not present'),
41 _('Connected'), up ? _('yes') : _('no'),
42 _('MAC'), dev ? dev.getMAC() : null,
43 _('RX'), dev ? '%.2mB (%d %s)'.format(dev.getRXBytes(), dev.getRXPackets(), _('Pkts.')) : null,
44 _('TX'), dev ? '%.2mB (%d %s)'.format(dev.getTXBytes(), dev.getTXPackets(), _('Pkts.')) : null
45 ])
46 ])
47 ]);
48 }
49
50 function render_status(node, ifc, with_device) {
51 var desc = null, c = [];
52
53 if (ifc.isDynamic())
54 desc = _('Virtual dynamic interface');
55 else if (ifc.isAlias())
56 desc = _('Alias Interface');
57 else if (!uci.get('network', ifc.getName()))
58 return L.itemlist(node, [
59 null, E('em', _('Interface is marked for deletion'))
60 ]);
61
62 var i18n = ifc.getI18n();
63 if (i18n)
64 desc = desc ? '%s (%s)'.format(desc, i18n) : i18n;
65
66 var changecount = with_device ? 0 : count_changes(ifc.getName()),
67 ipaddrs = changecount ? [] : ifc.getIPAddrs(),
68 ip6addrs = changecount ? [] : ifc.getIP6Addrs(),
69 errors = ifc.getErrors(),
70 maindev = ifc.getL3Device() || ifc.getDevice(),
71 macaddr = maindev ? maindev.getMAC() : null;
72
73 return L.itemlist(node, [
74 _('Protocol'), with_device ? null : (desc || '?'),
75 _('Device'), with_device ? (maindev ? maindev.getShortName() : E('em', _('Not present'))) : null,
76 _('Uptime'), (!changecount && ifc.isUp()) ? '%t'.format(ifc.getUptime()) : null,
77 _('MAC'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && macaddr) ? macaddr : null,
78 _('RX'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getRXBytes(), maindev.getRXPackets(), _('Pkts.')) : null,
79 _('TX'), (!changecount && !ifc.isDynamic() && !ifc.isAlias() && maindev) ? '%.2mB (%d %s)'.format(maindev.getTXBytes(), maindev.getTXPackets(), _('Pkts.')) : null,
80 _('IPv4'), ipaddrs[0],
81 _('IPv4'), ipaddrs[1],
82 _('IPv4'), ipaddrs[2],
83 _('IPv4'), ipaddrs[3],
84 _('IPv4'), ipaddrs[4],
85 _('IPv6'), ip6addrs[0],
86 _('IPv6'), ip6addrs[1],
87 _('IPv6'), ip6addrs[2],
88 _('IPv6'), ip6addrs[3],
89 _('IPv6'), ip6addrs[4],
90 _('IPv6'), ip6addrs[5],
91 _('IPv6'), ip6addrs[6],
92 _('IPv6'), ip6addrs[7],
93 _('IPv6'), ip6addrs[8],
94 _('IPv6'), ip6addrs[9],
95 _('IPv6-PD'), changecount ? null : ifc.getIP6Prefix(),
96 _('Information'), with_device ? null : (ifc.get('auto') != '0' ? null : _('Not started on boot')),
97 _('Error'), errors ? errors[0] : null,
98 _('Error'), errors ? errors[1] : null,
99 _('Error'), errors ? errors[2] : null,
100 _('Error'), errors ? errors[3] : null,
101 _('Error'), errors ? errors[4] : null,
102 null, changecount ? E('a', {
103 href: '#',
104 click: L.bind(ui.changes.displayChanges, ui.changes)
105 }, _('Interface has %d pending changes').format(changecount)) : null
106 ]);
107 }
108
109 function render_modal_status(node, ifc) {
110 var dev = ifc ? (ifc.getDevice() || ifc.getL3Device() || ifc.getL3Device()) : null;
111
112 L.dom.content(node, [
113 E('img', {
114 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
115 'title': dev ? dev.getTypeI18n() : _('Not present')
116 }),
117 ifc ? render_status(E('span'), ifc, true) : E('em', _('Interface not present or not connected yet.'))
118 ]);
119
120 return node;
121 }
122
123 function render_ifacebox_status(node, ifc) {
124 var dev = ifc.getL3Device() || ifc.getDevice(),
125 subdevs = ifc.getDevices(),
126 c = [ render_iface(dev, ifc.isAlias()) ];
127
128 if (subdevs && subdevs.length) {
129 var sifs = [ ' (' ];
130
131 for (var j = 0; j < subdevs.length; j++)
132 sifs.push(render_iface(subdevs[j]));
133
134 sifs.push(')');
135
136 c.push(E('span', {}, sifs));
137 }
138
139 c.push(E('br'));
140 c.push(E('small', {}, ifc.isAlias() ? _('Alias of "%s"').format(ifc.isAlias())
141 : (dev ? dev.getName() : E('em', _('Not present')))));
142
143 L.dom.content(node, c);
144
145 return firewall.getZoneByNetwork(ifc.getName()).then(L.bind(function(zone) {
146 this.style.backgroundColor = zone ? zone.getColor() : '#EEEEEE';
147 this.title = zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned');
148 }, node.previousElementSibling));
149 }
150
151 function iface_updown(up, id, ev, force) {
152 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)),
153 dsc = row.querySelector('[data-name="_ifacestat"] > div'),
154 btns = row.querySelectorAll('.cbi-section-actions .reconnect, .cbi-section-actions .down');
155
156 btns[+!up].blur();
157 btns[+!up].classList.add('spinning');
158
159 btns[0].disabled = true;
160 btns[1].disabled = true;
161
162 if (!up) {
163 L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res) {
164 var info = null; try { info = JSON.parse(res); } catch(e) {}
165
166 if (L.isObject(info) &&
167 Array.isArray(info.inbound_interfaces) &&
168 info.inbound_interfaces.filter(function(i) { return i == id })[0]) {
169
170 ui.showModal(_('Confirm disconnect'), [
171 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)),
172 E('div', { 'class': 'right' }, [
173 E('button', {
174 'class': 'cbi-button cbi-button-neutral',
175 'click': function(ev) {
176 btns[1].classList.remove('spinning');
177 btns[1].disabled = false;
178 btns[0].disabled = false;
179
180 ui.hideModal();
181 }
182 }, _('Cancel')),
183 ' ',
184 E('button', {
185 'class': 'cbi-button cbi-button-negative important',
186 'click': function(ev) {
187 dsc.setAttribute('disconnect', '');
188 L.dom.content(dsc, E('em', _('Interface is shutting down...')));
189
190 ui.hideModal();
191 }
192 }, _('Disconnect'))
193 ])
194 ]);
195 }
196 else {
197 dsc.setAttribute('disconnect', '');
198 L.dom.content(dsc, E('em', _('Interface is shutting down...')));
199 }
200 });
201 }
202 else {
203 dsc.setAttribute(up ? 'reconnect' : 'disconnect', force ? 'force' : '');
204 L.dom.content(dsc, E('em', up ? _('Interface is reconnecting...') : _('Interface is shutting down...')));
205 }
206 }
207
208 function get_netmask(s, use_cfgvalue) {
209 var readfn = use_cfgvalue ? 'cfgvalue' : 'formvalue',
210 addropt = s.children.filter(function(o) { return o.option == 'ipaddr'})[0],
211 addrvals = addropt ? L.toArray(addropt[readfn](s.section)) : [],
212 maskopt = s.children.filter(function(o) { return o.option == 'netmask'})[0],
213 maskval = maskopt ? maskopt[readfn](s.section) : null,
214 firstsubnet = maskval ? addrvals[0] + '/' + maskval : addrvals.filter(function(a) { return a.indexOf('/') > 0 })[0];
215
216 if (firstsubnet == null)
217 return null;
218
219 var mask = firstsubnet.split('/')[1];
220
221 if (!isNaN(mask))
222 mask = network.prefixToMask(+mask);
223
224 return mask;
225 }
226
227 return L.view.extend({
228 poll_status: function(map, networks) {
229 var resolveZone = null;
230
231 for (var i = 0; i < networks.length; i++) {
232 var ifc = networks[i],
233 row = map.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(ifc.getName()));
234
235 if (row == null)
236 continue;
237
238 var dsc = row.querySelector('[data-name="_ifacestat"] > div'),
239 box = row.querySelector('[data-name="_ifacebox"] .ifacebox-body'),
240 btn1 = row.querySelector('.cbi-section-actions .reconnect'),
241 btn2 = row.querySelector('.cbi-section-actions .down'),
242 stat = document.querySelector('[id="%s-ifc-status"]'.format(ifc.getName())),
243 resolveZone = render_ifacebox_status(box, ifc),
244 disabled = ifc ? !ifc.isUp() : true,
245 dynamic = ifc ? ifc.isDynamic() : false;
246
247 if (dsc.hasAttribute('reconnect')) {
248 L.dom.content(dsc, E('em', _('Interface is starting...')));
249 }
250 else if (dsc.hasAttribute('disconnect')) {
251 L.dom.content(dsc, E('em', _('Interface is stopping...')));
252 }
253 else if (ifc.getProtocol() || uci.get('network', ifc.getName()) == null) {
254 render_status(dsc, ifc, false);
255 }
256 else if (!ifc.getProtocol()) {
257 var e = map.querySelector('[id="cbi-network-%s"] .cbi-button-edit'.format(ifc.getName()));
258 if (e) e.disabled = true;
259
260 var link = L.url('admin/system/opkg') + '?query=luci-proto';
261 L.dom.content(dsc, [
262 E('em', _('Unsupported protocol type.')), E('br'),
263 E('a', { href: link }, _('Install protocol extensions...'))
264 ]);
265 }
266 else {
267 L.dom.content(dsc, E('em', _('Interface not present or not connected yet.')));
268 }
269
270 if (stat) {
271 var dev = ifc.getDevice();
272 L.dom.content(stat, [
273 E('img', {
274 'src': L.resource('icons/%s%s.png').format(dev ? dev.getType() : 'ethernet', (dev && dev.isUp()) ? '' : '_disabled'),
275 'title': dev ? dev.getTypeI18n() : _('Not present')
276 }),
277 render_status(E('span'), ifc, true)
278 ]);
279 }
280
281 btn1.disabled = btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic;
282 btn2.disabled = btn1.classList.contains('spinning') || btn2.classList.contains('spinning') || dynamic || disabled;
283 }
284
285 return Promise.all([ resolveZone, network.flushCache() ]);
286 },
287
288 load: function() {
289 return Promise.all([
290 network.getDSLModemType(),
291 uci.changes()
292 ]);
293 },
294
295 render: function(data) {
296 var dslModemType = data[0],
297 m, s, o;
298
299 m = new form.Map('network');
300 m.tabbed = true;
301 m.chain('dhcp');
302
303 s = m.section(form.GridSection, 'interface', _('Interfaces'));
304 s.anonymous = true;
305 s.addremove = true;
306 s.addbtntitle = _('Add new interface...');
307
308 s.load = function() {
309 return Promise.all([
310 network.getNetworks(),
311 firewall.getZones()
312 ]).then(L.bind(function(data) {
313 this.networks = data[0];
314 this.zones = data[1];
315 }, this));
316 };
317
318 s.tab('general', _('General Settings'));
319 s.tab('advanced', _('Advanced Settings'));
320 s.tab('physical', _('Physical Settings'));
321 s.tab('firewall', _('Firewall Settings'));
322 s.tab('dhcp', _('DHCP Server'));
323
324 s.cfgsections = function() {
325 return this.networks.map(function(n) { return n.getName() })
326 .filter(function(n) { return n != 'loopback' });
327 };
328
329 s.modaltitle = function(section_id) {
330 return _('Interfaces') + ' » ' + section_id.toUpperCase();
331 };
332
333 s.renderRowActions = function(section_id) {
334 var tdEl = this.super('renderRowActions', [ section_id, _('Edit') ]),
335 net = this.networks.filter(function(n) { return n.getName() == section_id })[0],
336 disabled = net ? !net.isUp() : true,
337 dynamic = net ? net.isDynamic() : false;
338
339 L.dom.content(tdEl.lastChild, [
340 E('button', {
341 'class': 'cbi-button cbi-button-neutral reconnect',
342 'click': iface_updown.bind(this, true, section_id),
343 'title': _('Reconnect this interface'),
344 'disabled': dynamic ? 'disabled' : null
345 }, _('Restart')),
346 E('button', {
347 'class': 'cbi-button cbi-button-neutral down',
348 'click': iface_updown.bind(this, false, section_id),
349 'title': _('Shutdown this interface'),
350 'disabled': (dynamic || disabled) ? 'disabled' : null
351 }, _('Stop')),
352 tdEl.lastChild.firstChild,
353 tdEl.lastChild.lastChild
354 ]);
355
356 if (!dynamic && net && !uci.get('network', net.getName())) {
357 tdEl.lastChild.childNodes[0].disabled = true;
358 tdEl.lastChild.childNodes[2].disabled = true;
359 tdEl.lastChild.childNodes[3].disabled = true;
360 }
361
362 return tdEl;
363 };
364
365 s.addModalOptions = function(s) {
366 var protoval = uci.get('network', s.section, 'proto'),
367 protoclass = protoval ? network.getProtocol(protoval) : null,
368 o, ifname_single, ifname_multi, proto_select, proto_switch, type, stp, igmp, ss, so;
369
370 if (!protoval)
371 return;
372
373 return network.getNetwork(s.section).then(L.bind(function(ifc) {
374 var protocols = network.getProtocols();
375
376 protocols.sort(function(a, b) {
377 return a.getProtocol() > b.getProtocol();
378 });
379
380 o = s.taboption('general', form.DummyValue, '_ifacestat_modal', _('Status'));
381 o.modalonly = true;
382 o.cfgvalue = L.bind(function(section_id) {
383 var net = this.networks.filter(function(n) { return n.getName() == section_id })[0];
384
385 return render_modal_status(E('div', {
386 'id': '%s-ifc-status'.format(section_id),
387 'class': 'ifacebadge large'
388 }), net);
389 }, this);
390 o.write = function() {};
391
392 proto_select = s.taboption('general', form.ListValue, 'proto', _('Protocol'));
393 proto_select.modalonly = true;
394
395 proto_switch = s.taboption('general', form.Button, '_switch_proto');
396 proto_switch.modalonly = true;
397 proto_switch.title = _('Really switch protocol?');
398 proto_switch.inputtitle = _('Switch protocol');
399 proto_switch.inputstyle = 'apply';
400 proto_switch.onclick = L.bind(function(ev) {
401 s.map.save()
402 .then(L.bind(m.load, m))
403 .then(L.bind(m.render, m))
404 .then(L.bind(this.renderMoreOptionsModal, this, s.section));
405 }, this);
406
407 o = s.taboption('general', form.Flag, 'auto', _('Bring up on boot'));
408 o.modalonly = true;
409 o.default = o.enabled;
410
411 type = s.taboption('physical', form.Flag, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
412 type.modalonly = true;
413 type.disabled = '';
414 type.enabled = 'bridge';
415 type.write = type.remove = function(section_id, value) {
416 var protocol = network.getProtocol(proto_select.formvalue(section_id)),
417 ifnameopt = this.section.children.filter(function(o) { return o.option == (value ? 'ifname_multi' : 'ifname_single') })[0];
418
419 if (!protocol.isVirtual() && !this.isActive(section_id))
420 return;
421
422 var old_ifnames = [],
423 devs = ifc.getDevices() || L.toArray(ifc.getDevice());
424
425 for (var i = 0; i < devs.length; i++)
426 old_ifnames.push(devs[i].getName());
427
428 var new_ifnames = L.toArray(ifnameopt.formvalue(section_id));
429
430 if (!value)
431 new_ifnames.length = Math.max(new_ifnames.length, 1);
432
433 old_ifnames.sort();
434 new_ifnames.sort();
435
436 for (var i = 0; i < Math.max(old_ifnames.length, new_ifnames.length); i++) {
437 if (old_ifnames[i] != new_ifnames[i]) {
438 // backup_ifnames()
439 for (var j = 0; j < old_ifnames.length; j++)
440 ifc.deleteDevice(old_ifnames[j]);
441
442 for (var j = 0; j < new_ifnames.length; j++)
443 ifc.addDevice(new_ifnames[j]);
444
445 break;
446 }
447 }
448
449 if (value)
450 uci.set('network', section_id, 'type', 'bridge');
451 else
452 uci.unset('network', section_id, 'type');
453 };
454
455 stp = s.taboption('physical', form.Flag, 'stp', _('Enable <abbr title="Spanning Tree Protocol">STP</abbr>'), _('Enables the Spanning Tree Protocol on this bridge'));
456
457 igmp = s.taboption('physical', form.Flag, 'igmp_snooping', _('Enable <abbr title="Internet Group Management Protocol">IGMP</abbr> snooping'), _('Enables IGMP snooping on this bridge'));
458
459 ifname_single = s.taboption('physical', widgets.DeviceSelect, 'ifname_single', _('Interface'));
460 ifname_single.nobridges = ifc.isBridge();
461 ifname_single.noaliases = false;
462 ifname_single.optional = false;
463 ifname_single.network = ifc.getName();
464 ifname_single.write = ifname_single.remove = function() {};
465
466 ifname_multi = s.taboption('physical', widgets.DeviceSelect, 'ifname_multi', _('Interface'));
467 ifname_multi.nobridges = ifc.isBridge();
468 ifname_multi.noaliases = true;
469 ifname_multi.multiple = true;
470 ifname_multi.optional = true;
471 ifname_multi.network = ifc.getName();
472 ifname_multi.display_size = 6;
473 ifname_multi.write = ifname_multi.remove = function() {};
474
475 ifname_single.cfgvalue = ifname_multi.cfgvalue = function(section_id) {
476 var devs = ifc.getDevices() || L.toArray(ifc.getDevice()),
477 ifnames = [];
478
479 for (var i = 0; i < devs.length; i++)
480 ifnames.push(devs[i].getName());
481
482 return ifnames;
483 };
484
485 if (L.hasSystemFeature('firewall')) {
486 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>create</em> field to define a new zone and attach the interface to it.'));
487 o.network = ifc.getName();
488 o.optional = true;
489
490 o.cfgvalue = function(section_id) {
491 return firewall.getZoneByNetwork(ifc.getName()).then(function(zone) {
492 return (zone != null ? zone.getName() : null);
493 });
494 };
495
496 o.write = o.remove = function(section_id, value) {
497 return Promise.all([
498 firewall.getZoneByNetwork(ifc.getName()),
499 (value != null) ? firewall.getZone(value) : null
500 ]).then(function(data) {
501 var old_zone = data[0],
502 new_zone = data[1];
503
504 if (old_zone == null && new_zone == null && (value == null || value == ''))
505 return;
506
507 if (old_zone != null && new_zone != null && old_zone.getName() == new_zone.getName())
508 return;
509
510 if (old_zone != null)
511 old_zone.deleteNetwork(ifc.getName());
512
513 if (new_zone != null)
514 new_zone.addNetwork(ifc.getName());
515 else if (value != null)
516 return firewall.addZone(value).then(function(new_zone) {
517 new_zone.addNetwork(ifc.getName());
518 });
519 });
520 };
521 }
522
523 for (var i = 0; i < protocols.length; i++) {
524 proto_select.value(protocols[i].getProtocol(), protocols[i].getI18n());
525
526 if (protocols[i].getProtocol() != uci.get('network', s.section, 'proto'))
527 proto_switch.depends('proto', protocols[i].getProtocol());
528
529 if (!protocols[i].isVirtual()) {
530 type.depends('proto', protocols[i].getProtocol());
531 stp.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
532 igmp.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
533 ifname_single.depends({ type: '', proto: protocols[i].getProtocol() });
534 ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
535 }
536 }
537
538 if (L.hasSystemFeature('dnsmasq') || L.hasSystemFeature('odhcpd')) {
539 o = s.taboption('dhcp', form.SectionValue, '_dhcp', form.TypedSection, 'dhcp');
540 o.depends('proto', 'static');
541
542 ss = o.subsection;
543 ss.uciconfig = 'dhcp';
544 ss.addremove = false;
545 ss.anonymous = true;
546
547 ss.tab('general', _('General Setup'));
548 ss.tab('advanced', _('Advanced Settings'));
549 ss.tab('ipv6', _('IPv6 Settings'));
550
551 ss.filter = function(section_id) {
552 return (uci.get('dhcp', section_id, 'interface') == ifc.getName());
553 };
554
555 ss.renderSectionPlaceholder = function() {
556 return E('div', { 'class': 'cbi-section-create' }, [
557 E('p', _('No DHCP Server configured for this interface') + ' &#160; '),
558 E('button', {
559 'class': 'cbi-button cbi-button-add',
560 'title': _('Setup DHCP Server'),
561 'click': ui.createHandlerFn(this, function(section_id, ev) {
562 this.map.save(function() {
563 uci.add('dhcp', 'dhcp', section_id);
564 uci.set('dhcp', section_id, 'interface', section_id);
565 uci.set('dhcp', section_id, 'start', 100);
566 uci.set('dhcp', section_id, 'limit', 150);
567 uci.set('dhcp', section_id, 'leasetime', '12h');
568 });
569 }, ifc.getName())
570 }, _('Setup DHCP Server'))
571 ]);
572 };
573
574 ss.taboption('general', form.Flag, 'ignore', _('Ignore interface'), _('Disable <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> for this interface.'));
575
576 so = ss.taboption('general', form.Value, 'start', _('Start'), _('Lowest leased address as offset from the network address.'));
577 so.optional = true;
578 so.datatype = 'or(uinteger,ip4addr("nomask"))';
579 so.default = '100';
580
581 so = ss.taboption('general', form.Value, 'limit', _('Limit'), _('Maximum number of leased addresses.'));
582 so.optional = true;
583 so.datatype = 'uinteger';
584 so.default = '150';
585
586 so = ss.taboption('general', form.Value, 'leasetime', _('Lease time'), _('Expiry time of leased addresses, minimum is 2 minutes (<code>2m</code>).'));
587 so.optional = true;
588 so.default = '12h';
589
590 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.'));
591 so.default = so.enabled;
592
593 ss.taboption('advanced', form.Flag, 'force', _('Force'), _('Force DHCP on this network even if another server is detected.'));
594
595 // XXX: is this actually useful?
596 //ss.taboption('advanced', form.Value, 'name', _('Name'), _('Define a name for this network.'));
597
598 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.'));
599 so.optional = true;
600 so.datatype = 'ip4addr';
601
602 so.render = function(option_index, section_id, in_table) {
603 this.placeholder = get_netmask(s, true);
604 return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
605 };
606
607 so.validate = function(section_id, value) {
608 var node = this.map.findElement('id', this.cbid(section_id));
609 if (node)
610 node.querySelector('input').setAttribute('placeholder', get_netmask(s, false));
611 return form.Value.prototype.validate.apply(this, [ section_id, value ]);
612 };
613
614 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.'));
615
616 for (var i = 0; i < ss.children.length; i++)
617 if (ss.children[i].option != 'ignore')
618 ss.children[i].depends('ignore', '0');
619
620 so = ss.taboption('ipv6', form.ListValue, 'ra', _('Router Advertisement-Service'));
621 so.value('', _('disabled'));
622 so.value('server', _('server mode'));
623 so.value('relay', _('relay mode'));
624 so.value('hybrid', _('hybrid mode'));
625
626 so = ss.taboption('ipv6', form.ListValue, 'dhcpv6', _('DHCPv6-Service'));
627 so.value('', _('disabled'));
628 so.value('server', _('server mode'));
629 so.value('relay', _('relay mode'));
630 so.value('hybrid', _('hybrid mode'));
631
632 so = ss.taboption('ipv6', form.ListValue, 'ndp', _('NDP-Proxy'));
633 so.value('', _('disabled'));
634 so.value('relay', _('relay mode'));
635 so.value('hybrid', _('hybrid mode'));
636
637 so = ss.taboption('ipv6', form.Flag , 'master', _('Master'), _('Set this interface as master for the dhcpv6 relay.'));
638 so.depends('dhcpv6', 'relay');
639 so.depends('dhcpv6', 'hybrid');
640
641 so = ss.taboption('ipv6', form.ListValue, 'ra_management', _('DHCPv6-Mode'), _('Default is stateless + stateful'));
642 so.value('0', _('stateless'));
643 so.value('1', _('stateless + stateful'));
644 so.value('2', _('stateful-only'));
645 so.depends('dhcpv6', 'server');
646 so.depends('dhcpv6', 'hybrid');
647 so.default = '1';
648
649 so = ss.taboption('ipv6', form.Flag, 'ra_default', _('Always announce default router'), _('Announce as default router even if no public prefix is available.'));
650 so.depends('ra', 'server');
651 so.depends('ra', 'hybrid');
652
653 ss.taboption('ipv6', form.DynamicList, 'dns', _('Announced DNS servers'));
654 ss.taboption('ipv6', form.DynamicList, 'domain', _('Announced DNS domains'));
655 }
656
657 ifc.renderFormOptions(s);
658
659 for (var i = 0; i < s.children.length; i++) {
660 o = s.children[i];
661
662 switch (o.option) {
663 case 'proto':
664 case 'delegate':
665 case 'auto':
666 case 'type':
667 case 'stp':
668 case 'igmp_snooping':
669 case 'ifname_single':
670 case 'ifname_multi':
671 case '_dhcp':
672 case '_zone':
673 case '_switch_proto':
674 case '_ifacestat_modal':
675 continue;
676
677 default:
678 if (o.deps.length)
679 for (var j = 0; j < o.deps.length; j++)
680 o.deps[j].proto = protoval;
681 else
682 o.depends('proto', protoval);
683 }
684 }
685 }, this));
686 };
687
688 s.handleAdd = function(ev) {
689 var m2 = new form.Map('network'),
690 s2 = m2.section(form.NamedSection, '_new_'),
691 protocols = network.getProtocols(),
692 proto, name, bridge, ifname_single, ifname_multi;
693
694 protocols.sort(function(a, b) {
695 return a.getProtocol() > b.getProtocol();
696 });
697
698 s2.render = function() {
699 return Promise.all([
700 {},
701 this.renderUCISection('_new_')
702 ]).then(this.renderContents.bind(this));
703 };
704
705 name = s2.option(form.Value, 'name', _('Name'));
706 name.rmempty = false;
707 name.datatype = 'uciname';
708 name.placeholder = _('New interface name…');
709 name.validate = function(section_id, value) {
710 if (uci.get('network', value) != null)
711 return _('The interface name is already used');
712
713 var pr = network.getProtocol(proto.formvalue(section_id), value),
714 ifname = pr.isVirtual() ? '%s-%s'.format(pr.getProtocol(), value) : 'br-%s'.format(value);
715
716 if (value.length > 15)
717 return _('The interface name is too long');
718
719 return true;
720 };
721
722 proto = s2.option(form.ListValue, 'proto', _('Protocol'));
723 proto.validate = name.validate;
724
725 bridge = s2.option(form.Flag, 'type', _('Bridge interfaces'), _('Creates a bridge over specified interface(s)'));
726 bridge.modalonly = true;
727 bridge.disabled = '';
728 bridge.enabled = 'bridge';
729
730 ifname_single = s2.option(widgets.DeviceSelect, 'ifname_single', _('Interface'));
731 ifname_single.noaliases = false;
732 ifname_single.optional = false;
733
734 ifname_multi = s2.option(widgets.DeviceSelect, 'ifname_multi', _('Interface'));
735 ifname_multi.nobridges = true;
736 ifname_multi.noaliases = true;
737 ifname_multi.multiple = true;
738 ifname_multi.optional = true;
739 ifname_multi.display_size = 6;
740
741 for (var i = 0; i < protocols.length; i++) {
742 proto.value(protocols[i].getProtocol(), protocols[i].getI18n());
743
744 if (!protocols[i].isVirtual()) {
745 bridge.depends({ proto: protocols[i].getProtocol() });
746 ifname_single.depends({ type: '', proto: protocols[i].getProtocol() });
747 ifname_multi.depends({ type: 'bridge', proto: protocols[i].getProtocol() });
748 }
749 }
750
751 m2.render().then(L.bind(function(nodes) {
752 ui.showModal(_('Add new interface...'), [
753 nodes,
754 E('div', { 'class': 'right' }, [
755 E('button', {
756 'class': 'btn',
757 'click': ui.hideModal
758 }, _('Cancel')), ' ',
759 E('button', {
760 'class': 'cbi-button cbi-button-positive important',
761 'click': ui.createHandlerFn(this, function(ev) {
762 var nameval = name.isValid('_new_') ? name.formvalue('_new_') : null,
763 protoval = proto.isValid('_new_') ? proto.formvalue('_new_') : null;
764
765 if (nameval == null || protoval == null || nameval == '' || protoval == '')
766 return;
767
768 return m.save(function() {
769 var section_id = uci.add('network', 'interface', nameval);
770
771 uci.set('network', section_id, 'proto', protoval);
772
773 if (ifname_single.isActive('_new_')) {
774 uci.set('network', section_id, 'ifname', ifname_single.formvalue('_new_'));
775 }
776 else if (ifname_multi.isActive('_new_')) {
777 uci.set('network', section_id, 'type', 'bridge');
778 uci.set('network', section_id, 'ifname', L.toArray(ifname_multi.formvalue('_new_')).join(' '));
779 }
780 }).then(L.bind(m.children[0].renderMoreOptionsModal, m.children[0], nameval));
781 })
782 }, _('Create interface'))
783 ])
784 ], 'cbi-modal');
785
786 nodes.querySelector('[id="%s"] input[type="text"]'.format(name.cbid('_new_'))).focus();
787 }, this));
788 };
789
790 s.handleRemove = function(section_id, ev) {
791 return network.deleteNetwork(section_id).then(L.bind(function(section_id, ev) {
792 return form.GridSection.prototype.handleRemove.apply(this, [section_id, ev]);
793 }, this, section_id, ev));
794 };
795
796 o = s.option(form.DummyValue, '_ifacebox');
797 o.modalonly = false;
798 o.textvalue = function(section_id) {
799 var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0],
800 zone = net ? this.section.zones.filter(function(z) { return !!z.getNetworks().filter(function(n) { return n == section_id })[0] })[0] : null;
801
802 if (!net)
803 return;
804
805 var node = E('div', { 'class': 'ifacebox' }, [
806 E('div', {
807 'class': 'ifacebox-head',
808 'style': 'background-color:%s'.format(zone ? zone.getColor() : '#EEEEEE'),
809 'title': zone ? _('Part of zone %q').format(zone.getName()) : _('No zone assigned')
810 }, E('strong', net.getName().toUpperCase())),
811 E('div', {
812 'class': 'ifacebox-body',
813 'id': '%s-ifc-devices'.format(section_id),
814 'data-network': section_id
815 }, [
816 E('img', {
817 'src': L.resource('icons/ethernet_disabled.png'),
818 'style': 'width:16px; height:16px'
819 }),
820 E('br'), E('small', '?')
821 ])
822 ]);
823
824 render_ifacebox_status(node.childNodes[1], net);
825
826 return node;
827 };
828
829 o = s.option(form.DummyValue, '_ifacestat');
830 o.modalonly = false;
831 o.textvalue = function(section_id) {
832 var net = this.section.networks.filter(function(n) { return n.getName() == section_id })[0];
833
834 if (!net)
835 return;
836
837 var node = E('div', { 'id': '%s-ifc-description'.format(section_id) });
838
839 render_status(node, net, false);
840
841 return node;
842 };
843
844 o = s.taboption('advanced', form.Flag, 'delegate', _('Use builtin IPv6-management'));
845 o.modalonly = true;
846 o.default = o.enabled;
847
848 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).'));
849 o.modalonly = true;
850 o.render = function(option_index, section_id, in_table) {
851 var protoopt = this.section.children.filter(function(o) { return o.option == 'proto' })[0],
852 protoval = protoopt ? protoopt.cfgvalue(section_id) : null;
853
854 this.default = (protoval == 'static') ? this.enabled : this.disabled;
855 return this.super('render', [ option_index, section_id, in_table ]);
856 };
857
858
859 s = m.section(form.TypedSection, 'globals', _('Global network options'));
860 s.addremove = false;
861 s.anonymous = true;
862
863 o = s.option(form.Value, 'ula_prefix', _('IPv6 ULA-Prefix'));
864 o.datatype = 'cidr6';
865
866
867 if (dslModemType != null) {
868 s = m.section(form.TypedSection, 'dsl', _('DSL'));
869 s.anonymous = true;
870
871 o = s.option(form.ListValue, 'annex', _('Annex'));
872 o.value('a', _('Annex A + L + M (all)'));
873 o.value('b', _('Annex B (all)'));
874 o.value('j', _('Annex J (all)'));
875 o.value('m', _('Annex M (all)'));
876 o.value('bdmt', _('Annex B G.992.1'));
877 o.value('b2', _('Annex B G.992.3'));
878 o.value('b2p', _('Annex B G.992.5'));
879 o.value('at1', _('ANSI T1.413'));
880 o.value('admt', _('Annex A G.992.1'));
881 o.value('alite', _('Annex A G.992.2'));
882 o.value('a2', _('Annex A G.992.3'));
883 o.value('a2p', _('Annex A G.992.5'));
884 o.value('l', _('Annex L G.992.3 POTS 1'));
885 o.value('m2', _('Annex M G.992.3'));
886 o.value('m2p', _('Annex M G.992.5'));
887
888 o = s.option(form.ListValue, 'tone', _('Tone'));
889 o.value('', _('auto'));
890 o.value('a', _('A43C + J43 + A43'));
891 o.value('av', _('A43C + J43 + A43 + V43'));
892 o.value('b', _('B43 + B43C'));
893 o.value('bv', _('B43 + B43C + V43'));
894
895 if (dslModemType == 'vdsl') {
896 o = s.option(form.ListValue, 'xfer_mode', _('Encapsulation mode'));
897 o.value('', _('auto'));
898 o.value('atm', _('ATM (Asynchronous Transfer Mode)'));
899 o.value('ptm', _('PTM/EFM (Packet Transfer Mode)'));
900
901 o = s.option(form.ListValue, 'line_mode', _('DSL line mode'));
902 o.value('', _('auto'));
903 o.value('adsl', _('ADSL'));
904 o.value('vdsl', _('VDSL'));
905
906 o = s.option(form.ListValue, 'ds_snr_offset', _('Downstream SNR offset'));
907 o.default = '0';
908
909 for (var i = -100; i <= 100; i += 5)
910 o.value(i, _('%.1f dB').format(i / 10));
911 }
912
913 s.option(form.Value, 'firmware', _('Firmware File'));
914 }
915
916
917 // Show ATM bridge section if we have the capabilities
918 if (L.hasSystemFeature('br2684ctl')) {
919 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.'));
920
921 s.addremove = true;
922 s.anonymous = true;
923 s.addbtntitle = _('Add ATM Bridge');
924
925 s.handleAdd = function(ev) {
926 var sections = uci.sections('network', 'atm-bridge'),
927 max_unit = -1;
928
929 for (var i = 0; i < sections.length; i++) {
930 var unit = +sections[i].unit;
931
932 if (!isNaN(unit) && unit > max_unit)
933 max_unit = unit;
934 }
935
936 return this.map.save(function() {
937 var sid = uci.add('network', 'atm-bridge');
938
939 uci.set('network', sid, 'unit', max_unit + 1);
940 uci.set('network', sid, 'atmdev', 0);
941 uci.set('network', sid, 'encaps', 'llc');
942 uci.set('network', sid, 'payload', 'bridged');
943 uci.set('network', sid, 'vci', 35);
944 uci.set('network', sid, 'vpi', 8);
945 });
946 };
947
948 s.tab('general', _('General Setup'));
949 s.tab('advanced', _('Advanced Settings'));
950
951 o = s.taboption('general', form.Value, 'vci', _('ATM Virtual Channel Identifier (VCI)'));
952 s.taboption('general', form.Value, 'vpi', _('ATM Virtual Path Identifier (VPI)'));
953
954 o = s.taboption('general', form.ListValue, 'encaps', _('Encapsulation mode'));
955 o.value('llc', _('LLC'));
956 o.value('vc', _('VC-Mux'));
957
958 s.taboption('advanced', form.Value, 'atmdev', _('ATM device number'));
959 s.taboption('advanced', form.Value, 'unit', _('Bridge unit number'));
960
961 o = s.taboption('advanced', form.ListValue, 'payload', _('Forwarding mode'));
962 o.value('bridged', _('bridged'));
963 o.value('routed', _('routed'));
964 }
965
966
967 return m.render().then(L.bind(function(m, nodes) {
968 L.Poll.add(L.bind(function() {
969 var section_ids = m.children[0].cfgsections(),
970 tasks = [];
971
972 for (var i = 0; i < section_ids.length; i++) {
973 var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])),
974 dsc = row.querySelector('[data-name="_ifacestat"] > div'),
975 btn1 = row.querySelector('.cbi-section-actions .reconnect'),
976 btn2 = row.querySelector('.cbi-section-actions .down');
977
978 if (dsc.getAttribute('reconnect') == '') {
979 dsc.setAttribute('reconnect', '1');
980 tasks.push(fs.exec('/sbin/ifup', [section_ids[i]]).catch(function(e) {
981 ui.addNotification(null, E('p', e.message));
982 }));
983 }
984 else if (dsc.getAttribute('disconnect') == '') {
985 dsc.setAttribute('disconnect', '1');
986 tasks.push(fs.exec('/sbin/ifdown', [section_ids[i]]).catch(function(e) {
987 ui.addNotification(null, E('p', e.message));
988 }));
989 }
990 else if (dsc.getAttribute('reconnect') == '1') {
991 dsc.removeAttribute('reconnect');
992 btn1.classList.remove('spinning');
993 btn1.disabled = false;
994 }
995 else if (dsc.getAttribute('disconnect') == '1') {
996 dsc.removeAttribute('disconnect');
997 btn2.classList.remove('spinning');
998 btn2.disabled = false;
999 }
1000 }
1001
1002 return Promise.all(tasks)
1003 .then(L.bind(network.getNetworks, network))
1004 .then(L.bind(this.poll_status, this, nodes));
1005 }, this), 5);
1006
1007 return nodes;
1008 }, this, m));
1009 }
1010 });