luci-mod-status,mod-network: Added fqdn-name to DHCPv4 lease table
[project/luci.git] / modules / luci-mod-network / htdocs / luci-static / resources / view / network / dhcp.js
1 'use strict';
2 'require view';
3 'require dom';
4 'require poll';
5 'require rpc';
6 'require uci';
7 'require form';
8 'require network';
9 'require validation';
10
11 var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus, CBILease6Status;
12
13 callHostHints = rpc.declare({
14 object: 'luci-rpc',
15 method: 'getHostHints',
16 expect: { '': {} }
17 });
18
19 callDUIDHints = rpc.declare({
20 object: 'luci-rpc',
21 method: 'getDUIDHints',
22 expect: { '': {} }
23 });
24
25 callDHCPLeases = rpc.declare({
26 object: 'luci-rpc',
27 method: 'getDHCPLeases',
28 expect: { '': {} }
29 });
30
31 CBILeaseStatus = form.DummyValue.extend({
32 renderWidget: function(section_id, option_id, cfgvalue) {
33 return E([
34 E('h4', _('Active DHCP Leases')),
35 E('table', { 'id': 'lease_status_table', 'class': 'table' }, [
36 E('tr', { 'class': 'tr table-titles' }, [
37 E('th', { 'class': 'th' }, _('Hostname')),
38 E('th', { 'class': 'th' }, _('IPv4 address')),
39 E('th', { 'class': 'th' }, _('MAC address')),
40 E('th', { 'class': 'th' }, _('Lease time remaining'))
41 ]),
42 E('tr', { 'class': 'tr placeholder' }, [
43 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
44 ])
45 ])
46 ]);
47 }
48 });
49
50 CBILease6Status = form.DummyValue.extend({
51 renderWidget: function(section_id, option_id, cfgvalue) {
52 return E([
53 E('h4', _('Active DHCPv6 Leases')),
54 E('table', { 'id': 'lease6_status_table', 'class': 'table' }, [
55 E('tr', { 'class': 'tr table-titles' }, [
56 E('th', { 'class': 'th' }, _('Host')),
57 E('th', { 'class': 'th' }, _('IPv6 address')),
58 E('th', { 'class': 'th' }, _('DUID')),
59 E('th', { 'class': 'th' }, _('Lease time remaining'))
60 ]),
61 E('tr', { 'class': 'tr placeholder' }, [
62 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
63 ])
64 ])
65 ]);
66 }
67 });
68
69 function calculateNetwork(addr, mask) {
70 addr = validation.parseIPv4(String(addr));
71
72 if (!isNaN(mask))
73 mask = validation.parseIPv4(network.prefixToMask(+mask));
74 else
75 mask = validation.parseIPv4(String(mask));
76
77 if (addr == null || mask == null)
78 return null;
79
80 return [
81 [
82 addr[0] & (mask[0] >>> 0 & 255),
83 addr[1] & (mask[1] >>> 0 & 255),
84 addr[2] & (mask[2] >>> 0 & 255),
85 addr[3] & (mask[3] >>> 0 & 255)
86 ].join('.'),
87 mask.join('.')
88 ];
89 }
90
91 function getDHCPPools() {
92 return uci.load('dhcp').then(function() {
93 let sections = uci.sections('dhcp', 'dhcp'),
94 tasks = [], pools = [];
95
96 for (var i = 0; i < sections.length; i++) {
97 if (sections[i].ignore == '1' || !sections[i].interface)
98 continue;
99
100 tasks.push(network.getNetwork(sections[i].interface).then(L.bind(function(section_id, net) {
101 var cidr = net ? (net.getIPAddrs()[0] || '').split('/') : null;
102
103 if (cidr && cidr.length == 2) {
104 var net_mask = calculateNetwork(cidr[0], cidr[1]);
105
106 pools.push({
107 section_id: section_id,
108 network: net_mask[0],
109 netmask: net_mask[1]
110 });
111 }
112 }, null, sections[i]['.name'])));
113 }
114
115 return Promise.all(tasks).then(function() {
116 return pools;
117 });
118 });
119 }
120
121 function validateHostname(sid, s) {
122 if (s == null || s == '')
123 return true;
124
125 if (s.length > 256)
126 return _('Expecting: %s').format(_('valid hostname'));
127
128 var labels = s.replace(/^\.+|\.$/g, '').split(/\./);
129
130 for (var i = 0; i < labels.length; i++)
131 if (!labels[i].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
132 return _('Expecting: %s').format(_('valid hostname'));
133
134 return true;
135 }
136
137 function validateAddressList(sid, s) {
138 if (s == null || s == '')
139 return true;
140
141 var m = s.match(/^\/(.+)\/$/),
142 names = m ? m[1].split(/\//) : [ s ];
143
144 for (var i = 0; i < names.length; i++) {
145 var res = validateHostname(sid, names[i]);
146
147 if (res !== true)
148 return res;
149 }
150
151 return true;
152 }
153
154 function validateServerSpec(sid, s) {
155 if (s == null || s == '')
156 return true;
157
158 var m = s.match(/^(?:\/(.+)\/)?(.*)$/);
159 if (!m)
160 return _('Expecting: %s').format(_('valid hostname'));
161
162 var res = validateAddressList(sid, m[1]);
163 if (res !== true)
164 return res;
165
166 if (m[2] == '' || m[2] == '#')
167 return true;
168
169 // ipaddr%scopeid#srvport@source@interface#srcport
170
171 m = m[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
172
173 if (!m)
174 return _('Expecting: %s').format(_('valid IP address'));
175
176 if (validation.parseIPv4(m[1])) {
177 if (m[3] != null && !validation.parseIPv4(m[3]))
178 return _('Expecting: %s').format(_('valid IPv4 address'));
179 }
180 else if (validation.parseIPv6(m[1])) {
181 if (m[3] != null && !validation.parseIPv6(m[3]))
182 return _('Expecting: %s').format(_('valid IPv6 address'));
183 }
184 else {
185 return _('Expecting: %s').format(_('valid IP address'));
186 }
187
188 if ((m[2] != null && +m[2] > 65535) || (m[4] != null && +m[4] > 65535))
189 return _('Expecting: %s').format(_('valid port value'));
190
191 return true;
192 }
193
194 function validateMACAddr(pools, sid, s) {
195 if (s == null || s == '')
196 return true;
197
198 var leases = uci.sections('dhcp', 'host'),
199 this_macs = L.toArray(s).map(function(m) { return m.toUpperCase() });
200
201 for (var i = 0; i < pools.length; i++) {
202 var this_net_mask = calculateNetwork(this.section.formvalue(sid, 'ip'), pools[i].netmask);
203
204 if (!this_net_mask)
205 continue;
206
207 for (var j = 0; j < leases.length; j++) {
208 if (leases[j]['.name'] == sid || !leases[j].ip)
209 continue;
210
211 var lease_net_mask = calculateNetwork(leases[j].ip, pools[i].netmask);
212
213 if (!lease_net_mask || this_net_mask[0] != lease_net_mask[0])
214 continue;
215
216 var lease_macs = L.toArray(leases[j].mac).map(function(m) { return m.toUpperCase() });
217
218 for (var k = 0; k < lease_macs.length; k++)
219 for (var l = 0; l < this_macs.length; l++)
220 if (lease_macs[k] == this_macs[l])
221 return _('The MAC address %h is already used by another static lease in the same DHCP pool').format(this_macs[l]);
222 }
223 }
224
225 return true;
226 }
227
228 return view.extend({
229 load: function() {
230 return Promise.all([
231 callHostHints(),
232 callDUIDHints(),
233 getDHCPPools()
234 ]);
235 },
236
237 render: function(hosts_duids_pools) {
238 var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
239 hosts = hosts_duids_pools[0],
240 duids = hosts_duids_pools[1],
241 pools = hosts_duids_pools[2],
242 m, s, o, ss, so;
243
244 m = new form.Map('dhcp', _('DHCP and DNS'), _('Dnsmasq is a combined <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server and <abbr title="Domain Name System">DNS</abbr>-Forwarder for <abbr title="Network Address Translation">NAT</abbr> firewalls'));
245
246 s = m.section(form.TypedSection, 'dnsmasq', _('Server Settings'));
247 s.anonymous = true;
248 s.addremove = false;
249
250 s.tab('general', _('General Settings'));
251 s.tab('files', _('Resolv and Hosts Files'));
252 s.tab('tftp', _('TFTP Settings'));
253 s.tab('advanced', _('Advanced Settings'));
254 s.tab('leases', _('Static Leases'));
255
256 s.taboption('general', form.Flag, 'domainneeded',
257 _('Domain required'),
258 _('Don\'t forward <abbr title="Domain Name System">DNS</abbr>-Requests without <abbr title="Domain Name System">DNS</abbr>-Name'));
259
260 s.taboption('general', form.Flag, 'authoritative',
261 _('Authoritative'),
262 _('This is the only <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> in the local network'));
263
264
265 s.taboption('files', form.Flag, 'readethers',
266 _('Use <code>/etc/ethers</code>'),
267 _('Read <code>/etc/ethers</code> to configure the <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server'));
268
269 s.taboption('files', form.Value, 'leasefile',
270 _('Leasefile'),
271 _('file where given <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-leases will be stored'));
272
273 s.taboption('files', form.Flag, 'noresolv',
274 _('Ignore resolve file')).optional = true;
275
276 o = s.taboption('files', form.Value, 'resolvfile',
277 _('Resolve file'),
278 _('local <abbr title="Domain Name System">DNS</abbr> file'));
279
280 o.depends('noresolv', '0');
281 o.placeholder = '/tmp/resolv.conf.d/resolv.conf.auto';
282 o.optional = true;
283
284
285 s.taboption('files', form.Flag, 'nohosts',
286 _('Ignore <code>/etc/hosts</code>')).optional = true;
287
288 s.taboption('files', form.DynamicList, 'addnhosts',
289 _('Additional Hosts files')).optional = true;
290
291 o = s.taboption('advanced', form.Flag, 'quietdhcp',
292 _('Suppress logging'),
293 _('Suppress logging of the routine operation of these protocols'));
294 o.optional = true;
295
296 o = s.taboption('advanced', form.Flag, 'sequential_ip',
297 _('Allocate IP sequentially'),
298 _('Allocate IP addresses sequentially, starting from the lowest available address'));
299 o.optional = true;
300
301 o = s.taboption('advanced', form.Flag, 'boguspriv',
302 _('Filter private'),
303 _('Do not forward reverse lookups for local networks'));
304 o.default = o.enabled;
305
306 s.taboption('advanced', form.Flag, 'filterwin2k',
307 _('Filter useless'),
308 _('Do not forward requests that cannot be answered by public name servers'));
309
310
311 s.taboption('advanced', form.Flag, 'localise_queries',
312 _('Localise queries'),
313 _('Localise hostname depending on the requesting subnet if multiple IPs are available'));
314
315 if (L.hasSystemFeature('dnsmasq', 'dnssec')) {
316 o = s.taboption('advanced', form.Flag, 'dnssec',
317 _('DNSSEC'));
318 o.optional = true;
319
320 o = s.taboption('advanced', form.Flag, 'dnsseccheckunsigned',
321 _('DNSSEC check unsigned'),
322 _('Requires upstream supports DNSSEC; verify unsigned domain responses really come from unsigned domains'));
323 o.default = o.enabled;
324 o.optional = true;
325 }
326
327 s.taboption('general', form.Value, 'local',
328 _('Local server'),
329 _('Local domain specification. Names matching this domain are never forwarded and are resolved from DHCP or hosts files only'));
330
331 s.taboption('general', form.Value, 'domain',
332 _('Local domain'),
333 _('Local domain suffix appended to DHCP names and hosts file entries'));
334
335 s.taboption('advanced', form.Flag, 'expandhosts',
336 _('Expand hosts'),
337 _('Add local domain suffix to names served from hosts files'));
338
339 s.taboption('advanced', form.Flag, 'nonegcache',
340 _('No negative cache'),
341 _('Do not cache negative replies, e.g. for not existing domains'));
342
343 s.taboption('advanced', form.Value, 'serversfile',
344 _('Additional servers file'),
345 _('This file may contain lines like \'server=/domain/1.2.3.4\' or \'server=1.2.3.4\' for domain-specific or full upstream <abbr title="Domain Name System">DNS</abbr> servers.'));
346
347 s.taboption('advanced', form.Flag, 'strictorder',
348 _('Strict order'),
349 _('<abbr title="Domain Name System">DNS</abbr> servers will be queried in the order of the resolvfile')).optional = true;
350
351 s.taboption('advanced', form.Flag, 'allservers',
352 _('All Servers'),
353 _('Query all available upstream <abbr title="Domain Name System">DNS</abbr> servers')).optional = true;
354
355 o = s.taboption('advanced', form.DynamicList, 'bogusnxdomain', _('Bogus NX Domain Override'),
356 _('List of hosts that supply bogus NX domain results'));
357
358 o.optional = true;
359 o.placeholder = '67.215.65.132';
360
361
362 s.taboption('general', form.Flag, 'logqueries',
363 _('Log queries'),
364 _('Write received DNS requests to syslog')).optional = true;
365
366 o = s.taboption('general', form.DynamicList, 'server', _('DNS forwardings'),
367 _('List of <abbr title="Domain Name System">DNS</abbr> servers to forward requests to'));
368
369 o.optional = true;
370 o.placeholder = '/example.org/10.1.2.3';
371 o.validate = validateServerSpec;
372
373
374 o = s.taboption('general', form.DynamicList, 'address', _('Addresses'),
375 _('List of domains to force to an IP address.'));
376
377 o.optional = true;
378 o.placeholder = '/router.local/192.168.0.1';
379
380
381 o = s.taboption('general', form.Flag, 'rebind_protection',
382 _('Rebind protection'),
383 _('Discard upstream RFC1918 responses'));
384
385 o.rmempty = false;
386
387
388 o = s.taboption('general', form.Flag, 'rebind_localhost',
389 _('Allow localhost'),
390 _('Allow upstream responses in the 127.0.0.0/8 range, e.g. for RBL services'));
391
392 o.depends('rebind_protection', '1');
393
394
395 o = s.taboption('general', form.DynamicList, 'rebind_domain',
396 _('Domain whitelist'),
397 _('List of domains to allow RFC1918 responses for'));
398 o.optional = true;
399
400 o.depends('rebind_protection', '1');
401 o.placeholder = 'ihost.netflix.com';
402 o.validate = validateAddressList;
403
404
405 o = s.taboption('advanced', form.Value, 'port',
406 _('<abbr title="Domain Name System">DNS</abbr> server port'),
407 _('Listening port for inbound DNS queries'));
408
409 o.optional = true;
410 o.datatype = 'port';
411 o.placeholder = 53;
412
413
414 o = s.taboption('advanced', form.Value, 'queryport',
415 _('<abbr title="Domain Name System">DNS</abbr> query port'),
416 _('Fixed source port for outbound DNS queries'));
417
418 o.optional = true;
419 o.datatype = 'port';
420 o.placeholder = _('any');
421
422
423 o = s.taboption('advanced', form.Value, 'dhcpleasemax',
424 _('<abbr title="maximal">Max.</abbr> <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> leases'),
425 _('Maximum allowed number of active DHCP leases'));
426
427 o.optional = true;
428 o.datatype = 'uinteger';
429 o.placeholder = _('unlimited');
430
431
432 o = s.taboption('advanced', form.Value, 'ednspacket_max',
433 _('<abbr title="maximal">Max.</abbr> <abbr title="Extension Mechanisms for Domain Name System">EDNS0</abbr> packet size'),
434 _('Maximum allowed size of EDNS.0 UDP packets'));
435
436 o.optional = true;
437 o.datatype = 'uinteger';
438 o.placeholder = 1280;
439
440
441 o = s.taboption('advanced', form.Value, 'dnsforwardmax',
442 _('<abbr title="maximal">Max.</abbr> concurrent queries'),
443 _('Maximum allowed number of concurrent DNS queries'));
444
445 o.optional = true;
446 o.datatype = 'uinteger';
447 o.placeholder = 150;
448
449 o = s.taboption('advanced', form.Value, 'cachesize',
450 _('Size of DNS query cache'),
451 _('Number of cached DNS entries (max is 10000, 0 is no caching)'));
452 o.optional = true;
453 o.datatype = 'range(0,10000)';
454 o.placeholder = 150;
455
456 s.taboption('tftp', form.Flag, 'enable_tftp',
457 _('Enable TFTP server')).optional = true;
458
459 o = s.taboption('tftp', form.Value, 'tftp_root',
460 _('TFTP server root'),
461 _('Root directory for files served via TFTP'));
462
463 o.optional = true;
464 o.depends('enable_tftp', '1');
465 o.placeholder = '/';
466
467
468 o = s.taboption('tftp', form.Value, 'dhcp_boot',
469 _('Network boot image'),
470 _('Filename of the boot image advertised to clients'));
471
472 o.optional = true;
473 o.depends('enable_tftp', '1');
474 o.placeholder = 'pxelinux.0';
475
476 o = s.taboption('general', form.Flag, 'localservice',
477 _('Local Service Only'),
478 _('Limit DNS service to subnets interfaces on which we are serving DNS.'));
479 o.optional = false;
480 o.rmempty = false;
481
482 o = s.taboption('general', form.Flag, 'nonwildcard',
483 _('Non-wildcard'),
484 _('Bind dynamically to interfaces rather than wildcard address (recommended as linux default)'));
485 o.default = o.enabled;
486 o.optional = false;
487 o.rmempty = true;
488
489 o = s.taboption('general', form.DynamicList, 'interface',
490 _('Listen Interfaces'),
491 _('Limit listening to these interfaces, and loopback.'));
492 o.optional = true;
493
494 o = s.taboption('general', form.DynamicList, 'notinterface',
495 _('Exclude interfaces'),
496 _('Prevent listening on these interfaces.'));
497 o.optional = true;
498
499 o = s.taboption('leases', form.SectionValue, '__leases__', form.GridSection, 'host', null,
500 _('Static leases are used to assign fixed IP addresses and symbolic hostnames to DHCP clients. They are also required for non-dynamic interface configurations where only hosts with a corresponding lease are served.') + '<br />' +
501 _('Use the <em>Add</em> Button to add a new lease entry. The <em>MAC address</em> identifies the host, the <em>IPv4 address</em> specifies the fixed address to use, and the <em>Hostname</em> is assigned as a symbolic name to the requesting host. The optional <em>Lease time</em> can be used to set non-standard host-specific lease time, e.g. 12h, 3d or infinite.'));
502
503 ss = o.subsection;
504
505 ss.addremove = true;
506 ss.anonymous = true;
507 ss.sortable = true;
508
509 so = ss.option(form.Value, 'name', _('Hostname'));
510 so.validate = validateHostname;
511 so.rmempty = true;
512 so.write = function(section, value) {
513 uci.set('dhcp', section, 'name', value);
514 uci.set('dhcp', section, 'dns', '1');
515 };
516 so.remove = function(section) {
517 uci.unset('dhcp', section, 'name');
518 uci.unset('dhcp', section, 'dns');
519 };
520
521 so = ss.option(form.Value, 'mac', _('<abbr title="Media Access Control">MAC</abbr>-Address'));
522 so.datatype = 'list(macaddr)';
523 so.rmempty = true;
524 so.cfgvalue = function(section) {
525 var macs = L.toArray(uci.get('dhcp', section, 'mac')),
526 result = [];
527
528 for (var i = 0, mac; (mac = macs[i]) != null; i++)
529 if (/^([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2})$/.test(mac))
530 result.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
531 parseInt(RegExp.$1, 16), parseInt(RegExp.$2, 16),
532 parseInt(RegExp.$3, 16), parseInt(RegExp.$4, 16),
533 parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)));
534
535 return result.length ? result.join(' ') : null;
536 };
537 so.renderWidget = function(section_id, option_index, cfgvalue) {
538 var node = form.Value.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]),
539 ipopt = this.section.children.filter(function(o) { return o.option == 'ip' })[0];
540
541 node.addEventListener('cbi-dropdown-change', L.bind(function(ipopt, section_id, ev) {
542 var mac = ev.detail.value.value;
543 if (mac == null || mac == '' || !hosts[mac])
544 return;
545
546 var iphint = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
547 if (iphint == null)
548 return;
549
550 var ip = ipopt.formvalue(section_id);
551 if (ip != null && ip != '')
552 return;
553
554 var node = ipopt.map.findElement('id', ipopt.cbid(section_id));
555 if (node)
556 dom.callClassMethod(node, 'setValue', iphint);
557 }, this, ipopt, section_id));
558
559 return node;
560 };
561 so.validate = validateMACAddr.bind(so, pools);
562 Object.keys(hosts).forEach(function(mac) {
563 var hint = hosts[mac].name || L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
564 so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
565 });
566
567 so = ss.option(form.Value, 'ip', _('<abbr title="Internet Protocol Version 4">IPv4</abbr>-Address'));
568 so.datatype = 'or(ip4addr,"ignore")';
569 so.validate = function(section, value) {
570 var m = this.section.formvalue(section, 'mac'),
571 n = this.section.formvalue(section, 'name');
572
573 if ((m == null || m == '') && (n == null || n == ''))
574 return _('One of hostname or mac address must be specified!');
575
576 if (value == null || value == '' || value == 'ignore')
577 return true;
578
579 var leases = uci.sections('dhcp', 'host');
580
581 for (var i = 0; i < leases.length; i++)
582 if (leases[i]['.name'] != section && leases[i].ip == value)
583 return _('The IP address %h is already used by another static lease').format(value);
584
585
586 for (var i = 0; i < pools.length; i++) {
587 var net_mask = calculateNetwork(value, pools[i].netmask);
588
589 if (net_mask && net_mask[0] == pools[i].network)
590 return true;
591 }
592
593 return _('The IP address is outside of any DHCP pool address range');
594 };
595
596 var ipaddrs = {};
597
598 Object.keys(hosts).forEach(function(mac) {
599 var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
600
601 for (var i = 0; i < addrs.length; i++)
602 ipaddrs[addrs[i]] = hosts[mac].name;
603 });
604
605 L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
606 so.value(ipv4, ipaddrs[ipv4] ? '%s (%s)'.format(ipv4, ipaddrs[ipv4]) : ipv4);
607 });
608
609 so = ss.option(form.Value, 'leasetime', _('Lease time'));
610 so.rmempty = true;
611
612 so = ss.option(form.Value, 'duid', _('<abbr title="The DHCP Unique Identifier">DUID</abbr>'));
613 so.datatype = 'and(rangelength(20,36),hexstring)';
614 Object.keys(duids).forEach(function(duid) {
615 so.value(duid, '%s (%s)'.format(duid, duids[duid].hostname || duids[duid].macaddr || duids[duid].ip6addr || '?'));
616 });
617
618 so = ss.option(form.Value, 'hostid', _('<abbr title="Internet Protocol Version 6">IPv6</abbr>-Suffix (hex)'));
619
620 o = s.taboption('leases', CBILeaseStatus, '__status__');
621
622 if (has_dhcpv6)
623 o = s.taboption('leases', CBILease6Status, '__status6__');
624
625 return m.render().then(function(mapEl) {
626 poll.add(function() {
627 return callDHCPLeases().then(function(leaseinfo) {
628 var leases = Array.isArray(leaseinfo.dhcp_leases) ? leaseinfo.dhcp_leases : [],
629 leases6 = Array.isArray(leaseinfo.dhcp6_leases) ? leaseinfo.dhcp6_leases : [];
630
631 cbi_update_table(mapEl.querySelector('#lease_status_table'),
632 leases.map(function(lease) {
633 var exp;
634
635 if (lease.expires === false)
636 exp = E('em', _('unlimited'));
637 else if (lease.expires <= 0)
638 exp = E('em', _('expired'));
639 else
640 exp = '%t'.format(lease.expires);
641
642 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
643 name = hint ? hint.name : null,
644 host = null;
645
646 if (name && lease.hostname && lease.hostname != name)
647 host = '%s (%s)'.format(lease.hostname, name);
648 else if (lease.hostname)
649 host = lease.hostname;
650
651 return [
652 host || '-',
653 lease.ipaddr,
654 lease.macaddr,
655 exp
656 ];
657 }),
658 E('em', _('There are no active leases')));
659
660 if (has_dhcpv6) {
661 cbi_update_table(mapEl.querySelector('#lease6_status_table'),
662 leases6.map(function(lease) {
663 var exp;
664
665 if (lease.expires === false)
666 exp = E('em', _('unlimited'));
667 else if (lease.expires <= 0)
668 exp = E('em', _('expired'));
669 else
670 exp = '%t'.format(lease.expires);
671
672 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
673 name = hint ? (hint.name || L.toArray(hint.ipaddrs || hint.ipv4)[0] || L.toArray(hint.ip6addrs || hint.ipv6)[0]) : null,
674 host = null;
675
676 if (name && lease.hostname && lease.hostname != name && lease.ip6addr != name)
677 host = '%s (%s)'.format(lease.hostname, name);
678 else if (lease.hostname)
679 host = lease.hostname;
680 else if (name)
681 host = name;
682
683 return [
684 host || '-',
685 lease.ip6addrs ? lease.ip6addrs.join(' ') : lease.ip6addr,
686 lease.duid,
687 exp
688 ];
689 }),
690 E('em', _('There are no active leases')));
691 }
692 });
693 });
694
695 return mapEl;
696 });
697 }
698 });