diff --git a/.github/eslint.config.mjs b/.github/eslint.config.mjs new file mode 100644 index 0000000..d25e9cd --- /dev/null +++ b/.github/eslint.config.mjs @@ -0,0 +1,119 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import globals from 'globals'; +import markdown from "@eslint/markdown"; +import json from '@eslint/json'; +import js from '@eslint/js'; + +export default defineConfig([ + globalIgnores([ + 'node_modules', + ]), + // Markdown + { + files: ["**/*.md"], + plugins: { + markdown, + }, + processor: "markdown/markdown", + }, + // applies only to JavaScript blocks inside of Markdown files + { + files: ["**/*.md/*.js"], + rules: { + strict: "off", + }, + }, + // JSON files + { + files: ['**/*.json'], + ignores: ['package-lock.json'], + plugins: { json }, + language: 'json/json', + extends: ['json/recommended'], + rules: { + 'json/no-duplicate-keys': 'error', + }, + }, + // JavaScript files + { + files: ['**/*.js'], + language: '@/js', + plugins: { js }, + extends: ['js/recommended'], + linterOptions: { + reportUnusedDisableDirectives: "off", + }, + languageOptions: { + sourceType: 'script', + ecmaVersion: 2026, + globals: { + ...globals.browser, + /* LuCI runtime / cbi exports */ + _: 'readonly', + N_: 'readonly', + L: 'readonly', + E: 'readonly', + TR: 'readonly', + cbi_d: 'readonly', + cbi_strings: 'readonly', + cbi_d_add: 'readonly', + cbi_d_check: 'readonly', + cbi_d_checkvalue: 'readonly', + cbi_d_update: 'readonly', + cbi_init: 'readonly', + cbi_update_table: 'readonly', + cbi_validate_form: 'readonly', + cbi_validate_field: 'readonly', + cbi_validate_named_section_add: 'readonly', + cbi_validate_reset: 'readonly', + cbi_row_swap: 'readonly', + cbi_tag_last: 'readonly', + cbi_submit: 'readonly', + cbi_dropdown_init: 'readonly', + isElem: 'readonly', + toElem: 'readonly', + matchesElem: 'readonly', + findParent: 'readonly', + sfh: 'readonly', + renderBadge: 'readonly', + /* modules */ + baseclass: 'readonly', + dom: 'readonly', + firewall: 'readonly', + fwtool: 'readonly', + form: 'readonly', + fs: 'readonly', + network: 'readonly', + nettools: 'readonly', + poll: 'readonly', + random: 'readonly', + request: 'readonly', + session: 'readonly', + rpc: 'readonly', + uci: 'readonly', + ui: 'readonly', + uqr: 'readonly', + validation: 'readonly', + view: 'readonly', + widgets: 'readonly', + /* dockerman */ + dm2: 'readonly', + jsapi: 'readonly', + }, + parserOptions: { + ecmaFeatures: { + globalReturn: true, + }, + }, + }, + rules: { + 'strict': 0, + 'no-prototype-builtins': 0, + 'no-empty': 0, + 'no-undef': 'warn', + 'no-unused-vars': ['off', { "caughtErrors": "none" }], + 'no-regex-spaces': 0, + 'no-control-regex': 0, + }, + }, +]); diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 0000000..3315da4 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,47 @@ +name: "ESLint" + +on: + push: + branches: ["main"] + paths: + - '**/*.js' + - '**/*.json' + - '**/*.md' + - '.github/eslint.config.mjs' + + pull_request: + branches: ["main"] + paths: + - '**/*.js' + - '**/*.json' + - '**/*.md' + - '.github/eslint.config.mjs' + +permissions: {} + +jobs: + eslint: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: latest + + - name: Install ESLint + run: | + npm install --no-audit --no-fund --save-dev \ + eslint@9 \ + globals \ + @eslint/json@latest \ + @eslint/js@9 \ + @eslint/markdown \ + eslint-formatter-gha + + - name: Run ESLint + run: npx eslint --config .github/eslint.config.mjs . diff --git a/.github/workflows/openwrt-release.yml b/.github/workflows/openwrt-release.yml new file mode 100644 index 0000000..8453670 --- /dev/null +++ b/.github/workflows/openwrt-release.yml @@ -0,0 +1,212 @@ +permissions: + contents: write +name: Build OpenWrt Package + +on: + push: + workflow_dispatch: + +jobs: + build: + name: Build for ${{ matrix.version }} / ${{ matrix.target }} + runs-on: ubuntu-latest + env: + REPO_APK_KEY: ${{ secrets.REPO_APK_KEY }} + PKG_NAME: ${{ github.event.repository.name }} + + strategy: + fail-fast: false + matrix: + include: + - version: openwrt-24.10 + target: x86_64 + suffix: all + # - version: openwrt-24.10 + # target: aarch64_cortex-a53 + # - version: openwrt-24.10 + # target: aarch64_generic + # - version: openwrt-24.10 + # target: arm_cortex-a7 + # - version: openwrt-24.10 + # target: arm_cortex-a9 + # - version: openwrt-24.10 + # target: mips_24kc + # - version: openwrt-24.10 + # target: mipsel_24kc + - version: openwrt-25.12 + target: x86_64 + suffix: noarch + # - version: openwrt-25.12 + # target: aarch64_cortex-a53 + # - version: openwrt-25.12 + # target: aarch64_generic + # - version: openwrt-25.12 + # target: arm_cortex-a7 + # - version: openwrt-25.12 + # target: arm_cortex-a9 + # - version: openwrt-25.12 + # target: mips_24kc + # - version: openwrt-25.12 + # target: mipsel_24kc + + steps: + - name: ๐Ÿงพ Checkout repo + uses: actions/checkout@v5 + + - name: ๐Ÿงฐ Set up melmac feed structure + run: | + set -e + # Also add a shim so ../../luci.mk from package/feeds/melmac/ works: + mkdir -p package + printf 'include $(TOPDIR)/feeds/luci/luci.mk\n' > package/luci.mk + + # LuCI-style layout so ../../luci.mk resolves from applications/ + mkdir -p feed/luci/applications/${PKG_NAME} + + # Add shim luci/luci.mk pointing to the official LuCI feed + mkdir -p feed/luci + printf 'include $(TOPDIR)/feeds/luci/luci.mk\n' > feed/luci/luci.mk + + # Copy the package sources into the feed subdir (exclude CI and feed itself) + rsync -av \ + --exclude='.git' \ + --exclude='feed' \ + --exclude='.github' \ + ./ feed/luci/applications/${PKG_NAME}/ + + - name: ๐Ÿงฎ Compute ARCH + run: | + # matrix.version is the release-line LABEL baked into artifact filenames + # (sync_repo.sh matches *_openwrt-25.12_*), NOT the SDK image tag. Pin the + # SDK to a specific STABLE point release so builds are reproducible and + # immune to -SNAPSHOT mirror churn (rotated/truncated tarballs -> + # sha256 mismatch). Bump these when a new point release ships. + case "${{ matrix.version }}" in + openwrt-25.12) SDK_VERSION="25.12.5" ;; + openwrt-24.10) SDK_VERSION="24.10.7" ;; + snapshots) SDK_VERSION="SNAPSHOT" ;; + *) SDK_VERSION="${{ matrix.version }}" ;; + esac + echo "ARCH=${{ matrix.target }}-${SDK_VERSION}" >> "$GITHUB_ENV" + + - name: ๐Ÿ—๏ธ Build with OpenWrt SDK + uses: openwrt/gh-action-sdk@v8 + env: + ARCH: ${{ env.ARCH }} + FEEDNAME: melmac + PACKAGES: ${{ env.PKG_NAME }} + INDEX: 0 + FEED_DIR: ${{ github.workspace }}/feed + NO_SHFMT_CHECK: 1 + NO_REFRESH_CHECK: 1 + PRIVATE_KEY: ${{ secrets.REPO_APK_KEY }} + + - name: ๐Ÿ” Debug build output + run: | + echo "=== Build directory structure ===" + find . -name "*.ipk" -type f 2>/dev/null || echo "No .ipk files found" + echo "=== bin directory structure ===" + ls -la bin/ 2>/dev/null || echo "No bin directory found" + if [ -d "bin/packages" ]; then + echo "=== packages directory structure ===" + find bin/packages/ -type f -name "*.ipk" 2>/dev/null || echo "No .ipk files in packages directory" + echo "=== Full packages directory tree ===" + ls -laR bin/packages/ 2>/dev/null || echo "No packages directory" + fi + + - name: ๐Ÿท๏ธ Rename APK with version and target + run: | + set -euo pipefail + PKG_VERSION=$(grep -E '^PKG_VERSION *:?=' Makefile | head -n1 | cut -d= -f2 | tr -d '[:space:]') + PKG_RELEASE=$(grep -E '^PKG_RELEASE *:?=' Makefile | head -n1 | cut -d= -f2 | tr -d '[:space:]') + for ext in ipk apk; do + # apk keeps OpenWrt's 'r' release prefix (โ€ฆ-r5.apk); opkg does not + case "$ext" in apk) REL="r${PKG_RELEASE}" ;; *) REL="${PKG_RELEASE}" ;; esac + for f in $(find bin/packages -type f -path "*/melmac/*.${ext}"); do + dir=$(dirname "$f") + if [ -n "${{ matrix.suffix }}" ]; then + mv -v "$f" "$dir/${PKG_NAME}-${PKG_VERSION}-${REL}_${{ matrix.version }}_${{ matrix.suffix }}.${ext}" + else + mv -v "$f" "$dir/${PKG_NAME}-${PKG_VERSION}-${REL}_${{ matrix.version }}_${{ matrix.target }}.${ext}" + fi + done + done + + - name: ๐Ÿ“ฆ Upload artifact for ${{ matrix.target }} + uses: actions/upload-artifact@v7 + with: + name: ${{ env.PKG_NAME }}-${{ matrix.target }}-${{ matrix.version }} + path: | + bin/packages/*/melmac/${{ env.PKG_NAME }}*.ipk + bin/packages/*/melmac/${{ env.PKG_NAME }}*.apk + # bin/packages/*/melmac/Packages* + # bin/packages/*/melmac/index.json + # bin/packages/*/melmac/packages.adb + if-no-files-found: ignore + + release: + name: Release ${{ github.repository }} + needs: build + runs-on: ubuntu-latest + + steps: + - name: ๐Ÿงพ Checkout repo + uses: actions/checkout@v5 + + - name: ๐Ÿ” Extract version and release from Makefile + id: version + run: | + PKG_VERSION=$(grep -E '^PKG_VERSION *:?=' Makefile | head -n1 | cut -d= -f2 | tr -d '[:space:]') + PKG_RELEASE=$(grep -E '^PKG_RELEASE *:?=' Makefile | head -n1 | cut -d= -f2 | tr -d '[:space:]') + COMBINED_VERSION="${PKG_VERSION}-${PKG_RELEASE}" + echo "๐Ÿ“ฆ Version: $COMBINED_VERSION" + echo "version=$COMBINED_VERSION" >> "$GITHUB_OUTPUT" + + - name: โฌ‡๏ธ Download all build artifacts + uses: actions/download-artifact@v8 + with: + path: ./release-assets + + - name: ๐Ÿ—ƒ๏ธ Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.version }} + name: Build v${{ steps.version.outputs.version }} + files: | + ./release-assets/**/*.ipk + ./release-assets/**/*.apk + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: ๐Ÿงน Prune stale release assets + # Delete assets on the tag that THIS build did not produce, so an arch or + # release line you stopped building no longer lingers on the release (and + # no longer gets re-published by sync_repo.sh, which pulls from releases). + # Gated on a fully green build + a non-empty keep set so a transient arch + # failure or an empty build can never wipe a still-good binary. + if: needs.build.result == 'success' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: v${{ steps.version.outputs.version }} + run: | + keep=$(find release-assets -type f \( -name '*.ipk' -o -name '*.apk' \) \ + -exec basename {} \; | sort -u) + [ -n "$keep" ] || { echo "no packages built โ€” skipping prune"; exit 0; } + gh release view "$TAG" --json assets --jq '.assets[].name' | while read -r a; do + case "$a" in *.ipk|*.apk) ;; *) continue ;; esac + printf '%s\n' "$keep" | grep -qxF -- "$a" || { + echo "pruning stale asset: $a" + gh release delete-asset "$TAG" "$a" --yes + } + done + - name: ๐Ÿ”” Notify repo.mossdef.org + # Event-driven sync: ping the repo so it publishes this release immediately + # instead of waiting for its hourly poll. Non-fatal if the token is absent. + if: needs.build.result == 'success' + env: + GH_TOKEN: ${{ secrets.SYNC_DISPATCH_TOKEN }} + run: | + gh api -X POST /repos/mossdef-org/repo.mossdef.org/dispatches \ + -f event_type=package-released \ + || echo "dispatch failed โ€” repo.mossdef.org still syncs on its hourly schedule" diff --git a/Makefile b/Makefile index 1f98310..0badcdd 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,18 @@ -# Copyright 2017-2024 MOSSDeF, Stan Grishin (stangri@melmac.ca). -# This is free software, licensed under AGPL-3.0-or-later. +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright 2017-2026 MOSSDeF, Stan Grishin (stangri@melmac.ca). include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-pbr PKG_LICENSE:=AGPL-3.0-or-later -PKG_MAINTAINER:=Stan Grishin -PKG_VERSION:=1.1.8 -PKG_RELEASE:=5 +PKG_MAINTAINER:=Stan Grishin , Erik Conijn +PKG_VERSION:=1.2.3 +PKG_RELEASE:=79 LUCI_TITLE:=Policy Based Routing Service Web UI -LUCI_URL:=https://github.com/stangri/luci-app-pbr/ +LUCI_URL:=https://docs.mossdef.org/pbr/ LUCI_DESCRIPTION:=Provides Web UI for Policy Based Routing Service. -LUCI_DEPENDS:=+luci-base +jsonfilter +pbr - -PKG_PROVIDES:=luci-app-vpnbypass luci-app-vpn-policy-routing +LUCI_DEPENDS:=+luci-base +jsonfilter +rpcd-mod-ucode +pbr define Package/$(PKG_NAME)/config # shown in make menuconfig diff --git a/README.md b/README.md index facd562..b06e9fc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,19 @@ -# README +# LuCI App for pbr -Documentation for this project is available at [https://docs.openwrt.melmac.net/luci-app-pbr/](https://docs.openwrt.melmac.net/luci-app-pbr/). +[![OpenWrt](https://img.shields.io/badge/OpenWrt-Compatible-blueviolet)](https://openwrt.org) +[![Web UI](https://img.shields.io/badge/Web_UI-Available-blue)](https://docs.mossdef.org/pbr/) +[![License](https://img.shields.io/badge/License-GPL--3.0-lightgrey)](https://github.com/stangri/luci-app-pbr/blob/master/LICENSE) +LuCI interface for the `pbr` (policy-based routing) package. +Provides a visual way to manage traffic policies for routing over specific WAN or VPN interfaces. + +## Highlights + +- Intuitive Web UI with policy table +- Add/edit/delete rules by IP, port, MAC, domain +- Interface-specific status panel and logs +- Quick toggles for services and resolver settings +- Integrated with OpenWrtโ€™s service management + +**Full documentation:** +[https://docs.mossdef.org/pbr/](https://docs.mossdef.org/pbr/) diff --git a/htdocs/luci-static/resources/pbr/status.js b/htdocs/luci-static/resources/pbr/status.js index d8b47bc..10d91e3 100644 --- a/htdocs/luci-static/resources/pbr/status.js +++ b/htdocs/luci-static/resources/pbr/status.js @@ -10,12 +10,15 @@ var pkg = { get Name() { return "pbr"; }, + get LuciCompat() { + return 34; + }, get ReadmeCompat() { - return "1.1.8"; + return "1.2.3"; }, get URL() { return ( - "https://docs.openwrt.melmac.net/" + + "https://docs.mossdef.org/" + pkg.Name + "/" + (pkg.ReadmeCompat ? pkg.ReadmeCompat + "/" : "") @@ -23,45 +26,70 @@ var pkg = { }, get DonateURL() { return ( - "https://docs.openwrt.melmac.net/" + + "https://docs.mossdef.org/" + pkg.Name + "/" + (pkg.ReadmeCompat ? pkg.ReadmeCompat + "/" : "") + - "#Donate" + "#donate" + ); + }, + isVersionMismatch: function (luci, pkg, rpcd) { + return luci !== pkg || pkg !== rpcd || luci !== rpcd; + }, + // HTML-escape an untrusted scalar value with LuCI's %h format specifier so + // config-derived text reflected into status messages cannot inject markup + // when appended via innerHTML by E()/dom.create. Trusted array infos built + // in this file (e.g. anchor tags) are passed through unchanged. + escapeInfo: function (info) { + return Array.isArray(info) + ? info + : info != null && info !== "" ? "%h".format(info) : info; + }, + formatMessage: function (info, template) { + if (!template) return _("Unknown message") + "
"; + return ( + (Array.isArray(info) + ? template.format(...info) + : template.format(info || " ")) + "
" ); }, + buildGatewayElements: function (gw) { + const gateways = Array.isArray(gw) ? gw : Object.values(gw); + const ipv6Enabled = L.uci.get(pkg.Name, "config", "ipv6_enabled") === "1"; + const elements = []; + gateways.forEach(function (g) { + const iface = g.name; + if (!iface) return; + if (elements.length) elements.push(E("br")); + // Prefer the labeled form (e.g. "mwan4:wan", "netifd:wan") sent by + // the principal app; fall back to the canonical iface name for + // older principal-app versions that don't emit `label`. + const display = g.label || iface; + const parts = [display]; + if (g.device_ipv4 && g.device_ipv4 !== iface) parts.push(g.device_ipv4); + parts.push(g.gateway_ipv4 || "0.0.0.0"); + if (ipv6Enabled) { + if (g.device_ipv6 && g.device_ipv6 !== iface) parts.push(g.device_ipv6); + parts.push(g.gateway_ipv6 || "::0"); + } + var line = parts.join("/"); + if (g.default) { + elements.push(line + " "); + elements.push(E("strong", {}, "โœ“")); + } else { + elements.push(line); + } + }); + return elements; + }, }; -var getGateways = rpc.declare({ - object: "luci." + pkg.Name, - method: "getGateways", - params: ["name"], -}); - -var getInitList = rpc.declare({ - object: "luci." + pkg.Name, - method: "getInitList", - params: ["name"], -}); - var getInitStatus = rpc.declare({ object: "luci." + pkg.Name, method: "getInitStatus", params: ["name"], }); -var getInterfaces = rpc.declare({ - object: "luci." + pkg.Name, - method: "getInterfaces", - params: ["name"], -}); - -var getPlatformSupport = rpc.declare({ - object: "luci." + pkg.Name, - method: "getPlatformSupport", - params: ["name"], -}); - var _setInitAction = rpc.declare({ object: "luci." + pkg.Name, method: "setInitAction", @@ -87,82 +115,113 @@ var RPC = { } }); }, - getInitList: function (name) { - getInitList(name).then( - function (result) { - this.emit("getInitList", result); - }.bind(this) - ); - }, - getInitStatus: function (name) { - getInitStatus(name).then( - function (result) { - this.emit("getInitStatus", result); - }.bind(this) - ); - }, - getGateways: function (name) { - getGateways(name).then( - function (result) { - this.emit("getGateways", result); - }.bind(this) - ); - }, - getPlatformSupport: function (name) { - getPlatformSupport(name).then( - function (result) { - this.emit("getPlatformSupport", result); - }.bind(this) - ); - }, - getInterfaces: function (name) { - getInterfaces(name).then( - function (result) { - this.emit("getInterfaces", result); - }.bind(this) - ); - }, setInitAction: function (name, action) { - _setInitAction(name, action).then( - function (result) { - this.emit("setInitAction", result); - }.bind(this) - ); + _setInitAction(name, action) + .then( + function (result) { + this.emit("setInitAction", { result: result, action: action }); + }.bind(this), + ) + .catch( + function (error) { + // Even if RPC call fails/times out, emit event to start polling + // This handles cases where the backend task starts but RPC times out + this.emit("setInitAction", { timeout: true, action: action }); + }.bind(this), + ); }, }; +// Poll service status until the expected running state is reached. +// expectRunning: true for start/restart/reload, false for stop. +var pollServiceStatus = function (expectRunning, callback) { + var maxAttempts = 300; // Max 5 minutes of polling + var attempt = 0; + + var checkStatus = function () { + attempt++; + + L.resolveDefault(getInitStatus(pkg.Name), {}) + .then(function (statusData) { + var isRunning = + statusData && statusData[pkg.Name] && statusData[pkg.Name].running; + + // Check if the expected state has been reached + if (expectRunning ? isRunning === true : isRunning !== true) { + callback(true); + } + // Check if timed out + else if (attempt >= maxAttempts) { + callback(false, "timeout"); + } + // Continue polling + else { + setTimeout(checkStatus, 1000); + } + }) + .catch(function (err) { + // Retry on error unless timed out + if (attempt < maxAttempts) { + setTimeout(checkStatus, 1000); + } else { + callback(false, "error"); + } + }); + }; + + // Start polling after a delay to give the backend time to start the task + setTimeout(checkStatus, 3000); +}; + var status = baseclass.extend({ render: function () { - return Promise.all([ - L.resolveDefault(getInitStatus(pkg.Name), {}), - // L.resolveDefault(getGateways(pkg.Name), {}), - ]).then(function (data) { - // var replyStatus = data[0]; - // var replyGateways = data[1]; - var reply; - var text; + return L.resolveDefault(getInitStatus(pkg.Name), {}).then(function (initStatus) { + var reply = initStatus?.[pkg.Name] || {}; + reply = { + enabled: reply.enabled || false, + running: reply.running || false, + running_iptables: reply.running_iptables || false, + running_nft: reply.running_nft || false, + running_nft_file: reply.running_nft_file || false, + version: reply.version || null, + packageCompat: reply.packageCompat || 0, + rpcdCompat: reply.rpcdCompat || 0, + gateways: reply.gatewaysList || [], + errors: (reply.errors || []).map(function (e) { + return Object.assign({}, e, { info: pkg.escapeInfo(e.info) }); + }), + warnings: (reply.warnings || []).map(function (e) { + return Object.assign({}, e, { info: pkg.escapeInfo(e.info) }); + }), + }; - 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: [], - }; + if ( + pkg.isVersionMismatch( + pkg.LuciCompat, + reply.packageCompat, + reply.rpcdCompat, + ) + ) { + reply.warnings.push({ + code: "warningInternalVersionMismatch", + info: [ + reply.packageCompat, + pkg.LuciCompat, + reply.rpcdCompat, + '', + "", + ], + }); } + var text; var header = E("h2", {}, _("Policy Based Routing - Status")); var statusTitle = E( "label", - { class: "cbi-value-title" }, - _("Service Status") + { class: "cbi-value-title", for: pkg.Name + "-status" }, + _("Service Status"), ); if (reply.version) { text = _("Version %s").format(reply.version) + " - "; @@ -187,7 +246,7 @@ var status = baseclass.extend({ } else { text = _("Not installed or not found"); } - var statusText = E("div", {}, text); + var statusText = E("output", { id: pkg.Name + "-status" }, text); var statusField = E("div", { class: "cbi-value-field" }, statusText); var statusDiv = E("div", { class: "cbi-value" }, [ statusTitle, @@ -198,26 +257,28 @@ var status = baseclass.extend({ if (reply.gateways) { var gatewaysTitle = E( "label", - { class: "cbi-value-title" }, - _("Service Gateways") + { class: "cbi-value-title", for: pkg.Name + "-gateways" }, + _("Service Gateways"), ); - text = + var gatewaysDescr = E("div", { class: "cbi-value-description" }, _( - "The %s indicates default gateway. See the %sREADME%s for details." + "The %s indicates default gateway. See the %sREADME%s for details.", ).format( "โœ“", '', - "" + "", ) + "
" + - _("Please %sdonate%s to support development of this project.").format( + "
" + + _( + "Please %sdonate%s to support development of this project.", + ).format( "", - "" - ); - var gatewaysDescr = E("div", { class: "cbi-value-description" }, text); - var gatewaysText = E("div", {}, reply.gateways); + "", + )); + var gatewaysText = E("output", { id: pkg.Name + "-gateways" }, pkg.buildGatewayElements(reply.gateways)); var gatewaysField = E("div", { class: "cbi-value-field" }, [ gatewaysText, gatewaysDescr, @@ -230,76 +291,100 @@ var status = baseclass.extend({ var warningsDiv = []; if (reply.warnings && reply.warnings.length) { - var textLabelsTable = { + var warningTable = { + warningInternalVersionMismatch: _( + "Internal version mismatch (package: %s, luci app: %s, luci rpcd: %s), you may need to update packages or reboot the device, please check the %sREADME%s.", + ), warningResolverNotSupported: _( - "Resolver set (%s) is not supported on this system." - ).format(L.uci.get(pkg.Name, "config", "resolver_set")), + "Resolver set (%s) is not supported on this system.", + ).format(pkg.escapeInfo(L.uci.get(pkg.Name, "config", "resolver_set"))), warningAGHVersionTooLow: _( - "Installed AdGuardHome (%s) doesn't support 'ipset_file' option." + "Installed AdGuardHome (%s) doesn't support 'ipset_file' option.", ), warningPolicyProcessCMD: _("%s"), warningTorUnsetParams: _( - "Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'" + "Please unset 'src_addr', 'src_port' and 'dest_port' for policy '%s'", ), warningTorUnsetProto: _( - "Please unset 'proto' or set 'proto' to 'all' for policy '%s'" + "Please unset 'proto' or set 'proto' to 'all' for policy '%s'", ), warningTorUnsetChainIpt: _( - "Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'" + "Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'", ), warningTorUnsetChainNft: _( - "Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'" + "Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'", ), warningInvalidOVPNConfig: _( - "Invalid OpenVPN config for %s interface" + "Invalid OpenVPN config for %s interface", ), warningOutdatedLuciPackage: _( - "The WebUI application (luci-app-pbr) is outdated, please update it" + "The WebUI application (luci-app-pbr) is outdated, please update it", ), warningOutdatedPrincipalPackage: _( - "The principal package (pbr) is outdated, please update it" + "The principal package (pbr) is outdated, please update it", ), warningBadNftCallsInUserFile: _( - "Incompatible nft calls detected in user include file, disabling fw4 nft file support" + "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" + "Dnsmasq instance (%s) targeted in settings, but it doesn't have its own confdir", ), warningDhcpLanForce: _( _( - "Please set 'dhcp.%%s.force=1' to speed up service start-up %s(more info)%s" + "Please set 'dhcp.%%s.force=1' to speed up service start-up %s(more info)%s", ).format( "", - "" - ) + "", + ), + ), + warningSummary: _("Warnings encountered, please check %s"), + warningIncompatibleDHCPOption6: _( + "Incompatible DHCP Option 6 for interface %s", + ), + warningNetifdMissingInterfaceLocal: _( + "Netifd setup: option netifd_interface_local is missing, assuming '%s'", + ), + warningUplinkDown: _( + "Uplink/WAN interface is still down, going back to boot mode", + ), + warningDynamicRoutingMode: _( + "Running in dynamic routing tables mode. Consider installing netifd extensions or mwan4 for more efficient operation. See %sREADME%s.", + ).format( + '', + "", + ), + warningInterfaceRoutingUnknownGateway4: _( + "Unknown IPv4 gateway for '%s'", + ), + warningInterfaceRoutingUnknownGateway6: _( + "Unknown IPv6 gateway for '%s'", ), }; var warningsTitle = E( "label", - { class: "cbi-value-title" }, - _("Service Warnings") + { class: "cbi-value-title", for: pkg.Name + "-warnings" }, + _("Service Warnings"), ); - var text = ""; + text = ""; reply.warnings.forEach((element) => { - if (element.id && textLabelsTable[element.id]) { - if (element.id !== "warningPolicyProcessCMD") { - text += - (textLabelsTable[element.id] + ".").format( - element.extra || " " - ) + "
"; - } + if (element.code && warningTable[element.code]) { + text += pkg.formatMessage(element.info, warningTable[element.code]); } else { text += _("Unknown warning") + "
"; } }); - var warningsText = E("div", { class: "cbi-value-description" }, text); + text += _("Warnings encountered, please check the %sREADME%s").format( + '', + "!
", + ); + var warningsText = E("output", { id: pkg.Name + "-warnings", class: "cbi-value-description" }, text); var warningsField = E( "div", { class: "cbi-value-field" }, - warningsText + warningsText, ); warningsDiv = E("div", { class: "cbi-value" }, [ warningsTitle, @@ -309,51 +394,54 @@ var status = baseclass.extend({ var errorsDiv = []; if (reply.errors && reply.errors.length) { - var textLabelsTable = { + var errorTable = { errorConfigValidation: _("Config (%s) validation failure").format( - "/etc/config/" + pkg.Name + "/etc/config/" + pkg.Name, ), 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")), + "Resolver set support (%s) requires ipset, but ipset binary cannot be found", + ).format(pkg.escapeInfo(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")), + "Resolver set support (%s) requires nftables, but nft binary cannot be found", + ).format(pkg.escapeInfo(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")), + "Resolver set (%s) is not supported on this system", + ).format(pkg.escapeInfo(L.uci.get(pkg.Name, "config", "resolver_set"))), errorServiceDisabled: _( - "The %s service is currently disabled" + "The %s service is currently disabled", ).format(pkg.Name), - errorNoWanGateway: _( - "The %s service failed to discover WAN gateway" + errorNoUplinkGateway: _( + "The %s service failed to discover uplink gateway", ).format(pkg.Name), - errorNoWanInterface: _( - "The %s interface not found, you need to set the 'pbr.config.procd_wan_interface' option" + errorNoUplinkInterface: _( + "The %s interface not found, you need to set the 'pbr.config.uplink_interface' option", ), - errorNoWanInterfaceHint: _( - "Refer to https://docs.openwrt.melmac.net/pbr/#procd_wan_interface" + errorNoUplinkInterfaceHint: _( + "Refer to %sREADME%s for details", + ).format( + '', + "!
", ), errorIpsetNameTooLong: _( - "The ipset name '%s' is longer than allowed 31 characters" + "The ipset name '%s' is longer than allowed 31 characters", ), errorNftsetNameTooLong: _( - "The nft set name '%s' is longer than allowed 255 characters" + "The nft set name '%s' is longer than allowed 255 characters", ), errorUnexpectedExit: _( - "Unexpected exit or service termination: '%s'" + "Unexpected exit or service termination: '%s'", ), errorPolicyNoSrcDest: _( - "Policy '%s' has no source/destination parameters" + "Policy '%s' has no source/destination parameters", ), errorPolicyNoInterface: _("Policy '%s' has no assigned interface"), errorPolicyNoDns: _("Policy '%s' has no assigned DNS"), errorPolicyProcessNoInterfaceDns: _( - "Interface '%s' has no assigned DNS" + "Interface '%s' has no assigned DNS", ), errorPolicyUnknownInterface: _( - "Policy '%s' has an unknown interface" + "Policy '%s' has an unknown interface", ), errorPolicyProcessCMD: _("%s"), errorFailedSetup: _("Failed to set up '%s'"), @@ -362,80 +450,104 @@ var status = baseclass.extend({ 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" + "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" + "Skipping IPv6 policy '%s' as IPv6 support is disabled", ), errorPolicyProcessUnknownFwmark: _( - "Unknown packet mark for interface '%s'" + "Unknown packet mark for interface '%s'", ), errorPolicyProcessMismatchFamily: _( - "Mismatched IP family between in policy '%s'" + "Mismatched IP family between in policy '%s'", ), errorPolicyProcessUnknownProtocol: _( - "Unknown protocol in policy '%s'" + "Unknown protocol in policy '%s'", ), errorPolicyProcessInsertionFailed: _( - "Insertion failed for both IPv4 and IPv6 for policy '%s'" + "Insertion failed for both IPv4 and IPv6 for policy '%s'", ), errorPolicyProcessInsertionFailedIpv4: _( - "Insertion failed for IPv4 for policy '%s'" + "Insertion failed for IPv4 for policy '%s'", ), + errorPolicyProcessUnknownEntry: _("Unknown entry in policy '%s'"), errorInterfaceRoutingEmptyValues: _( - "Received empty tid/mark or interface name when setting up routing" + "Received empty tid/mark or interface name when setting up routing", + ), + errorInterfaceMarkOverflow: _( + "Interface mark for '%s' exceeds the fwmask value", ), errorFailedToResolve: _("Failed to resolve '%s'"), errorInvalidOVPNConfig: _( - "Invalid OpenVPN config for '%s' interface" + "Invalid OpenVPN config for '%s' interface", ), - errorNftFileInstall: _("Failed to install fw4 nft file '%s'"), + errorNftMainFileInstall: _("Failed to install fw4 nft file '%s'"), errorNoDownloadWithSecureReload: _( - "Policy '%s' refers to URL which can't be downloaded in 'secure_reload' mode" + "Policy '%s' refers to URL which can't be downloaded in 'secure_reload' mode", ), errorDownloadUrlNoHttps: _( - "Failed to download '%s', HTTPS is not supported" + "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" - ), + "The '%s' schema requires curl, but it's not detected on this system", + ).format("file://"), errorTryFailed: _("Command failed: '%s'"), errorIncompatibleUserFile: _( - "Incompatible custom user file detected '%s'" + "Incompatible custom user file detected '%s'", + ), + errorUserFileUnsafeNft: _( + "Unsafe nft command in custom user file '%s'", ), errorDefaultFw4TableMissing: _("Default fw4 table '%s' is missing"), errorDefaultFw4ChainMissing: _("Default fw4 chain '%s' is missing"), errorRequiredBinaryMissing: _("Required binary '%s' is missing"), errorInterfaceRoutingUnknownDevType: _( - "Unknown IPv6 Link type for device '%s'" + "Unknown IPv6 Link type for device '%s'", + ), + errorMktempFileCreate: _( + "Failed to create temporary file with mktemp mask: '%s'", + ), + errorSummary: _("Errors encountered, please check %s"), + errorNftNetifdFileInstall: _( + "Netifd setup: failed to install fw4 netifd nft file '%s'", + ), + errorNftNetifdFileDelete: _( + "Netifd setup: failed to delete fw4 netifd nft file '%s'", + ), + errorNetifdMissingOption: _( + "Netifd setup: required option '%s' is missing", + ), + errorNetifdInvalidGateway4: _( + "Netifd setup: invalid value of netifd_interface_default option '%s'", + ), + errorNetifdInvalidGateway6: _( + "Netifd setup: invalid value of netifd_interface_default6 option '%s'", + ), + errorUplinkDown: _( + "Uplink/WAN interface is still down, increase value of 'procd_boot_trigger_delay' option", ), }; var errorsTitle = E( "label", - { class: "cbi-value-title" }, - _("Service Errors") + { class: "cbi-value-title", for: pkg.Name + "-errors" }, + _("Service Errors"), ); - var text = ""; + text = ""; reply.errors.forEach((element) => { - if (element.id && textLabelsTable[element.id]) { - if (element.id !== "errorPolicyProcessCMD") { - text += - (textLabelsTable[element.id] + "!").format( - element.extra || " " - ) + "
"; - } + if (element.code && errorTable[element.code]) { + text += pkg.formatMessage(element.info, errorTable[element.code]); } else { - text += _("Unknown error!") + "
"; + text += _("Unknown error") + "
"; } }); text += _("Errors encountered, please check the %sREADME%s").format( - '', - "!
" + '', + "!
", ); - var errorsText = E("div", { class: "cbi-value-description" }, text); + var errorsText = E("output", { id: pkg.Name + "-errors", class: "cbi-value-description" }, text); var errorsField = E("div", { class: "cbi-value-field" }, errorsText); errorsDiv = E("div", { class: "cbi-value" }, [ errorsTitle, @@ -447,7 +559,7 @@ var status = baseclass.extend({ var btn_gap_long = E( "span", {}, - "        " + "        ", ); var btn_start = E( @@ -460,13 +572,13 @@ var status = baseclass.extend({ E( "p", { class: "spinning" }, - _("Starting %s service").format(pkg.Name) + _("Starting %s service").format(pkg.Name), ), ]); return RPC.setInitAction(pkg.Name, "start"); }, }, - _("Start") + _("Start"), ); var btn_action = E( @@ -479,13 +591,13 @@ var status = baseclass.extend({ E( "p", { class: "spinning" }, - _("Restarting %s service").format(pkg.Name) + _("Restarting %s service").format(pkg.Name), ), ]); return RPC.setInitAction(pkg.Name, "restart"); }, }, - _("Restart") + _("Restart"), ); var btn_stop = E( @@ -498,13 +610,13 @@ var status = baseclass.extend({ E( "p", { class: "spinning" }, - _("Stopping %s service").format(pkg.Name) + _("Stopping %s service").format(pkg.Name), ), ]); return RPC.setInitAction(pkg.Name, "stop"); }, }, - _("Stop") + _("Stop"), ); var btn_enable = E( @@ -517,13 +629,13 @@ var status = baseclass.extend({ E( "p", { class: "spinning" }, - _("Enabling %s service").format(pkg.Name) + _("Enabling %s service").format(pkg.Name), ), ]); return RPC.setInitAction(pkg.Name, "enable"); }, }, - _("Enable") + _("Enable"), ); var btn_disable = E( @@ -536,13 +648,13 @@ var status = baseclass.extend({ E( "p", { class: "spinning" }, - _("Disabling %s service").format(pkg.Name) + _("Disabling %s service").format(pkg.Name), ), ]); return RPC.setInitAction(pkg.Name, "disable"); }, }, - _("Disable") + _("Disable"), ); if (reply.enabled) { @@ -567,10 +679,10 @@ var status = baseclass.extend({ var buttonsTitle = E( "label", - { class: "cbi-value-title" }, - _("Service Control") + { class: "cbi-value-title", for: pkg.Name + "-buttons" }, + _("Service Control"), ); - var buttonsText = E("div", {}, [ + var buttonsText = E("output", { id: pkg.Name + "-buttons" }, [ btn_start, btn_gap, btn_action, @@ -588,20 +700,20 @@ var status = baseclass.extend({ var donateTitle = E( "label", - { class: "cbi-value-title" }, - _("Donate to the Project") + { class: "cbi-value-title", for: pkg.Name + "-donate" }, + _("Donate to the Project"), ); var donateText = E( "div", { class: "cbi-value-field" }, E( - "div", - { class: "cbi-value-description" }, + "output", + { id: pkg.Name + "-donate", class: "cbi-value-description" }, _("Please %sdonate%s to support development of this project.").format( "", - "" - ) - ) + "", + ), + ), ); var donateDiv = reply.version @@ -622,14 +734,28 @@ var status = baseclass.extend({ }); RPC.on("setInitAction", function (reply) { - ui.hideModal(); - location.reload(); + var action = reply && reply.action; + if (action === "start" || action === "restart" || action === "reload") { + // Long-running: poll until service is running + pollServiceStatus(true, function () { + ui.hideModal(); + location.reload(); + }); + } else if (action === "stop") { + // Poll until service has stopped + pollServiceStatus(false, function () { + ui.hideModal(); + location.reload(); + }); + } else { + // enable/disable are fast, just reload immediately + ui.hideModal(); + location.reload(); + } }); return L.Class.extend({ status: status, pkg: pkg, getInitStatus: getInitStatus, - getInterfaces: getInterfaces, - getPlatformSupport: getPlatformSupport, }); diff --git a/htdocs/luci-static/resources/view/pbr/overview.js b/htdocs/luci-static/resources/view/pbr/overview.js index 0b35268..262e37d 100644 --- a/htdocs/luci-static/resources/view/pbr/overview.js +++ b/htdocs/luci-static/resources/view/pbr/overview.js @@ -6,33 +6,31 @@ "require rpc"; "require view"; "require pbr.status as pbr"; +/* global pbr */ var pkg = pbr.pkg; return view.extend({ load: function () { return Promise.all([ - L.resolveDefault(pbr.getInterfaces(pkg.Name), {}), - L.resolveDefault(pbr.getPlatformSupport(pkg.Name), {}), + L.resolveDefault(pbr.getInitStatus(pkg.Name), {}), L.resolveDefault(L.uci.load(pkg.Name), {}), ]); }, render: function (data) { var status, m, s, o; + var statusData = (data[0] && data[0][pkg.Name]) || {}; 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, + interfaces: statusData.interfaces || ["wan"], + interface_labels: statusData.interface_labels || {}, + protocols: statusData.protocols || [], + platform: statusData.platform || { + nft_installed: false, + adguardhome_installed: false, + dnsmasq_installed: false, + unbound_installed: false, + dnsmasq_nftset_support: false, }, }; @@ -52,14 +50,12 @@ return view.extend({ "", '', + '#service-configuration-settings" target="_blank">', "", "

" ) ); - s.tab("tab_webui", _("Web UI Configuration")); - o = s.taboption( "tab_basic", form.ListValue, @@ -78,7 +74,7 @@ return view.extend({ "strict_enforcement", _("Strict enforcement"), _("See the %sREADME%s for details.").format( - '', + '', "" ) ); @@ -100,7 +96,7 @@ return view.extend({ text += _( "Please check the %sREADME%s before changing this option." ).format( - '', + '', "" ); @@ -112,18 +108,21 @@ return view.extend({ 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"; - } + o.default = "none"; if (reply.platform.dnsmasq_nftset_support) { o.value("dnsmasq.nftset", _("Dnsmasq nft set")); o.default = "dnsmasq.nftset"; + } else if ( + L.uci.get(pkg.Name, "config", "resolver_set") === "dnsmasq.nftset" + ) { + // Support detection can fail transiently, for instance when dnsmasq + // is not installed or not yet running. Without the stored value in + // the choice list the select falls back to its first entry and + // saving would silently rewrite resolver_set to "none". The + // description above already states that support is missing. + o.value("dnsmasq.nftset", _("Dnsmasq nft set")); } + o.rmempty = false; o = s.taboption( "tab_basic", @@ -140,8 +139,12 @@ return view.extend({ "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*." + "Allows to specify the list of interface names to be explicitly supported by the service. " + + "Can be useful if your OpenVPN tunnels have dev option other than tun* or tap* or specific use cases " + + "of WireGuard servers. See the %sREADME%s for details." + ).format( + '', + "" ) ); o.optional = false; @@ -152,8 +155,13 @@ return view.extend({ "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." + "Allows to specify the list of interface names to be ignored by the service. " + + "Can be useful for an OpenVPN server running on OpenWrt device. WireGuard servers, which " + + "have a listen_port defined, are handled automatically, do not add those here." + + "See the %sREADME%s for details." + ).format( + '', + "" ) ); o.optional = false; @@ -179,7 +187,7 @@ return view.extend({ o.value("", _("No Change")); reply.interfaces.forEach((element) => { if (element.toLowerCase() !== "ignore") { - o.value(element); + o.value(element, reply.interface_labels[element] || element); } }); o.rmempty = true; @@ -187,10 +195,49 @@ return view.extend({ o = s.taboption( "tab_advanced", form.Value, - "wan_mark", - _("WAN Table FW Mark"), + "uplink_interface", + _("Default Uplink Interface (IPv4)"), + _("Force the default IPv4 uplink interface used by the service. " + + "Select from the list of known interfaces or enter a custom interface name.") + ); + if (Array.isArray(reply.interfaces)) { + reply.interfaces.forEach((element) => { + if (element.toLowerCase() !== "ignore") { + o.value(element, reply.interface_labels[element] || element); + } + }); + } + o.datatype = "network"; + o.default = "wan"; + o.rmempty = false; + + o = s.taboption( + "tab_advanced", + form.Value, + "uplink_interface6", + _("Default Uplink Interface (IPv6)"), + _("Force the default IPv6 uplink interface used by the service. " + + "Select from the list of known interfaces or enter a custom interface name.") + ); + if (Array.isArray(reply.interfaces)) { + reply.interfaces.forEach((element) => { + if (element.toLowerCase() !== "ignore") { + o.value(element, reply.interface_labels[element] || element); + } + }); + } + o.datatype = "network"; + o.default = "wan6"; + o.rmempty = false; + o.depends("ipv6_enabled", "1"); + + o = s.taboption( + "tab_advanced", + form.Value, + "uplink_mark", + _("Uplink Interface Table FW Mark"), _( - "Starting (WAN) FW Mark for marks used by the service. High starting mark is " + + "Starting (Uplink Interface) FW Mark for marks used by the service. High starting mark is " + "used to avoid conflict with SQM/QoS. Change with caution together with" ) + " " + @@ -219,30 +266,19 @@ return view.extend({ o.datatype = "hexstring"; o = s.taboption( - "tab_webui", - form.ListValue, - "webui_show_ignore_target", - _("Add Ignore Target"), + "tab_advanced", + form.Value, + "uplink_ip_rules_priority", + _("Uplink IP Rules Priority"), _( - "Adds 'ignore' to the list of interfaces for policies. See the %sREADME%s for details." - ).format( - '', - "" + "Starting (Uplink/WAN) ip rules priority used by the pbr service. High starting priority is " + + "used to avoid conflict with other services, this can be changed by user." ) ); - 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; + o.rmempty = true; + o.placeholder = "30000"; + o.datatype = "uinteger"; + o.default = "30000"; s = m.section( form.GridSection, @@ -253,7 +289,7 @@ return view.extend({ "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. For more information on options, check the %sREADME%s." ).format( - '', + '', "" ) ); @@ -293,34 +329,80 @@ return view.extend({ 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()); + o.value("", _("all")); + o.default = ""; + var popularProtos = ["tcp", "udp", "tcp udp", "icmp"]; + var hasPopular = false; + popularProtos.forEach(function (p) { + if (p === "tcp udp") { + if (reply.protocols.indexOf("tcp") !== -1 && reply.protocols.indexOf("udp") !== -1) { + o.value(p); + hasPopular = true; + } + } else if (reply.protocols.indexOf(p) !== -1) { + o.value(p); + hasPopular = true; + } + }); + var hasOther = false; + reply.protocols.forEach(function (p) { + if (popularProtos.indexOf(p) === -1) { + o.value(p); + hasOther = true; } }); o.rmempty = true; + if (hasPopular && hasOther) { + var _protoRenderWidget = o.renderWidget; + o.renderWidget = function () { + var node = _protoRenderWidget.apply(this, arguments); + var sel = node.querySelector ? node.querySelector("select") : null; + if (!sel && node.nodeName === "SELECT") sel = node; + if (sel) { + var lastOpt = null; + sel.querySelectorAll("option").forEach(function (opt) { + if (popularProtos.indexOf(opt.value) !== -1) + lastOpt = opt; + }); + if (lastOpt && lastOpt.nextElementSibling) { + sel.insertBefore( + E("option", { "disabled": "", "style": "text-align:center" }, + "โ”€โ”€ " + _("All Protocols") + " โ”€โ”€"), + lastOpt.nextSibling + ); + } + } + var ul = node.querySelector ? node.querySelector("ul") : null; + if (ul) { + var lastLi = null; + ul.querySelectorAll("li[data-value]").forEach(function (li) { + if (popularProtos.indexOf(li.getAttribute("data-value")) !== -1) + lastLi = li; + }); + if (lastLi && lastLi.nextElementSibling) { + lastLi.parentNode.insertBefore( + E("li", { + "unselectable": "", + "style": "text-align:center;opacity:0.6;font-size:90%" + }, "โ”€โ”€ " + _("All Protocols") + " โ”€โ”€"), + lastLi.nextSibling + ); + } + } + return node; + }; + } 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.value(element, reply.interface_labels[element] || element); }); o.datatype = "network"; o.rmempty = false; @@ -333,7 +415,7 @@ return view.extend({ "Name, local address and remote DNS fields are required. Multiple local " + "addresses/devices can be space separated. For more information on options, check the %sREADME%s." ).format( - '', + '', "" ) ); @@ -361,9 +443,15 @@ return view.extend({ o.rmempty = false; o.datatype = "list(or(cidr,host,network,ipaddr))"; reply.interfaces.forEach((element) => { - element === "ignore" || o.value(element); + element === "ignore" || o.value(element, reply.interface_labels[element] || element); }); + o = s.option(form.Value, "dest_dns_port", _("Remote DNS Port")); + o.optional = true; + o.rmempty = true; + o.datatype = "port"; + o.default = "53"; + s = m.section( form.NamedSection, "config", @@ -372,7 +460,7 @@ return view.extend({ _( "Set DSCP tags (in range between 1 and 63) for specific interfaces. See the %sREADME%s for details." ).format( - '', + '', "" ) ); @@ -395,7 +483,7 @@ return view.extend({ "Run the following user files after setting up but before restarting DNSMASQ. " + "See the %sREADME%s for details." ).format( - '', + '', "" ) ); diff --git a/htdocs/luci-static/resources/view/status/include/72_pbr.js b/htdocs/luci-static/resources/view/status/include/72_pbr.js index b065497..75dcca7 100644 --- a/htdocs/luci-static/resources/view/status/include/72_pbr.js +++ b/htdocs/luci-static/resources/view/status/include/72_pbr.js @@ -3,6 +3,7 @@ "require form"; "require baseclass"; "require pbr.status as pbr"; +/* global pbr */ var pkg = pbr.pkg; @@ -14,63 +15,53 @@ return baseclass.extend({ }, 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: [], - }; - } + try { + var reply = (data[0] && data[0][pkg.Name]) || {}; - 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"); + 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 { - modeText = _("unknown"); + if (reply.enabled) { + statusText = _("Inactive"); + } else { + statusText = _("Inactive (Disabled)"); + } } } else { - if (reply.enabled) { - statusText = _("Inactive"); - } else { - statusText = _("Inactive (Disabled)"); - } + versionText = _("Not installed or not found"); } - } 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), - ]), - ]); + 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; + return table; + } catch (e) { + return E("div", { class: "alert-message warning" }, + _("Unable to retrieve %s status").format("PBR")); + } }, }); diff --git a/po/templates/pbr.pot b/po/templates/pbr.pot index cd347bf..9e05ae3 100644 --- a/po/templates/pbr.pot +++ b/po/templates/pbr.pot @@ -1,217 +1,208 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:240 -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:358 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:304 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:440 msgid "%s" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:316 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:395 msgid "%s binary cannot be found" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:48 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:46 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/status/include/72_pbr.js:40 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:27 msgid "Active" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:116 -msgid "AdGuardHome ipset" -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:165 msgid "Add" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:225 -msgid "Add Ignore Target" -msgstr "" - -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:227 -msgid "" -"Adds 'ignore' to the list of interfaces for policies. See the %sREADME%s for " -"details." +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:44 +msgid "Advanced Configuration" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:46 -msgid "Advanced Configuration" +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:320 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:337 +msgid "All Protocols" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:143 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:131 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*." +"Allows to specify the list of interface names to be explicitly supported by " +"the service. Can be useful if your OpenVPN tunnels have dev option other " +"than tun* or tap* or specific use cases of WireGuard servers. See the " +"%sREADME%s for details." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:155 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:147 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." +"Allows to specify the list of interface names to be ignored by the service. " +"Can be useful for an OpenVPN server running on OpenWrt device. WireGuard " +"servers, which have a listen_port defined, are handled automatically, do not " +"add those here.See the %sREADME%s for details." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:43 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:41 msgid "Basic Configuration" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:312 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:346 msgid "Chain" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:405 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:491 msgid "Command failed: '%s'" 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:67 msgid "Condensed output" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:313 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:392 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:64 msgid "Controls both system log and console output verbosity." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:393 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:431 msgid "Custom User File Includes" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:361 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:443 msgid "Custom user file '%s' not found or empty" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:331 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:363 msgid "DNS Policies" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:384 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:422 msgid "DSCP Tag" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:371 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:409 msgid "DSCP Tagging" 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:173 msgid "Default ICMP Interface" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:410 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:499 msgid "Default fw4 chain '%s' is missing" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:409 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:498 msgid "Default fw4 table '%s' is missing" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:545 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:654 msgid "Disable" msgstr "" -#: 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:134 -#: 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:110 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:122 msgid "Disabled" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:539 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:648 msgid "Disabling %s service" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:243 -msgid "Display these protocols in protocol column in Web UI." -msgstr "" - -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:266 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:330 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:120 -msgid "Dnsmasq ipset" -msgstr "" - -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:124 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:112 msgid "Dnsmasq nft set" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:85 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:81 msgid "Do not enforce policies when their gateway is down" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:592 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:701 msgid "Donate to the Project" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:526 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:635 msgid "Enable" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:135 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:234 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:265 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:345 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:406 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:123 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:251 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:377 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:444 msgid "Enabled" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:520 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:629 msgid "Enabling %s service" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:363 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:445 msgid "Error running custom user file '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:434 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:510 +msgid "Errors encountered, please check %s" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:543 msgid "Errors encountered, please check the %sREADME%s" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:210 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:207 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:401 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:508 +msgid "Failed to create temporary file with mktemp mask: '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:487 msgid "Failed to download '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:399 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:485 msgid "Failed to download '%s', HTTPS is not supported" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:394 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:480 msgid "Failed to install fw4 nft file '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:360 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:442 msgid "Failed to reload '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:390 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:476 msgid "Failed to resolve '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:359 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:441 msgid "Failed to set up '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:367 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:449 msgid "Failed to set up any gateway" 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:174 msgid "Force the ICMP protocol interface." msgstr "" @@ -219,87 +210,102 @@ msgstr "" msgid "Grant UCI and file access for luci-app-pbr" 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:120 msgid "IPv6 Support" 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:145 msgid "Ignored Interfaces" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:52 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:39 msgid "Inactive" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:54 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:41 msgid "Inactive (Disabled)" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:407 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:345 +msgid "Incompatible DHCP Option 6 for interface %s" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:493 msgid "Incompatible custom user file detected '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:263 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:327 msgid "" "Incompatible nft calls detected in user include file, disabling fw4 nft file " "support" 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:166 msgid "Insert" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:385 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:467 msgid "Insertion failed for IPv4 for policy '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:382 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:464 msgid "Insertion failed for both IPv4 and IPv6 for policy '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:238 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:302 msgid "Installed AdGuardHome (%s) doesn't support 'ipset_file' option." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:321 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:353 msgid "Interface" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:353 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:435 msgid "Interface '%s' has no assigned DNS" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:254 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:474 +msgid "Interface mark for '%s' exceeds the fwmask value" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:296 +msgid "" +"Internal version mismatch (package: %s, luci app: %s, luci rpcd: %s), you " +"may need to update packages or reboot the device, please check the " +"%sREADME%s." +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:318 msgid "Invalid OpenVPN config for %s interface" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:392 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:478 msgid "Invalid OpenVPN config for '%s' interface" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:271 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:352 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:257 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:384 msgid "Local addresses / devices" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:277 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:263 msgid "Local ports" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:376 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:458 msgid "Mismatched IP family between in policy '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:65 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:52 msgid "Mode" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:269 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:349 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:255 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:381 msgid "Name" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:252 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:238 msgid "" "Name, interface and at least one other field are required. Multiple local " "and remote addresses/devices/domains and ports can be space separated. " @@ -307,94 +313,118 @@ msgid "" "fields are left blank. For more information on options, check the %sREADME%s." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:333 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:365 msgid "" "Name, local address and remote DNS fields are required. Multiple local " "addresses/devices can be space separated. For more information on options, " "check the %sREADME%s." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:179 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:515 +msgid "Netifd setup: failed to delete fw4 netifd nft file '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:512 +msgid "Netifd setup: failed to install fw4 netifd nft file '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:521 +msgid "Netifd setup: invalid value of netifd_interface_default option '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:524 +msgid "Netifd setup: invalid value of netifd_interface_default6 option '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:348 +msgid "Netifd setup: option netifd_interface_local is missing, assuming '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:518 +msgid "Netifd setup: required option '%s' is missing" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:176 msgid "No Change" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:188 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:58 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:247 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:45 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:63 msgid "Output verbosity" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:411 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:449 msgid "Path" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:215 -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:600 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:276 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:709 msgid "Please %sdonate%s to support development of this project." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:101 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:97 msgid "Please check the %sREADME%s before changing this option." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:270 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:334 msgid "" "Please set 'dhcp.%%s.force=1' to speed up service start-up %s(more info)%s" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:248 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:312 msgid "Please unset 'chain' or set 'chain' to 'PREROUTING' for policy '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:251 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:315 msgid "Please unset 'chain' or set 'chain' to 'prerouting' for policy '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:245 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:309 msgid "Please unset 'proto' or set 'proto' to 'all' for policy '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:242 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:306 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:250 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:236 msgid "Policies" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:356 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:438 msgid "Policy '%s' has an unknown interface" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:351 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:433 msgid "Policy '%s' has no assigned DNS" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:350 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:432 msgid "Policy '%s' has no assigned interface" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:348 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:430 msgid "Policy '%s' has no source/destination parameters" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:396 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:482 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:10 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:11 msgid "Policy Based Routing" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:40 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:38 msgid "Policy Based Routing - Configuration" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:161 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:220 msgid "Policy Based Routing - Status" msgstr "" @@ -402,299 +432,354 @@ msgstr "" msgid "Policy Routing" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:295 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:281 msgid "Protocol" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:388 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:471 msgid "Received empty tid/mark or interface name when setting up routing" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:336 -msgid "Refer to https://docs.openwrt.melmac.net/pbr/#procd_wan_interface" +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:415 +msgid "Refer to %sREADME%s for details" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:359 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:391 msgid "Remote DNS" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:283 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:399 +msgid "Remote DNS Port" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:269 msgid "Remote addresses / domains" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:289 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:275 msgid "Remote ports" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:411 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:500 msgid "Required binary '%s' is missing" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:368 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:450 msgid "Resolver '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:324 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:403 msgid "Resolver set (%s) is not supported on this system" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:235 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:299 msgid "Resolver set (%s) is not supported on this system." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:318 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:397 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:321 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:400 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:488 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:597 msgid "Restart" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:482 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:591 msgid "Restarting %s service" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:165 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:162 msgid "Rule Create option" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:395 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:433 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:170 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:229 msgid "Running" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:80 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:354 +msgid "" +"Running in dynamic routing tables mode. Consider installing netifd " +"extensions or mwan4 for more efficient operation. See %sREADME%s." +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:76 msgid "See the %sREADME%s for details." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:166 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:163 msgid "Select Add for -A/add and Insert for -I/Insert." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:571 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:680 msgid "Service Control" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:419 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:533 msgid "Service Errors" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:197 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:208 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:194 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:205 msgid "Service FW Mask" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:202 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:261 msgid "Service Gateways" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:165 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:224 msgid "Service Status" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:283 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:363 msgid "Service Warnings" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:373 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:411 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:370 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:452 msgid "Skipping IPv6 policy '%s' as IPv6 support is disabled" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:469 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:578 msgid "Start" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:463 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:572 msgid "Starting %s service" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:193 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:190 +msgid "" +"Starting (Uplink Interface) 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:224 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" +"Starting (Uplink/WAN) ip rules priority used by the pbr service. High " +"starting priority is used to avoid conflict with other services, this can be " +"changed by user." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:63 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:50 msgid "Status" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:507 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:616 msgid "Stop" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:184 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:243 msgid "Stopped (Disabled)." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:182 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:241 msgid "Stopped." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:501 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:610 msgid "Stopping %s service" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:79 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:75 msgid "Strict enforcement" 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:82 msgid "Strictly enforce policies when their gateway is down" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:141 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:129 msgid "Supported Interfaces" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:242 -msgid "Supported Protocols" -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:66 msgid "Suppress/No output" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:362 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:444 msgid "Syntax error in custom user file '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:206 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:265 msgid "The %s indicates default gateway. See the %sREADME%s for details." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:333 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:412 msgid "" "The %s interface not found, you need to set the 'pbr.config." -"procd_wan_interface' option" +"uplink_interface' option" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:96 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:92 msgid "The %s is not supported on this system." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:330 -msgid "The %s service failed to discover WAN gateway" +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:409 +msgid "The %s service failed to discover uplink gateway" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:327 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:406 msgid "The %s service is currently disabled" msgstr "" -#: 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:88 msgid "The %s support is unknown." msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:257 -msgid "The WebUI application (luci-app-pbr) is outdated, please update it" +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:489 +msgid "The '%s' schema requires curl, but it's not detected on this system" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:403 -msgid "The file:// schema requires curl, but it's not detected on this system" +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:321 +msgid "The WebUI application (luci-app-pbr) is outdated, please update it" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:339 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:421 msgid "The ipset name '%s' is longer than allowed 31 characters" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:342 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:424 msgid "The nft set name '%s' is longer than allowed 255 characters" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:260 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:324 msgid "The principal package (pbr) is outdated, please update it" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:345 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:64 +msgid "Unable to retrieve %s status" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:427 msgid "Unexpected exit or service termination: '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:413 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:505 +msgid "Unknown Gateway for device '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:502 msgid "Unknown IPv6 Link type for device '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:431 -msgid "Unknown error!" +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:469 +msgid "Unknown entry in policy '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:540 +msgid "Unknown error" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:373 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:49 +msgid "Unknown message" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:455 msgid "Unknown packet mark for interface '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:379 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:461 msgid "Unknown protocol in policy '%s'" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:295 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:370 msgid "Unknown warning" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:365 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:496 +msgid "Unsafe nft command in custom user file '%s'" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:222 +msgid "Uplink IP Rules Priority" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:188 +msgid "Uplink Interface Table FW Mark" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:351 +msgid "Uplink/WAN interface is still down, going back to boot mode" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:527 +msgid "" +"Uplink/WAN interface is still down, increase value of " +"'procd_boot_trigger_delay' option" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:447 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:111 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:107 msgid "Use resolver set support for domains" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:72 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:68 msgid "Verbose output" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:64 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:51 msgid "Version" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:168 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:227 msgid "Version %s" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:191 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:214 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:211 msgid "WAN Table FW Mark" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:61 -msgid "Web UI Configuration" +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:343 +msgid "Warnings encountered, please check %s" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:304 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:373 +msgid "Warnings encountered, please check the %sREADME%s" +msgstr "" + +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/pbr/overview.js:282 msgid "all" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:174 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:44 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:233 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:31 msgid "fw4 nft file mode" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:172 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:42 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:231 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:29 msgid "iptables mode" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:176 -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:46 +#: applications/luci-app-pbr/htdocs/luci-static/resources/pbr/status.js:235 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:33 msgid "nft mode" msgstr "" -#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:48 +#: applications/luci-app-pbr/htdocs/luci-static/resources/view/status/include/72_pbr.js:35 msgid "unknown" msgstr "" diff --git a/root/usr/libexec/rpcd/luci.pbr b/root/usr/libexec/rpcd/luci.pbr deleted file mode 100755 index 477137f..0000000 --- a/root/usr/libexec/rpcd/luci.pbr +++ /dev/null @@ -1,343 +0,0 @@ -#!/bin/sh -# Copyright 2022 Stan Grishin (stangri@melmac.ca) -# shellcheck disable=SC2018,SC2019,SC2039,SC3043,SC3057,SC3060 - -# TechRef: https://openwrt.org/docs/techref/rpcd -# TESTS -# ubus -v list luci.pbr -# ubus -S call luci.pbr getInitList '{"name": "pbr" }' -# ubus -S call luci.pbr getInitStatus '{"name": "pbr" }' -# ubus -S call luci.pbr getPlatformSupport '{"name": "pbr" }' -# ubus -S call luci.pbr getGateways '{"name": "pbr" }' -# ubus -S call luci.pbr getInterfaces '{"name": "pbr" }' - -readonly luciCompat='11' -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 - -# compatibility with old luci app versions -is_running_iptables() { iptables -t mangle -L | grep -q PBR_PREROUTING >/dev/null 2>&1; } -is_running() { is_running_iptables || is_running_nft; } -check_ipset() { { [ -n "$ipset" ] && "$ipset" help hash:net; } >/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' -} - -get_init_list() { - local name - name="$(basename "$1")" - name="${name:-$packageName}" - json_init - 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 - json_close_object - json_dump - json_cleanup -} - -set_init_action() { - local name action="$2" cmd - name="$(basename "$1")" - name="${name:-$packageName}" - if [ ! -f "/etc/init.d/$packageName" ]; then - print_json_string 'error' 'Init script not found!' - return - fi - case $action in - enable) - cmd="/etc/init.d/${name} ${action}" - cmd="${cmd} && uci_set ${name} config enabled 1 && uci_commit $name" - ;; - disable) - 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}" - ;; - esac - if [ -n "$cmd" ] && eval "$cmd" 1>/dev/null 2>&1; then - print_json_bool 'result' '1' - else - print_json_bool 'result' '0' - fi -} - -get_init_status() { - local name - name="$(basename "$1")" - name="${name:-$packageName}" - local gateways warnings errors - gateways="$(ubus_get_status gateways | sed "s|\\\n|
|g;s|\(\\\033[^<]*\)|โœ“|g;")" - warnings="$(ubus_get_status warnings)" - errors="$(ubus_get_status errors)" - json_init - 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 "$packageName"; then - json_add_boolean 'running_iptables' '1' - else - json_add_boolean 'running_iptables' '0' - fi - 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' "$PKG_VERSION" - json_add_string 'gateways' "$gateways" - json_add_array 'errors' - if [ -n "$errors" ]; then - while read -r line; do - if str_contains "$line" ' '; then - error_id="${line% *}" - error_extra="${line#* }" - else - error_id="$line" - unset error_extra - fi - json_add_object - json_add_string 'id' "$error_id" - json_add_string 'extra' "$error_extra" - json_close_object - done </dev/null 2>&1", packageName, action)) == 0) { + let uci_ctx = cursor(); + uci_ctx.load(packageName); + uci_ctx.set(packageName, 'config', 'enabled', val); + uci_ctx.commit(packageName); + result = true; + } + break; + } + case 'start': case 'stop': case 'reload': case 'restart': + if (system(sprintf("/etc/init.d/%s %s >/dev/null 2>&1", packageName, action)) == 0) + result = true; + break; + } + return { result: result }; + } + }, +}; + +return { 'luci.pbr': methods };