Merge pull request #1735 from sumpfralle/olsr-jsoninfo-parser-handle-empty-result
[oweals/luci.git] / modules / luci-mod-network / htdocs / luci-static / resources / view / network / dhcp.js
1 'use strict';
2 'require rpc';
3 'require uci';
4 'require form';
5
6 var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus;
7
8 callHostHints = rpc.declare({
9         object: 'luci',
10         method: 'getHostHints',
11         expect: { '': {} }
12 });
13
14 callDUIDHints = rpc.declare({
15         object: 'luci',
16         method: 'getDUIDHints',
17         expect: { '': {} }
18 });
19
20 callDHCPLeases = rpc.declare({
21         object: 'luci',
22         method: 'getDHCPLeases',
23         params: [ 'family' ],
24         expect: { dhcp_leases: [] }
25 });
26
27 CBILeaseStatus = form.DummyValue.extend({
28         renderWidget: function(section_id, option_id, cfgvalue) {
29                 return E([
30                         E('h4', _('Active DHCP Leases')),
31                         E('div', { 'id': 'lease_status_table', 'class': 'table' }, [
32                                 E('div', { 'class': 'tr table-titles' }, [
33                                         E('div', { 'class': 'th' }, _('Hostname')),
34                                         E('div', { 'class': 'th' }, _('IPv4-Address')),
35                                         E('div', { 'class': 'th' }, _('MAC-Address')),
36                                         E('div', { 'class': 'th' }, _('Leasetime remaining'))
37                                 ]),
38                                 E('div', { 'class': 'tr placeholder' }, [
39                                         E('div', { 'class': 'td' }, E('em', _('Collecting data...')))
40                                 ])
41                         ])
42                 ]);
43         }
44 });
45
46 return L.view.extend({
47
48
49         load: function() {
50                 return Promise.all([
51                         callHostHints(),
52                         callDUIDHints()
53                 ]);
54         },
55
56         render: function(hosts_duids) {
57                 var hosts = hosts_duids[0],
58                     duids = hosts_duids[1],
59                     m, s, o, ss, so;
60
61                 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'));
62
63                 s = m.section(form.TypedSection, 'dnsmasq', _('Server Settings'));
64                 s.anonymous = true;
65                 s.addremove = false;
66
67                 s.tab('general', _('General Settings'));
68                 s.tab('files', _('Resolv and Hosts Files'));
69                 s.tab('tftp', _('TFTP Settings'));
70                 s.tab('advanced', _('Advanced Settings'));
71                 s.tab('leases', _('Static Leases'));
72
73                 s.taboption('general', form.Flag, 'domainneeded',
74                         _('Domain required'),
75                         _('Don\'t forward <abbr title="Domain Name System">DNS</abbr>-Requests without <abbr title="Domain Name System">DNS</abbr>-Name'));
76
77                 s.taboption('general', form.Flag, 'authoritative',
78                         _('Authoritative'),
79                         _('This is the only <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> in the local network'));
80
81
82                 s.taboption('files', form.Flag, 'readethers',
83                         _('Use <code>/etc/ethers</code>'),
84                         _('Read <code>/etc/ethers</code> to configure the <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server'));
85
86                 s.taboption('files', form.Value, 'leasefile',
87                         _('Leasefile'),
88                         _('file where given <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-leases will be stored'));
89
90                 s.taboption('files', form.Flag, 'noresolv',
91                         _('Ignore resolve file')).optional = true;
92
93                 o = s.taboption('files', form.Value, 'resolvfile',
94                         _('Resolve file'),
95                         _('local <abbr title="Domain Name System">DNS</abbr> file'));
96
97                 o.depends('noresolv', '');
98                 o.optional = true;
99
100
101                 s.taboption('files', form.Flag, 'nohosts',
102                         _('Ignore <code>/etc/hosts</code>')).optional = true;
103
104                 s.taboption('files', form.DynamicList, 'addnhosts',
105                         _('Additional Hosts files')).optional = true;
106
107                 o = s.taboption('advanced', form.Flag, 'quietdhcp',
108                         _('Suppress logging'),
109                         _('Suppress logging of the routine operation of these protocols'));
110                 o.optional = true;
111
112                 o = s.taboption('advanced', form.Flag, 'sequential_ip',
113                         _('Allocate IP sequentially'),
114                         _('Allocate IP addresses sequentially, starting from the lowest available address'));
115                 o.optional = true;
116
117                 o = s.taboption('advanced', form.Flag, 'boguspriv',
118                         _('Filter private'),
119                         _('Do not forward reverse lookups for local networks'));
120                 o.default = o.enabled;
121
122                 s.taboption('advanced', form.Flag, 'filterwin2k',
123                         _('Filter useless'),
124                         _('Do not forward requests that cannot be answered by public name servers'));
125
126
127                 s.taboption('advanced', form.Flag, 'localise_queries',
128                         _('Localise queries'),
129                         _('Localise hostname depending on the requesting subnet if multiple IPs are available'));
130
131                 //local have_dnssec_support = luci.util.checklib('/usr/sbin/dnsmasq', 'libhogweed.so');
132                 var have_dnssec_support = true;
133
134                 if (have_dnssec_support) {
135                         o = s.taboption('advanced', form.Flag, 'dnssec',
136                                 _('DNSSEC'));
137                         o.optional = true;
138
139                         o = s.taboption('advanced', form.Flag, 'dnsseccheckunsigned',
140                                 _('DNSSEC check unsigned'),
141                                 _('Requires upstream supports DNSSEC; verify unsigned domain responses really come from unsigned domains'));
142                         o.optional = true;
143                 }
144
145                 s.taboption('general', form.Value, 'local',
146                         _('Local server'),
147                         _('Local domain specification. Names matching this domain are never forwarded and are resolved from DHCP or hosts files only'));
148
149                 s.taboption('general', form.Value, 'domain',
150                         _('Local domain'),
151                         _('Local domain suffix appended to DHCP names and hosts file entries'));
152
153                 s.taboption('advanced', form.Flag, 'expandhosts',
154                         _('Expand hosts'),
155                         _('Add local domain suffix to names served from hosts files'));
156
157                 s.taboption('advanced', form.Flag, 'nonegcache',
158                         _('No negative cache'),
159                         _('Do not cache negative replies, e.g. for not existing domains'));
160
161                 s.taboption('advanced', form.Value, 'serversfile',
162                         _('Additional servers file'),
163                         _('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.'));
164
165                 s.taboption('advanced', form.Flag, 'strictorder',
166                         _('Strict order'),
167                         _('<abbr title="Domain Name System">DNS</abbr> servers will be queried in the order of the resolvfile')).optional = true;
168
169                 s.taboption('advanced', form.Flag, 'allservers',
170                         _('All Servers'),
171                         _('Query all available upstream <abbr title="Domain Name System">DNS</abbr> servers')).optional = true;
172
173                 o = s.taboption('advanced', form.DynamicList, 'bogusnxdomain', _('Bogus NX Domain Override'),
174                         _('List of hosts that supply bogus NX domain results'));
175
176                 o.optional = true;
177                 o.placeholder = '67.215.65.132';
178
179
180                 s.taboption('general', form.Flag, 'logqueries',
181                         _('Log queries'),
182                         _('Write received DNS requests to syslog')).optional = true;
183
184                 o = s.taboption('general', form.DynamicList, 'server', _('DNS forwardings'),
185                         _('List of <abbr title="Domain Name System">DNS</abbr> servers to forward requests to'));
186
187                 o.optional = true;
188                 o.placeholder = '/example.org/10.1.2.3';
189
190
191                 o = s.taboption('general', form.Flag, 'rebind_protection',
192                         _('Rebind protection'),
193                         _('Discard upstream RFC1918 responses'));
194
195                 o.rmempty = false;
196
197
198                 o = s.taboption('general', form.Flag, 'rebind_localhost',
199                         _('Allow localhost'),
200                         _('Allow upstream responses in the 127.0.0.0/8 range, e.g. for RBL services'));
201
202                 o.depends('rebind_protection', '1');
203
204
205                 o = s.taboption('general', form.DynamicList, 'rebind_domain',
206                         _('Domain whitelist'),
207                         _('List of domains to allow RFC1918 responses for'));
208                 o.optional = true;
209
210                 o.depends('rebind_protection', '1');
211                 o.datatype = 'host(1)';
212                 o.placeholder = 'ihost.netflix.com';
213
214
215                 o = s.taboption('advanced', form.Value, 'port',
216                         _('<abbr title="Domain Name System">DNS</abbr> server port'),
217                         _('Listening port for inbound DNS queries'));
218
219                 o.optional = true;
220                 o.datatype = 'port';
221                 o.placeholder = 53;
222
223
224                 o = s.taboption('advanced', form.Value, 'queryport',
225                         _('<abbr title="Domain Name System">DNS</abbr> query port'),
226                         _('Fixed source port for outbound DNS queries'));
227
228                 o.optional = true;
229                 o.datatype = 'port';
230                 o.placeholder = _('any');
231
232
233                 o = s.taboption('advanced', form.Value, 'dhcpleasemax',
234                         _('<abbr title="maximal">Max.</abbr> <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> leases'),
235                         _('Maximum allowed number of active DHCP leases'));
236
237                 o.optional = true;
238                 o.datatype = 'uinteger';
239                 o.placeholder = _('unlimited');
240
241
242                 o = s.taboption('advanced', form.Value, 'ednspacket_max',
243                         _('<abbr title="maximal">Max.</abbr> <abbr title="Extension Mechanisms for Domain Name System">EDNS0</abbr> packet size'),
244                         _('Maximum allowed size of EDNS.0 UDP packets'));
245
246                 o.optional = true;
247                 o.datatype = 'uinteger';
248                 o.placeholder = 1280;
249
250
251                 o = s.taboption('advanced', form.Value, 'dnsforwardmax',
252                         _('<abbr title="maximal">Max.</abbr> concurrent queries'),
253                         _('Maximum allowed number of concurrent DNS queries'));
254
255                 o.optional = true;
256                 o.datatype = 'uinteger';
257                 o.placeholder = 150;
258
259                 o = s.taboption('advanced', form.Value, 'cachesize',
260                         _('Size of DNS query cache'),
261                         _('Number of cached DNS entries (max is 10000, 0 is no caching)'));
262                 o.optional = true;
263                 o.datatype = 'range(0,10000)';
264                 o.placeholder = 150;
265
266                 s.taboption('tftp', form.Flag, 'enable_tftp',
267                         _('Enable TFTP server')).optional = true;
268
269                 o = s.taboption('tftp', form.Value, 'tftp_root',
270                         _('TFTP server root'),
271                         _('Root directory for files served via TFTP'));
272
273                 o.optional = true;
274                 o.depends('enable_tftp', '1');
275                 o.placeholder = '/';
276
277
278                 o = s.taboption('tftp', form.Value, 'dhcp_boot',
279                         _('Network boot image'),
280                         _('Filename of the boot image advertised to clients'));
281
282                 o.optional = true;
283                 o.depends('enable_tftp', '1');
284                 o.placeholder = 'pxelinux.0';
285
286                 o = s.taboption('general', form.Flag, 'localservice',
287                         _('Local Service Only'),
288                         _('Limit DNS service to subnets interfaces on which we are serving DNS.'));
289                 o.optional = false;
290                 o.rmempty = false;
291
292                 o = s.taboption('general', form.Flag, 'nonwildcard',
293                         _('Non-wildcard'),
294                         _('Bind dynamically to interfaces rather than wildcard address (recommended as linux default)'));
295                 o.optional = false;
296                 o.rmempty = true;
297
298                 o = s.taboption('general', form.DynamicList, 'interface',
299                         _('Listen Interfaces'),
300                         _('Limit listening to these interfaces, and loopback.'));
301                 o.optional = true;
302
303                 o = s.taboption('general', form.DynamicList, 'notinterface',
304                         _('Exclude interfaces'),
305                         _('Prevent listening on these interfaces.'));
306                 o.optional = true;
307
308                 o = s.taboption('leases', form.SectionValue, '__leases__', form.GridSection, 'host', null,
309                         _('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 />' +
310                         _('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.'));
311
312                 ss = o.subsection;
313
314                 ss.addremove = true;
315                 ss.anonymous = true;
316
317                 so = ss.option(form.Value, 'name', _('Hostname'));
318                 so.datatype = 'hostname("strict")';
319                 so.rmempty  = true;
320                 so.write = function(section, value) {
321                         uci.set('dhcp', section, 'name', value);
322                         uci.set('dhcp', section, 'dns', '1');
323                 };
324                 so.remove = function(section) {
325                         uci.unset('dhcp', section, 'name');
326                         uci.unset('dhcp', section, 'dns');
327                 };
328
329                 so = ss.option(form.Value, 'mac', _('<abbr title="Media Access Control">MAC</abbr>-Address'));
330                 so.datatype = 'list(unique(macaddr))';
331                 so.rmempty  = true;
332                 so.cfgvalue = function(section) {
333                         var macs = uci.get('dhcp', section, 'mac'),
334                             result = [];
335
336                         if (!Array.isArray(macs))
337                                 macs = (macs != null && macs != '') ? macs.split(/\ss+/) : [];
338
339                         for (var i = 0, mac; (mac = macs[i]) != null; i++)
340                                 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))
341                                         result.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
342                                                 parseInt(RegExp.$1, 16), parseInt(RegExp.$2, 16),
343                                                 parseInt(RegExp.$3, 16), parseInt(RegExp.$4, 16),
344                                                 parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)));
345
346                         return result.length ? result.join(' ') : null;
347                 };
348                 so.renderWidget = function(section_id, option_index, cfgvalue) {
349                         var node = form.Value.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]),
350                             ipopt = this.section.children.filter(function(o) { return o.option == 'ip' })[0];
351
352                         node.addEventListener('cbi-dropdown-change', L.bind(function(ipopt, section_id, ev) {
353                                 var mac = ev.detail.value.value;
354                                 if (mac == null || mac == '' || !hosts[mac] || !hosts[mac].ipv4)
355                                         return;
356
357                                 var ip = ipopt.formvalue(section_id);
358                                 if (ip != null && ip != '')
359                                         return;
360
361                                 var node = ipopt.map.findElement('id', ipopt.cbid(section_id));
362                                 if (node)
363                                         L.dom.callClassMethod(node, 'setValue', hosts[mac].ipv4);
364                         }, this, ipopt, section_id));
365
366                         return node;
367                 };
368                 Object.keys(hosts).forEach(function(mac) {
369                         var hint = hosts[mac].name || hosts[mac].ipv4;
370                         so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
371                 });
372
373                 so = ss.option(form.Value, 'ip', _('<abbr title="Internet Protocol Version 4">IPv4</abbr>-Address'));
374                 so.datatype = 'or(ip4addr,"ignore")';
375                 so.validate = function(section, value) {
376                         var mac = this.map.lookupOption('mac', section),
377                             name = this.map.lookupOption('name', section),
378                             m = mac ? mac[0].formvalue(section) : null,
379                             n = name ? name[0].formvalue(section) : null;
380
381                         if ((m == null || m == '') && (n == null || n == ''))
382                                 return _('One of hostname or mac address must be specified!');
383
384                         return true;
385                 };
386                 Object.keys(hosts).forEach(function(mac) {
387                         if (hosts[mac].ipv4) {
388                                 var hint = hosts[mac].name;
389                                 so.value(hosts[mac].ipv4, hint ? '%s (%s)'.format(hosts[mac].ipv4, hint) : hosts[mac].ipv4);
390                         }
391                 });
392
393                 so = ss.option(form.Value, 'leasetime', _('Lease time'));
394                 so.rmempty = true;
395
396                 so = ss.option(form.Value, 'duid', _('<abbr title="The DHCP Unique Identifier">DUID</abbr>'));
397                 so.datatype = 'and(rangelength(20,36),hexstring)';
398                 Object.keys(duids).forEach(function(duid) {
399                         so.value(duid, '%s (%s)'.format(duid, duids[duid].name || '?'));
400                 });
401
402                 so = ss.option(form.Value, 'hostid', _('<abbr title="Internet Protocol Version 6">IPv6</abbr>-Suffix (hex)'));
403
404                 o = s.taboption('leases', CBILeaseStatus, '__status__');
405
406                 return m.render().then(function(mapEl) {
407                         L.Poll.add(function() {
408                                 return callDHCPLeases(4).then(function(leases) {
409                                         cbi_update_table(mapEl.querySelector('#lease_status_table'),
410                                                 leases.map(function(lease) {
411                                                         var exp;
412
413                                                         if (lease.expires === false)
414                                                                 exp = E('em', _('unlimited'));
415                                                         else if (lease.expires <= 0)
416                                                                 exp = E('em', _('expired'));
417                                                         else
418                                                                 exp = '%t'.format(lease.expires);
419
420                                                         return [
421                                                                 lease.hostname || '?',
422                                                                 lease.ipaddr,
423                                                                 lease.macaddr,
424                                                                 exp
425                                                         ];
426                                                 }),
427                                                 E('em', _('There are no active leases')));
428                                 });
429                         });
430
431                         return mapEl;
432                 });
433         }
434 });