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