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