summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-pbr/htdocs/luci-static
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-pbr/htdocs/luci-static')
-rw-r--r--applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js313
-rw-r--r--applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js246
2 files changed, 559 insertions, 0 deletions
diff --git a/applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js b/applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js
new file mode 100644
index 0000000000..8cd36bca7c
--- /dev/null
+++ b/applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js
@@ -0,0 +1,313 @@
+// Copyright 2022 Stan Grishin <stangri@melmac.ca>
+// This code wouldn't have been possible without help from [@vsviridov](https://github.com/vsviridov)
+
+"require ui";
+"require rpc";
+"require uci";
+"require form";
+"require baseclass";
+
+var pkg = {
+ get Name() {
+ return "pbr";
+ },
+ get URL() {
+ return "https://docs.openwrt.melmac.net/" + pkg.Name + "/";
+ },
+};
+
+var getGateways = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getGateways",
+ params: ["name"],
+});
+
+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 getInterfaces = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getInterfaces",
+ params: ["name"],
+});
+
+var getPlatformSupport = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "getPlatformSupport",
+ params: ["name"],
+});
+
+var _setInitAction = rpc.declare({
+ object: "luci." + pkg.Name,
+ method: "setInitAction",
+ params: ["name", "action"],
+ expect: { result: false },
+});
+
+var RPC = {
+ listeners: [],
+ on: function on(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 emit(event, data) {
+ this.listeners.forEach(function (listener) {
+ if (listener.event === event) {
+ listener.callback(data);
+ }
+ });
+ },
+ getInitList: function getInitList(name) {
+ getInitList(name).then(function (result) {
+ this.emit('getInitList', result);
+ }.bind(this));
+ },
+ getInitStatus: function getInitStatus(name) {
+ getInitStatus(name).then(function (result) {
+ this.emit('getInitStatus', result);
+ }.bind(this));
+ },
+ getGateways: function getGateways(name) {
+ getGateways(name).then(function (result) {
+ this.emit('getGateways', result);
+ }.bind(this));
+ },
+ getPlatformSupport: function getPlatformSupport(name) {
+ getPlatformSupport(name).then(function (result) {
+ this.emit('getPlatformSupport', result);
+ }.bind(this));
+ },
+ getInterfaces: function getInterfaces(name) {
+ getInterfaces(name).then(function (result) {
+ this.emit('getInterfaces', result);
+ }.bind(this));
+ },
+ setInitAction: function setInitAction(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(), {}),
+// L.resolveDefault(getGateways(), {}),
+ ]).then(function (data) {
+// var replyStatus = data[0];
+// var replyGateways = data[1];
+ var reply = data[0][pkg.Name];
+ var text;
+ var header = E('h2', {}, _("Policy Based Routing - Status"));
+ var statusTitle = E('label', { class: 'cbi-value-title' }, _("Service Status"));
+ if (reply.version) {
+ if (reply.running) {
+ if (reply.running_iptables) {
+ text = _("Running (version: %s using iptables)").format(reply.version);
+ }
+ else if (reply.running_nft) {
+ text = _("Running (version: %s using nft)").format(reply.version);
+ }
+ else {
+ text = _("Running (version: %s)").format(reply.version);
+ }
+ }
+ else {
+ if (reply.enabled) {
+ text = _("Stopped (version: %s)").format(reply.version);
+ }
+ else {
+ text = _("Stopped (Disabled)");
+ }
+ }
+ }
+ 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 gatewaysDiv = [];
+ if (reply.gateways) {
+ var gatewaysTitle = E('label', { class: 'cbi-value-title' }, _("Service Gateways"));
+ text = _("The %s indicates default gateway. See the %sREADME%s for details.").format("<strong>✓</strong>",
+ "<a href=\"" + pkg.URL + "#a-word-about-default-routing \" target=\"_blank\">", "</a>")
+ var gatewaysDescr = E('div', { class: 'cbi-value-description' }, text);
+ var gatewaysText = E('div', {}, reply.gateways);
+ var gatewaysField = E('div', { class: 'cbi-value-field' }, [gatewaysText, gatewaysDescr]);
+ gatewaysDiv = E('div', { class: 'cbi-value' }, [gatewaysTitle, gatewaysField]);
+ }
+
+ var warningsDiv = [];
+ if (reply.warnings && reply.warnings.length) {
+ var textLabelsTable = {
+ warningResolverNotSupported: _("Resolver set (%s) is not supported on this system.").format(uci.get(pkg.Name, 'config', 'resolver_set')),
+ warningAGHVersionTooLow: _("Installed AdGuardHome (%s) doesn't support 'ipset_file' option."),
+ warningPolicyProcess: _("%s")
+ };
+ var warningsTitle = E('label', { class: 'cbi-value-title' }, _("Service Warnings"));
+ var text = "";
+ (reply.warnings).forEach(element => {
+ text += (textLabelsTable[element.id]).format(element.extra || ' ') + "<br />";
+ });
+ var warningsText = E('div', {}, text);
+ var warningsField = E('div', { class: 'cbi-value-field' }, warningsText);
+ warningsDiv = E('div', { class: 'cbi-value' }, [warningsTitle, warningsField]);
+ }
+
+ var errorsDiv = [];
+ if (reply.errors && reply.errors.length) {
+ var textLabelsTable = {
+ errorConfigValidation: _("Config (%s) validation failure!").format('/etc/config/' + pkg.Name),
+ errorNoIpFull: _("ip-full binary cannot be found!"),
+ errorNoIpset: _("Resolver set support (%s) requires ipset, but ipset binary cannot be found!").format(uci.get(pkg.Name, 'config', 'resolver_set')),
+ errorNoNft: _("Resolver set support (%s) requires nftables, but nft binary cannot be found!").format(uci.get(pkg.Name, 'config', 'resolver_set')),
+ errorResolverNotSupported: _("Resolver set (%s) is not supported on this system!").format(uci.get(pkg.Name, 'config', 'resolver_set')),
+ errorServiceDisabled: _("The %s service is currently disabled!").format(pkg.Name),
+ errorNoWanGateway: _("The %s service failed to discover WAN gateway!").format(pkg.Name),
+ errorIpsetNameTooLong: _("The ipset name '%s' is longer than allowed 31 characters!"),
+ errorNftsetNameTooLong: _("The nft set name '%s' is longer than allowed 31 characters!"),
+ errorUnexpectedExit: _("Unexpected exit or service termination: '%s'!"),
+ errorPolicyNoSrcDest: _("Policy '%s' has no source/destination parameters!"),
+ errorPolicyNoInterface: _("Policy '%s' has no assigned interface!"),
+ errorPolicyUnknownInterface: _("Policy '%s' has an unknown interface!"),
+ errorPolicyProcess: _("%s"),
+ errorFailedSetup: _("Failed to set up '%s'!"),
+ errorFailedReload: _("Failed to reload '%s'!"),
+ errorUserFileNotFound: _("Custom user file '%s' not found or empty!"),
+ ererrorUserFileSyntax: _("Syntax error in custom user file '%s'!"),
+ errorUserFileRunning: _("Error running custom user file '%s'!"),
+ errorUserFileNoCurl: _("Use of 'curl' is detected in custom user file '%s', but 'curl' isn't installed!"),
+ errorNoGateways: _("Failed to set up any gateway!")
+ };
+ var errorsTitle = E('label', { class: 'cbi-value-title' }, _("Service Errors"));
+ var text = "";
+ (reply.errors).forEach(element => {
+ text += (textLabelsTable[element.id]).format(element.extra || ' ') + "<br />";
+ });
+ var errorsText = E('div', {}, text);
+ var errorsField = E('div', { class: 'cbi-value-field' }, errorsText);
+ errorsDiv = E('div', { class: 'cbi-value' }, [errorsTitle, errorsField]);
+ }
+
+ 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.enabled) {
+ btn_enable.disabled = true;
+ btn_disable.disabled = false;
+ if (reply.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);
+ if (reply.version) {
+ var buttonsDiv = E('div', { class: 'cbi-value' }, [buttonsTitle, buttonsField]);
+ }
+ else {
+ var buttonsDiv = [];
+ }
+
+ return E('div', {}, [header, statusDiv, gatewaysDiv, warningsDiv, errorsDiv, buttonsDiv]);
+ });
+ },
+});
+
+RPC.on('setInitAction', function (reply) {
+ ui.hideModal();
+ location.reload();
+});
+
+return L.Class.extend({
+ status: status,
+ getInterfaces: getInterfaces,
+ getPlatformSupport: getPlatformSupport
+});
diff --git a/applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js b/applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js
new file mode 100644
index 0000000000..32bc6cb2de
--- /dev/null
+++ b/applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js
@@ -0,0 +1,246 @@
+// Copyright 2022 Stan Grishin <stangri@melmac.ca>
+// This code wouldn't have been possible without help from [@vsviridov](https://github.com/vsviridov)
+
+'use strict';
+'require form';
+'require rpc';
+'require uci';
+'require view';
+'require pbr.status as pbr';
+
+var pkg = {
+ get Name() { return 'pbr'; },
+ get URL() { return 'https://docs.openwrt.melmac.net/' + pkg.Name + '/'; }
+};
+
+return view.extend({
+ load: function () {
+ return Promise.all([
+ uci.load(pkg.Name)
+ ]);
+ },
+
+ render: function () {
+ return Promise.all([
+ L.resolveDefault(pbr.getInterfaces(), {}),
+ L.resolveDefault(pbr.getPlatformSupport(), {}),
+ ]).then(function (data) {
+ var arrInterfaces = data[0][pkg.Name].interfaces;
+ var replyPlatform = data[1][pkg.Name];
+ var status, m, s, o;
+
+ status = new pbr.status();
+ m = new form.Map(pkg.Name, _("Policy Based Routing - Configuration"));
+
+ s = m.section(form.NamedSection, 'config', pkg.Name);
+ s.tab("tab_basic", _("Basic Configuration"));
+ s.tab("tab_advanced", _("Advanced Configuration"),
+ _("%sWARNING:%s Please make sure to check the %sREADME%s before changing anything in this section! " +
+ "Change any of the settings below with extreme caution!%s").format(
+ "<br/>&#160;&#160;&#160;&#160;<b>", "</b>",
+ "<a href=\"" + pkg.URL + "#service-configuration-settings \" target=\"_blank\">", "</a>", "<br/><br/>"));
+ s.tab("tab_webui", _("Web UI Configuration"))
+
+ o = s.taboption("tab_basic", form.ListValue, "verbosity", _("Output verbosity"),
+ _("Controls both system log and console output verbosity."));
+ o.value("0", _("Suppress/No output"));
+ o.value("1", _("Condensed output"));
+ o.value("2", _("Verbose output"));
+ o.default = "2";
+
+ o = s.taboption("tab_basic", form.ListValue, "strict_enforcement", _("Strict enforcement"),
+ _("See the %sREADME%s for details.").format(
+ "<a href=\"" + pkg.URL + "#strict-enforcement\" target=\"_blank\">", "</a>"));
+ o.value("0", _("Do not enforce policies when their gateway is down"));
+ o.value("1", _("Strictly enforce policies when their gateway is down"));
+ o.default = "1";
+
+ var text = "";
+ if (!(replyPlatform.adguardhome_ipset_support)) {
+ text += _("The %s is not supported on this system.").format("<i>adguardhome.ipset</i>") + "<br />"
+ }
+ if (!(replyPlatform.dnsmasq_ipset_support)) {
+ text += _("The %s is not supported on this system.").format("<i>dnsmasq.ipset</i>") + "<br />"
+ }
+ if (!(replyPlatform.dnsmasq_nftset_support)) {
+ text += _("The %s is not supported on this system.").format("<i>dnsmasq.nftset</i>") + "<br />"
+ }
+ text += _("Please check the %sREADME%s before changing this option.").format(
+ "<a href=\"" + pkg.URL + "#use-resolvers-set-support\" target=\"_blank\">", "</a>");
+ o = s.taboption("tab_basic", form.ListValue, "resolver_set", _("Use resolver set support for domains"), text);
+ o.value("none", _("Disabled"));
+ if (replyPlatform.adguardhome_ipset_support) {
+ o.value("adguardhome.ipset", _("AdGuardHome ipset"));
+ o.default = ("adguardhome.ipset", _("AdGuardHome ipset"));
+ }
+ if (replyPlatform.dnsmasq_ipset_support) {
+ o.value("dnsmasq.ipset", _("Dnsmasq ipset"));
+ o.default = ("dnsmasq.ipset", _("Dnsmasq ipset"));
+ }
+ if (replyPlatform.dnsmasq_nftset_support) {
+ o.value("dnsmasq.nftset", _("Dnsmasq nft set"));
+ o.default = ("dnsmasq.nftset", _("Dnsmasq nft set"));
+ }
+
+ o = s.taboption("tab_basic", form.ListValue, "ipv6_enabled", _("IPv6 Support"));
+ o.value("0", _("Disabled"));
+ o.value("1", _("Enabled"));
+
+ o = s.taboption("tab_advanced", form.DynamicList, "supported_interface", _("Supported Interfaces"),
+ _("Allows to specify the list of interface names (in lower case) to be explicitly supported by the service. " +
+ "Can be useful if your OpenVPN tunnels have dev option other than tun* or tap*."));
+ o.optional = false;
+
+ o = s.taboption("tab_advanced", form.DynamicList, "ignored_interface", _("Ignored Interfaces"),
+ _("Allows to specify the list of interface names (in lower case) to be ignored by the service. " +
+ "Can be useful if running both VPN server and VPN client on the router."));
+ o.optional = false;
+
+ o = s.taboption("tab_advanced", form.ListValue, "rule_create_option", _("Rule Create option"),
+ _("Select Add for -A/add and Insert for -I/Insert."));
+ o.value("add", _("Add"));
+ o.value("insert", _("Insert"));
+ o.default = "add";
+
+ o = s.taboption("tab_advanced", form.ListValue, "icmp_interface", _("Default ICMP Interface"),
+ _("Force the ICMP protocol interface."));
+ o.value("", _("No Change"));
+ arrInterfaces.forEach(element => {
+ if (element.toLowerCase() !== "ignore") {
+ o.value(element);
+ }
+ });
+ o.rmempty = true;
+
+ o = s.taboption("tab_advanced", form.Value, "wan_tid", _("WAN Table ID"),
+ _("Starting (WAN) Table ID number for tables created by the service."));
+ o.rmempty = true;
+ o.placeholder = "201";
+ o.datatype = "and(uinteger, min(201))";
+
+ o = s.taboption("tab_advanced", form.Value, "wan_mark", _("WAN Table FW Mark"),
+ _("Starting (WAN) FW Mark for marks used by the service. High starting mark is " +
+ "used to avoid conflict with SQM/QoS. Change with caution together with") +
+ " " + _("Service FW Mask") + ".");
+ o.rmempty = true;
+ o.placeholder = "010000";
+ o.datatype = "hexstring";
+
+ o = s.taboption("tab_advanced", form.Value, "fw_mask", _("Service FW Mask"),
+ _("FW Mask used by the service. High mask is used to avoid conflict with SQM/QoS. " +
+ "Change with caution together with") + " " + _("WAN Table FW Mark") + ".");
+ o.rmempty = true;
+ o.placeholder = "ff0000";
+ o.datatype = "hexstring";
+
+ o = s.taboption("tab_webui", form.ListValue, "webui_show_ignore_target", _("Add Ignore Target"),
+ _("Adds 'ignore' to the list of interfaces for policies. See the %sREADME%s for details.").format(
+ "<a href=\"" + pkg.URL + "#ignore-target\" target=\"_blank\">", "</a>"));
+ o.value("0", _("Disabled"))
+ o.value("1", _("Enabled"))
+ o.default = "0";
+ o.optional = false;
+
+ o = s.taboption("tab_webui", form.DynamicList, "webui_supported_protocol", _("Supported Protocols"),
+ _("Display these protocols in protocol column in Web UI."));
+ o.optional = false;
+
+ s = m.section(form.GridSection, 'policy', _('Policies'),
+ _("Name, interface and at least one other field are required. Multiple local and remote " +
+ "addresses/devices/domains and ports can be space separated. Placeholders below represent just " +
+ "the format/syntax and will not be used if fields are left blank."));
+ s.rowcolors = true;
+ s.sortable = true;
+ s.anonymous = true;
+ s.addremove = true;
+
+ o = s.option(form.Flag, "enabled", _("Enabled"));
+ o.default = "1";
+ o.editable = true;
+
+ o = s.option(form.Value, "name", _("Name"));
+
+ o = s.option(form.Value, "src_addr", _("Local addresses / devices"));
+ o.datatype = "list(neg(or(cidr,host,ipmask,ipaddr,macaddr,network)))";
+ o.rmempty = true;
+ o.default = "";
+
+ o = s.option(form.Value, "src_port", _("Local ports"));
+ o.datatype = "list(neg(or(portrange,port)))";
+ o.placeholder = "0-65535";
+ o.rmempty = true;
+ o.default = "";
+
+ o = s.option(form.Value, "dest_addr", _("Remote addresses / domains"));
+ o.datatype = "list(neg(or(cidr,host,ipmask,ipaddr,macaddr,network)))";
+ o.rmempty = true;
+ o.default = "";
+
+ o = s.option(form.Value, "dest_port", _("Remote ports"));
+ o.datatype = "list(neg(or(portrange,port)))";
+ o.placeholder = "0-65535";
+ o.rmempty = true;
+ o.default = "";
+
+ o = s.option(form.ListValue, "proto", _("Protocol"));
+ var proto = L.toArray(uci.get(pkg.Name, "config", "webui_supported_protocol"));
+ if (!proto.length) {
+ proto = ["all", "tcp", "udp", "tcp udp", "icmp"]
+ }
+ proto.forEach(element => {
+ if (element === "all") {
+ o.value("", _("all"));
+ o.default = ("", _("all"));
+ }
+ else {
+ o.value(element.toLowerCase());
+ }
+ });
+ o.rmempty = true;
+
+ o = s.option(form.ListValue, "chain", _("Chain"));
+ o.value("", "prerouting");
+ o.value("forward", "forward");
+ o.value("input", "input");
+ o.value("output", "output");
+ o.value("postrouting", "postrouting");
+ o.default = ("", "prerouting");
+ o.rmempty = true;
+
+ o = s.option(form.ListValue, "interface", _("Interface"));
+ arrInterfaces.forEach(element => {
+ o.value(element);
+ });
+ o.datatype = "network";
+ o.rmempty = false;
+
+ s = m.section(form.NamedSection, 'config', pkg.Name, _("DSCP Tagging"),
+ _("Set DSCP tags (in range between 1 and 63) for specific interfaces. See the %sREADME%s for details.").format(
+ "<a href=\"" + pkg.URL + "#dscp-tag-based-policies" + "\" target=\"_blank\">", "</a>"));
+ arrInterfaces.forEach(element => {
+ if (element.toLowerCase() !== "ignore") {
+ o = s.option(form.Value, element + "_dscp", element.toUpperCase() + " " + _("DSCP Tag"));
+ o.datatype = "and(uinteger, min(1), max(63))";
+ }
+ });
+
+ s = m.section(form.GridSection, 'include', _("Custom User File Includes"),
+ _("Run the following user files after setting up but before restarting DNSMASQ. " +
+ "See the %sREADME%s for details.").format(
+ "<a href=\"" + pkg.URL + "#custom-user-files\" target=\"_blank\">", "</a>"));
+ s.sortable = true;
+ s.anonymous = true;
+ s.addremove = true;
+
+ o = s.option(form.Flag, "enabled", _("Enabled"));
+ o.optional = false;
+ o.editable = true;
+
+ o = s.option(form.Value, "path", _("Path"));
+ o.optional = false;
+ o.editable = true;
+
+ return Promise.all([status.render(), m.render()]);
+ })
+ }
+});