diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboard.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboard.js
index be74520..6fd97cf 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboard.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboard.js
@@ -1,22 +1,22 @@
-'use strict';
-'require baseclass';
-'require form';
-'require ui';
-'require uci';
-'require fs';
-'require view.podkop.main as main';
+"use strict";
+"require baseclass";
+"require form";
+"require ui";
+"require uci";
+"require fs";
+"require view.podkop.main as main";
function createDashboardContent(section) {
- const o = section.option(form.DummyValue, '_mount_node');
- o.rawhtml = true;
- o.cfgvalue = () => {
- main.DashboardTab.initController();
- return main.DashboardTab.render();
- };
+ const o = section.option(form.DummyValue, "_mount_node");
+ o.rawhtml = true;
+ o.cfgvalue = () => {
+ main.DashboardTab.initController();
+ return main.DashboardTab.render();
+ };
}
const EntryPoint = {
- createDashboardContent,
+ createDashboardContent,
};
return baseclass.extend(EntryPoint);
diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnostic.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnostic.js
index f2cf0df..e6ac146 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnostic.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnostic.js
@@ -1,22 +1,22 @@
-'use strict';
-'require baseclass';
-'require form';
-'require ui';
-'require uci';
-'require fs';
-'require view.podkop.main as main';
+"use strict";
+"require baseclass";
+"require form";
+"require ui";
+"require uci";
+"require fs";
+"require view.podkop.main as main";
function createDiagnosticContent(section) {
- const o = section.option(form.DummyValue, '_mount_node');
- o.rawhtml = true;
- o.cfgvalue = () => {
- main.DiagnosticTab.initController();
- return main.DiagnosticTab.render();
- };
+ const o = section.option(form.DummyValue, "_mount_node");
+ o.rawhtml = true;
+ o.cfgvalue = () => {
+ main.DiagnosticTab.initController();
+ return main.DiagnosticTab.render();
+ };
}
const EntryPoint = {
- createDiagnosticContent,
+ createDiagnosticContent,
};
return baseclass.extend(EntryPoint);
diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
index c1656bb..f699e17 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
@@ -1,77 +1,98 @@
-'use strict';
-'require view';
-'require form';
-'require baseclass';
-'require network';
-'require view.podkop.main as main';
+"use strict";
+"require view";
+"require form";
+"require baseclass";
+"require network";
+"require view.podkop.main as main";
// Settings content
-'require view.podkop.settings as settings';
+"require view.podkop.settings as settings";
// Sections content
-'require view.podkop.section as section';
+"require view.podkop.section as section";
// Dashboard content
-'require view.podkop.dashboard as dashboard';
+"require view.podkop.dashboard as dashboard";
// Diagnostic content
-'require view.podkop.diagnostic as diagnostic';
-
+"require view.podkop.diagnostic as diagnostic";
const EntryPoint = {
async render() {
main.injectGlobalStyles();
- const podkopMap = new form.Map('podkop', _('Podkop Settings'), _('Configuration for Podkop service'));
+ const podkopMap = new form.Map(
+ "podkop",
+ _("Podkop Settings"),
+ _("Configuration for Podkop service"),
+ );
// Enable tab views
podkopMap.tabbed = true;
// Sections tab
- const sectionsSection = podkopMap.section(form.TypedSection, 'section', _('Sections'));
+ const sectionsSection = podkopMap.section(
+ form.TypedSection,
+ "section",
+ _("Sections"),
+ );
sectionsSection.anonymous = false;
sectionsSection.addremove = true;
- sectionsSection.template = 'cbi/simpleform';
+ sectionsSection.template = "cbi/simpleform";
// Render section content
section.createSectionContent(sectionsSection);
// Settings tab
- const settingsSection = podkopMap.section(form.TypedSection, 'settings', _('Settings'));
+ const settingsSection = podkopMap.section(
+ form.TypedSection,
+ "settings",
+ _("Settings"),
+ );
settingsSection.anonymous = true;
settingsSection.addremove = false;
// Make it named [ config settings 'settings' ]
- settingsSection.cfgsections = function () { return ['settings']; };
+ settingsSection.cfgsections = function () {
+ return ["settings"];
+ };
// Render settings content
settings.createSettingsContent(settingsSection);
-
// Diagnostic tab
- const diagnosticSection = podkopMap.section(form.TypedSection, 'diagnostic', _('Diagnostics'));
+ const diagnosticSection = podkopMap.section(
+ form.TypedSection,
+ "diagnostic",
+ _("Diagnostics"),
+ );
diagnosticSection.anonymous = true;
diagnosticSection.addremove = false;
- diagnosticSection.cfgsections = function () { return ['diagnostic']; };
+ diagnosticSection.cfgsections = function () {
+ return ["diagnostic"];
+ };
// Render diagnostic content
diagnostic.createDiagnosticContent(diagnosticSection);
// Dashboard tab
- const dashboardSection = podkopMap.section(form.TypedSection, 'dashboard', _('Dashboard'));
+ const dashboardSection = podkopMap.section(
+ form.TypedSection,
+ "dashboard",
+ _("Dashboard"),
+ );
dashboardSection.anonymous = true;
dashboardSection.addremove = false;
- dashboardSection.cfgsections = function () { return ['dashboard']; };
+ dashboardSection.cfgsections = function () {
+ return ["dashboard"];
+ };
// Render dashboard content
dashboard.createDashboardContent(dashboardSection);
-
-
// Inject core service
main.coreService();
return podkopMap.render();
- }
-}
-
+ },
+};
return view.extend(EntryPoint);
diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js
index 0443d0d..7903b96 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js
@@ -1,598 +1,619 @@
-'use strict';
-'require form';
-'require baseclass';
-'require ui';
-'require tools.widgets as widgets';
-'require view.podkop.main as main';
+"use strict";
+"require form";
+"require baseclass";
+"require ui";
+"require tools.widgets as widgets";
+"require view.podkop.main as main";
function createSectionContent(section) {
- let o = section.option(
- form.ListValue,
- 'connection_type',
- _('Connection Type'),
- _('Select between VPN and Proxy connection methods for traffic routing'),
- );
- o.value('proxy', 'Proxy');
- o.value('vpn', 'VPN');
- o.value('block', 'Block');
+ let o = section.option(
+ form.ListValue,
+ "connection_type",
+ _("Connection Type"),
+ _("Select between VPN and Proxy connection methods for traffic routing"),
+ );
+ o.value("proxy", "Proxy");
+ o.value("vpn", "VPN");
+ o.value("block", "Block");
+ o = section.option(
+ form.ListValue,
+ "proxy_config_type",
+ _("Configuration Type"),
+ _("Select how to configure the proxy"),
+ );
+ o.value("url", _("Connection URL"));
+ o.value("outbound", _("Outbound Config"));
+ o.value("urltest", _("URLTest"));
+ o.default = "url";
+ o.depends("connection_type", "proxy");
- o = section.option(
- form.ListValue,
- 'proxy_config_type',
- _('Configuration Type'),
- _('Select how to configure the proxy'),
- );
- o.value('url', _('Connection URL'));
- o.value('outbound', _('Outbound Config'));
- o.value('urltest', _('URLTest'));
- o.default = 'url';
- o.depends('connection_type', 'proxy');
+ o = section.option(
+ form.TextValue,
+ "proxy_string",
+ _("Proxy Configuration URL"),
+ "",
+ );
+ o.depends("proxy_config_type", "url");
+ o.rows = 5;
+ // Enable soft wrapping for multi-line proxy URLs (e.g., for URLTest proxy links)
+ o.wrap = "soft";
+ // Render as a textarea to allow multiple proxy URLs/configs
+ o.textarea = true;
+ o.rmempty = false;
+ o.sectionDescriptions = new Map();
+ o.placeholder =
+ "vless://uuid@server:port?type=tcp&security=tls#main\n// backup ss://method:pass@server:port\n// backup2 vless://uuid@server:port?type=grpc&security=reality#alt\n// backup3 trojan://04agAQapcl@127.0.0.1:33641?type=tcp&security=none#trojan-tcp-none \n// socks5://127.0.0.1:1080";
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- o = section.option(
- form.TextValue,
- 'proxy_string',
- _('Proxy Configuration URL'),
- '',
- );
- o.depends('proxy_config_type', 'url');
- o.rows = 5;
- // Enable soft wrapping for multi-line proxy URLs (e.g., for URLTest proxy links)
- o.wrap = 'soft';
- // Render as a textarea to allow multiple proxy URLs/configs
- o.textarea = true;
- o.rmempty = false;
- o.sectionDescriptions = new Map();
- o.placeholder = 'vless://uuid@server:port?type=tcp&security=tls#main\n// backup ss://method:pass@server:port\n// backup2 vless://uuid@server:port?type=grpc&security=reality#alt\n// backup3 trojan://04agAQapcl@127.0.0.1:33641?type=tcp&security=none#trojan-tcp-none \n// socks5://127.0.0.1:1080';
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ try {
+ const activeConfigs = main.splitProxyString(value);
- try {
- const activeConfigs = main.splitProxyString(value);
+ if (!activeConfigs.length) {
+ return _(
+ "No active configuration found. One configuration is required.",
+ );
+ }
- if (!activeConfigs.length) {
- return _('No active configuration found. One configuration is required.');
- }
+ if (activeConfigs.length > 1) {
+ return _(
+ "Multiply active configurations found. Please leave one configuration.",
+ );
+ }
- if (activeConfigs.length > 1) {
- return _('Multiply active configurations found. Please leave one configuration.');
- }
-
- const validation = main.validateProxyUrl(activeConfigs[0]);
-
- if (validation.valid) {
- return true;
- }
-
- return validation.message;
- } catch (e) {
- return `${_('Invalid URL format:')} ${e?.message}`;
- }
- };
-
- o = section.option(
- form.TextValue,
- 'outbound_json',
- _('Outbound Configuration'),
- _('Enter complete outbound configuration in JSON format'),
- );
- o.depends('proxy_config_type', 'outbound');
- o.rows = 10;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
-
- const validation = main.validateOutboundJson(value);
-
- if (validation.valid) {
- return true;
- }
-
- return validation.message;
- };
-
- o = section.option(
- form.DynamicList,
- 'urltest_proxy_links',
- _('URLTest Proxy Links'),
- );
- o.depends('proxy_config_type', 'urltest');
- o.placeholder = 'vless://, ss://, trojan://, socks4/5:// links';
- o.rmempty = false;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
-
- const validation = main.validateProxyUrl(value);
-
- if (validation.valid) {
- return true;
- }
-
- return validation.message;
- };
-
- o = section.option(
- form.Flag,
- 'enable_udp_over_tcp',
- _('Shadowsocks/Socks UDP over TCP'),
- _('Apply for socks/Shadowsocks 2022'),
- );
- o.default = '0';
- o.depends('connection_type', 'proxy');
- o.rmempty = false;
-
- o = section.option(
- widgets.DeviceSelect,
- 'interface',
- _('Network Interface'),
- _('Select network interface for VPN connection'),
- );
- o.depends('connection_type', 'vpn');
- o.noaliases = true;
- o.nobridges = false;
- o.noinactive = false;
- o.filter = function (section_id, value) {
- // Blocked interface names that should never be selectable
- const blockedInterfaces = [
- 'br-lan',
- 'eth0',
- 'eth1',
- 'wan',
- 'phy0-ap0',
- 'phy1-ap0',
- 'pppoe-wan',
- 'lan',
- ];
-
- // Reject immediately if the value matches any blocked interface
- if (blockedInterfaces.includes(value)) {
- return false;
- }
-
- // Try to find the device object with the given name
- const device = this.devices.find((dev) => dev.getName() === value);
-
- // If no device is found, allow the value
- if (!device) {
- return true;
- }
-
- // Get the device type (e.g., "wifi", "ethernet", etc.)
- const type = device.getType();
-
- // Reject wireless-related devices
- const isWireless =
- type === 'wifi' || type === 'wireless' || type.includes('wlan');
-
- return !isWireless;
- };
-
- o = section.option(
- form.Flag,
- 'domain_resolver_enabled',
- _('Domain Resolver'),
- _('Enable built-in DNS resolver for domains handled by this section'),
- );
- o.default = '0';
- o.rmempty = false;
- o.depends('connection_type', 'vpn');
-
- o = section.option(
- form.ListValue,
- 'domain_resolver_dns_type',
- _('DNS Protocol Type'),
- _('Select the DNS protocol type for the domain resolver'),
- );
- o.value('doh', _('DNS over HTTPS (DoH)'));
- o.value('dot', _('DNS over TLS (DoT)'));
- o.value('udp', _('UDP (Unprotected DNS)'));
- o.default = 'udp';
- o.rmempty = false;
- o.depends('domain_resolver_enabled', '1');
-
- o = section.option(
- form.Value,
- 'domain_resolver_dns_server',
- _('DNS Server'),
- _('Select or enter DNS server address'),
- );
- Object.entries(main.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
- o.value(key, _(label));
- });
- o.default = '8.8.8.8';
- o.rmempty = false;
- o.depends('domain_resolver_enabled', '1');
- o.validate = function (section_id, value) {
- const validation = main.validateDNS(value);
-
- if (validation.valid) {
- return true;
- }
-
- return validation.message;
- };
-
- o = section.option(
- form.DynamicList,
- 'community_lists',
- _('Community Lists'),
- _('Select a predefined list for routing') +
- ' github.com/itdoginfo/allow-domains',
- );
- o.placeholder = 'Service list';
- Object.entries(main.DOMAIN_LIST_OPTIONS).forEach(([key, label]) => {
- o.value(key, _(label));
- });
- o.rmempty = true;
- let lastValues = [];
- let isProcessing = false;
-
- o.onchange = function (ev, section_id, value) {
- if (isProcessing) return;
- isProcessing = true;
-
- try {
- const values = Array.isArray(value) ? value : [value];
- let newValues = [...values];
- let notifications = [];
-
- const selectedRegionalOptions = main.REGIONAL_OPTIONS.filter((opt) =>
- newValues.includes(opt),
- );
-
- if (selectedRegionalOptions.length > 1) {
- const lastSelected =
- selectedRegionalOptions[selectedRegionalOptions.length - 1];
- const removedRegions = selectedRegionalOptions.slice(0, -1);
- newValues = newValues.filter(
- (v) => v === lastSelected || !main.REGIONAL_OPTIONS.includes(v),
- );
- notifications.push(
- E('p', { class: 'alert-message warning' }, [
- E('strong', {}, _('Regional options cannot be used together')),
- E('br'),
- _(
- 'Warning: %s cannot be used together with %s. Previous selections have been removed.',
- ).format(removedRegions.join(', '), lastSelected),
- ]),
- );
- }
-
- if (newValues.includes('russia_inside')) {
- const removedServices = newValues.filter(
- (v) => !main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
- );
- if (removedServices.length > 0) {
- newValues = newValues.filter((v) =>
- main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
- );
- notifications.push(
- E('p', { class: 'alert-message warning' }, [
- E('strong', {}, _('Russia inside restrictions')),
- E('br'),
- _(
- 'Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.',
- ).format(
- main.ALLOWED_WITH_RUSSIA_INSIDE.map(
- (key) => main.DOMAIN_LIST_OPTIONS[key],
- )
- .filter((label) => label !== 'Russia inside')
- .join(', '),
- removedServices.join(', '),
- ),
- ]),
- );
- }
- }
-
- if (JSON.stringify(newValues.sort()) !== JSON.stringify(values.sort())) {
- this.getUIElement(section_id).setValue(newValues);
- }
-
- notifications.forEach((notification) =>
- ui.addNotification(null, notification),
- );
- lastValues = newValues;
- } catch (e) {
- console.error('Error in onchange handler:', e);
- } finally {
- isProcessing = false;
- }
- };
-
- o = section.option(
- form.ListValue,
- 'user_domain_list_type',
- _('User Domain List Type'),
- _('Select the list type for adding custom domains'),
- );
- o.value('disabled', _('Disabled'));
- o.value('dynamic', _('Dynamic List'));
- o.value('text', _('Text List'));
- o.default = 'disabled';
- o.rmempty = false;
-
- o = section.option(
- form.DynamicList,
- 'user_domains',
- _('User Domains'),
- _('Enter domain names without protocols, e.g. example.com or sub.example.com'),
- );
- o.placeholder = 'Domains list';
- o.depends('user_domain_list_type', 'dynamic');
- o.rmempty = false;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
-
- const validation = main.validateDomain(value, true);
-
- if (validation.valid) {
- return true;
- }
-
- return validation.message;
- };
-
- o = section.option(
- form.TextValue,
- 'user_domains_text',
- _('User Domains List'),
- _('Enter domain names separated by commas, spaces, or newlines. You can add comments using //'),
- );
- o.placeholder = 'example.com, sub.example.com\n// Social networks\ndomain.com test.com // personal domains';
- o.depends('user_domain_list_type', 'text');
- o.rows = 8;
- o.rmempty = false;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
-
- const domains = main.parseValueList(value);
-
- if (!domains.length) {
- return _('At least one valid domain must be specified. Comments-only content is not allowed.');
- }
-
- const {valid, results} = main.bulkValidate(domains, row => main.validateDomain(row, true));
-
- if (!valid) {
- const errors = results
- .filter((validation) => !validation.valid) // Leave only failed validations
- .map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors
-
- return [_('Validation errors:'), ...errors].join('\n');
- }
+ const validation = main.validateProxyUrl(activeConfigs[0]);
+ if (validation.valid) {
return true;
- };
+ }
- o = section.option(
- form.ListValue,
- 'user_subnet_list_type',
- _('User Subnet List Type'),
- _('Select the list type for adding custom subnets'),
+ return validation.message;
+ } catch (e) {
+ return `${_("Invalid URL format:")} ${e?.message}`;
+ }
+ };
+
+ o = section.option(
+ form.TextValue,
+ "outbound_json",
+ _("Outbound Configuration"),
+ _("Enter complete outbound configuration in JSON format"),
+ );
+ o.depends("proxy_config_type", "outbound");
+ o.rows = 10;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ const validation = main.validateOutboundJson(value);
+
+ if (validation.valid) {
+ return true;
+ }
+
+ return validation.message;
+ };
+
+ o = section.option(
+ form.DynamicList,
+ "urltest_proxy_links",
+ _("URLTest Proxy Links"),
+ );
+ o.depends("proxy_config_type", "urltest");
+ o.placeholder = "vless://, ss://, trojan://, socks4/5:// links";
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ const validation = main.validateProxyUrl(value);
+
+ if (validation.valid) {
+ return true;
+ }
+
+ return validation.message;
+ };
+
+ o = section.option(
+ form.Flag,
+ "enable_udp_over_tcp",
+ _("Shadowsocks/Socks UDP over TCP"),
+ _("Apply for socks/Shadowsocks 2022"),
+ );
+ o.default = "0";
+ o.depends("connection_type", "proxy");
+ o.rmempty = false;
+
+ o = section.option(
+ widgets.DeviceSelect,
+ "interface",
+ _("Network Interface"),
+ _("Select network interface for VPN connection"),
+ );
+ o.depends("connection_type", "vpn");
+ o.noaliases = true;
+ o.nobridges = false;
+ o.noinactive = false;
+ o.filter = function (section_id, value) {
+ // Blocked interface names that should never be selectable
+ const blockedInterfaces = [
+ "br-lan",
+ "eth0",
+ "eth1",
+ "wan",
+ "phy0-ap0",
+ "phy1-ap0",
+ "pppoe-wan",
+ "lan",
+ ];
+
+ // Reject immediately if the value matches any blocked interface
+ if (blockedInterfaces.includes(value)) {
+ return false;
+ }
+
+ // Try to find the device object with the given name
+ const device = this.devices.find((dev) => dev.getName() === value);
+
+ // If no device is found, allow the value
+ if (!device) {
+ return true;
+ }
+
+ // Get the device type (e.g., "wifi", "ethernet", etc.)
+ const type = device.getType();
+
+ // Reject wireless-related devices
+ const isWireless =
+ type === "wifi" || type === "wireless" || type.includes("wlan");
+
+ return !isWireless;
+ };
+
+ o = section.option(
+ form.Flag,
+ "domain_resolver_enabled",
+ _("Domain Resolver"),
+ _("Enable built-in DNS resolver for domains handled by this section"),
+ );
+ o.default = "0";
+ o.rmempty = false;
+ o.depends("connection_type", "vpn");
+
+ o = section.option(
+ form.ListValue,
+ "domain_resolver_dns_type",
+ _("DNS Protocol Type"),
+ _("Select the DNS protocol type for the domain resolver"),
+ );
+ o.value("doh", _("DNS over HTTPS (DoH)"));
+ o.value("dot", _("DNS over TLS (DoT)"));
+ o.value("udp", _("UDP (Unprotected DNS)"));
+ o.default = "udp";
+ o.rmempty = false;
+ o.depends("domain_resolver_enabled", "1");
+
+ o = section.option(
+ form.Value,
+ "domain_resolver_dns_server",
+ _("DNS Server"),
+ _("Select or enter DNS server address"),
+ );
+ Object.entries(main.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
+ o.value(key, _(label));
+ });
+ o.default = "8.8.8.8";
+ o.rmempty = false;
+ o.depends("domain_resolver_enabled", "1");
+ o.validate = function (section_id, value) {
+ const validation = main.validateDNS(value);
+
+ if (validation.valid) {
+ return true;
+ }
+
+ return validation.message;
+ };
+
+ o = section.option(
+ form.DynamicList,
+ "community_lists",
+ _("Community Lists"),
+ _("Select a predefined list for routing") +
+ ' github.com/itdoginfo/allow-domains',
+ );
+ o.placeholder = "Service list";
+ Object.entries(main.DOMAIN_LIST_OPTIONS).forEach(([key, label]) => {
+ o.value(key, _(label));
+ });
+ o.rmempty = true;
+ let lastValues = [];
+ let isProcessing = false;
+
+ o.onchange = function (ev, section_id, value) {
+ if (isProcessing) return;
+ isProcessing = true;
+
+ try {
+ const values = Array.isArray(value) ? value : [value];
+ let newValues = [...values];
+ let notifications = [];
+
+ const selectedRegionalOptions = main.REGIONAL_OPTIONS.filter((opt) =>
+ newValues.includes(opt),
+ );
+
+ if (selectedRegionalOptions.length > 1) {
+ const lastSelected =
+ selectedRegionalOptions[selectedRegionalOptions.length - 1];
+ const removedRegions = selectedRegionalOptions.slice(0, -1);
+ newValues = newValues.filter(
+ (v) => v === lastSelected || !main.REGIONAL_OPTIONS.includes(v),
+ );
+ notifications.push(
+ E("p", { class: "alert-message warning" }, [
+ E("strong", {}, _("Regional options cannot be used together")),
+ E("br"),
+ _(
+ "Warning: %s cannot be used together with %s. Previous selections have been removed.",
+ ).format(removedRegions.join(", "), lastSelected),
+ ]),
+ );
+ }
+
+ if (newValues.includes("russia_inside")) {
+ const removedServices = newValues.filter(
+ (v) => !main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
+ );
+ if (removedServices.length > 0) {
+ newValues = newValues.filter((v) =>
+ main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
+ );
+ notifications.push(
+ E("p", { class: "alert-message warning" }, [
+ E("strong", {}, _("Russia inside restrictions")),
+ E("br"),
+ _(
+ "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.",
+ ).format(
+ main.ALLOWED_WITH_RUSSIA_INSIDE.map(
+ (key) => main.DOMAIN_LIST_OPTIONS[key],
+ )
+ .filter((label) => label !== "Russia inside")
+ .join(", "),
+ removedServices.join(", "),
+ ),
+ ]),
+ );
+ }
+ }
+
+ if (JSON.stringify(newValues.sort()) !== JSON.stringify(values.sort())) {
+ this.getUIElement(section_id).setValue(newValues);
+ }
+
+ notifications.forEach((notification) =>
+ ui.addNotification(null, notification),
+ );
+ lastValues = newValues;
+ } catch (e) {
+ console.error("Error in onchange handler:", e);
+ } finally {
+ isProcessing = false;
+ }
+ };
+
+ o = section.option(
+ form.ListValue,
+ "user_domain_list_type",
+ _("User Domain List Type"),
+ _("Select the list type for adding custom domains"),
+ );
+ o.value("disabled", _("Disabled"));
+ o.value("dynamic", _("Dynamic List"));
+ o.value("text", _("Text List"));
+ o.default = "disabled";
+ o.rmempty = false;
+
+ o = section.option(
+ form.DynamicList,
+ "user_domains",
+ _("User Domains"),
+ _(
+ "Enter domain names without protocols, e.g. example.com or sub.example.com",
+ ),
+ );
+ o.placeholder = "Domains list";
+ o.depends("user_domain_list_type", "dynamic");
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ const validation = main.validateDomain(value, true);
+
+ if (validation.valid) {
+ return true;
+ }
+
+ return validation.message;
+ };
+
+ o = section.option(
+ form.TextValue,
+ "user_domains_text",
+ _("User Domains List"),
+ _(
+ "Enter domain names separated by commas, spaces, or newlines. You can add comments using //",
+ ),
+ );
+ o.placeholder =
+ "example.com, sub.example.com\n// Social networks\ndomain.com test.com // personal domains";
+ o.depends("user_domain_list_type", "text");
+ o.rows = 8;
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
+
+ const domains = main.parseValueList(value);
+
+ if (!domains.length) {
+ return _(
+ "At least one valid domain must be specified. Comments-only content is not allowed.",
+ );
+ }
+
+ const { valid, results } = main.bulkValidate(domains, (row) =>
+ main.validateDomain(row, true),
);
- o.value('disabled', _('Disabled'));
- o.value('dynamic', _('Dynamic List'));
- o.value('text', _('Text List (comma/space/newline separated)'));
- o.default = 'disabled';
- o.rmempty = false;
- o = section.option(
- form.DynamicList,
- 'user_subnets',
- _('User Subnets'),
- _('Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses'),
- );
- o.placeholder = 'IP or subnet';
- o.depends('user_subnet_list_type', 'dynamic');
- o.rmempty = false;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ if (!valid) {
+ const errors = results
+ .filter((validation) => !validation.valid) // Leave only failed validations
+ .map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors
- const validation = main.validateSubnet(value);
+ return [_("Validation errors:"), ...errors].join("\n");
+ }
- if (validation.valid) {
- return true;
- }
+ return true;
+ };
- return validation.message;
- };
+ o = section.option(
+ form.ListValue,
+ "user_subnet_list_type",
+ _("User Subnet List Type"),
+ _("Select the list type for adding custom subnets"),
+ );
+ o.value("disabled", _("Disabled"));
+ o.value("dynamic", _("Dynamic List"));
+ o.value("text", _("Text List (comma/space/newline separated)"));
+ o.default = "disabled";
+ o.rmempty = false;
- o = section.option(
- form.TextValue,
- 'user_subnets_text',
- _('User Subnets List'),
- _(
- 'Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. ' +
- 'You can add comments using //'
- ),
- );
- o.placeholder =
- '103.21.244.0/22\n// Google DNS\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 // Cloudflare and Quad9';
- o.depends('user_subnet_list_type', 'text');
- o.rows = 10;
- o.rmempty = false;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ o = section.option(
+ form.DynamicList,
+ "user_subnets",
+ _("User Subnets"),
+ _(
+ "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses",
+ ),
+ );
+ o.placeholder = "IP or subnet";
+ o.depends("user_subnet_list_type", "dynamic");
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- const subnets = main.parseValueList(value);
+ const validation = main.validateSubnet(value);
- if (!subnets.length) {
- return _('At least one valid subnet or IP must be specified. Comments-only content is not allowed.');
- }
+ if (validation.valid) {
+ return true;
+ }
- const {valid, results} = main.bulkValidate(subnets, main.validateSubnet);
+ return validation.message;
+ };
- if (!valid) {
- const errors = results
- .filter((validation) => !validation.valid) // Leave only failed validations
- .map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors
+ o = section.option(
+ form.TextValue,
+ "user_subnets_text",
+ _("User Subnets List"),
+ _(
+ "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. " +
+ "You can add comments using //",
+ ),
+ );
+ o.placeholder =
+ "103.21.244.0/22\n// Google DNS\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 // Cloudflare and Quad9";
+ o.depends("user_subnet_list_type", "text");
+ o.rows = 10;
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- return [_('Validation errors:'), ...errors].join('\n');
- }
+ const subnets = main.parseValueList(value);
- return true;
- };
+ if (!subnets.length) {
+ return _(
+ "At least one valid subnet or IP must be specified. Comments-only content is not allowed.",
+ );
+ }
- o = section.option(
- form.DynamicList,
- 'local_domain_lists',
- _('Local Domain Lists'),
- _('Specify the path to the list file located on the router filesystem'),
- );
- o.placeholder = '/path/file.lst';
- o.rmempty = true;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ const { valid, results } = main.bulkValidate(subnets, main.validateSubnet);
- const validation = main.validatePath(value);
+ if (!valid) {
+ const errors = results
+ .filter((validation) => !validation.valid) // Leave only failed validations
+ .map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors
- if (validation.valid) {
- return true;
- }
+ return [_("Validation errors:"), ...errors].join("\n");
+ }
- return validation.message;
- };
+ return true;
+ };
- o = section.option(
- form.DynamicList,
- 'local_subnet_lists',
- _('Local Subnet Lists'),
- _('Specify the path to the list file located on the router filesystem'),
- );
- o.placeholder = '/path/file.lst';
- o.rmempty = true;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ o = section.option(
+ form.DynamicList,
+ "local_domain_lists",
+ _("Local Domain Lists"),
+ _("Specify the path to the list file located on the router filesystem"),
+ );
+ o.placeholder = "/path/file.lst";
+ o.rmempty = true;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- const validation = main.validatePath(value);
+ const validation = main.validatePath(value);
- if (validation.valid) {
- return true;
- }
+ if (validation.valid) {
+ return true;
+ }
- return validation.message;
- };
+ return validation.message;
+ };
- o = section.option(
- form.DynamicList,
- 'remote_domain_lists',
- _('Remote Domain Lists'),
- _('Specify remote URLs to download and use domain lists'),
- );
- o.placeholder = 'https://example.com/domains.srs';
- o.rmempty = true;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ o = section.option(
+ form.DynamicList,
+ "local_subnet_lists",
+ _("Local Subnet Lists"),
+ _("Specify the path to the list file located on the router filesystem"),
+ );
+ o.placeholder = "/path/file.lst";
+ o.rmempty = true;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- const validation = main.validateUrl(value);
+ const validation = main.validatePath(value);
- if (validation.valid) {
- return true;
- }
+ if (validation.valid) {
+ return true;
+ }
- return validation.message;
- };
+ return validation.message;
+ };
- o = section.option(
- form.DynamicList,
- 'remote_subnet_lists',
- _('Remote Subnet Lists'),
- _('Specify remote URLs to download and use subnet lists'),
- );
- o.placeholder = 'https://example.com/subnets.srs';
- o.rmempty = true;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ o = section.option(
+ form.DynamicList,
+ "remote_domain_lists",
+ _("Remote Domain Lists"),
+ _("Specify remote URLs to download and use domain lists"),
+ );
+ o.placeholder = "https://example.com/domains.srs";
+ o.rmempty = true;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- const validation = main.validateUrl(value);
+ const validation = main.validateUrl(value);
- if (validation.valid) {
- return true;
- }
+ if (validation.valid) {
+ return true;
+ }
- return validation.message;
- };
+ return validation.message;
+ };
- o = section.option(
- form.DynamicList,
- 'fully_routed_ips',
- _('Fully Routed IPs'),
- _('Specify local IP addresses or subnets whose traffic will always be routed through the configured route'),
- );
- o.placeholder = '192.168.1.2 or 192.168.1.0/24';
- o.rmempty = true;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ o = section.option(
+ form.DynamicList,
+ "remote_subnet_lists",
+ _("Remote Subnet Lists"),
+ _("Specify remote URLs to download and use subnet lists"),
+ );
+ o.placeholder = "https://example.com/subnets.srs";
+ o.rmempty = true;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- const validation = main.validateSubnet(value);
+ const validation = main.validateUrl(value);
- if (validation.valid) {
- return true;
- }
+ if (validation.valid) {
+ return true;
+ }
- return validation.message;
- };
+ return validation.message;
+ };
- o = section.option(
- form.Flag,
- 'mixed_proxy_enabled',
- _('Enable Mixed Proxy'),
- _('Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies'),
- );
- o.default = '0';
- o.rmempty = false;
+ o = section.option(
+ form.DynamicList,
+ "fully_routed_ips",
+ _("Fully Routed IPs"),
+ _(
+ "Specify local IP addresses or subnets whose traffic will always be routed through the configured route",
+ ),
+ );
+ o.placeholder = "192.168.1.2 or 192.168.1.0/24";
+ o.rmempty = true;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- o = section.option(
- form.Value,
- 'mixed_proxy_port',
- _('Mixed Proxy Port'),
- _(
- 'Specify the port number on which the mixed proxy will run for this section. ' +
- 'Make sure the selected port is not used by another service'
- ),
- );
- o.rmempty = false;
- o.depends('mixed_proxy_enabled', '1');
+ const validation = main.validateSubnet(value);
+
+ if (validation.valid) {
+ return true;
+ }
+
+ return validation.message;
+ };
+
+ o = section.option(
+ form.Flag,
+ "mixed_proxy_enabled",
+ _("Enable Mixed Proxy"),
+ _(
+ "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies",
+ ),
+ );
+ o.default = "0";
+ o.rmempty = false;
+
+ o = section.option(
+ form.Value,
+ "mixed_proxy_port",
+ _("Mixed Proxy Port"),
+ _(
+ "Specify the port number on which the mixed proxy will run for this section. " +
+ "Make sure the selected port is not used by another service",
+ ),
+ );
+ o.rmempty = false;
+ o.depends("mixed_proxy_enabled", "1");
}
const EntryPoint = {
- createSectionContent,
-}
+ createSectionContent,
+};
return baseclass.extend(EntryPoint);
diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js
index 59b37b0..b36e70a 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js
@@ -1,394 +1,394 @@
-'use strict';
-'require form';
-'require uci';
-'require baseclass';
-'require tools.widgets as widgets';
-'require view.podkop.main as main';
+"use strict";
+"require form";
+"require uci";
+"require baseclass";
+"require tools.widgets as widgets";
+"require view.podkop.main as main";
function createSettingsContent(section) {
- let o = section.option(
- form.ListValue,
- 'dns_type',
- _('DNS Protocol Type'),
- _('Select DNS protocol to use'),
- );
- o.value('doh', _('DNS over HTTPS (DoH)'));
- o.value('dot', _('DNS over TLS (DoT)'));
- o.value('udp', _('UDP (Unprotected DNS)'));
- o.default = 'udp';
- o.rmempty = false;
+ let o = section.option(
+ form.ListValue,
+ "dns_type",
+ _("DNS Protocol Type"),
+ _("Select DNS protocol to use"),
+ );
+ o.value("doh", _("DNS over HTTPS (DoH)"));
+ o.value("dot", _("DNS over TLS (DoT)"));
+ o.value("udp", _("UDP (Unprotected DNS)"));
+ o.default = "udp";
+ o.rmempty = false;
- o = section.option(
- form.Value,
- 'dns_server',
- _('DNS Server'),
- _('Select or enter DNS server address'),
- );
- Object.entries(main.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
- o.value(key, _(label));
- });
- o.default = '8.8.8.8';
- o.rmempty = false;
- o.validate = function (section_id, value) {
- const validation = main.validateDNS(value);
+ o = section.option(
+ form.Value,
+ "dns_server",
+ _("DNS Server"),
+ _("Select or enter DNS server address"),
+ );
+ Object.entries(main.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
+ o.value(key, _(label));
+ });
+ o.default = "8.8.8.8";
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ const validation = main.validateDNS(value);
- if (validation.valid) {
- return true;
- }
+ if (validation.valid) {
+ return true;
+ }
- return validation.message;
- };
+ return validation.message;
+ };
- o = section.option(
- form.Value,
- 'bootstrap_dns_server',
- _('Bootstrap DNS server'),
- _(
- 'The DNS server used to look up the IP address of an upstream DNS server',
- ),
- );
- Object.entries(main.BOOTSTRAP_DNS_SERVER_OPTIONS).forEach(([key, label]) => {
- o.value(key, _(label));
- });
- o.default = '77.88.8.8';
- o.rmempty = false;
- o.validate = function (section_id, value) {
- const validation = main.validateDNS(value);
+ o = section.option(
+ form.Value,
+ "bootstrap_dns_server",
+ _("Bootstrap DNS server"),
+ _(
+ "The DNS server used to look up the IP address of an upstream DNS server",
+ ),
+ );
+ Object.entries(main.BOOTSTRAP_DNS_SERVER_OPTIONS).forEach(([key, label]) => {
+ o.value(key, _(label));
+ });
+ o.default = "77.88.8.8";
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ const validation = main.validateDNS(value);
- if (validation.valid) {
- return true;
- }
+ if (validation.valid) {
+ return true;
+ }
- return validation.message;
- };
+ return validation.message;
+ };
- o = section.option(
- form.Value,
- 'dns_rewrite_ttl',
- _('DNS Rewrite TTL'),
- _('Time in seconds for DNS record caching (default: 60)'),
- );
- o.default = '60';
- o.rmempty = false;
- o.validate = function (section_id, value) {
- if (!value) {
- return _('TTL value cannot be empty');
- }
+ o = section.option(
+ form.Value,
+ "dns_rewrite_ttl",
+ _("DNS Rewrite TTL"),
+ _("Time in seconds for DNS record caching (default: 60)"),
+ );
+ o.default = "60";
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value) {
+ return _("TTL value cannot be empty");
+ }
- const ttl = parseInt(value);
- if (isNaN(ttl) || ttl < 0) {
- return _('TTL must be a positive number');
- }
+ const ttl = parseInt(value);
+ if (isNaN(ttl) || ttl < 0) {
+ return _("TTL must be a positive number");
+ }
- return true;
- };
+ return true;
+ };
- o = section.option(
- form.Flag,
- 'enable_output_network_interface',
- _('Enable Output Network Interface'),
- _('You can select Output Network Interface, by default autodetect'),
- );
- o.default = '0';
- o.rmempty = false;
+ o = section.option(
+ form.Flag,
+ "enable_output_network_interface",
+ _("Enable Output Network Interface"),
+ _("You can select Output Network Interface, by default autodetect"),
+ );
+ o.default = "0";
+ o.rmempty = false;
- o = section.option(
- widgets.DeviceSelect,
- 'output_network_interface',
- _('Output Network Interface'),
- _('Select the network interface to which the traffic will originate'),
- );
- o.noaliases = true;
- o.multiple = false;
- o.depends('enable_output_network_interface', '1');
- o.filter = function (section_id, value) {
- // Blocked interface names that should never be selectable
- const blockedInterfaces = ['br-lan'];
+ o = section.option(
+ widgets.DeviceSelect,
+ "output_network_interface",
+ _("Output Network Interface"),
+ _("Select the network interface to which the traffic will originate"),
+ );
+ o.noaliases = true;
+ o.multiple = false;
+ o.depends("enable_output_network_interface", "1");
+ o.filter = function (section_id, value) {
+ // Blocked interface names that should never be selectable
+ const blockedInterfaces = ["br-lan"];
- // Reject immediately if the value matches any blocked interface
- if (blockedInterfaces.includes(value)) {
- return false;
- }
+ // Reject immediately if the value matches any blocked interface
+ if (blockedInterfaces.includes(value)) {
+ return false;
+ }
- // Reject tun*, wg*, vpn*, awg*, oc*
- if (
- value.startsWith('tun') ||
- value.startsWith('wg') ||
- value.startsWith('vpn') ||
- value.startsWith('awg') ||
- value.startsWith('oc')
- ) {
- return false;
- }
+ // Reject tun*, wg*, vpn*, awg*, oc*
+ if (
+ value.startsWith("tun") ||
+ value.startsWith("wg") ||
+ value.startsWith("vpn") ||
+ value.startsWith("awg") ||
+ value.startsWith("oc")
+ ) {
+ return false;
+ }
- // Try to find the device object with the given name
- const device = this.devices.find((dev) => dev.getName() === value);
+ // Try to find the device object with the given name
+ const device = this.devices.find((dev) => dev.getName() === value);
- // If no device is found, allow the value
- if (!device) {
- return true;
- }
+ // If no device is found, allow the value
+ if (!device) {
+ return true;
+ }
- // Get the device type (e.g., "wifi", "ethernet", etc.)
- const type = device.getType();
+ // Get the device type (e.g., "wifi", "ethernet", etc.)
+ const type = device.getType();
- // Reject wireless-related devices
- const isWireless =
- type === 'wifi' || type === 'wireless' || type.includes('wlan');
+ // Reject wireless-related devices
+ const isWireless =
+ type === "wifi" || type === "wireless" || type.includes("wlan");
- return !isWireless;
- };
+ return !isWireless;
+ };
- o = section.option(
- widgets.DeviceSelect,
- 'source_network_interfaces',
- _('Source Network Interface'),
- _('Select the network interface from which the traffic will originate'),
- );
- o.default = 'br-lan';
- o.noaliases = true;
- o.nobridges = false;
- o.noinactive = false;
- o.multiple = true;
- o.filter = function (section_id, value) {
- // Block specific interface names from being selectable
- const blocked = ['wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan'];
- if (blocked.includes(value)) {
- return false;
- }
+ o = section.option(
+ widgets.DeviceSelect,
+ "source_network_interfaces",
+ _("Source Network Interface"),
+ _("Select the network interface from which the traffic will originate"),
+ );
+ o.default = "br-lan";
+ o.noaliases = true;
+ o.nobridges = false;
+ o.noinactive = false;
+ o.multiple = true;
+ o.filter = function (section_id, value) {
+ // Block specific interface names from being selectable
+ const blocked = ["wan", "phy0-ap0", "phy1-ap0", "pppoe-wan"];
+ if (blocked.includes(value)) {
+ return false;
+ }
- // Try to find the device object by its name
- const device = this.devices.find((dev) => dev.getName() === value);
+ // Try to find the device object by its name
+ const device = this.devices.find((dev) => dev.getName() === value);
- // If no device is found, allow the value
- if (!device) {
- return true;
- }
+ // If no device is found, allow the value
+ if (!device) {
+ return true;
+ }
- // Check the type of the device
- const type = device.getType();
+ // Check the type of the device
+ const type = device.getType();
- // Consider any Wi-Fi / wireless / wlan device as invalid
- const isWireless =
- type === 'wifi' || type === 'wireless' || type.includes('wlan');
+ // Consider any Wi-Fi / wireless / wlan device as invalid
+ const isWireless =
+ type === "wifi" || type === "wireless" || type.includes("wlan");
- // Allow only non-wireless devices
- return !isWireless;
- };
+ // Allow only non-wireless devices
+ return !isWireless;
+ };
- o = section.option(
- form.Flag,
- 'enable_badwan_interface_monitoring',
- _('Interface Monitoring'),
- _('Interface monitoring for Bad WAN'),
- );
- o.default = '0';
- o.rmempty = false;
+ o = section.option(
+ form.Flag,
+ "enable_badwan_interface_monitoring",
+ _("Interface Monitoring"),
+ _("Interface monitoring for Bad WAN"),
+ );
+ o.default = "0";
+ o.rmempty = false;
- o = section.option(
- widgets.NetworkSelect,
- 'badwan_monitored_interfaces',
- _('Monitored Interfaces'),
- _('Select the WAN interfaces to be monitored'),
- );
- o.depends('enable_badwan_interface_monitoring', '1');
- o.multiple = true;
- o.filter = function (section_id, value) {
- // Reject if the value is in the blocked list ['lan', 'loopback']
- if (['lan', 'loopback'].includes(value)) {
- return false;
- }
+ o = section.option(
+ widgets.NetworkSelect,
+ "badwan_monitored_interfaces",
+ _("Monitored Interfaces"),
+ _("Select the WAN interfaces to be monitored"),
+ );
+ o.depends("enable_badwan_interface_monitoring", "1");
+ o.multiple = true;
+ o.filter = function (section_id, value) {
+ // Reject if the value is in the blocked list ['lan', 'loopback']
+ if (["lan", "loopback"].includes(value)) {
+ return false;
+ }
- // Reject if the value starts with '@' (means it's an alias/reference)
- if (value.startsWith('@')) {
- return false;
- }
+ // Reject if the value starts with '@' (means it's an alias/reference)
+ if (value.startsWith("@")) {
+ return false;
+ }
- // Otherwise allow it
- return true;
- };
+ // Otherwise allow it
+ return true;
+ };
- o = section.option(
- form.Value,
- 'badwan_reload_delay',
- _('Interface Monitoring Delay'),
- _('Delay in milliseconds before reloading podkop after interface UP'),
- );
- o.depends('enable_badwan_interface_monitoring', '1');
- o.default = '2000';
- o.rmempty = false;
- o.validate = function (section_id, value) {
- if (!value) {
- return _('Delay value cannot be empty');
- }
- return true;
- };
+ o = section.option(
+ form.Value,
+ "badwan_reload_delay",
+ _("Interface Monitoring Delay"),
+ _("Delay in milliseconds before reloading podkop after interface UP"),
+ );
+ o.depends("enable_badwan_interface_monitoring", "1");
+ o.default = "2000";
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value) {
+ return _("Delay value cannot be empty");
+ }
+ return true;
+ };
- o = section.option(
- form.Flag,
- 'enable_yacd',
- _('Enable YACD'),
- `${main.getClashUIUrl()}`,
- );
- o.default = '0';
- o.rmempty = false;
+ o = section.option(
+ form.Flag,
+ "enable_yacd",
+ _("Enable YACD"),
+ `${main.getClashUIUrl()}`,
+ );
+ o.default = "0";
+ o.rmempty = false;
- o = section.option(
- form.Flag,
- 'disable_quic',
- _('Disable QUIC'),
- _(
- 'Disable the QUIC protocol to improve compatibility or fix issues with video streaming',
- ),
- );
- o.default = '0';
- o.rmempty = false;
+ o = section.option(
+ form.Flag,
+ "disable_quic",
+ _("Disable QUIC"),
+ _(
+ "Disable the QUIC protocol to improve compatibility or fix issues with video streaming",
+ ),
+ );
+ o.default = "0";
+ o.rmempty = false;
- o = section.option(
- form.ListValue,
- 'update_interval',
- _('List Update Frequency'),
- _('Select how often the domain or subnet lists are updated automatically'),
- );
- Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => {
- o.value(key, _(label));
- });
- o.default = '1d';
- o.rmempty = false;
+ o = section.option(
+ form.ListValue,
+ "update_interval",
+ _("List Update Frequency"),
+ _("Select how often the domain or subnet lists are updated automatically"),
+ );
+ Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => {
+ o.value(key, _(label));
+ });
+ o.default = "1d";
+ o.rmempty = false;
- o = section.option(
- form.Flag,
- 'download_lists_via_proxy',
- _('Download Lists via Proxy/VPN'),
- _('Downloading all lists via main Proxy/VPN'),
- );
- o.default = '0';
- o.rmempty = false;
+ o = section.option(
+ form.Flag,
+ "download_lists_via_proxy",
+ _("Download Lists via Proxy/VPN"),
+ _("Downloading all lists via main Proxy/VPN"),
+ );
+ o.default = "0";
+ o.rmempty = false;
- o = section.option(
- form.ListValue,
- 'download_lists_via_proxy_section',
- _('Download Lists via specific proxy section'),
- _('Downloading all lists via specific Proxy/VPN'),
- );
+ o = section.option(
+ form.ListValue,
+ "download_lists_via_proxy_section",
+ _("Download Lists via specific proxy section"),
+ _("Downloading all lists via specific Proxy/VPN"),
+ );
- o.rmempty = false;
- o.depends('download_lists_via_proxy', '1');
- o.cfgvalue = function (section_id) {
- return uci.get('podkop', section_id, 'download_lists_via_proxy_section');
- };
- o.load = function () {
- const sections = this.map?.data?.state?.values?.podkop ?? {};
+ o.rmempty = false;
+ o.depends("download_lists_via_proxy", "1");
+ o.cfgvalue = function (section_id) {
+ return uci.get("podkop", section_id, "download_lists_via_proxy_section");
+ };
+ o.load = function () {
+ const sections = this.map?.data?.state?.values?.podkop ?? {};
- this.keylist = [];
- this.vallist = [];
+ this.keylist = [];
+ this.vallist = [];
- for (const secName in sections) {
- const sec = sections[secName];
- if (sec['.type'] === 'section') {
- this.keylist.push(secName);
- this.vallist.push(secName);
- }
- }
+ for (const secName in sections) {
+ const sec = sections[secName];
+ if (sec[".type"] === "section") {
+ this.keylist.push(secName);
+ this.vallist.push(secName);
+ }
+ }
- return Promise.resolve();
- };
+ return Promise.resolve();
+ };
- o = section.option(
- form.Flag,
- 'dont_touch_dhcp',
- _('Dont Touch My DHCP!'),
- _('Podkop will not modify your DHCP configuration'),
- );
- o.default = '0';
- o.rmempty = false;
+ o = section.option(
+ form.Flag,
+ "dont_touch_dhcp",
+ _("Dont Touch My DHCP!"),
+ _("Podkop will not modify your DHCP configuration"),
+ );
+ o.default = "0";
+ o.rmempty = false;
- o = section.option(
- form.ListValue,
- 'config_path',
- _('Config File Path'),
- _(
- 'Select path for sing-box config file. Change this ONLY if you know what you are doing',
- ),
- );
- o.value('/etc/sing-box/config.json', 'Flash (/etc/sing-box/config.json)');
- o.value('/tmp/sing-box/config.json', 'RAM (/tmp/sing-box/config.json)');
- o.default = '/etc/sing-box/config.json';
- o.rmempty = false;
+ o = section.option(
+ form.ListValue,
+ "config_path",
+ _("Config File Path"),
+ _(
+ "Select path for sing-box config file. Change this ONLY if you know what you are doing",
+ ),
+ );
+ o.value("/etc/sing-box/config.json", "Flash (/etc/sing-box/config.json)");
+ o.value("/tmp/sing-box/config.json", "RAM (/tmp/sing-box/config.json)");
+ o.default = "/etc/sing-box/config.json";
+ o.rmempty = false;
- o = section.option(
- form.Value,
- 'cache_path',
- _('Cache File Path'),
- _(
- 'Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing',
- ),
- );
- o.value('/tmp/sing-box/cache.db', 'RAM (/tmp/sing-box/cache.db)');
- o.value(
- '/usr/share/sing-box/cache.db',
- 'Flash (/usr/share/sing-box/cache.db)',
- );
- o.default = '/tmp/sing-box/cache.db';
- o.rmempty = false;
- o.validate = function (section_id, value) {
- if (!value) {
- return _('Cache file path cannot be empty');
- }
+ o = section.option(
+ form.Value,
+ "cache_path",
+ _("Cache File Path"),
+ _(
+ "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing",
+ ),
+ );
+ o.value("/tmp/sing-box/cache.db", "RAM (/tmp/sing-box/cache.db)");
+ o.value(
+ "/usr/share/sing-box/cache.db",
+ "Flash (/usr/share/sing-box/cache.db)",
+ );
+ o.default = "/tmp/sing-box/cache.db";
+ o.rmempty = false;
+ o.validate = function (section_id, value) {
+ if (!value) {
+ return _("Cache file path cannot be empty");
+ }
- if (!value.startsWith('/')) {
- return _('Path must be absolute (start with /)');
- }
+ if (!value.startsWith("/")) {
+ return _("Path must be absolute (start with /)");
+ }
- if (!value.endsWith('cache.db')) {
- return _('Path must end with cache.db');
- }
+ if (!value.endsWith("cache.db")) {
+ return _("Path must end with cache.db");
+ }
- const parts = value.split('/').filter(Boolean);
- if (parts.length < 2) {
- return _('Path must contain at least one directory (like /tmp/cache.db)');
- }
+ const parts = value.split("/").filter(Boolean);
+ if (parts.length < 2) {
+ return _("Path must contain at least one directory (like /tmp/cache.db)");
+ }
- return true;
- };
+ return true;
+ };
- o = section.option(
- form.Flag,
- 'exclude_ntp',
- _('Exclude NTP'),
- _(
- 'Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN',
- ),
- );
- o.default = '0';
- o.rmempty = false;
+ o = section.option(
+ form.Flag,
+ "exclude_ntp",
+ _("Exclude NTP"),
+ _(
+ "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN",
+ ),
+ );
+ o.default = "0";
+ o.rmempty = false;
- o = section.option(
- form.DynamicList,
- 'routing_excluded_ips',
- _('Routing Excluded IPs'),
- _('Specify a local IP address to be excluded from routing'),
- );
- o.placeholder = 'IP';
- o.rmempty = true;
- o.validate = function (section_id, value) {
- // Optional
- if (!value || value.length === 0) {
- return true;
- }
+ o = section.option(
+ form.DynamicList,
+ "routing_excluded_ips",
+ _("Routing Excluded IPs"),
+ _("Specify a local IP address to be excluded from routing"),
+ );
+ o.placeholder = "IP";
+ o.rmempty = true;
+ o.validate = function (section_id, value) {
+ // Optional
+ if (!value || value.length === 0) {
+ return true;
+ }
- const validation = main.validateIPV4(value);
+ const validation = main.validateIPV4(value);
- if (validation.valid) {
- return true;
- }
+ if (validation.valid) {
+ return true;
+ }
- return validation.message;
- };
+ return validation.message;
+ };
}
const EntryPoint = {
- createSettingsContent,
+ createSettingsContent,
};
return baseclass.extend(EntryPoint);