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);