luci-app-pbr: update to 1.1.4-5 6999/head
authorStan Grishin <stangri@melmac.ca>
Mon, 18 Mar 2024 02:03:13 +0000 (02:03 +0000)
committerStan Grishin <stangri@melmac.ca>
Mon, 18 Mar 2024 02:04:15 +0000 (02:04 +0000)
* sync with the principal package
* add the status include file

Signed-off-by: Stan Grishin <stangri@melmac.ca>
applications/luci-app-pbr/Makefile
applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js
applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js
applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js [new file with mode: 0644]
applications/luci-app-pbr/po/templates/pbr.pot
applications/luci-app-pbr/root/usr/libexec/rpcd/luci.pbr
applications/luci-app-pbr/root/usr/share/rpcd/acl.d/luci-app-pbr.json

index 3b04a9500d4302fcf96f19e5a10cde547d3e5745..6eee45f9bb00a3d2bce9588784c721f26976aee8 100644 (file)
@@ -1,15 +1,16 @@
-# Copyright 2017-2022 Stan Grishin (stangri@melmac.ca)
+# Copyright 2017-2023 MOSSDeF, Stan Grishin (stangri@melmac.ca)
 # This is free software, licensed under the GNU General Public License v3.
 
 include $(TOPDIR)/rules.mk
 
 PKG_LICENSE:=GPL-3.0-or-later
 PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
-PKG_VERSION:=1.1.1-7
+PKG_VERSION:=1.1.4-5
 
 LUCI_TITLE:=Policy Based Routing Service Web UI
 LUCI_DESCRIPTION:=Provides Web UI for Policy Based Routing Service.
-LUCI_DEPENDS:=+luci-base +jsonfilter +pbr
+LUCI_DEPENDS:=+luci-base +jsonfilter +pbr-service
+LUCI_PKGARCH:=all
 
 PKG_PROVIDES:=luci-app-vpnbypass luci-app-vpn-policy-routing
 
index 24f624c9452f49a57e6769a46ea2f9960153ca34..99a6e73844a31f48740b69539c526d1fa136f2d3 100644 (file)
@@ -3,7 +3,6 @@
 
 "require ui";
 "require rpc";
-"require uci";
 "require form";
 "require baseclass";
 
@@ -55,8 +54,8 @@ var _setInitAction = rpc.declare({
 
 var RPC = {
        listeners: [],
-       on: function on(event, callback) {
-               var pair = { event: event, callback: callback }
+       on: function (event, callback) {
+               var pair = { event: event, callback: callback };
                this.listeners.push(pair);
                return function unsubscribe() {
                        this.listeners = this.listeners.filter(function (listener) {
@@ -64,65 +63,77 @@ var RPC = {
                        });
                }.bind(this);
        },
-       emit: function emit(event, data) {
+       emit: function (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));
+       getInitList: function (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));
+       getInitStatus: function (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));
+       getGateways: function (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));
+       getPlatformSupport: function (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));
+       getInterfaces: function (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));
+       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(), {}),
-//                     L.resolveDefault(getGateways(), {}),
+                       L.resolveDefault(getInitStatus(pkg.Name), {}),
+                       //                      L.resolveDefault(getGateways(pkg.Name), {}),
                ]).then(function (data) {
-//                     var replyStatus = data[0];
-//                     var replyGateways = data[1];
+                       //                      var replyStatus = data[0];
+                       //                      var replyGateways = data[1];
                        var reply;
                        var text;
 
                        if (data[0] && data[0][pkg.Name]) {
                                reply = data[0][pkg.Name];
-                       }
-                       else {
+                       } else {
                                reply = {
                                        enabled: null,
                                        running: null,
                                        running_iptables: null,
                                        running_nft: null,
+                                       running_nft_file: null,
                                        version: null,
                                        gateways: null,
                                        errors: [],
@@ -130,187 +141,351 @@ var status = baseclass.extend({
                                };
                        }
 
-                       var header = E('h2', {}, _("Policy Based Routing - Status"));
-                       var statusTitle = E('label', { class: 'cbi-value-title' }, _("Service Status"));
+                       var header = E("h2", {}, _("Policy Based Routing - Status"));
+                       var statusTitle = E(
+                               "label",
+                               { class: "cbi-value-title" },
+                               _("Service Status")
+                       );
                        if (reply.version) {
+                               text = _("Version %s").format(reply.version) + " - ";
                                if (reply.running) {
+                                       text += _("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);
+                                               text += " (" + _("iptables mode") + ").";
+                                       } else if (reply.running_nft_file) {
+                                               text += " (" + _("fw4 nft file mode") + ").";
+                                       } else if (reply.running_nft) {
+                                               text += " (" + _("nft mode") + ").";
+                                       } else {
+                                               text += ".";
                                        }
-                                       else {
-                                               text = _("Running (version: %s)").format(reply.version);
-                                       }
-                               }
-                               else {
+                               } else {
                                        if (reply.enabled) {
-                                               text = _("Stopped (version: %s)").format(reply.version);
-                                       }
-                                       else {
-                                               text = _("Stopped (Disabled)");
+                                               text += _("Stopped.");
+                                       } else {
+                                               text += _("Stopped (Disabled).");
                                        }
                                }
-                       }
-                       else {
+                       } 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 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 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 + '#AWordAboutDefaultRouting" 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."),
+                                       warningResolverNotSupported: _(
+                                               "Resolver set (%s) is not supported on this system."
+                                       ).format(L.uci.get(pkg.Name, "config", "resolver_set")),
+                                       warningAGHVersionTooLow: _(
+                                               "Installed AdGuardHome (%s) doesn't support 'ipset_file' option."
+                                       ),
                                        warningPolicyProcessCMD: _("%s"),
-                                       warningTorUnsetParams: _("Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'"),
-                                       warningTorUnsetProto: _("Please unset 'proto' or set 'proto' to 'all' for policy '%s'"),
-                                       warningTorUnsetChainIpt: _("Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'"),
-                                       warningTorUnsetChainNft: _("Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'"),
-                                       warningInvalidOVPNConfig: _("Invalid OpenVPN config for %s interface"),
-                                       warningOutdatedWebUIApp: _("The WebUI application is outdated (version %s), please update it"),
+                                       warningTorUnsetParams: _(
+                                               "Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'"
+                                       ),
+                                       warningTorUnsetProto: _(
+                                               "Please unset 'proto' or set 'proto' to 'all' for policy '%s'"
+                                       ),
+                                       warningTorUnsetChainIpt: _(
+                                               "Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'"
+                                       ),
+                                       warningTorUnsetChainNft: _(
+                                               "Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'"
+                                       ),
+                                       warningInvalidOVPNConfig: _(
+                                               "Invalid OpenVPN config for %s interface"
+                                       ),
+                                       warningOutdatedWebUIApp: _(
+                                               "The WebUI application is outdated (version %s), please update it"
+                                       ),
+                                       warningBadNftCallsInUserFile: _(
+                                               "Incompatible nft calls detected in user include file, disabling fw4 nft file support."
+                                       ),
+                                       warningDnsmasqInstanceNoConfdir: _(
+                                               "Dnsmasq instance (%s) targeted in settings, but it doesn't have its own confdir."
+                                       ),
                                };
-                               var warningsTitle = E('label', { class: 'cbi-value-title' }, _("Service Warnings"));
+                               var warningsTitle = E(
+                                       "label",
+                                       { class: "cbi-value-title" },
+                                       _("Service Warnings")
+                               );
                                var text = "";
-                               (reply.warnings).forEach(element => {
+                               reply.warnings.forEach((element) => {
                                        if (element.id && textLabelsTable[element.id]) {
-                                               if (element.id !== 'warningPolicyProcessCMD') {
-                                                       text += (textLabelsTable[element.id] + '.').format(element.extra || ' ') + "<br />";
+                                               if (element.id !== "warningPolicyProcessCMD") {
+                                                       text +=
+                                                               (textLabelsTable[element.id] + ".").format(
+                                                                       element.extra || " "
+                                                               ) + "<br />";
                                                }
-                                       }
-                                       else {
-                                               text += _("Unknown Warning.") + "<br />";
+                                       } else {
+                                               text += _("Unknown warning") + "<br />";
                                        }
                                });
-                               var warningsText = E('div', {}, text);
-                               var warningsField = E('div', { class: 'cbi-value-field' }, warningsText);
-                               warningsDiv = E('div', { class: 'cbi-value' }, [warningsTitle, warningsField]);
+                               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: _("%s binary cannot be found").format('ip-full'),
-                                       errorNoIptables: _("%s binary cannot be found").format('iptables'),
-                                       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"),
+                                       errorConfigValidation: _("Config (%s) validation failure").format(
+                                               "/etc/config/" + pkg.Name
+                                       ),
+                                       errorNoIpFull: _("%s binary cannot be found").format("ip-full"),
+                                       errorNoIptables: _("%s binary cannot be found").format("iptables"),
+                                       errorNoIpset: _(
+                                               "Resolver set support (%s) requires ipset, but ipset binary cannot be found"
+                                       ).format(L.uci.get(pkg.Name, "config", "resolver_set")),
+                                       errorNoNft: _(
+                                               "Resolver set support (%s) requires nftables, but nft binary cannot be found"
+                                       ).format(L.uci.get(pkg.Name, "config", "resolver_set")),
+                                       errorResolverNotSupported: _(
+                                               "Resolver set (%s) is not supported on this system"
+                                       ).format(L.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 255 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"),
+                                       errorPolicyUnknownInterface: _(
+                                               "Policy '%s' has an unknown interface"
+                                       ),
                                        errorPolicyProcessCMD: _("%s"),
                                        errorFailedSetup: _("Failed to set up '%s'"),
                                        errorFailedReload: _("Failed to reload '%s'"),
                                        errorUserFileNotFound: _("Custom user file '%s' not found or empty"),
                                        errorUserFileSyntax: _("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"),
+                                       errorUserFileNoCurl: _(
+                                               "Use of 'curl' is detected in custom user file '%s', but 'curl' isn't installed"
+                                       ),
                                        errorNoGateways: _("Failed to set up any gateway"),
-                                       errorResolver: _("Resolver %s"),
-                                       errorPolicyProcessNoIpv6: _("Skipping IPv6 policy '%s' as IPv6 support is disabled"),
-                                       errorPolicyProcessUnknownFwmark: _("Unknown packet mark for interface '%s'"),
-                                       errorPolicyProcessMismatchFamily: _("Mismatched IP family between in policy %s"),
-                                       errorPolicyProcessUnknownProtocol: _("Unknown protocol in policy %s"),
-                                       errorPolicyProcessInsertionFailed: _("Insertion failed for both IPv4 and IPv6 for policy %s"),
-                                       errorPolicyProcessInsertionFailedIpv4: _("Insertion failed for IPv4 for policy %s"),
-                                       errorInterfaceRoutingEmptyValues: _("Received empty tid/mark or interface name when setting up routing"),
-                                       errorFailedToResolve: _("Failed to resolve %s"),
-                                       errorInvalidOVPNConfig: _("Invalid OpenVPN config for %s interface"),
+                                       errorResolver: _("Resolver '%s'"),
+                                       errorPolicyProcessNoIpv6: _(
+                                               "Skipping IPv6 policy '%s' as IPv6 support is disabled"
+                                       ),
+                                       errorPolicyProcessUnknownFwmark: _(
+                                               "Unknown packet mark for interface '%s'"
+                                       ),
+                                       errorPolicyProcessMismatchFamily: _(
+                                               "Mismatched IP family between in policy '%s'"
+                                       ),
+                                       errorPolicyProcessUnknownProtocol: _(
+                                               "Unknown protocol in policy '%s'"
+                                       ),
+                                       errorPolicyProcessInsertionFailed: _(
+                                               "Insertion failed for both IPv4 and IPv6 for policy '%s'"
+                                       ),
+                                       errorPolicyProcessInsertionFailedIpv4: _(
+                                               "Insertion failed for IPv4 for policy '%s'"
+                                       ),
+                                       errorInterfaceRoutingEmptyValues: _(
+                                               "Received empty tid/mark or interface name when setting up routing"
+                                       ),
+                                       errorFailedToResolve: _("Failed to resolve '%s'"),
+                                       errorInvalidOVPNConfig: _(
+                                               "Invalid OpenVPN config for '%s' interface"
+                                       ),
+                                       errorNftFileInstall: _("Failed to install fw4 nft file '%s'"),
+                                       errorNoDownloadWithSecureReload: _(
+                                               "Policy '%s' refers to URL which can't be downloaded in 'secure_reload' mode!"
+                                       ),
+                                       errorDownloadUrlNoHttps: _(
+                                               "Failed to download '%s', HTTPS is not supported!"
+                                       ),
+                                       errorDownloadUrl: _("Failed to download '%s'!"),
+                                       errorFileSchemaRequiresCurl: _(
+                                               "The file:// schema requires curl, but it's not detected on this system!"
+                                       ),
                                };
-                               var errorsTitle = E('label', { class: 'cbi-value-title' }, _("Service Errors"));
+                               var errorsTitle = E(
+                                       "label",
+                                       { class: "cbi-value-title" },
+                                       _("Service Errors")
+                               );
                                var text = "";
-                               (reply.errors).forEach(element => {
+                               reply.errors.forEach((element) => {
                                        if (element.id && textLabelsTable[element.id]) {
-                                               if (element.id !== 'errorPolicyProcessCMD') {
-                                                       text += (textLabelsTable[element.id] + '!').format(element.extra || ' ') + "<br />";
+                                               if (element.id !== "errorPolicyProcessCMD") {
+                                                       text +=
+                                                               (textLabelsTable[element.id] + "!").format(
+                                                                       element.extra || " "
+                                                               ) + "<br />";
                                                }
-                                       }
-                                       else {
-                                               text += _("Unknown Error!") + "<br />";
+                                       } else {
+                                               text += _("Unknown error!") + "<br />";
                                        }
                                });
-                               var errorsText = E('div', {}, text);
-                               var errorsField = E('div', { class: 'cbi-value-field' }, errorsText);
-                               errorsDiv = E('div', { class: 'cbi-value' }, [errorsTitle, errorsField]);
+                               text += _("Errors encountered, please check the %sREADME%s!").format(
+                                       '<a href="' + pkg.URL + '" target="_blank">',
+                                       "</a><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_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_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_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_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_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'));
+                       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;
@@ -319,14 +494,12 @@ var status = baseclass.extend({
                                        btn_start.disabled = true;
                                        btn_action.disabled = false;
                                        btn_stop.disabled = false;
-                               }
-                               else {
+                               } else {
                                        btn_start.disabled = false;
                                        btn_action.disabled = true;
                                        btn_stop.disabled = true;
                                }
-                       }
-                       else {
+                       } else {
                                btn_start.disabled = true;
                                btn_action.disabled = true;
                                btn_stop.disabled = true;
@@ -334,22 +507,39 @@ var status = baseclass.extend({
                                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]);
+                       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.version
+                               ? E("div", { class: "cbi-value" }, [buttonsTitle, buttonsField])
+                               : "";
+                       return E("div", {}, [
+                               header,
+                               statusDiv,
+                               gatewaysDiv,
+                               warningsDiv,
+                               errorsDiv,
+                               buttonsDiv,
+                       ]);
                });
        },
 });
 
-RPC.on('setInitAction', function (reply) {
+RPC.on("setInitAction", function (reply) {
        ui.hideModal();
        location.reload();
 });
@@ -357,5 +547,5 @@ RPC.on('setInitAction', function (reply) {
 return L.Class.extend({
        status: status,
        getInterfaces: getInterfaces,
-       getPlatformSupport: getPlatformSupport
+       getPlatformSupport: getPlatformSupport,
 });
index 2b5adaeb2ef92c84c7c443da82c61513917e73aa..99f8a502fa703af01ee9a457a3ccc97681c1092d 100644 (file)
 // 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';
+"use strict";
+"require form";
+"require rpc";
+"require view";
+"require pbr.status as pbr";
 
 var pkg = {
-       get Name() { return 'pbr'; },
-       get URL() { return 'https://docs.openwrt.melmac.net/' + pkg.Name + '/'; }
+       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)
+                       L.resolveDefault(pbr.getInterfaces(pkg.Name), {}),
+                       L.resolveDefault(pbr.getPlatformSupport(pkg.Name), {}),
+                       L.resolveDefault(L.uci.load(pkg.Name), {}),
                ]);
        },
 
-       render: function () {
-               return Promise.all([
-                       L.resolveDefault(pbr.getInterfaces(), {}),
-                       L.resolveDefault(pbr.getPlatformSupport(), {}),
-               ]).then(function (data) {
-                       var arrInterfaces;
-                       var replyPlatform;
-                       var status, m, s, o;
-
-                       if (data[0] && data[0][pkg.Name] && data[0][pkg.Name].interfaces) {
-                               arrInterfaces = data[0][pkg.Name].interfaces;
-                       }
-                       else {
-                               arrInterfaces = ["wan"];
-                       }
-
-                       if (data[1] && data[1][pkg.Name]) {
-                               replyPlatform = data[1][pkg.Name];
-                       }
-                       else {
-                               replyPlatform = {
-                                       ipset_installed: null,
-                                       nft_installed: null,
-                                       adguardhome_installed: null,
-                                       dnsmasq_installed: null,
-                                       unbound_installed: null,
-                                       adguardhome_ipset_support: null,
-                                       dnsmasq_ipset_support: null,
-                                       dnsmasq_nftset_support: null,
-                               };
-                       }
-
-                       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 === null) {
-                               text += _("The %s support is unknown.").format("<i>adguardhome.ipset</i>") + "<br />"
-                       }
-                       else 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 === null) {
-                               text += _("The %s support is unknown.").format("<i>dnsmasq.ipset</i>") + "<br />"
-                       }
-                       else 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 === null) {
-                               text += _("The %s support is unknown.").format("<i>dnsmasq.nftset</i>") + "<br />"
-                       }
-                       else 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"));
+       render: function (data) {
+               var status, m, s, o;
+               var reply = {
+                       interfaces: (data[0] &&
+                               data[0][pkg.Name] &&
+                               data[0][pkg.Name].interfaces) || ["wan"],
+                       platform: (data[1] && data[1][pkg.Name]) || {
+                               ipset_installed: null,
+                               nft_installed: null,
+                               adguardhome_installed: null,
+                               dnsmasq_installed: null,
+                               unbound_installed: null,
+                               adguardhome_ipset_support: null,
+                               dnsmasq_ipset_support: null,
+                               dnsmasq_nftset_support: null,
+                       },
+               };
+
+               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 +
+                                       '#ServiceConfigurationSettings" 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 + '#StrictEnforcement" 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 (reply.platform.adguardhome_ipset_support === null) {
+                       text +=
+                               _("The %s support is unknown.").format("<i>adguardhome.ipset</i>") +
+                               "<br />";
+               } else if (!reply.platform.adguardhome_ipset_support) {
+                       text +=
+                               _("The %s is not supported on this system.").format(
+                                       "<i>adguardhome.ipset</i>"
+                               ) + "<br />";
+               }
+               if (reply.platform.dnsmasq_ipset_support === null) {
+                       text +=
+                               _("The %s support is unknown.").format("<i>dnsmasq.ipset</i>") +
+                               "<br />";
+               } else if (!reply.platform.dnsmasq_ipset_support) {
+                       text +=
+                               _("The %s is not supported on this system.").format(
+                                       "<i>dnsmasq.ipset</i>"
+                               ) + "<br />";
+               }
+               if (reply.platform.dnsmasq_nftset_support === null) {
+                       text +=
+                               _("The %s support is unknown.").format("<i>dnsmasq.nftset</i>") +
+                               "<br />";
+               } else if (!reply.platform.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 + '#UseResolversSetSupport" target="_blank">',
+                       "</a>"
+               );
+
+               o = s.taboption(
+                       "tab_basic",
+                       form.ListValue,
+                       "resolver_set",
+                       _("Use resolver set support for domains"),
+                       text
+               );
+               o.value("none", _("Disabled"));
+               if (reply.platform.adguardhome_ipset_support) {
+                       o.value("adguardhome.ipset", _("AdGuardHome ipset"));
+                       o.default = "adguardhome.ipset";
+               }
+               if (reply.platform.dnsmasq_ipset_support) {
+                       o.value("dnsmasq.ipset", _("Dnsmasq ipset"));
+                       o.default = "dnsmasq.ipset";
+               }
+               if (reply.platform.dnsmasq_nftset_support) {
+                       o.value("dnsmasq.nftset", _("Dnsmasq nft set"));
+                       o.default = "dnsmasq.nftset";
+               }
+
+               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"));
+               reply.interfaces.forEach((element) => {
+                       if (element.toLowerCase() !== "ignore") {
+                               o.value(element);
                        }
-
-                       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 " +
+               });
+               o.rmempty = true;
+
+               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 + '#IgnoreTarget" 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"]
+                                       "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,string)))";
+               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,string)))";
+               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(
+                       L.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 = "";
+                       } else {
+                               o.value(element.toLowerCase());
                        }
-                       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.rmempty = false;
-
-                       o = s.option(form.Value, "path", _("Path"));
-                       o.optional = false;
-                       o.editable = true;
-                       o.rmempty = false;
-
-                       return Promise.all([status.render(), m.render()]);
-               })
-       }
+               });
+               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 = "";
+               o.rmempty = true;
+
+               o = s.option(form.ListValue, "interface", _("Interface"));
+               reply.interfaces.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 + "#DSCPTag-BasedPolicies" + '" target="_blank">',
+                               "</a>"
+                       )
+               );
+               reply.interfaces.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 + '#CustomUserFiles" 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.rmempty = false;
+
+               o = s.option(form.Value, "path", _("Path"));
+               o.optional = false;
+               o.editable = true;
+               o.rmempty = false;
+
+               return Promise.all([status.render(), m.render()]);
+       },
 });
diff --git a/applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js b/applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js
new file mode 100644 (file)
index 0000000..536c398
--- /dev/null
@@ -0,0 +1,88 @@
+"require ui";
+"require rpc";
+"require form";
+"require baseclass";
+
+var pkg = {
+       get Name() {
+               return "pbr";
+       },
+       get URL() {
+               return "https://docs.openwrt.melmac.net/" + pkg.Name + "/";
+       },
+};
+
+var getInitStatus = rpc.declare({
+       object: "luci." + pkg.Name,
+       method: "getInitStatus",
+       params: ["name"],
+});
+
+return baseclass.extend({
+       title: _("Policy Based Routing"),
+
+       load: function () {
+               return Promise.all([getInitStatus(pkg.Name)]);
+       },
+
+       render: function (data) {
+               var reply;
+               if (data[0] && data[0][pkg.Name]) {
+                       reply = data[0][pkg.Name];
+               } else {
+                       reply = {
+                               enabled: null,
+                               running: null,
+                               running_iptables: null,
+                               running_nft: null,
+                               running_nft_file: null,
+                               version: null,
+                               gateways: null,
+                               errors: [],
+                               warnings: [],
+                       };
+               }
+
+               var versionText,
+                       statusText = "",
+                       modeText = "";
+               if (reply.version) {
+                       versionText = reply.version;
+                       if (reply.running) {
+                               statusText = _("Active");
+                               if (reply.running_iptables) {
+                                       modeText = _("iptables mode");
+                               } else if (reply.running_nft_file) {
+                                       modeText = _("fw4 nft file mode");
+                               } else if (reply.running_nft) {
+                                       modeText = _("nft mode");
+                               } else {
+                                       modeText = _("unknown");
+                               }
+                       } else {
+                               if (reply.enabled) {
+                                       statusText = _("Inactive");
+                               } else {
+                                       statusText = _("Inactive (Disabled)");
+                               }
+                       }
+               } else {
+                       versionText = _("Not installed or not found");
+               }
+
+               var table = E("table", { class: "table", id: "pbr_status_table" }, [
+                       E("tr", { class: "tr table-titles" }, [
+                               E("th", { class: "th" }, _("Status")),
+                               E("th", { class: "th" }, _("Version")),
+                               E("th", { class: "th" }, _("Mode")),
+                       ]),
+                       E("tr", { class: "tr" }, [
+                               E("td", { class: "td" }, statusText),
+                               E("td", { class: "td" }, versionText),
+                               E("td", { class: "td" }, modeText),
+                       ]),
+               ]);
+
+               return table;
+       },
+});
index 8c5194160abbf02d5fab9300184a68d1702bfa66..a516be3d8e6a90e39a92876089e042c5cac87ce5 100644 (file)
 msgid ""
 msgstr "Content-Type: text/plain; charset=UTF-8"
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:179
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:221
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:215
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:310
 msgid "%s"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:208
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:209
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:277
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:278
 msgid "%s binary cannot be found"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:61
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:56
 msgid ""
 "%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"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:105
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:106
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:52
+msgid "Active"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:144
 msgid "AdGuardHome ipset"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:133
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:196
 msgid "Add"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:168
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:253
 msgid "Add Ignore Target"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:169
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:255
 msgid ""
 "Adds 'ignore' to the list of interfaces for policies. See the %sREADME%s for "
 "details."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:60
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:54
 msgid "Advanced Configuration"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:122
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:171
 msgid ""
 "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*."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:127
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:183
 msgid ""
 "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."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:59
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:51
 msgid "Basic Configuration"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:233
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:337
 msgid "Chain"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:70
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:79
 msgid "Condensed output"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:207
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:274
 msgid "Config (%s) validation failure"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:68
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:76
 msgid "Controls both system log and console output verbosity."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:259
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:379
 msgid "Custom User File Includes"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:224
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:313
 msgid "Custom user file '%s' not found or empty"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:254
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:370
 msgid "DSCP Tag"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:249
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:357
 msgid "DSCP Tagging"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:137
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:204
 msgid "Default ICMP Interface"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:313
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:487
 msgid "Disable"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:103
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:118
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:171
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:142
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:162
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:261
 msgid "Disabled"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:309
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:481
 msgid "Disabling %s service"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:177
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:271
 msgid "Display these protocols in protocol column in Web UI."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:109
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:110
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:238
+msgid ""
+"Dnsmasq instance (%s) targeted in settings, but it doesn't have its own "
+"confdir."
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:148
 msgid "Dnsmasq ipset"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:113
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:114
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:152
 msgid "Dnsmasq nft set"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:77
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:93
 msgid "Do not enforce policies when their gateway is down"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:302
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:468
 msgid "Enable"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:119
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:172
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:189
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:267
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:163
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:262
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:290
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:392
 msgid "Enabled"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:298
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:462
 msgid "Enabling %s service"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:226
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:315
 msgid "Error running custom user file '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:162
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:376
+msgid "Errors encountered, please check the %sREADME%s!"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:238
 msgid ""
 "FW Mask used by the service. High mask is used to avoid conflict with SQM/"
 "QoS. Change with caution together with"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:223
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:353
+msgid "Failed to download '%s'!"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:351
+msgid "Failed to download '%s', HTTPS is not supported!"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:346
+msgid "Failed to install fw4 nft file '%s'"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:312
 msgid "Failed to reload '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:237
-msgid "Failed to resolve %s"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:342
+msgid "Failed to resolve '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:222
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:311
 msgid "Failed to set up '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:228
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:319
 msgid "Failed to set up any gateway"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:138
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:205
 msgid "Force the ICMP protocol interface."
 msgstr ""
 
@@ -176,56 +199,77 @@ msgstr ""
 msgid "Grant UCI and file access for luci-app-pbr"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:117
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:160
 msgid "IPv6 Support"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:126
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:181
 msgid "Ignored Interfaces"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:134
-msgid "Insert"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:64
+msgid "Inactive"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:66
+msgid "Inactive (Disabled)"
 msgstr ""
 
 #: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:235
-msgid "Insertion failed for IPv4 for policy %s"
+msgid ""
+"Incompatible nft calls detected in user include file, disabling fw4 nft file "
+"support."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:234
-msgid "Insertion failed for both IPv4 and IPv6 for policy %s"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:197
+msgid "Insert"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:337
+msgid "Insertion failed for IPv4 for policy '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:178
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:334
+msgid "Insertion failed for both IPv4 and IPv6 for policy '%s'"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:213
 msgid "Installed AdGuardHome (%s) doesn't support 'ipset_file' option."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:242
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:346
 msgid "Interface"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:184
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:238
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:229
 msgid "Invalid OpenVPN config for %s interface"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:195
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:344
+msgid "Invalid OpenVPN config for '%s' interface"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:296
 msgid "Local addresses / devices"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:200
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:302
 msgid "Local ports"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:232
-msgid "Mismatched IP family between in policy %s"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:328
+msgid "Mismatched IP family between in policy '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:193
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:77
+msgid "Mode"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:294
 msgid "Name"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:181
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:280
 msgid ""
 "Name, interface and at least one other field are required. Multiple local "
 "and remote addresses/devices/domains and ports can be space separated. "
@@ -233,63 +277,73 @@ msgid ""
 "fields are left blank."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:139
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:207
 msgid "No Change"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:157
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:171
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:70
 msgid "Not installed or not found"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:67
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:75
 msgid "Output verbosity"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:272
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:397
 msgid "Path"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:100
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:129
 msgid "Please check the %sREADME%s before changing this option."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:182
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:223
 msgid "Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:183
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:226
 msgid "Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:181
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:220
 msgid "Please unset 'proto' or set 'proto' to 'all' for policy '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:180
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:217
 msgid "Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:180
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:278
 msgid "Policies"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:220
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:308
 msgid "Policy '%s' has an unknown interface"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:219
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:306
 msgid "Policy '%s' has no assigned interface"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:218
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:304
 msgid "Policy '%s' has no source/destination parameters"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:56
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:348
+msgid ""
+"Policy '%s' refers to URL which can't be downloaded in 'secure_reload' mode!"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:22
+msgid "Policy Based Routing"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:48
 msgid "Policy Based Routing - Configuration"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:133
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:144
 msgid "Policy Based Routing - Status"
 msgstr ""
 
@@ -297,259 +351,277 @@ msgstr ""
 msgid "Policy Routing"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:217
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:320
 msgid "Protocol"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:236
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:340
 msgid "Received empty tid/mark or interface name when setting up routing"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:206
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:308
 msgid "Remote addresses / domains"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:211
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:314
 msgid "Remote ports"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:229
-msgid "Resolver %s"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:320
+msgid "Resolver '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:212
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:286
 msgid "Resolver set (%s) is not supported on this system"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:177
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:210
 msgid "Resolver set (%s) is not supported on this system."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:210
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:280
 msgid ""
 "Resolver set support (%s) requires ipset, but ipset binary cannot be found"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:211
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:283
 msgid ""
 "Resolver set support (%s) requires nftables, but nft binary cannot be found"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:280
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:430
 msgid "Restart"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:276
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:424
 msgid "Restarting %s service"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:131
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:193
 msgid "Rule Create option"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:260
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:381
 msgid ""
 "Run the following user files after setting up but before restarting DNSMASQ. "
 "See the %sREADME%s for details."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:138
-msgid "Running (version: %s using iptables)"
-msgstr ""
-
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:141
-msgid "Running (version: %s using nft)"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:153
+msgid "Running"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:144
-msgid "Running (version: %s)"
-msgstr ""
-
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:75
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:88
 msgid "See the %sREADME%s for details."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:132
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:194
 msgid "Select Add for -A/add and Insert for -I/Insert."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:337
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:513
 msgid "Service Control"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:240
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:361
 msgid "Service Errors"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:156
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:161
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:225
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:236
 msgid "Service FW Mask"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:165
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:185
 msgid "Service Gateways"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:134
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:148
 msgid "Service Status"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:187
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:244
 msgid "Service Warnings"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:250
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:359
 msgid ""
 "Set DSCP tags (in range between 1 and 63) for specific interfaces. See the "
 "%sREADME%s for details."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:230
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:322
 msgid "Skipping IPv6 policy '%s' as IPv6 support is disabled"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:269
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:411
 msgid "Start"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:265
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:405
 msgid "Starting %s service"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:154
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:221
 msgid ""
 "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"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:148
-msgid "Starting (WAN) Table ID number for tables created by the service."
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:75
+msgid "Status"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:291
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:449
 msgid "Stop"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:152
-msgid "Stopped (Disabled)"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:167
+msgid "Stopped (Disabled)."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:149
-msgid "Stopped (version: %s)"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:165
+msgid "Stopped."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:287
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:443
 msgid "Stopping %s service"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:74
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:87
 msgid "Strict enforcement"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:78
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:94
 msgid "Strictly enforce policies when their gateway is down"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:121
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:169
 msgid "Supported Interfaces"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:176
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:270
 msgid "Supported Protocols"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:69
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:78
 msgid "Suppress/No output"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:225
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:314
 msgid "Syntax error in custom user file '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:166
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:188
 msgid "The %s indicates default gateway. See the %sREADME%s for details."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:86
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:92
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:98
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:104
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:114
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:124
 msgid "The %s is not supported on this system."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:214
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:292
 msgid "The %s service failed to discover WAN gateway"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:213
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:289
 msgid "The %s service is currently disabled"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:83
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:89
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:95
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:100
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:110
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:120
 msgid "The %s support is unknown."
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:185
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:232
 msgid "The WebUI application is outdated (version %s), please update it"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:215
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:355
+msgid "The file:// schema requires curl, but it's not detected on this system!"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:295
 msgid "The ipset name '%s' is longer than allowed 31 characters"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:216
-msgid "The nft set name '%s' is longer than allowed 31 characters"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:298
+msgid "The nft set name '%s' is longer than allowed 255 characters"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:217
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:301
 msgid "Unexpected exit or service termination: '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:249
-msgid "Unknown Error!"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:373
+msgid "Unknown error!"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:196
-msgid "Unknown Warning."
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:325
+msgid "Unknown packet mark for interface '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:231
-msgid "Unknown packet mark for interface '%s'"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:331
+msgid "Unknown protocol in policy '%s'"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:233
-msgid "Unknown protocol in policy %s"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:256
+msgid "Unknown warning"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:227
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:317
 msgid ""
 "Use of 'curl' is detected in custom user file '%s', but 'curl' isn't "
 "installed"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:102
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:139
 msgid "Use resolver set support for domains"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:71
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:80
 msgid "Verbose output"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:153
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:163
-msgid "WAN Table FW Mark"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:76
+msgid "Version"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:147
-msgid "WAN Table ID"
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:151
+msgid "Version %s"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:65
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:219
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:242
+msgid "WAN Table FW Mark"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:69
 msgid "Web UI Configuration"
 msgstr ""
 
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:224
-#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:225
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:329
 msgid "all"
 msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:157
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:56
+msgid "fw4 nft file mode"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:155
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:54
+msgid "iptables mode"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:159
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:58
+msgid "nft mode"
+msgstr ""
+
+#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:60
+msgid "unknown"
+msgstr ""
index 57af8010f03ba87fd25ca31c6e7998863ca1e2b1..8412b0c4bc71a14767ab99d7877e8c2283d0c79c 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 # Copyright 2022 Stan Grishin (stangri@melmac.ca)
-# shellcheck disable=SC1091,SC2018,SC2019,SC2039,SC3043,SC3057,SC3060
+# shellcheck disable=SC2018,SC2019,SC2039,SC3043,SC3057,SC3060
 
 # TechRef: https://openwrt.org/docs/techref/rpcd
 # TESTS
 # ubus -S call luci.pbr getGateways '{"name": "pbr" }'
 # ubus -S call luci.pbr getInterfaces '{"name": "pbr" }'
 
-. /lib/functions.sh
-. /lib/functions/network.sh
-. /usr/share/libubox/jshn.sh
-
-readonly packageName="pbr"
-# shellcheck disable=SC2155
-readonly ipset="$(command -v ipset)"
-# shellcheck disable=SC2155
-readonly agh="$(command -v AdGuardHome)"
-readonly aghConfigFile='/etc/adguardhome.yaml'
-# shellcheck disable=SC2155
-readonly nft="$(command -v nft)"
-
-is_enabled() { uci -q get "${1}.config.enabled"; }
-is_running_iptables() { iptables -t mangle -L | grep -q PBR_PREROUTING >/dev/null 2>&1; }
-is_running_nft() { "$nft" list table inet fw4 | grep chain | grep -q pbr_mark_ >/dev/null 2>&1; }
-is_running() { is_running_iptables || is_running_nft; }
-get_version() { grep -m1 -A2 -w "^Package: $1$" /usr/lib/opkg/status | sed -n 's/Version: //p'; }
-print_json_bool() { json_init; json_add_boolean "$1" "$2"; json_dump; json_cleanup; }
-print_json_string() { json_init; json_add_string "$1" "$2"; json_dump; json_cleanup; }
-logger() { /usr/bin/logger -t "$packageName" "$@"; }
-ubus_get_status() { ubus call service list "{ 'name': '$packageName' }" | jsonfilter -e "@['${packageName}'].instances.main.data.status.${1}"; }
-ubus_get_gateway() { ubus call service list "{ 'name': '$packageName' }" | jsonfilter -e "@['${packageName}'].instances.main.data.gateways[@.name='${1}']${2:+.$2}"; }
-is_greater() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }
-is_greater_or_equal() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" = "$2"; }
-opkg_get_version() { grep -m1 -A1 "Package: $1$" '/usr/lib/opkg/status' | grep -m1 'Version: ' | sed 's|Version: \(.*\)|\1|'; }
+readonly pbrFunctionsFile='/etc/init.d/pbr'
+if [ -s "$pbrFunctionsFile" ]; then
+# shellcheck source=../../../../../pbr/files/etc/init.d/pbr
+       . "$pbrFunctionsFile"
+else
+       print_json_string 'error' "pbr init.d file ($pbrFunctionsFile) not found!"
+       logger -t pbr 'error' "pbr init.d file ($pbrFunctionsFile) not found!"
+fi
 
 get_init_list() {
        local name
        name="$(basename "$1")"
        name="${name:-$packageName}" 
        json_init
-       json_add_object "$name"
-       json_add_boolean 'enabled' "$(is_enabled "$name")"
-       if is_running "$name"; then
+       json_add_object "$packageName"
+       json_add_boolean 'enabled' "$(is_enabled "$packageName")"
+       if is_running "$packageName"; then
                json_add_boolean 'running' '1'
        else
                json_add_boolean 'running' '0'
@@ -59,22 +41,27 @@ set_init_action() {
        local name action="$2" cmd
        name="$(basename "$1")"
        name="${name:-$packageName}" 
-       if [ ! -f "/etc/init.d/$name" ]; then
+       if [ ! -f "/etc/init.d/$packageName" ]; then
                print_json_string 'error' 'Init script not found!'
                return
        fi
        case $action in
                enable)
-                       cmd="uci -q set ${name}.config.enabled=1 && uci commit $name";;
+                       cmd="/etc/init.d/${name} ${action}"
+                       cmd="${cmd} && uci_set ${name} config enabled 1 && uci_commit $name"
+               ;;
                disable)
-                       cmd="uci -q set ${name}.config.enabled=0 && uci commit $name";;
+                       cmd="/etc/init.d/${name} ${action}"
+                       cmd="${cmd} && uci_set ${name} config enabled 0 && uci_commit $name"
+               ;;
                start|stop|reload|restart)
-                       cmd="/etc/init.d/${name} ${action}";;
+                       cmd="/etc/init.d/${name} ${action}"
+               ;;
        esac
-       if [ -n "$cmd" ] && eval "${cmd}" 1>/dev/null 2>&1; then
-               print_json_bool "result" '1'
+       if [ -n "$cmd" ] && eval "$cmd" 1>/dev/null 2>&1; then
+               print_json_bool 'result' '1'
        else
-               print_json_bool "result" '0'
+               print_json_bool 'result' '0'
        fi
 }
 
@@ -83,30 +70,35 @@ get_init_status() {
        name="$(basename "$1")"
        name="${name:-$packageName}" 
        local version gateways warnings errors
-       [ -z "$version" ] && version="$(get_version "$name")"
-       [ -z "$version" ] && version="$(get_version "${name}-iptables")"
-       [ -z "$version" ] && version="$(get_version "${name}-netifd")"
+       [ -z "$version" ] && version="$(opkg_get_version "${name}")"
+       [ -z "$version" ] && version="$(opkg_get_version "${name}-iptables")"
+       [ -z "$version" ] && version="$(opkg_get_version "${name}-netifd")"
        gateways="$(ubus_get_status gateways | sed "s|\\\n|<br />|g;s|\(\\\033[^<]*\)|✓|g;")"
        warnings="$(ubus_get_status warnings)"
        errors="$(ubus_get_status errors)"
        json_init
-       json_add_object  "$name"
-       json_add_boolean 'enabled' "$(is_enabled "$name")"
-       if is_running "$name"; then
+       json_add_object  "$packageName"
+       json_add_boolean 'enabled' "$(is_enabled "$packageName")"
+       if is_running "$packageName"; then
                json_add_boolean 'running' '1'
        else
                json_add_boolean 'running' '0'
        fi
-       if is_running_iptables "$name"; then
+       if is_running_iptables "$packageName"; then
                json_add_boolean 'running_iptables' '1'
        else
                json_add_boolean 'running_iptables' '0'
        fi
-       if is_running_nft "$name"; then
+       if is_running_nft "$packageName"; then
                json_add_boolean 'running_nft' '1'
        else
                json_add_boolean 'running_nft' '0'
        fi
+       if is_running_nft_file "$packageName"; then
+               json_add_boolean 'running_nft_file' '1'
+       else
+               json_add_boolean 'running_nft_file' '0'
+       fi
        json_add_string 'version' "$version"
        json_add_string 'gateways' "$gateways"
        json_add_array 'errors'
@@ -158,37 +150,12 @@ EOF
        json_cleanup
 }
 
-check_ipset() { { [ -n "$ipset" ] && "$ipset" help hash:net; } >/dev/null 2>&1; }
-check_nft() { [ -n "$nft" ]; }
-check_agh() { [ -n "$agh" ] && [ -s "$aghConfigFile" ]; }
-check_dnsmasq() { command -v dnsmasq >/dev/null 2>&1; }
-check_unbound() { command -v unbound >/dev/null 2>&1; }
-check_agh_ipset() {
-       check_ipset || return 1
-       check_agh || return 1
-       is_greater_or_equal "$($agh --version | sed 's|AdGuard Home, version v\(.*\)|\1|' | sed 's|-.*||')" '0.107.13'
-}
-check_dnsmasq_ipset() {
-       local o;
-       check_ipset || return 1
-       check_dnsmasq || return 1
-       o="$(dnsmasq -v 2>/dev/null)"
-       ! echo "$o" | grep -q 'no-ipset' && echo "$o" | grep -q 'ipset'
-}
-check_dnsmasq_nftset() {
-       local o;
-       check_nft || return 1
-       check_dnsmasq || return 1
-       o="$(dnsmasq -v 2>/dev/null)"
-       ! echo "$o" | grep -q 'no-nftset' && echo "$o" | grep -q 'nftset'
-}
-
 get_platform_support() {
        local name
        name="$(basename "$1")"
        name="${name:-$packageName}" 
        json_init
-       json_add_object "$name"
+       json_add_object "$packageName"
        if check_ipset; then
                json_add_boolean 'ipset_installed' '1'
        else
@@ -236,58 +203,16 @@ get_platform_support() {
 
 # shellcheck disable=SC3037
 get_gateways() {
-       local name="${1:-$packageName}"
-       echo -en "{\"$name\":{\"gateways\":"
-       ubus call service list "{ 'name': '$name' }" | jsonfilter -e "@.${name}.instances.main.data.gateways"
+       echo -en "{\"$packageName\":{\"gateways\":"
+       ubus_get_gateways
        echo -en "}}"
 }
 
-str_contains() { [ -n "$1" ] && [ -n "$2" ] && [ "${1//$2}" != "$1" ]; }
-str_contains_word() { echo "$1" | grep -q -w "$2"; }
-str_to_lower() { echo "$1" | tr 'A-Z' 'a-z'; }
-str_to_upper() { echo "$1" | tr 'a-z' 'A-Z'; }
-is_ignore_target() { [ "$(str_to_lower "$1")" = 'ignore' ]; }
-is_dslite() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:6}" = "dslite" ]; }
-is_l2tp() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:4}" = "l2tp" ]; }
-is_oc() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:11}" = "openconnect" ]; }
-is_ovpn() { local dev; network_get_device dev "$1"; [ "${dev:0:3}" = "tun" ] || [ "${dev:0:3}" = "tap" ] || [ -f "/sys/devices/virtual/net/${dev}/tun_flags" ]; }
-is_pptp() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:4}" = "pptp" ]; }
-is_softether() { local dev; network_get_device dev "$1"; [ "${dev:0:4}" = "vpn_" ]; }
-is_tor() { [ "$(str_to_lower "$1")" = "tor" ]; }
-is_tor_running() {
-       local ret=0
-       if [ -s "/etc/tor/torrc" ]; then
-               json_load "$(ubus call service list "{ 'name': 'tor' }")"
-               json_select 'tor'; json_select 'instances'; json_select 'instance1';
-               json_get_var ret 'running'; json_cleanup
-       fi
-       if [ "$ret" = "0" ]; then return 1; else return 0; fi
-}
-is_wg() { local proto; proto=$(uci -q get network."$1".proto); [ "${proto:0:9}" = "wireguard" ]; }
-is_tunnel() { is_dslite "$1" || is_l2tp "$1" || is_oc "$1" || is_ovpn "$1" || is_pptp "$1" || is_softether "$1" || is_tor "$1" || is_wg "$1"; }
-is_wan() { [ "$1" = "$wanIface4" ] || { [ "${1##wan}" != "$1" ] && [ "${1##wan6}" = "$1" ]; } || [ "${1%%wan}" != "$1" ]; }
-is_wan6() { [ -n "$wanIface6" ] && [ "$1" = "$wanIface6" ] || [ "${1/#wan6}" != "$1" ] || [ "${1/%wan6}" != "$1" ]; }
-is_ignored_interface() { str_contains_word "$ignored_interface" "$1"; }
-is_supported_interface() { str_contains_word "$supported_interface" "$1" || { ! is_ignored_interface "$1" && { is_wan "$1" || is_wan6 "$1" || is_tunnel "$1"; }; } || is_ignore_target "$1"; }
-pbr_find_iface() {
-       local iface i param="$2"
-       [ "$param" = 'wan6' ] || param='wan'
-       "network_find_${param}" iface
-       is_tunnel "$iface" && unset iface
-       if [ -z "$iface" ]; then
-               for i in $ifacesAll; do
-                       if "is_${param}" "$i"; then break; else unset i; fi
-               done
-       fi
-       eval "$1"='${iface:-$i}'
-}
-_find_firewall_wan_zone() { [ "$(uci -q get "firewall.${1}.name")" = "wan" ] && firewallWanZone="$1"; }
-_build_ifaces_all() { ifacesAll="${ifacesAll}${1} "; }
-_build_ifaces_supported() { is_supported_interface "$1" && ! str_contains "$ifacesSupported" "$1" && ifacesSupported="${ifacesSupported}${1} "; }
 get_supported_interfaces() {
-       local name i
-       name="$(basename "$1")"
-       name="${name:-$packageName}" 
+       _find_firewall_wan_zone() { [ "$(uci_get 'firewall' "$1" 'name')" = "wan" ] && firewallWanZone="$1"; }
+       _build_ifaces_all() { ifacesAll="${ifacesAll}${1} "; }
+       _build_ifaces_supported() { is_supported_interface "$1" && ! str_contains "$ifacesSupported" "$1" && ifacesSupported="${ifacesSupported}${1} "; }
+       local i
        local firewallWanZone
        local ifacesAll ifacesSupported
        local webui_show_ignore_target
@@ -304,19 +229,18 @@ get_supported_interfaces() {
        pbr_find_iface wanIface6 'wan6'
        config_load 'firewall'
        config_foreach _find_firewall_wan_zone 'zone'
-       for i in $(uci -q get "firewall.${firewallWanZone}.network"); do
+       for i in $(uci_get 'firewall' "$firewallWanZone" 'network'); do
                is_supported_interface "$i" && ! str_contains "$ifacesSupported" "$1" && ifacesSupported="${ifacesSupported}${i} "
        done
        config_load 'network'
        config_foreach _build_ifaces_supported 'interface'
-       if is_tor_running; then
-               ifacesSupported="$ifacesSupported tor"
-       fi
-       if [ "$webui_show_ignore_target" -eq "1" ]; then
-               ifacesSupported="$ifacesSupported ignore"
-       fi
+       is_tor_running && ifacesSupported="$ifacesSupported tor"
+       for i in $supported_interface; do
+               is_xray "$i" && ifacesSupported="$ifacesSupported $i"
+       done
+       [ "$webui_show_ignore_target" -eq "1" ] && ifacesSupported="$ifacesSupported ignore"
        json_init
-       json_add_object "$name"
+       json_add_object "$packageName"
        json_add_array 'interfaces'
        for i in $ifacesSupported; do
                json_add_string '' "$i"
@@ -359,35 +283,35 @@ case "$1" in
                                json_load "$input"
                                json_get_var name 'name'
                                json_cleanup
-                               get_gateways "$name"
+                               get_gateways "$packageName"
                                ;;
                        getInitList)
                                read -r input
                                json_load "$input"
                                json_get_var name 'name'
                                json_cleanup
-                               get_init_list "$name"
+                               get_init_list "$packageName"
                                ;;
                        getInitStatus)
                                read -r input
                                json_load "$input"
                                json_get_var name 'name'
                                json_cleanup
-                               get_init_status "$name"
+                               get_init_status "$packageName"
                                ;;
                        getInterfaces)
                                read -r input
                                json_load "$input"
                                json_get_var name 'name'
                                json_cleanup
-                               get_supported_interfaces "$name"
+                               get_supported_interfaces "$packageName"
                                ;;
                        getPlatformSupport)
                                read -r input
                                json_load "$input"
                                json_get_var name 'name'
                                json_cleanup
-                               get_platform_support "$name"
+                               get_platform_support "$packageName"
                                ;;
                        setInitAction)
                                read -r input
@@ -395,7 +319,7 @@ case "$1" in
                                json_get_var name 'name'
                                json_get_var action 'action'
                                json_cleanup
-                               set_init_action "$name" "$action"
+                               set_init_action "$packageName" "$action"
                                ;;
                esac
        ;;
index 39178f3790ec44b6f0fe99588d638b735d9d1896..6c2f7de6d78a994e00f366543a1d7c8d6cd53fb1 100644 (file)
                                        "getPlatformSupport"
                                ]
                        },
+                       "file": {
+                               "/usr/share/nftables.d/ruleset-post/30-pbr.nft": [
+                                       "read"
+                               ],
+                               "/var/run/pbr.nft": [
+                                       "read"
+                               ]
+                       },
                        "uci": [
                                "pbr"
                        ]