summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-https-dns-proxy/htdocs/luci-static/resources')
-rw-r--r--applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/https-dns-proxy/status.js434
-rw-r--r--applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/view/https-dns-proxy/overview.js427
-rw-r--r--applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/view/status/include/71_https-dns-proxy.js153
3 files changed, 1014 insertions, 0 deletions
diff --git a/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/https-dns-proxy/status.js b/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/https-dns-proxy/status.js
new file mode 100644
index 0000000000..0ae128b923
--- /dev/null
+++ b/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/https-dns-proxy/status.js
@@ -0,0 +1,434 @@
+// Copyright MOSSDeF, 2023 Stan Grishin <stangri@melmac.ca>
+// This code wouldn't have been possible without help from:
+// - [@stokito](https://github.com/stokito)
+// - [@vsviridov](https://github.com/vsviridov)
+// noinspection JSAnnotator
+
+"require ui";
+"require rpc";
+"require form";
+"require baseclass";
+
+var pkg = {
+ get Name() {
+ return "https-dns-proxy";
+ },
+ get URL() {
+ return "https://docs.openwrt.melmac.net/" + pkg.Name + "/";
+ },
+ templateToRegexp: function (template) {
+ return RegExp(
+ "^" +
+ template
+ .split(/(\{\w+\})/g)
+ .map((part) => {
+ let placeholder = part.match(/^\{(\w+)\}$/);
+ if (placeholder) return `(?<${placeholder[1]}>.*?)`;
+ else return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ })
+ .join("") +
+ "$"
+ );
+ },
+};
+
+var getInitList = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getInitList",
+ params: ["name"],
+});
+
+var getInitStatus = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getInitStatus",
+ params: ["name"],
+});
+
+var getPlatformSupport = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getPlatformSupport",
+ params: ["name"],
+});
+
+var getProviders = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getProviders",
+ params: ["name"],
+});
+
+var getRuntime = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getRuntime",
+ params: ["name"],
+});
+
+var _setInitAction = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "setInitAction",
+ params: ["name", "action"],
+ expect: { result: false },
+});
+
+var RPC = {
+ listeners: [],
+ on: function (event, callback) {
+ var pair = { event: event, callback: callback };
+ this.listeners.push(pair);
+ return function unsubscribe() {
+ this.listeners = this.listeners.filter(function (listener) {
+ return listener !== pair;
+ });
+ }.bind(this);
+ },
+ emit: function (event, data) {
+ this.listeners.forEach(function (listener) {
+ if (listener.event === event) {
+ listener.callback(data);
+ }
+ });
+ },
+ getInitList: function (name) {
+ getInitList(name).then(
+ function (result) {
+ this.emit("getInitList", result);
+ }.bind(this)
+ );
+ },
+ getInitStatus: function (name) {
+ getInitStatus(name).then(
+ function (result) {
+ this.emit("getInitStatus", result);
+ }.bind(this)
+ );
+ },
+ getPlatformSupport: function (name) {
+ getPlatformSupport(name).then(
+ function (result) {
+ this.emit("getPlatformSupport", result);
+ }.bind(this)
+ );
+ },
+ getProviders: function (name) {
+ getProviders(name).then(
+ function (result) {
+ this.emit("getProviders", result);
+ }.bind(this)
+ );
+ },
+ getRuntime: function (name) {
+ getRuntime(name).then(
+ function (result) {
+ this.emit("getRuntime", result);
+ }.bind(this)
+ );
+ },
+ setInitAction: function (name, action) {
+ _setInitAction(name, action).then(
+ function (result) {
+ this.emit("setInitAction", result);
+ }.bind(this)
+ );
+ },
+};
+
+var status = baseclass.extend({
+ render: function () {
+ return Promise.all([
+ L.resolveDefault(getInitStatus(pkg.Name), {}),
+ L.resolveDefault(getProviders(pkg.Name), {}),
+ L.resolveDefault(getRuntime(pkg.Name), {}),
+ ]).then(function (data) {
+ var text;
+ var reply = {
+ status: (data[0] && data[0][pkg.Name]) || {
+ enabled: null,
+ running: null,
+ force_dns_active: null,
+ version: null,
+ },
+ providers: (data[1] && data[1][pkg.Name]) || { providers: [] },
+ runtime: (data[2] && data[2][pkg.Name]) || { instances: [] },
+ };
+ reply.providers.sort(function (a, b) {
+ return _(a.title).localeCompare(_(b.title));
+ });
+ reply.providers.push({
+ title: "Custom",
+ template: "{option}",
+ params: { option: { type: "text" } },
+ });
+
+ var header = E("h2", {}, _("HTTPS DNS Proxy - Status"));
+ var statusTitle = E(
+ "label",
+ { class: "cbi-value-title" },
+ _("Service Status")
+ );
+ if (reply.status.version) {
+ if (reply.status.running) {
+ text = _("Version %s - Running.").format(reply.status.version);
+ if (reply.status.force_dns_active) {
+ text += "<br />" + _("Force DNS ports:");
+ reply.status.force_dns_ports.forEach((element) => {
+ text += " " + element;
+ });
+ text += ".";
+ }
+ } else {
+ if (reply.status.enabled) {
+ text = _("Version %s - Stopped.").format(reply.status.version);
+ } else {
+ text = _("Version %s - Stopped (Disabled).").format(
+ reply.status.version
+ );
+ }
+ }
+ } else {
+ text = _("Not installed or not found");
+ }
+ var statusText = E("div", {}, text);
+ var statusField = E("div", { class: "cbi-value-field" }, statusText);
+ var statusDiv = E("div", { class: "cbi-value" }, [
+ statusTitle,
+ statusField,
+ ]);
+
+ var instancesDiv = [];
+ if (reply.runtime.instances) {
+ var instancesTitle = E(
+ "label",
+ { class: "cbi-value-title" },
+ _("Service Instances")
+ );
+ text = _("See the %sREADME%s for details.").format(
+ '<a href="' +
+ pkg.URL +
+ '#a-word-about-default-routing " target="_blank">',
+ "</a>"
+ );
+ var instancesDescr = E("div", { class: "cbi-value-description" }, "");
+
+ text = "";
+ Object.values(reply.runtime.instances).forEach((element) => {
+ var resolver;
+ var address;
+ var port;
+ var name;
+ var option;
+ var found;
+ element.command.forEach((param, index, arr) => {
+ if (param === "-r") resolver = arr[index + 1];
+ if (param === "-a") address = arr[index + 1];
+ if (param === "-p") port = arr[index + 1];
+ });
+ resolver = resolver || "Unknown";
+ address = address || "127.0.0.1";
+ port = port || "Unknown";
+ reply.providers.forEach((prov) => {
+ let regexp = pkg.templateToRegexp(prov.template);
+ if (!found && regexp.test(resolver)) {
+ found = true;
+ name = _(prov.title);
+ let match = resolver.match(regexp);
+ if (match[1] != null) {
+ if (
+ prov.params &&
+ prov.params.option &&
+ prov.params.option.options
+ ) {
+ prov.params.option.options.forEach((opt) => {
+ if (opt.value === match[1]) option = _(opt.description);
+ });
+ name += " (" + option + ")";
+ } else {
+ if (match[1] !== "") name += " (" + match[1] + ")";
+ }
+ }
+ }
+ });
+ if (address === "127.0.0.1")
+ text += _("%s%s%s proxy on port %s.%s").format(
+ "<strong>",
+ name,
+ "</strong>",
+ port,
+ "<br />"
+ );
+ else
+ text += _("%s%s%s proxy at %s on port %s.%s").format(
+ "<strong>",
+ name,
+ "</strong>",
+ address,
+ port,
+ "<br />"
+ );
+ });
+ var instancesText = E("div", {}, text);
+ var instancesField = E("div", { class: "cbi-value-field" }, [
+ instancesText,
+ instancesDescr,
+ ]);
+ instancesDiv = E("div", { class: "cbi-value" }, [
+ instancesTitle,
+ instancesField,
+ ]);
+ }
+
+ var btn_gap = E("span", {}, "&#160;&#160;");
+ var btn_gap_long = E(
+ "span",
+ {},
+ "&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;"
+ );
+ var btn_start = E(
+ "button",
+ {
+ class: "btn cbi-button cbi-button-apply",
+ disabled: true,
+ click: function (ev) {
+ ui.showModal(null, [
+ E(
+ "p",
+ { class: "spinning" },
+ _("Starting %s service").format(pkg.Name)
+ ),
+ ]);
+ return RPC.setInitAction(pkg.Name, "start");
+ },
+ },
+ _("Start")
+ );
+
+ var btn_action = E(
+ "button",
+ {
+ class: "btn cbi-button cbi-button-apply",
+ disabled: true,
+ click: function (ev) {
+ ui.showModal(null, [
+ E(
+ "p",
+ { class: "spinning" },
+ _("Restarting %s service").format(pkg.Name)
+ ),
+ ]);
+ return RPC.setInitAction(pkg.Name, "restart");
+ },
+ },
+ _("Restart")
+ );
+
+ var btn_stop = E(
+ "button",
+ {
+ class: "btn cbi-button cbi-button-reset",
+ disabled: true,
+ click: function (ev) {
+ ui.showModal(null, [
+ E(
+ "p",
+ { class: "spinning" },
+ _("Stopping %s service").format(pkg.Name)
+ ),
+ ]);
+ return RPC.setInitAction(pkg.Name, "stop");
+ },
+ },
+ _("Stop")
+ );
+
+ var btn_enable = E(
+ "button",
+ {
+ class: "btn cbi-button cbi-button-apply",
+ disabled: true,
+ click: function (ev) {
+ ui.showModal(null, [
+ E(
+ "p",
+ { class: "spinning" },
+ _("Enabling %s service").format(pkg.Name)
+ ),
+ ]);
+ return RPC.setInitAction(pkg.Name, "enable");
+ },
+ },
+ _("Enable")
+ );
+
+ var btn_disable = E(
+ "button",
+ {
+ class: "btn cbi-button cbi-button-reset",
+ disabled: true,
+ click: function (ev) {
+ ui.showModal(null, [
+ E(
+ "p",
+ { class: "spinning" },
+ _("Disabling %s service").format(pkg.Name)
+ ),
+ ]);
+ return RPC.setInitAction(pkg.Name, "disable");
+ },
+ },
+ _("Disable")
+ );
+
+ if (reply.status.enabled) {
+ btn_enable.disabled = true;
+ btn_disable.disabled = false;
+ if (reply.status.running) {
+ btn_start.disabled = true;
+ btn_action.disabled = false;
+ btn_stop.disabled = false;
+ } else {
+ btn_start.disabled = false;
+ btn_action.disabled = true;
+ btn_stop.disabled = true;
+ }
+ } else {
+ btn_start.disabled = true;
+ btn_action.disabled = true;
+ btn_stop.disabled = true;
+ btn_enable.disabled = false;
+ btn_disable.disabled = true;
+ }
+
+ var buttonsTitle = E(
+ "label",
+ { class: "cbi-value-title" },
+ _("Service Control")
+ );
+ var buttonsText = E("div", {}, [
+ btn_start,
+ btn_gap,
+ btn_action,
+ btn_gap,
+ btn_stop,
+ btn_gap_long,
+ btn_enable,
+ btn_gap,
+ btn_disable,
+ ]);
+ var buttonsField = E("div", { class: "cbi-value-field" }, buttonsText);
+ var buttonsDiv = reply.status.version
+ ? E("div", { class: "cbi-value" }, [buttonsTitle, buttonsField])
+ : "";
+ return E("div", {}, [header, statusDiv, instancesDiv, buttonsDiv]);
+ });
+ },
+});
+
+RPC.on("setInitAction", function (reply) {
+ ui.hideModal();
+ location.reload();
+});
+
+return L.Class.extend({
+ status: status,
+ getPlatformSupport: getPlatformSupport,
+ getProviders: getProviders,
+ getRuntime: getRuntime,
+});
diff --git a/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/view/https-dns-proxy/overview.js b/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/view/https-dns-proxy/overview.js
new file mode 100644
index 0000000000..86cafd1030
--- /dev/null
+++ b/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/view/https-dns-proxy/overview.js
@@ -0,0 +1,427 @@
+// Copyright 2023 MOSSDeF, Stan Grishin <stangri@melmac.ca>
+// This code wouldn't have been possible without help from:
+// - [@jow-](https://github.com/jow-)
+// - [@stokito](https://github.com/stokito)
+// - [@vsviridov](https://github.com/vsviridov)
+// noinspection JSAnnotator
+
+"use strict";
+"require form";
+"require rpc";
+"require view";
+"require https-dns-proxy.status as hdp";
+
+var pkg = {
+ get Name() {
+ return "https-dns-proxy";
+ },
+
+ get URL() {
+ return "https://docs.openwrt.melmac.net/" + pkg.Name + "/";
+ },
+
+ templateToRegexp: function (template) {
+ return RegExp(
+ "^" +
+ template
+ .split(/(\{\w+\})/g)
+ .map((part) => {
+ let placeholder = part.match(/^\{(\w+)\}$/);
+ if (placeholder) return `(?<${placeholder[1]}>.*?)`;
+ else return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ })
+ .join("") +
+ "$"
+ );
+ },
+
+ templateToResolver: function (template, args) {
+ return template.replace(/{(\w+)}/g, (_, v) => args[v]);
+ },
+};
+
+return view.extend({
+ load: function () {
+ return Promise.all([
+ L.resolveDefault(hdp.getPlatformSupport(pkg.Name), {}),
+ L.resolveDefault(hdp.getProviders(pkg.Name), {}),
+ L.resolveDefault(L.uci.load(pkg.Name), {}),
+ L.resolveDefault(L.uci.load("dhcp"), {}),
+ ]);
+ },
+
+ render: function (data) {
+ var reply = {
+ platform: (data[0] && data[0][pkg.Name]) || {
+ http2_support: null,
+ http3_support: null,
+ },
+ providers: (data[1] && data[1][pkg.Name]) || { providers: [] },
+ };
+ reply.providers.sort(function (a, b) {
+ return _(a.title).localeCompare(_(b.title));
+ });
+ reply.providers.push({
+ title: "Custom",
+ template: "{option}",
+ params: { option: { type: "text" } },
+ });
+
+ var status, m, s, o, p;
+ var text;
+
+ status = new hdp.status();
+
+ m = new form.Map(pkg.Name, _("HTTPS DNS Proxy - Configuration"));
+
+ s = m.section(form.NamedSection, "config", pkg.Name);
+
+ o = s.option(
+ form.ListValue,
+ "dnsmasq_config_update_option",
+ _("Update DNSMASQ Config on Start/Stop"),
+ _(
+ "If update option is selected, the %s'DNS Forwards' section of DHCP and DNS%s will be automatically updated to use selected DoH providers (%smore information%s)."
+ ).format(
+ '<a href="' + L.url("admin", "network", "dhcp") + '">',
+ "</a>",
+ '<a href="' + pkg.URL + "#default-settings" + '" target="_blank">',
+ "</a>"
+ )
+ );
+ o.value("*", _("Update all configs"));
+ o.value("+", _("Update select configs"));
+ o.value("-", _("Do not update configs"));
+ o.default = "*";
+ o.retain = true;
+ o.cfgvalue = function (section_id) {
+ let val = this.map.data.get(
+ this.map.config,
+ section_id,
+ "dnsmasq_config_update"
+ );
+ if (val && val[0]) {
+ switch (val[0]) {
+ case "*":
+ case "-":
+ return val[0];
+ default:
+ return "+";
+ }
+ } else return "*";
+ };
+ o.write = function (section_id, formvalue) {
+ L.uci.set(pkg.Name, section_id, "dnsmasq_config_update", formvalue);
+ };
+
+ o = s.option(
+ form.MultiValue,
+ "dnsmasq_config_update",
+ _("Select the DNSMASQ Configs to update")
+ );
+ Object.values(L.uci.sections("dhcp", "dnsmasq")).forEach(function (
+ element
+ ) {
+ var description;
+ var key;
+ if (element[".name"] === L.uci.resolveSID("dhcp", element[".name"])) {
+ key = element[".index"];
+ description = "dnsmasq[" + element[".index"] + "]";
+ } else {
+ key = element[".name"];
+ description = element[".name"];
+ }
+ o.value(key, description);
+ });
+ o.depends("dnsmasq_config_update_option", "+");
+ o.retain = true;
+
+ o = s.option(
+ form.ListValue,
+ "force_dns",
+ _("Force Router DNS"),
+ _("Forces Router DNS use on local devices, also known as DNS Hijacking.")
+ );
+ o.value("0", _("Let local devices use their own DNS servers if set"));
+ o.value("1", _("Force Router DNS server to all local devices"));
+ o.default = "1";
+
+ o = s.option(
+ form.ListValue,
+ "canary_domains_icloud",
+ _("Canary Domains iCloud"),
+ _(
+ "Blocks access to iCloud Private Relay resolvers, forcing local devices to use router for DNS resolution (%smore information%s)."
+ ).format(
+ '<a href="' + pkg.URL + "#canary_domains_icloud" + '" target="_blank">',
+ "</a>"
+ )
+ );
+ o.value("0", _("Let local devices use iCloud Private Relay"));
+ o.value("1", _("Force Router DNS server to all local devices"));
+ o.depends("force_dns", "1");
+ o.default = "1";
+
+ o = s.option(
+ form.ListValue,
+ "canary_domains_mozilla",
+ _("Canary Domains Mozilla"),
+ _(
+ "Blocks access to Mozilla Encrypted resolvers, forcing local devices to use router for DNS resolution (%smore information%s)."
+ ).format(
+ '<a href="' +
+ pkg.URL +
+ "#canary_domains_mozilla" +
+ '" target="_blank">',
+ "</a>"
+ )
+ );
+ o.value("0", _("Let local devices use Mozilla Private Relay"));
+ o.value("1", _("Force Router DNS server to all local devices"));
+ o.depends("force_dns", "1");
+ o.default = "1";
+
+ text = "";
+ if (!reply.platform.http2_support)
+ text +=
+ _(
+ "Please note that %s is not supported on this system (%smore information%s)."
+ ).format(
+ "<i>HTTP/2</i>",
+ '<a href="' + pkg.URL + "#http2-support" + '" target="_blank">',
+ "</a>"
+ ) + "<br />";
+ if (!reply.platform.http3_support)
+ text +=
+ _(
+ "Please note that %s is not supported on this system (%smore information%s)."
+ ).format(
+ "<i>HTTP/3 (QUIC)</i>",
+ '<a href="' + pkg.URL + "#http3-quic-support" + '" target="_blank">',
+ "</a>"
+ ) + "<br />";
+
+ s = m.section(
+ form.GridSection,
+ "https-dns-proxy",
+ _("HTTPS DNS Proxy - Instances"),
+ text
+ );
+ s.rowcolors = true;
+ s.sortable = true;
+ s.anonymous = true;
+ s.addremove = true;
+
+ s.sectiontitle = (section_id) => {
+ var provText;
+ var found;
+ reply.providers.forEach((prov) => {
+ var option;
+ let regexp = pkg.templateToRegexp(prov.template);
+ let resolver = L.uci.get(pkg.Name, section_id, "resolver_url");
+ resolver = resolver === undefined ? null : resolver;
+ if (!found && resolver && regexp.test(resolver)) {
+ found = true;
+ provText = _(prov.title);
+ let match = resolver.match(regexp);
+ if (match[1] != null) {
+ if (
+ prov.params &&
+ prov.params.option &&
+ prov.params.option.options
+ ) {
+ prov.params.option.options.forEach((opt) => {
+ if (opt.value === match[1]) {
+ option = _(opt.description);
+ }
+ });
+ provText += " (" + option + ")";
+ } else {
+ if (match[1] !== "") provText += " (" + match[1] + ")";
+ }
+ }
+ }
+ });
+ return provText || _("Unknown");
+ };
+
+ var _provider;
+ _provider = s.option(form.ListValue, "_provider", _("Provider"));
+ _provider.modalonly = true;
+ _provider.cfgvalue = function (section_id) {
+ let resolver = this.map.data.get(
+ this.map.config,
+ section_id,
+ "resolver_url"
+ );
+ if (resolver === undefined || resolver === null) return null;
+ let found;
+ let ret;
+ reply.providers.forEach((prov, i) => {
+ let regexp = pkg.templateToRegexp(prov.template);
+ if (!found && regexp.test(resolver)) {
+ found = true;
+ ret = prov.template;
+ }
+ });
+ return ret || "";
+ };
+ _provider.write = function (section_id, formvalue) {
+ L.uci.set(pkg.Name, section_id, "resolver_url", formvalue);
+ };
+
+ reply.providers.forEach((prov, i) => {
+ if (prov.http2_only && !reply.platform.http2_support) return;
+ if (prov.http3_only && !reply.platform.http3_support) return;
+ _provider.value(prov.template, _(prov.title));
+ if (
+ prov.params &&
+ prov.params.option &&
+ prov.params.option.type &&
+ prov.params.option.type === "select"
+ ) {
+ let optName = prov.params.option.description || _("Parameter");
+ var _paramList = s.option(form.ListValue, "_paramList_" + i, optName);
+ _paramList.template = prov.template;
+ _paramList.modalonly = true;
+ if (prov.params.option.default) {
+ _paramList.default = prov.params.option.default;
+ }
+ prov.params.option.options.forEach((opt) => {
+ let val = opt.value || "";
+ let descr = opt.description || "";
+ _paramList.value(val, descr);
+ });
+ _paramList.depends("_provider", prov.template);
+ _paramList.write = function (section_id, formvalue) {
+ let template = this.map.data.get(
+ this.map.config,
+ section_id,
+ "resolver_url"
+ );
+ if (!formvalue && _paramList.template !== template) return 0;
+ let resolver = pkg.templateToResolver(_paramList.template, {
+ option: formvalue || "",
+ });
+ L.uci.set(pkg.Name, section_id, "resolver_url", resolver);
+ };
+ _paramList.remove = _paramList.write;
+ } else if (
+ prov.params &&
+ prov.params.option &&
+ prov.params.option.type &&
+ prov.params.option.type === "text"
+ ) {
+ let optName = prov.params.option.description || _("Parameter");
+ var _paramText = s.option(form.Value, "_paramText_" + i, optName);
+ _paramText.template = prov.template;
+ _paramText.modalonly = true;
+ _paramText.depends("_provider", prov.template);
+ _paramText.optional = !(
+ prov.params.option.default && prov.params.option.default !== ""
+ );
+ _paramText.cfgvalue = function (section_id) {
+ let resolver = this.map.data.get(
+ this.map.config,
+ section_id,
+ "resolver_url"
+ );
+ if (resolver === undefined || resolver === null) return null;
+ let regexp = pkg.templateToRegexp(prov.template);
+ let match = resolver.match(regexp);
+ return (match && match[1]) || null;
+ };
+ _paramText.write = function (section_id, formvalue) {
+ let template = this.map.data.get(
+ this.map.config,
+ section_id,
+ "resolver_url"
+ );
+ if (!formvalue && _paramText.template !== template) return 0;
+ let resolver = pkg.templateToResolver(_paramText.template, {
+ option: formvalue || "",
+ });
+ L.uci.set(pkg.Name, section_id, "resolver_url", resolver);
+ };
+ _paramText.remove = _paramText.write;
+ }
+ });
+
+ o = s.option(form.Value, "bootstrap_dns", _("Bootstrap DNS"));
+ o.default = "";
+ o.modalonly = true;
+ o.optional = true;
+
+ o = s.option(form.Value, "listen_addr", _("Listen Address"));
+ o.datatype = "ipaddr";
+ o.default = "";
+ o.optional = true;
+ o.placeholder = "127.0.0.1";
+
+ o = s.option(form.Value, "listen_port", _("Listen Port"));
+ o.datatype = "port";
+ o.default = "";
+ o.optional = true;
+ o.placeholder = "5053";
+
+ o = s.option(form.Value, "user", _("Run As User"));
+ o.default = "";
+ o.modalonly = true;
+ o.optional = true;
+
+ o = s.option(form.Value, "group", _("Run As Group"));
+ o.default = "";
+ o.modalonly = true;
+ o.optional = true;
+
+ o = s.option(form.Value, "dscp_codepoint", _("DSCP Codepoint"));
+ o.datatype = "and(uinteger, range(0,63))";
+ o.default = "";
+ o.modalonly = true;
+ o.optional = true;
+
+ o = s.option(form.Value, "verbosity", _("Logging Verbosity"));
+ o.datatype = "and(uinteger, range(0,4))";
+ o.default = "";
+ o.modalonly = true;
+ o.optional = true;
+
+ o = s.option(form.Value, "logfile", _("Logging File Path"));
+ o.default = "";
+ o.modalonly = true;
+ o.optional = true;
+
+ o = s.option(form.Value, "polling_interval", _("Polling Interval"));
+ o.datatype = "and(uinteger, range(5,3600))";
+ o.default = "";
+ o.modalonly = true;
+ o.optional = true;
+
+ o = s.option(form.Value, "proxy_server", _("Proxy Server"));
+ o.default = "";
+ o.modalonly = true;
+ o.optional = true;
+
+ o = s.option(form.ListValue, "use_http1", _("Use HTTP/1"));
+ o.modalonly = true;
+ o.optional = true;
+ o.rmempty = true;
+ o.value("", _("Use negotiated HTTP version"));
+ o.value("1", _("Force use of HTTP/1"));
+ o.default = "";
+
+ o = s.option(
+ form.ListValue,
+ "use_ipv6_resolvers_only",
+ _("Use IPv6 resolvers")
+ );
+ o.modalonly = true;
+ o.optional = true;
+ o.rmempty = true;
+ o.value("", _("Use any family DNS resolvers"));
+ o.value("1", _("Force use of IPv6 DNS resolvers"));
+ o.default = "";
+
+ return Promise.all([status.render(), m.render()]);
+ },
+});
diff --git a/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/view/status/include/71_https-dns-proxy.js b/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/view/status/include/71_https-dns-proxy.js
new file mode 100644
index 0000000000..8723b6e653
--- /dev/null
+++ b/applications/luci-app-https-dns-proxy/htdocs/luci-static/resources/view/status/include/71_https-dns-proxy.js
@@ -0,0 +1,153 @@
+"require ui";
+"require rpc";
+"require uci";
+"require form";
+"require baseclass";
+
+var pkg = {
+ get Name() {
+ return "https-dns-proxy";
+ },
+ get URL() {
+ return "https://docs.openwrt.melmac.net/" + pkg.Name + "/";
+ },
+ templateToRegexp: function (template) {
+ return RegExp(
+ "^" +
+ template
+ .split(/(\{\w+\})/g)
+ .map((part) => {
+ let placeholder = part.match(/^\{(\w+)\}$/);
+ if (placeholder) return `(?<${placeholder[1]}>.*?)`;
+ else return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ })
+ .join("") +
+ "$"
+ );
+ },
+};
+
+var getInitStatus = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getInitStatus",
+ params: ["name"],
+});
+
+var getPlatformSupport = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getPlatformSupport",
+ params: ["name"],
+});
+
+var getProviders = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getProviders",
+ params: ["name"],
+});
+
+var getRuntime = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getRuntime",
+ params: ["name"],
+});
+
+return baseclass.extend({
+ title: _("HTTPS DNS Proxy Instances"),
+
+ load: function () {
+ return Promise.all([
+ getInitStatus(pkg.Name),
+ getProviders(pkg.Name),
+ getRuntime(pkg.Name),
+ ]);
+ },
+
+ render: function (data) {
+ var reply = {
+ status: (data[0] && data[0][pkg.Name]) || {
+ enabled: null,
+ running: null,
+ force_dns_active: null,
+ version: null,
+ },
+ providers: (data[1] && data[1][pkg.Name]) || { providers: [] },
+ runtime: (data[2] && data[2][pkg.Name]) || { instances: [] },
+ };
+ reply.providers.sort(function (a, b) {
+ return _(a.title).localeCompare(_(b.title));
+ });
+ reply.providers.push({
+ title: "Custom",
+ template: "{option}",
+ params: { option: { type: "text" } },
+ });
+
+ var forceDnsText = "";
+ if (reply.status.force_dns_active) {
+ reply.status.force_dns_ports.forEach((element) => {
+ forceDnsText += element + " ";
+ });
+ } else {
+ forceDnsText = "-";
+ }
+
+ var table = E(
+ "table",
+ { class: "table", id: "https-dns-proxy_status_table" },
+ [
+ E("tr", { class: "tr table-titles" }, [
+ E("th", { class: "th" }, _("Name / Type")),
+ E("th", { class: "th" }, _("Listen Address")),
+ E("th", { class: "th" }, _("Listen Port")),
+ E("th", { class: "th" }, _("Force DNS Ports")),
+ ]),
+ ]
+ );
+
+ var rows = [];
+ if (reply.runtime.instances) {
+ Object.values(reply.runtime.instances).forEach((element) => {
+ var resolver;
+ var address;
+ var port;
+ var name;
+ var option;
+ var found;
+ element.command.forEach((param, index, arr) => {
+ if (param === "-r") resolver = arr[index + 1];
+ if (param === "-a") address = arr[index + 1];
+ if (param === "-p") port = arr[index + 1];
+ });
+ resolver = resolver || "Unknown";
+ address = address || "127.0.0.1";
+ port = port || "Unknown";
+ reply.providers.forEach((prov) => {
+ let regexp = pkg.templateToRegexp(prov.template);
+ if (!found && regexp.test(resolver)) {
+ found = true;
+ name = _(prov.title);
+ let match = resolver.match(regexp);
+ if (match[1] != null) {
+ if (
+ prov.params &&
+ prov.params.option &&
+ prov.params.option.options
+ ) {
+ prov.params.option.options.forEach((opt) => {
+ if (opt.value === match[1]) option = _(opt.description);
+ });
+ name += " (" + option + ")";
+ } else {
+ if (match[1] !== "") name += " (" + match[1] + ")";
+ }
+ }
+ }
+ });
+ rows.push([name, address, port, forceDnsText]);
+ });
+ }
+ cbi_update_table(table, rows, E("em", _("There are no active instances.")));
+
+ return table;
+ },
+});