889f132ab086957201128ac267a2df4cdaa42ba7
10 'require tools.widgets as widgets';
12 var callHostHints
, callDUIDHints
, callDHCPLeases
, CBILeaseStatus
, CBILease6Status
;
14 callHostHints
= rpc
.declare({
16 method
: 'getHostHints',
20 callDUIDHints
= rpc
.declare({
22 method
: 'getDUIDHints',
26 callDHCPLeases
= rpc
.declare({
28 method
: 'getDHCPLeases',
32 CBILeaseStatus
= form
.DummyValue
.extend({
33 renderWidget: function(section_id
, option_id
, cfgvalue
) {
35 E('h4', _('Active DHCP Leases')),
36 E('table', { 'id': 'lease_status_table', 'class': 'table' }, [
37 E('tr', { 'class': 'tr table-titles' }, [
38 E('th', { 'class': 'th' }, _('Hostname')),
39 E('th', { 'class': 'th' }, _('IPv4 address')),
40 E('th', { 'class': 'th' }, _('MAC address')),
41 E('th', { 'class': 'th' }, _('Lease time remaining'))
43 E('tr', { 'class': 'tr placeholder' }, [
44 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
51 CBILease6Status
= form
.DummyValue
.extend({
52 renderWidget: function(section_id
, option_id
, cfgvalue
) {
54 E('h4', _('Active DHCPv6 Leases')),
55 E('table', { 'id': 'lease6_status_table', 'class': 'table' }, [
56 E('tr', { 'class': 'tr table-titles' }, [
57 E('th', { 'class': 'th' }, _('Host')),
58 E('th', { 'class': 'th' }, _('IPv6 address')),
59 E('th', { 'class': 'th' }, _('DUID')),
60 E('th', { 'class': 'th' }, _('Lease time remaining'))
62 E('tr', { 'class': 'tr placeholder' }, [
63 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
70 function calculateNetwork(addr
, mask
) {
71 addr
= validation
.parseIPv4(String(addr
));
74 mask
= validation
.parseIPv4(network
.prefixToMask(+mask
));
76 mask
= validation
.parseIPv4(String(mask
));
78 if (addr
== null || mask
== null)
83 addr
[0] & (mask
[0] >>> 0 & 255),
84 addr
[1] & (mask
[1] >>> 0 & 255),
85 addr
[2] & (mask
[2] >>> 0 & 255),
86 addr
[3] & (mask
[3] >>> 0 & 255)
92 function getDHCPPools() {
93 return uci
.load('dhcp').then(function() {
94 let sections
= uci
.sections('dhcp', 'dhcp'),
95 tasks
= [], pools
= [];
97 for (var i
= 0; i
< sections
.length
; i
++) {
98 if (sections
[i
].ignore
== '1' || !sections
[i
].interface)
101 tasks
.push(network
.getNetwork(sections
[i
].interface).then(L
.bind(function(section_id
, net
) {
102 var cidr
= net
? (net
.getIPAddrs()[0] || '').split('/') : null;
104 if (cidr
&& cidr
.length
== 2) {
105 var net_mask
= calculateNetwork(cidr
[0], cidr
[1]);
108 section_id
: section_id
,
109 network
: net_mask
[0],
113 }, null, sections
[i
]['.name'])));
116 return Promise
.all(tasks
).then(function() {
122 function validateHostname(sid
, s
) {
123 if (s
== null || s
== '')
127 return _('Expecting: %s').format(_('valid hostname'));
129 var labels
= s
.replace(/^\*?\.?|\.$/g, '').split(/\./);
131 for (var i
= 0; i
< labels
.length
; i
++)
132 if (!labels
[i
].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
133 return _('Expecting: %s').format(_('valid hostname'));
138 function validateAddressList(sid
, s
) {
139 if (s
== null || s
== '')
142 var m
= s
.match(/^\/(.+)\/$/),
143 names
= m
? m
[1].split(/\//) : [ s
];
145 for (var i
= 0; i
< names
.length
; i
++) {
146 var res
= validateHostname(sid
, names
[i
]);
155 function validateServerSpec(sid
, s
) {
156 if (s
== null || s
== '')
159 var m
= s
.match(/^(\/.*\/)?(.*)$/);
161 return _('Expecting: %s').format(_('valid hostname'));
163 if (m
[1] != '//' && m
[1] != '/#/') {
164 var res
= validateAddressList(sid
, m
[1]);
169 if (m
[2] == '' || m
[2] == '#')
172 // ipaddr%scopeid#srvport@source@interface#srcport
174 m
= m
[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
177 return _('Expecting: %s').format(_('valid IP address'));
179 if (validation
.parseIPv4(m
[1])) {
180 if (m
[3] != null && !validation
.parseIPv4(m
[3]))
181 return _('Expecting: %s').format(_('valid IPv4 address'));
183 else if (validation
.parseIPv6(m
[1])) {
184 if (m
[3] != null && !validation
.parseIPv6(m
[3]))
185 return _('Expecting: %s').format(_('valid IPv6 address'));
188 return _('Expecting: %s').format(_('valid IP address'));
191 if ((m
[2] != null && +m
[2] > 65535) || (m
[4] != null && +m
[4] > 65535))
192 return _('Expecting: %s').format(_('valid port value'));
197 function validateMACAddr(pools
, sid
, s
) {
198 if (s
== null || s
== '')
201 var leases
= uci
.sections('dhcp', 'host'),
202 this_macs
= L
.toArray(s
).map(function(m
) { return m
.toUpperCase() });
204 for (var i
= 0; i
< pools
.length
; i
++) {
205 var this_net_mask
= calculateNetwork(this.section
.formvalue(sid
, 'ip'), pools
[i
].netmask
);
210 for (var j
= 0; j
< leases
.length
; j
++) {
211 if (leases
[j
]['.name'] == sid
|| !leases
[j
].ip
)
214 var lease_net_mask
= calculateNetwork(leases
[j
].ip
, pools
[i
].netmask
);
216 if (!lease_net_mask
|| this_net_mask
[0] != lease_net_mask
[0])
219 var lease_macs
= L
.toArray(leases
[j
].mac
).map(function(m
) { return m
.toUpperCase() });
221 for (var k
= 0; k
< lease_macs
.length
; k
++)
222 for (var l
= 0; l
< this_macs
.length
; l
++)
223 if (lease_macs
[k
] == this_macs
[l
])
224 return _('The MAC address %h is already used by another static lease in the same DHCP pool').format(this_macs
[l
]);
237 network
.getNetworks()
241 render: function(hosts_duids_pools
) {
242 var has_dhcpv6
= L
.hasSystemFeature('dnsmasq', 'dhcpv6') || L
.hasSystemFeature('odhcpd'),
243 hosts
= hosts_duids_pools
[0],
244 duids
= hosts_duids_pools
[1],
245 pools
= hosts_duids_pools
[2],
246 networks
= hosts_duids_pools
[3],
249 m
= new form
.Map('dhcp', _('DHCP and DNS'),
250 _('Dnsmasq is a lightweight <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> server and <abbr title="Domain Name System">DNS</abbr> forwarder.'));
252 s
= m
.section(form
.TypedSection
, 'dnsmasq');
256 s
.tab('general', _('General Settings'));
257 s
.tab('relay', _('Relay'));
258 s
.tab('files', _('Resolv and Hosts Files'));
259 s
.tab('pxe_tftp', _('PXE/TFTP Settings'));
260 s
.tab('advanced', _('Advanced Settings'));
261 s
.tab('leases', _('Static Leases'));
262 s
.tab('hosts', _('Hostnames'));
263 s
.tab('srvhosts', _('SRV'));
264 s
.tab('mxhosts', _('MX'));
265 s
.tab('ipsets', _('IP Sets'));
267 s
.taboption('general', form
.Flag
, 'domainneeded',
268 _('Domain required'),
269 _('Do not forward DNS queries without dots or domain parts.'));
271 s
.taboption('general', form
.Flag
, 'authoritative',
273 _('This is the only DHCP server in the local network.'));
275 s
.taboption('general', form
.Value
, 'local',
277 _('Never forward matching domains and subdomains, resolve from DHCP or hosts files only.'));
279 s
.taboption('general', form
.Value
, 'domain',
281 _('Local domain suffix appended to DHCP names and hosts file entries.'));
283 o
= s
.taboption('general', form
.Flag
, 'logqueries',
285 _('Write received DNS queries to syslog.'));
288 o
= s
.taboption('general', form
.DynamicList
, 'server',
289 _('DNS forwardings'),
290 _('List of upstream resolvers to forward queries to.'));
292 o
.placeholder
= '/example.org/10.1.2.3';
293 o
.validate
= validateServerSpec
;
295 o
= s
.taboption('general', form
.DynamicList
, 'address',
297 _('Resolve specified FQDNs to an IP.') + '<br />' +
298 _('Syntax: <code>/fqdn[/fqdn…]/[ipaddr]</code>.') + '<br />' +
299 _('<code>/#/</code> matches any domain. <code>/example.com/</code> returns NXDOMAIN.') + '<br />' +
300 _('<code>/example.com/#</code> returns NULL addresses (<code>0.0.0.0</code> and <code>::</code>) for example.com and its subdomains.'));
302 o
.placeholder
= '/router.local/router.lan/192.168.0.1';
304 o
= s
.taboption('general', form
.DynamicList
, 'ipset',
306 _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.'));
308 o
.placeholder
= '/example.org/ipset,ipset6';
310 o
= s
.taboption('general', form
.Flag
, 'rebind_protection',
311 _('Rebind protection'),
312 _('Discard upstream responses containing <a href="%s">RFC1918</a> addresses.').format('https://www.rfc-editor.org/rfc/rfc1918') + '<br />' +
313 _('Discard also upstream responses containing <a href="%s">RFC4193</a>, Link-Local and private IPv4-Mapped <a href="%s">RFC4291</a> IPv6 Addresses.').format('https://www.rfc-editor.org/rfc/rfc4193', 'https://www.rfc-editor.org/rfc/rfc4291'));
316 o
= s
.taboption('general', form
.Flag
, 'rebind_localhost',
317 _('Allow localhost'),
318 _('Exempt <code>127.0.0.0/8</code> and <code>::1</code> from rebinding checks, e.g. for RBL services.'));
319 o
.depends('rebind_protection', '1');
321 o
= s
.taboption('general', form
.DynamicList
, 'rebind_domain',
322 _('Domain whitelist'),
323 _('List of domains to allow RFC1918 responses for.'));
324 o
.depends('rebind_protection', '1');
326 o
.placeholder
= 'ihost.netflix.com';
327 o
.validate
= validateAddressList
;
329 o
= s
.taboption('general', form
.Flag
, 'localservice',
330 _('Local service only'),
331 _('Accept DNS queries only from hosts whose address is on a local subnet.'));
335 o
= s
.taboption('general', form
.Flag
, 'nonwildcard',
337 _('Bind dynamically to interfaces rather than wildcard address.'));
338 o
.default = o
.enabled
;
342 o
= s
.taboption('general', widgets
.NetworkSelect
, 'interface',
343 _('Listen interfaces'),
344 _('Listen only on the specified interfaces, and loopback if not excluded explicitly.'));
348 o
= s
.taboption('general', widgets
.NetworkSelect
, 'notinterface',
349 _('Exclude interfaces'),
350 _('Do not listen on the specified interfaces.'));
355 o
= s
.taboption('relay', form
.SectionValue
, '__relays__', form
.TableSection
, 'relay', null,
356 _('Relay DHCP requests elsewhere. OK: v4↔v4, v6↔v6. Not OK: v4↔v6, v6↔v4.')
357 + '<br />' + _('Note: you may also need a DHCP Proxy (currently unavailable) when specifying a non-standard Relay To port(<code>addr#port</code>).')
358 + '<br />' + _('You may add multiple unique Relay To on the same Listen addr.'));
366 ss
.nodescriptions
= true;
368 so
= ss
.option(form
.Value
, 'local_addr', _('Relay from'));
370 so
.datatype
= 'ipaddr';
372 for (var family
= 4; family
<= 6; family
+= 2) {
373 for (var i
= 0; i
< networks
.length
; i
++) {
374 if (networks
[i
].getName() != 'loopback') {
375 var addrs
= (family
== 6) ? networks
[i
].getIP6Addrs() : networks
[i
].getIPAddrs();
376 for (var j
= 0; j
< addrs
.length
; j
++) {
377 var addr
= addrs
[j
].split('/')[0];
378 so
.value(addr
, E([], [
380 widgets
.NetworkSelect
.prototype.renderIfaceBadge(networks
[i
]),
388 so
= ss
.option(form
.Value
, 'server_addr', _('Relay to address'));
391 so
.placeholder
= '192.168.10.1#535';
393 so
.validate = function(section
, value
) {
394 var m
= this.section
.formvalue(section
, 'local_addr'),
395 n
= this.section
.formvalue(section
, 'server_addr'),
399 return _('Both "Relay from" and "Relay to address" must be specified.');
403 if (p
.length
> 1 && !/^[0-9]+$/.test(p
[1]))
404 return _('Expected port number.');
408 if ((validation
.parseIPv6(m
) && validation
.parseIPv6(n
)) ||
409 validation
.parseIPv4(m
) && validation
.parseIPv4(n
))
412 return _('Address families of "Relay from" and "Relay to address" must match.')
417 so
= ss
.option(widgets
.NetworkSelect
, 'interface', _('Only accept replies via'));
420 so
.placeholder
= 'lan';
422 s
.taboption('files', form
.Flag
, 'readethers',
423 _('Use <code>/etc/ethers</code>'),
424 _('Read <code>/etc/ethers</code> to configure the DHCP server.'));
426 s
.taboption('files', form
.Value
, 'leasefile',
428 _('File to store DHCP lease information.'));
430 o
= s
.taboption('files', form
.Flag
, 'noresolv',
431 _('Ignore resolv file'));
434 o
= s
.taboption('files', form
.Value
, 'resolvfile',
436 _('File with upstream resolvers.'));
437 o
.depends('noresolv', '0');
438 o
.placeholder
= '/tmp/resolv.conf.d/resolv.conf.auto';
441 o
= s
.taboption('files', form
.Flag
, 'nohosts',
442 _('Ignore <code>/etc/hosts</code>'));
445 o
= s
.taboption('files', form
.DynamicList
, 'addnhosts',
446 _('Additional hosts files'));
448 o
.placeholder
= '/etc/dnsmasq.hosts';
450 o
= s
.taboption('advanced', form
.Flag
, 'quietdhcp',
451 _('Suppress logging'),
452 _('Suppress logging of the routine operation for the DHCP protocol.'));
455 o
= s
.taboption('advanced', form
.Flag
, 'sequential_ip',
456 _('Allocate IPs sequentially'),
457 _('Allocate IP addresses sequentially, starting from the lowest available address.'));
460 o
= s
.taboption('advanced', form
.Flag
, 'boguspriv',
462 _('Do not forward reverse lookups for local networks.'));
463 o
.default = o
.enabled
;
465 s
.taboption('advanced', form
.Flag
, 'filterwin2k',
466 _('Filter SRV/SOA service discovery'),
467 _('Filters SRV/SOA service discovery, to avoid triggering dial-on-demand links.') + '<br />' +
468 _('May prevent VoIP or other services from working.'));
470 o
= s
.taboption('advanced', form
.Flag
, 'filter_aaaa',
471 _('Filter IPv6 AAAA records'),
472 _('Remove IPv6 addresses from the results and only return IPv4 addresses.') + '<br />' +
473 _('Can be useful if ISP has IPv6 nameservers but does not provide IPv6 routing.'));
476 o
= s
.taboption('advanced', form
.Flag
, 'filter_a',
477 _('Filter IPv4 A records'),
478 _('Remove IPv4 addresses from the results and only return IPv6 addresses.'));
481 s
.taboption('advanced', form
.Flag
, 'localise_queries',
482 _('Localise queries'),
483 _('Return answers to DNS queries matching the subnet from which the query was received if multiple IPs are available.'));
485 if (L
.hasSystemFeature('dnsmasq', 'dnssec')) {
486 o
= s
.taboption('advanced', form
.Flag
, 'dnssec',
488 _('Validate DNS replies and cache DNSSEC data, requires upstream to support DNSSEC.'));
491 o
= s
.taboption('advanced', form
.Flag
, 'dnsseccheckunsigned',
492 _('DNSSEC check unsigned'),
493 _('Verify unsigned domain responses really come from unsigned domains.'));
494 o
.default = o
.enabled
;
498 s
.taboption('advanced', form
.Flag
, 'expandhosts',
500 _('Add local domain suffix to names served from hosts files.'));
502 s
.taboption('advanced', form
.Flag
, 'nonegcache',
503 _('No negative cache'),
504 _('Do not cache negative replies, e.g. for non-existent domains.'));
506 o
= s
.taboption('advanced', form
.Value
, 'serversfile',
507 _('Additional servers file'),
508 _('File listing upstream resolvers, optionally domain-specific, e.g. <code>server=1.2.3.4</code>, <code>server=/domain/1.2.3.4</code>.'));
509 o
.placeholder
= '/etc/dnsmasq.servers';
511 o
= s
.taboption('advanced', form
.Flag
, 'strictorder',
513 _('Upstream resolvers will be queried in the order of the resolv file.'));
516 o
= s
.taboption('advanced', form
.Flag
, 'allservers',
518 _('Query all available upstream resolvers.'));
521 o
= s
.taboption('advanced', form
.DynamicList
, 'bogusnxdomain',
522 _('IPs to override with NXDOMAIN'),
523 _('List of IP addresses to convert into NXDOMAIN responses.'));
525 o
.placeholder
= '64.94.110.11';
527 o
= s
.taboption('advanced', form
.Value
, 'port',
528 _('DNS server port'),
529 _('Listening port for inbound DNS queries.'));
534 o
= s
.taboption('advanced', form
.Value
, 'queryport',
536 _('Fixed source port for outbound DNS queries.'));
539 o
.placeholder
= _('any');
541 o
= s
.taboption('advanced', form
.Value
, 'dhcpleasemax',
542 _('Max. DHCP leases'),
543 _('Maximum allowed number of active DHCP leases.'));
545 o
.datatype
= 'uinteger';
546 o
.placeholder
= _('unlimited');
548 o
= s
.taboption('advanced', form
.Value
, 'ednspacket_max',
549 _('Max. EDNS0 packet size'),
550 _('Maximum allowed size of EDNS0 UDP packets.'));
552 o
.datatype
= 'uinteger';
553 o
.placeholder
= 1280;
555 o
= s
.taboption('advanced', form
.Value
, 'dnsforwardmax',
556 _('Max. concurrent queries'),
557 _('Maximum allowed number of concurrent DNS queries.'));
559 o
.datatype
= 'uinteger';
562 o
= s
.taboption('advanced', form
.Value
, 'cachesize',
563 _('Size of DNS query cache'),
564 _('Number of cached DNS entries, 10000 is maximum, 0 is no caching.'));
566 o
.datatype
= 'range(0,10000)';
569 o
= s
.taboption('pxe_tftp', form
.Flag
, 'enable_tftp',
570 _('Enable TFTP server'),
571 _('Enable the built-in single-instance TFTP server.'));
574 o
= s
.taboption('pxe_tftp', form
.Value
, 'tftp_root',
575 _('TFTP server root'),
576 _('Root directory for files served via TFTP. <em>Enable TFTP server</em> and <em>TFTP server root</em> turn on the TFTP server and serve files from <em>TFTP server root</em>.'));
577 o
.depends('enable_tftp', '1');
581 o
= s
.taboption('pxe_tftp', form
.Value
, 'dhcp_boot',
582 _('Network boot image'),
583 _('Filename of the boot image advertised to clients.'));
584 o
.depends('enable_tftp', '1');
586 o
.placeholder
= 'pxelinux.0';
588 /* PXE - https://openwrt.org/docs/guide-user/base-system/dhcp#booting_options */
589 o = s.taboption('pxe_tftp', form.SectionValue, '__pxe__', form.GridSection, 'boot', null,
590 _('Special <abbr title="Preboot eXecution Environment">PXE</abbr> boot options for Dnsmasq.'));
594 ss.nodescriptions = true;
596 so = ss.option(form.Value, 'filename',
598 _('Host requests this filename from the boot server.'));
600 so.placeholder = 'pxelinux.0';
602 so = ss.option(form.Value, 'servername',
604 _('The hostname of the boot server'));
606 so.placeholder = 'myNAS';
608 so = ss.option(form.Value, 'serveraddress',
610 _('The IP address of the boot server'));
612 so.placeholder = '192.168.1.2';
614 so = ss.option(form.DynamicList, 'dhcp_option',
616 _('Options for the Network-ID. (Note: needs also Network-ID.) E.g. "<code>42,192.168.1.4</code>" for NTP server, "<code>3,192.168.4.4</code>" for default route. <code>0.0.0.0</code> means "the address of the system running dnsmasq".'));
618 so.placeholder = '42,192.168.1.4';
620 so = ss.option(widgets.DeviceSelect, 'networkid',
622 _('Apply DHCP Options to this net. (Empty = all clients).'));
626 so = ss.option(form.Flag, 'force',
628 _('Always send DHCP Options. Sometimes needed, with e.g. PXELinux.'));
631 so = ss.option(form.Value, 'instance',
633 _('Dnsmasq instance to which this boot section is bound. If unspecified, the section is valid for all dnsmasq instances.'));
636 Object.values(L.uci.sections('dhcp', 'dnsmasq')).forEach(function(val, index) {
637 so.value(index, '%s (Domain: %s, Local: %s)'.format(index, val.domain || '?', val.local || '?'));
640 o = s.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
641 _('Bind service records to a domain name: specify the location of services. See <a href="%s">RFC2782</a>.').format('https://datatracker.ietf.org/doc/html/rfc2782')
642 + '<br />' + _('_service: _sip, _ldap, _imap, _stun, _xmpp-client, … . (Note: while _http is possible, no browsers support SRV records.)')
643 + '<br />' + _('_proto: _tcp, _udp, _sctp, _quic, … .')
644 + '<br />' + _('You may add multiple records for the same Target.')
645 + '<br />' + _('Larger weights (of the same prio) are given a proportionately higher probability of being selected.'));
654 so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax: <code>_service._proto.example.com</code>.'));
656 so.datatype = 'hostname';
657 so.placeholder = '_sip._tcp.example.com';
659 so = ss.option(form.Value, 'target', _('Target'), _('CNAME or fqdn'));
661 so.datatype = 'hostname';
662 so.placeholder = 'sip.example.com';
664 so = ss.option(form.Value, 'port', _('Port'));
666 so.datatype = 'port';
667 so.placeholder = '5060';
669 so = ss.option(form.Value, 'class', _('Priority'), _('Ordinal: lower comes first.'));
671 so.datatype = 'range(0,65535)';
672 so.placeholder = '10';
674 so = ss.option(form.Value, 'weight', _('Weight'));
676 so.datatype = 'range(0,65535)';
677 so.placeholder = '50';
679 o = s.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
680 _('Bind service records to a domain name: specify the location of services.')
681 + '<br />' + _('You may add multiple records for the same domain.'));
689 ss.nodescriptions = true;
691 so = ss.option(form.Value, 'domain', _('Domain'));
693 so.datatype = 'hostname';
694 so.placeholder = 'example.com';
696 so = ss.option(form.Value, 'relay', _('Relay'));
698 so.datatype = 'hostname';
699 so.placeholder = 'relay.example.com';
701 so = ss.option(form.Value, 'pref', _('Priority'), _('Ordinal: lower comes first.'));
703 so.datatype = 'range(0,65535)';
704 so.placeholder = '0';
706 o = s.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
707 _('Hostnames are used to bind a domain name to an IP address. This setting is redundant for hostnames already configured with static leases, but it can be useful to rebind an FQDN.'));
715 so = ss.option(form.Value, 'name', _('Hostname'));
717 so.datatype = 'hostname';
719 so = ss.option(form.Value, 'ip', _('IP address'));
721 so.datatype = 'ipaddr';
725 Object.keys(hosts).forEach(function(mac) {
726 var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
728 for (var i = 0; i < addrs.length; i++)
729 ipaddrs[addrs[i]] = hosts[mac].name || mac;
732 L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
733 so.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
736 o = s.taboption('ipsets', form.SectionValue, '__ipsets__', form.GridSection, 'ipset', null,
737 _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.') + '<br />' +
738 _('The netfilter components below are only regarded when running fw4.'));
746 ss.nodescriptions = true;
747 ss.modaltitle = _('Edit IP set');
749 so = ss.option(form.DynamicList, 'name', _('Name of the set'));
752 so.datatype = 'string';
754 so = ss.option(form.DynamicList, 'domain', _('FQDN'));
757 so.datatype = 'hostname';
759 so = ss.option(form.Value, 'table', _('Netfilter table name'), _('Defaults to fw4.'));
761 so.placeholder = 'fw4';
764 so = ss.option(form.ListValue, 'table_family', _('Table IP family'), _('Defaults to IPv4+6.') + ' ' + _('Can be hinted by adding 4 or 6 to the name.') + '<br />' +
765 _('Adding an IPv6 to an IPv4 set and vice-versa silently fails.'));
768 so.value('inet', _('IPv4+6'));
769 so.value('ip', _('IPv4'));
770 so.value('ip6', _('IPv6'));
772 o = s.taboption('leases', form.SectionValue, '__leases__', form.GridSection, 'host', null,
773 _('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 />' +
774 _('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.'));
782 so = ss.option(form.Value, 'name', _('Hostname'));
783 so.validate = validateHostname;
785 so.write = function(section, value) {
786 uci.set('dhcp', section, 'name', value);
787 uci.set('dhcp', section, 'dns', '1');
789 so.remove = function(section) {
790 uci.unset('dhcp', section, 'name');
791 uci.unset('dhcp', section, 'dns');
794 so = ss.option(form.Value, 'mac', _('MAC address'));
795 so.datatype = 'list(macaddr)';
797 so.cfgvalue = function(section) {
798 var macs = L.toArray(uci.get('dhcp', section, 'mac')),
801 for (var i = 0, mac; (mac = macs[i]) != null; i++)
802 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))
803 result.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
804 parseInt(RegExp.$1, 16), parseInt(RegExp.$2, 16),
805 parseInt(RegExp.$3, 16), parseInt(RegExp.$4, 16),
806 parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)));
808 return result.length ? result.join(' ') : null;
810 so.renderWidget = function(section_id, option_index, cfgvalue) {
811 var node = form.Value.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]),
812 ipopt = this.section.children.filter(function(o) { return o.option == 'ip' })[0];
814 node.addEventListener('cbi-dropdown-change', L.bind(function(ipopt, section_id, ev) {
815 var mac = ev.detail.value.value;
816 if (mac == null || mac == '' || !hosts[mac])
819 var iphint = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
823 var ip = ipopt.formvalue(section_id);
824 if (ip != null && ip != '')
827 var node = ipopt.map.findElement('id', ipopt.cbid(section_id));
829 dom.callClassMethod(node, 'setValue', iphint);
830 }, this, ipopt, section_id));
834 so.validate = validateMACAddr.bind(so, pools);
835 Object.keys(hosts).forEach(function(mac) {
836 var hint = hosts[mac].name || L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
837 so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
840 so = ss.option(form.Value, 'ip', _('IPv4 address'));
841 so.datatype = 'or(ip4addr,"ignore")';
842 so.validate = function(section, value) {
843 var m = this.section.formvalue(section, 'mac'),
844 n = this.section.formvalue(section, 'name');
846 if ((m && !m.length > 0) && !n)
847 return _('One of hostname or MAC address must be specified!');
849 if (!value || value == 'ignore')
852 var leases = uci.sections('dhcp', 'host');
854 for (var i = 0; i < leases.length; i++)
855 if (leases[i]['.name'] != section && leases[i].ip == value)
856 return _('The IP address %h is already used by another static lease').format(value);
858 for (var i = 0; i < pools.length; i++) {
859 var net_mask = calculateNetwork(value, pools[i].netmask);
861 if (net_mask && net_mask[0] == pools[i].network)
865 return _('The IP address is outside of any DHCP pool address range');
868 L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
869 so.value(ipv4, ipaddrs[ipv4] ? '%s (%s)'.format(ipv4, ipaddrs[ipv4]) : ipv4);
872 so = ss.option(form.Value, 'leasetime', _('Lease time'));
875 so = ss.option(form.Value, 'duid', _('DUID'));
876 so.datatype = 'and(rangelength(20,36),hexstring)';
877 Object.keys(duids).forEach(function(duid) {
878 so.value(duid, '%s (%s)'.format(duid, duids[duid].hostname || duids[duid].macaddr || duids[duid].ip6addr || '?'));
881 so = ss.option(form.Value, 'hostid', _('IPv6 suffix (hex)'));
883 o = s.taboption('leases', CBILeaseStatus, '__status__');
886 o = s.taboption('leases', CBILease6Status, '__status6__');
888 return m.render().then(function(mapEl) {
889 poll.add(function() {
890 return callDHCPLeases().then(function(leaseinfo) {
891 var leases = Array.isArray(leaseinfo.dhcp_leases) ? leaseinfo.dhcp_leases : [],
892 leases6 = Array.isArray(leaseinfo.dhcp6_leases) ? leaseinfo.dhcp6_leases : [];
894 cbi_update_table(mapEl.querySelector('#lease_status_table'),
895 leases.map(function(lease) {
898 if (lease.expires === false)
899 exp = E('em', _('unlimited'));
900 else if (lease.expires <= 0)
901 exp = E('em', _('expired'));
903 exp = '%t'.format(lease.expires);
905 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
906 name = hint ? hint.name : null,
909 if (name && lease.hostname && lease.hostname != name)
910 host = '%s (%s)'.format(lease.hostname, name);
911 else if (lease.hostname)
912 host = lease.hostname;
921 E('em', _('There are no active leases')));
924 cbi_update_table(mapEl.querySelector('#lease6_status_table'),
925 leases6.map(function(lease) {
928 if (lease.expires === false)
929 exp = E('em', _('unlimited'));
930 else if (lease.expires <= 0)
931 exp = E('em', _('expired'));
933 exp = '%t'.format(lease.expires);
935 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
936 name = hint ? (hint.name || L.toArray(hint.ipaddrs || hint.ipv4)[0] || L.toArray(hint.ip6addrs || hint.ipv6)[0]) : null,
939 if (name && lease.hostname && lease.hostname != name && lease.ip6addr != name)
940 host = '%s (%s)'.format(lease.hostname, name);
941 else if (lease.hostname)
942 host = lease.hostname;
948 lease.ip6addrs ? lease.ip6addrs.join(' ') : lease.ip6addr,
953 E('em', _('There are no active leases')));