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