luci-app-https-dns-proxy: add status->overview include file
[project/luci.git] / applications / luci-app-https-dns-proxy / htdocs / luci-static / resources / view / https-dns-proxy / overview.js
1 // Copyright 2023 MOSSDeF, Stan Grishin <stangri@melmac.ca>
2 // This code wouldn't have been possible without help from:
3 // - [@jow-](https://github.com/jow-)
4 // - [@stokito](https://github.com/stokito)
5 // - [@vsviridov](https://github.com/vsviridov)
6 // noinspection JSAnnotator
7
8 "use strict";
9 "require form";
10 "require rpc";
11 "require uci";
12 "require view";
13 "require https-dns-proxy.status as hdp";
14
15 var pkg = {
16
17 get Name() {
18 return "https-dns-proxy";
19 },
20
21 get URL() {
22 return "https://docs.openwrt.melmac.net/" + pkg.Name + "/";
23 },
24
25 templateToRegexp: function (template) {
26 return RegExp(
27 "^" +
28 template
29 .split(/(\{\w+\})/g)
30 .map((part) => {
31 let placeholder = part.match(/^\{(\w+)\}$/);
32 if (placeholder) return `(?<${placeholder[1]}>.*?)`;
33 else return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34 })
35 .join("") +
36 "$"
37 );
38 },
39
40 templateToResolver: function (template, args) {
41 return template.replace(/{(\w+)}/g, (_, v) => args[v]);
42 },
43 };
44
45 return view.extend({
46 load: function () {
47 return Promise.all([
48 L.resolveDefault(hdp.getPlatformSupport(pkg.Name), {}),
49 L.resolveDefault(hdp.getProviders(pkg.Name), {}),
50 uci.load(pkg.Name),
51 uci.load("dhcp"),
52 ]);
53 },
54
55 render: function (data) {
56 var reply = {
57 platform: (data[0] && data[0][pkg.Name]) || {
58 http2_support: null,
59 http3_support: null,
60 },
61 providers: (data[1] && data[1][pkg.Name]) || { providers: [] },
62 };
63 reply.providers.sort(function (a, b) {
64 return _(a.title).localeCompare(_(b.title));
65 });
66 reply.providers.push({
67 title: "Custom",
68 template: "{option}",
69 params: { option: { type: "text" } },
70 });
71
72 var status, m, s, o, p;
73 var text;
74
75 status = new hdp.status();
76
77 m = new form.Map(pkg.Name, _("HTTPS DNS Proxy - Configuration"));
78
79 s = m.section(form.NamedSection, "config", pkg.Name);
80
81 o = s.option(
82 form.ListValue,
83 "dnsmasq_config_update",
84 _("Update DNSMASQ Config on Start/Stop"),
85 _(
86 "If update option is selected, the %s'DNS forwardings' section of DHCP and DNS%s will be automatically updated to use selected DoH providers (%smore information%s)."
87 ).format(
88 '<a href="' + L.url("admin", "network", "dhcp") + '">',
89 "</a>",
90 '<a href="' + pkg.URL + "#default-settings" + '" target="_blank">',
91 "</a>"
92 )
93 );
94 o.value("*", _("Update all configs"));
95
96 var sections = uci.sections("dhcp", "dnsmasq");
97 sections.forEach((element) => {
98 var description;
99 var key;
100 if (element[".name"] === uci.resolveSID("dhcp", element[".name"])) {
101 key = element[".index"];
102 description = "dnsmasq[" + element[".index"] + "]";
103 } else {
104 key = element[".name"];
105 description = element[".name"];
106 }
107 o.value(key, _("Update %s only").format(description));
108 });
109 o.value("-", _("Do not update configs"));
110 o.default = "*";
111
112 o = s.option(
113 form.ListValue,
114 "force_dns",
115 _("Force Router DNS"),
116 _("Forces Router DNS use on local devices, also known as DNS Hijacking.")
117 );
118 o.value("0", _("Let local devices use their own DNS servers if set"));
119 o.value("1", _("Force Router DNS server to all local devices"));
120 o.default = "1";
121
122 o = s.option(
123 form.ListValue,
124 "canary_domains_icloud",
125 _("Canary Domains iCloud"),
126 _(
127 "Blocks access to iCloud Private Relay resolvers, forcing local devices to use router for DNS resolution (%smore information%s)."
128 ).format(
129 '<a href="' + pkg.URL + "#canary_domains_icloud" + '" target="_blank">',
130 "</a>"
131 )
132 );
133 o.value("0", _("Let local devices use iCloud Private Relay"));
134 o.value("1", _("Force Router DNS server to all local devices"));
135 o.depends("force_dns", "1");
136 o.default = "1";
137
138 o = s.option(
139 form.ListValue,
140 "canary_domains_mozilla",
141 _("Canary Domains Mozilla"),
142 _(
143 "Blocks access to Mozilla Encrypted resolvers, forcing local devices to use router for DNS resolution (%smore information%s)."
144 ).format(
145 '<a href="' +
146 pkg.URL +
147 "#canary_domains_mozilla" +
148 '" target="_blank">',
149 "</a>"
150 )
151 );
152 o.value("0", _("Let local devices use Mozilla Private Relay"));
153 o.value("1", _("Force Router DNS server to all local devices"));
154 o.depends("force_dns", "1");
155 o.default = "1";
156
157 text = "";
158 if (!reply.platform.http2_support)
159 text +=
160 _(
161 "Please note that %s is not supported on this system (%smore information%s)."
162 ).format(
163 "<i>HTTP/2</i>",
164 '<a href="' + pkg.URL + "#http2-support" + '" target="_blank">',
165 "</a>"
166 ) + "<br />";
167 if (!reply.platform.http3_support)
168 text +=
169 _(
170 "Please note that %s is not supported on this system (%smore information%s)."
171 ).format(
172 "<i>HTTP/3 (QUIC)</i>",
173 '<a href="' + pkg.URL + "#http3-quic-support" + '" target="_blank">',
174 "</a>"
175 ) + "<br />";
176
177 s = m.section(
178 form.GridSection,
179 "https-dns-proxy",
180 _("HTTPS DNS Proxy - Instances"),
181 text
182 );
183 s.rowcolors = true;
184 s.sortable = true;
185 s.anonymous = true;
186 s.addremove = true;
187
188 s.sectiontitle = (section_id) => {
189 var provText;
190 var found;
191 reply.providers.forEach((prov) => {
192 var option;
193 let regexp = pkg.templateToRegexp(prov.template);
194 let resolver = uci.get(pkg.Name, section_id, "resolver_url");
195 resolver = resolver === undefined ? null : resolver;
196 if (!found && resolver && regexp.test(resolver)) {
197 found = true;
198 provText = _(prov.title);
199 let match = resolver.match(regexp);
200 if (match[1] != null) {
201 if (
202 prov.params &&
203 prov.params.option &&
204 prov.params.option.options
205 ) {
206 prov.params.option.options.forEach((opt) => {
207 if (opt.value === match[1]) {
208 option = _(opt.description);
209 }
210 });
211 provText += " (" + option + ")";
212 } else {
213 if (match[1] !== "") provText += " (" + match[1] + ")";
214 }
215 }
216 }
217 });
218 return provText || _("Unknown");
219 };
220
221 var _provider;
222 _provider = s.option(form.ListValue, "_provider", _("Provider"));
223 _provider.modalonly = true;
224 _provider.cfgvalue = function (section_id) {
225 let resolver = this.map.data.get(
226 this.map.config,
227 section_id,
228 "resolver_url"
229 );
230 if (resolver === undefined || resolver === null) return null;
231 let found;
232 let ret;
233 reply.providers.forEach((prov, i) => {
234 let regexp = pkg.templateToRegexp(prov.template);
235 if (!found && regexp.test(resolver)) {
236 found = true;
237 ret = prov.template;
238 }
239 });
240 return ret || "";
241 };
242 _provider.write = function (section_id, formvalue) {
243 uci.set(pkg.Name, section_id, "resolver_url", formvalue);
244 };
245
246 reply.providers.forEach((prov, i) => {
247 if (prov.http2_only && !reply.platform.http2_support) return;
248 if (prov.http3_only && !reply.platform.http3_support) return;
249 _provider.value(prov.template, _(prov.title));
250 if (
251 prov.params &&
252 prov.params.option &&
253 prov.params.option.type &&
254 prov.params.option.type === "select"
255 ) {
256 let optName = prov.params.option.description || _("Parameter");
257 var _paramList = s.option(form.ListValue, "_paramList_" + i, optName);
258 _paramList.template = prov.template;
259 _paramList.modalonly = true;
260 if (prov.params.option.default) {
261 _paramList.default = prov.params.option.default;
262 }
263 prov.params.option.options.forEach((opt) => {
264 let val = opt.value || "";
265 let descr = opt.description || "";
266 _paramList.value(val, descr);
267 });
268 _paramList.depends("_provider", prov.template);
269 _paramList.write = function (section_id, formvalue) {
270 let template = this.map.data.get(
271 this.map.config,
272 section_id,
273 "resolver_url"
274 );
275 if (_paramList.template !== template) return 0;
276 let resolver = pkg.templateToResolver(template, {
277 option: formvalue || "",
278 });
279 uci.set(pkg.Name, section_id, "resolver_url", resolver);
280 };
281 _paramList.remove = _paramList.write;
282 } else if (
283 prov.params &&
284 prov.params.option &&
285 prov.params.option.type &&
286 prov.params.option.type === "text"
287 ) {
288 let optName = prov.params.option.description || _("Parameter");
289 var _paramText = s.option(form.Value, "_paramText_" + i, optName);
290 _paramText.template = prov.template;
291 _paramText.modalonly = true;
292 _paramText.depends("_provider", prov.template);
293 _paramText.optional = !(
294 prov.params.option.default && prov.params.option.default !== ""
295 );
296 _paramText.cfgvalue = function (section_id) {
297 let resolver = this.map.data.get(
298 this.map.config,
299 section_id,
300 "resolver_url"
301 );
302 if (resolver === undefined || resolver === null) return null;
303 let regexp = pkg.templateToRegexp(prov.template);
304 let match = resolver.match(regexp);
305 return (match && match[1]) || null;
306 };
307 _paramText.write = function (section_id, formvalue) {
308 let template = this.map.data.get(
309 this.map.config,
310 section_id,
311 "resolver_url"
312 );
313 if (_paramText.template !== template) return 0;
314 let resolver = pkg.templateToResolver(template, {
315 option: formvalue || "",
316 });
317 uci.set(pkg.Name, section_id, "resolver_url", resolver);
318 };
319 _paramText.remove = _paramText.write;
320 }
321 });
322
323 o = s.option(form.Value, "bootstrap_dns", _("Bootstrap DNS"));
324 o.default = "";
325 o.modalonly = true;
326 o.optional = true;
327
328 o = s.option(form.Value, "listen_addr", _("Listen Address"));
329 o.datatype = "ipaddr";
330 o.default = "";
331 o.optional = true;
332 o.placeholder = "127.0.0.1";
333
334 o = s.option(form.Value, "listen_port", _("Listen Port"));
335 o.datatype = "port";
336 o.default = "";
337 o.optional = true;
338 o.placeholder = "5053";
339
340 o = s.option(form.Value, "user", _("Run As User"));
341 o.default = "";
342 o.modalonly = true;
343 o.optional = true;
344
345 o = s.option(form.Value, "group", _("Run As Group"));
346 o.default = "";
347 o.modalonly = true;
348 o.optional = true;
349
350 o = s.option(form.Value, "dscp_codepoint", _("DSCP Codepoint"));
351 o.datatype = "and(uinteger, range(0,63))";
352 o.default = "";
353 o.modalonly = true;
354 o.optional = true;
355
356 o = s.option(form.Value, "verbosity", _("Logging Verbosity"));
357 o.datatype = "and(uinteger, range(0,4))";
358 o.default = "";
359 o.modalonly = true;
360 o.optional = true;
361
362 o = s.option(form.Value, "logfile", _("Logging File Path"));
363 o.default = "";
364 o.modalonly = true;
365 o.optional = true;
366
367 o = s.option(form.Value, "polling_interval", _("Polling Interval"));
368 o.datatype = "and(uinteger, range(5,3600))";
369 o.default = "";
370 o.modalonly = true;
371 o.optional = true;
372
373 o = s.option(form.Value, "proxy_server", _("Proxy Server"));
374 o.default = "";
375 o.modalonly = true;
376 o.optional = true;
377
378 o = s.option(form.ListValue, "use_http1", _("Use HTTP/1"));
379 o.modalonly = true;
380 o.optional = true;
381 o.rmempty = true;
382 o.value("", _("Use negotiated HTTP version"));
383 o.value("1", _("Force use of HTTP/1"));
384 o.default = "";
385
386 o = s.option(
387 form.ListValue,
388 "use_ipv6_resolvers_only",
389 _("Use IPv6 resolvers")
390 );
391 o.modalonly = true;
392 o.optional = true;
393 o.rmempty = true;
394 o.value("", _("Use any family DNS resolvers"));
395 o.value("1", _("Force use of IPv6 DNS resolvers"));
396 o.default = "";
397
398 return Promise.all([status.render(), m.render()]);
399 },
400 });