From d176f24a7f9a53383feff1aad4fbe4ba2925102d Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 8 Oct 2025 23:26:53 +0300 Subject: [PATCH 001/121] chore: change podkop config luci builder --- fe-app-podkop/src/styles.ts | 22 +- .../resources/view/podkop/additionalTab.js | 362 ----- .../resources/view/podkop/configSection.js | 779 ----------- .../resources/view/podkop/dashboard.js | 22 + .../resources/view/podkop/dashboardTab.js | 26 - .../resources/view/podkop/diagnosticTab.js | 1220 ----------------- .../luci-static/resources/view/podkop/main.js | 22 +- .../resources/view/podkop/podkop.js | 102 +- .../resources/view/podkop/section.js | 586 ++++++++ .../resources/view/podkop/settings.js | 283 ++++ .../resources/view/podkop/utils.js | 163 --- 11 files changed, 977 insertions(+), 2610 deletions(-) delete mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js delete mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js create mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboard.js delete mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboardTab.js delete mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnosticTab.js create mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js create mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js delete mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/utils.js diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index b135ef5..6c3f706 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -24,10 +24,30 @@ export const GlobalStyles = ` display: none; } -#cbi-podkop-main-_status > div { +#cbi-podkop-dashboard-_mount_node > div { width: 100%; } +#cbi-podkop-dashboard > h3 { + display: none; +} + +#cbi-podkop-settings > h3 { + display: none; +} + +#cbi-podkop-section > h3:nth-child(1) { + display: none; +} + +.cbi-section-remove { + margin-bottom: -32px; +} + +.cbi-value { + margin-bottom: 20px !important; +} + /* Dashboard styles */ .pdk_dashboard-page { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js deleted file mode 100644 index f06ae90..0000000 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/additionalTab.js +++ /dev/null @@ -1,362 +0,0 @@ -'use strict'; -'require form'; -'require baseclass'; -'require tools.widgets as widgets'; -'require view.podkop.main as main'; - -function createAdditionalSection(mainSection) { - let o = mainSection.tab('additional', _('Additional Settings')); - - o = mainSection.taboption( - 'additional', - form.Flag, - 'yacd', - _('Yacd enable'), - `${main.getClashUIUrl()}`, - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = 'main'; - - o = mainSection.taboption( - 'additional', - form.Flag, - 'exclude_ntp', - _('Exclude NTP'), - _('Allows you to exclude NTP protocol traffic from the tunnel'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = 'main'; - - o = mainSection.taboption( - 'additional', - form.Flag, - 'quic_disable', - _('QUIC disable'), - _('For issues with the video stream'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = 'main'; - - o = mainSection.taboption( - 'additional', - form.ListValue, - 'update_interval', - _('List Update Frequency'), - _('Select how often the lists will be updated'), - ); - Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { - o.value(key, _(label)); - }); - o.default = '1d'; - o.rmempty = false; - o.ucisection = 'main'; - - o = mainSection.taboption( - 'additional', - 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.ucisection = 'main'; - - o = mainSection.taboption( - 'additional', - 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.ucisection = 'main'; - o.validate = function (section_id, value) { - const validation = main.validateDNS(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = mainSection.taboption( - 'additional', - 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.ucisection = 'main'; - o.validate = function (section_id, value) { - const validation = main.validateDNS(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = mainSection.taboption( - 'additional', - form.Value, - 'dns_rewrite_ttl', - _('DNS Rewrite TTL'), - _('Time in seconds for DNS record caching (default: 60)'), - ); - o.default = '60'; - o.rmempty = false; - o.ucisection = 'main'; - 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'); - } - - return true; - }; - - o = mainSection.taboption( - 'additional', - 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.ucisection = 'main'; - - o = mainSection.taboption( - 'additional', - 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.ucisection = 'main'; - 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.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)'); - } - - return true; - }; - - o = mainSection.taboption( - 'additional', - widgets.DeviceSelect, - 'iface', - _('Source Network Interface'), - _('Select the network interface from which the traffic will originate'), - ); - o.ucisection = 'main'; - 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); - - // If no device is found, allow the value - if (!device) { - return true; - } - - // 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'); - - // Allow only non-wireless devices - return !isWireless; - }; - - o = mainSection.taboption( - 'additional', - form.Flag, - 'mon_restart_ifaces', - _('Interface monitoring'), - _('Interface monitoring for bad WAN'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = 'main'; - - o = mainSection.taboption( - 'additional', - widgets.NetworkSelect, - 'restart_ifaces', - _('Interface for monitoring'), - _('Select the WAN interfaces to be monitored'), - ); - o.ucisection = 'main'; - o.depends('mon_restart_ifaces', '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; - } - - // Otherwise allow it - return true; - }; - - o = mainSection.taboption( - 'additional', - form.Value, - 'procd_reload_delay', - _('Interface Monitoring Delay'), - _('Delay in milliseconds before reloading podkop after interface UP'), - ); - o.ucisection = 'main'; - o.depends('mon_restart_ifaces', '1'); - o.default = '2000'; - o.rmempty = false; - o.validate = function (section_id, value) { - if (!value) { - return _('Delay value cannot be empty'); - } - return true; - }; - - o = mainSection.taboption( - 'additional', - form.Flag, - 'dont_touch_dhcp', - _('Dont touch my DHCP!'), - _('Podkop will not change the DHCP config'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = 'main'; - - o = mainSection.taboption( - 'additional', - form.Flag, - 'detour', - _('Proxy download of lists'), - _('Downloading all lists via main Proxy/VPN'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = 'main'; - - // Extra IPs and exclusions (main section) - o = mainSection.taboption( - 'basic', - form.Flag, - 'exclude_from_ip_enabled', - _('IP for exclusion'), - _('Specify local IP addresses that will never use the configured route'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = 'main'; - - o = mainSection.taboption( - 'basic', - form.DynamicList, - 'exclude_traffic_ip', - _('Local IPs'), - _('Enter valid IPv4 addresses'), - ); - o.placeholder = 'IP'; - o.depends('exclude_from_ip_enabled', '1'); - o.rmempty = false; - o.ucisection = 'main'; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validateIPV4(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = mainSection.taboption( - 'basic', - form.Flag, - 'socks5', - _('Mixed enable'), - _('Browser port: 2080'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = 'main'; -} - -return baseclass.extend({ - createAdditionalSection, -}); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js deleted file mode 100644 index 64c1134..0000000 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/configSection.js +++ /dev/null @@ -1,779 +0,0 @@ -'use strict'; -'require baseclass'; -'require form'; -'require ui'; -'require network'; -'require view.podkop.main as main'; -'require tools.widgets as widgets'; - -function createConfigSection(section) { - const s = section; - - let o = s.tab('basic', _('Basic Settings')); - - o = s.taboption( - 'basic', - form.ListValue, - 'mode', - _('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.ucisection = s.section; - - o = s.taboption( - 'basic', - 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('mode', 'proxy'); - o.ucisection = s.section; - - o = s.taboption( - 'basic', - 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.ucisection = s.section; - 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'; - - o.renderWidget = function (section_id, option_index, cfgvalue) { - const original = form.TextValue.prototype.renderWidget.apply(this, [ - section_id, - option_index, - cfgvalue, - ]); - const container = E('div', {}); - container.appendChild(original); - - if (cfgvalue) { - try { - const activeConfig = cfgvalue - .split('\n') - .map((line) => line.trim()) - .find((line) => line && !line.startsWith('//')); - - if (activeConfig) { - if (activeConfig.includes('#')) { - const label = activeConfig.split('#').pop(); - if (label && label.trim()) { - const decodedLabel = decodeURIComponent(label); - const descDiv = E( - 'div', - { class: 'cbi-value-description' }, - _('Current config: ') + decodedLabel, - ); - container.appendChild(descDiv); - } else { - const descDiv = E( - 'div', - { class: 'cbi-value-description' }, - _('Config without description'), - ); - container.appendChild(descDiv); - } - } else { - const descDiv = E( - 'div', - { class: 'cbi-value-description' }, - _('Config without description'), - ); - container.appendChild(descDiv); - } - } - } catch (e) { - console.error('Error parsing config label:', e); - const descDiv = E( - 'div', - { class: 'cbi-value-description' }, - _('Config without description'), - ); - container.appendChild(descDiv); - } - } else { - const defaultDesc = E( - 'div', - { class: 'cbi-value-description' }, - _( - 'Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs', - ), - ); - container.appendChild(defaultDesc); - } - - return container; - }; - - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - try { - const activeConfigs = main.splitProxyString(value); - - 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.', - ); - } - - const validation = main.validateProxyUrl(activeConfigs[0]); - - if (validation.valid) { - return true; - } - - return validation.message; - } catch (e) { - return `${_('Invalid URL format:')} ${e?.message}`; - } - }; - - o = s.taboption( - 'basic', - form.TextValue, - 'outbound_json', - _('Outbound Configuration'), - _('Enter complete outbound configuration in JSON format'), - ); - o.depends('proxy_config_type', 'outbound'); - o.rows = 10; - o.ucisection = s.section; - 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 = s.taboption( - 'basic', - form.DynamicList, - 'urltest_proxy_links', - _('URLTest Proxy Links'), - ); - o.depends('proxy_config_type', 'urltest'); - o.placeholder = 'vless://, ss://, trojan:// 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 = s.taboption( - 'basic', - form.Flag, - 'ss_uot', - _('Shadowsocks UDP over TCP'), - _('Apply for SS2022'), - ); - o.default = '0'; - o.depends('mode', 'proxy'); - o.rmempty = false; - o.ucisection = s.section; - - o = s.taboption( - 'basic', - widgets.DeviceSelect, - 'interface', - _('Network Interface'), - _('Select network interface for VPN connection'), - ); - o.depends('mode', 'vpn'); - o.ucisection = s.section; - 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 = s.taboption( - 'basic', - 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('mode', 'vpn'); - o.ucisection = s.section; - - o = s.taboption( - 'basic', - 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.ucisection = s.section; - - o = s.taboption( - 'basic', - 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.ucisection = s.section; - o.validate = function (section_id, value) { - const validation = main.validateDNS(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = s.taboption( - 'basic', - form.Flag, - 'community_lists_enabled', - _('Community Lists'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = s.section; - - o = s.taboption( - 'basic', - form.DynamicList, - 'community_lists', - _('Service List'), - _('Select predefined service 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.depends('community_lists_enabled', '1'); - o.rmempty = false; - o.ucisection = s.section; - - 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 = s.taboption( - 'basic', - form.ListValue, - 'user_domain_list_type', - _('User Domain List Type'), - _('Select how to add your custom domains'), - ); - o.value('disabled', _('Disabled')); - o.value('dynamic', _('Dynamic List')); - o.value('text', _('Text List')); - o.default = 'disabled'; - o.rmempty = false; - o.ucisection = s.section; - - o = s.taboption( - 'basic', - form.DynamicList, - 'user_domains', - _('User Domains'), - _( - 'Enter domain names without protocols (example: sub.example.com or example.com)', - ), - ); - o.placeholder = 'Domains list'; - o.depends('user_domain_list_type', 'dynamic'); - o.rmempty = false; - o.ucisection = s.section; - 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 = s.taboption( - 'basic', - form.TextValue, - 'user_domains_text', - _('User Domains List'), - _( - 'Enter domain names separated by comma, space or newline. You can add comments after //', - ), - ); - 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.ucisection = s.section; - 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'); - } - - return true; - }; - - o = s.taboption( - 'basic', - form.Flag, - 'local_domain_lists_enabled', - _('Local Domain Lists'), - _('Use the list from the router filesystem'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = s.section; - - o = s.taboption( - 'basic', - form.DynamicList, - 'local_domain_lists', - _('Local Domain List Paths'), - _('Enter the list file path'), - ); - o.placeholder = '/path/file.lst'; - o.depends('local_domain_lists_enabled', '1'); - o.rmempty = false; - o.ucisection = s.section; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validatePath(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = s.taboption( - 'basic', - form.Flag, - 'remote_domain_lists_enabled', - _('Remote Domain Lists'), - _('Download and use domain lists from remote URLs'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = s.section; - - o = s.taboption( - 'basic', - form.DynamicList, - 'remote_domain_lists', - _('Remote Domain URLs'), - _('Enter full URLs starting with http:// or https://'), - ); - o.placeholder = 'URL'; - o.depends('remote_domain_lists_enabled', '1'); - o.rmempty = false; - o.ucisection = s.section; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validateUrl(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = s.taboption( - 'basic', - form.Flag, - 'local_subnet_lists_enabled', - _('Local Subnet Lists'), - _('Use the list from the router filesystem'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = s.section; - - o = s.taboption( - 'basic', - form.DynamicList, - 'local_subnet_lists', - _('Local Subnet List Paths'), - _('Enter the list file path'), - ); - o.placeholder = '/path/file.lst'; - o.depends('local_subnet_lists_enabled', '1'); - o.rmempty = false; - o.ucisection = s.section; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validatePath(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = s.taboption( - 'basic', - form.ListValue, - 'user_subnet_list_type', - _('User Subnet List Type'), - _('Select how to add your 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.ucisection = s.section; - - o = s.taboption( - 'basic', - form.DynamicList, - 'user_subnets', - _('User Subnets'), - _( - 'Enter subnets in CIDR notation (example: 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.ucisection = s.section; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validateSubnet(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = s.taboption( - 'basic', - form.TextValue, - 'user_subnets_text', - _('User Subnets List'), - _( - 'Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //', - ), - ); - 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.ucisection = s.section; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const subnets = main.parseValueList(value); - - if (!subnets.length) { - return _( - 'At least one valid subnet or IP must be specified. Comments-only content is not allowed.', - ); - } - - const { valid, results } = main.bulkValidate(subnets, main.validateSubnet); - - 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'); - } - - return true; - }; - - o = s.taboption( - 'basic', - form.Flag, - 'remote_subnet_lists_enabled', - _('Remote Subnet Lists'), - _('Download and use subnet lists from remote URLs'), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = s.section; - - o = s.taboption( - 'basic', - form.DynamicList, - 'remote_subnet_lists', - _('Remote Subnet URLs'), - _('Enter full URLs starting with http:// or https://'), - ); - o.placeholder = 'URL'; - o.depends('remote_subnet_lists_enabled', '1'); - o.rmempty = false; - o.ucisection = s.section; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validateUrl(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = s.taboption( - 'basic', - form.Flag, - 'all_traffic_from_ip_enabled', - _('IP for full redirection'), - _( - 'Specify local IP addresses whose traffic will always use the configured route', - ), - ); - o.default = '0'; - o.rmempty = false; - o.ucisection = s.section; - - o = s.taboption( - 'basic', - form.DynamicList, - 'all_traffic_ip', - _('Local IPs'), - _('Enter valid IPv4 addresses'), - ); - o.placeholder = 'IP'; - o.depends('all_traffic_from_ip_enabled', '1'); - o.rmempty = false; - o.ucisection = s.section; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validateSubnet(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; -} - -return baseclass.extend({ - createConfigSection, -}); 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 new file mode 100644 index 0000000..423b47f --- /dev/null +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboard.js @@ -0,0 +1,22 @@ +'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.initDashboardController(); + return main.renderDashboard(); + }; +} + +const EntryPoint = { + createDashboardContent, +}; + +return baseclass.extend(EntryPoint); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboardTab.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboardTab.js deleted file mode 100644 index a5056dc..0000000 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/dashboardTab.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; -'require baseclass'; -'require form'; -'require ui'; -'require uci'; -'require fs'; -'require view.podkop.utils as utils'; -'require view.podkop.main as main'; - -function createDashboardSection(mainSection) { - let o = mainSection.tab('dashboard', _('Dashboard')); - - o = mainSection.taboption('dashboard', form.DummyValue, '_status'); - o.rawhtml = true; - o.cfgvalue = () => { - main.initDashboardController(); - - return main.renderDashboard(); - }; -} - -const EntryPoint = { - createDashboardSection, -}; - -return baseclass.extend(EntryPoint); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnosticTab.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnosticTab.js deleted file mode 100644 index 1de1f76..0000000 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnosticTab.js +++ /dev/null @@ -1,1220 +0,0 @@ -'use strict'; -'require baseclass'; -'require form'; -'require ui'; -'require uci'; -'require fs'; -'require view.podkop.utils as utils'; -'require view.podkop.main as main'; - -// Cache system for network requests -const fetchCache = {}; - -// Helper function to fetch with cache -async function cachedFetch(url, options = {}) { - const cacheKey = url; - const currentTime = Date.now(); - - // If we have a valid cached response, return it - if ( - fetchCache[cacheKey] && - currentTime - fetchCache[cacheKey].timestamp < main.CACHE_TIMEOUT - ) { - console.log(`Using cached response for ${url}`); - return Promise.resolve(fetchCache[cacheKey].response.clone()); - } - - // Otherwise, make a new request - try { - const response = await fetch(url, options); - - // Cache the response - fetchCache[cacheKey] = { - response: response.clone(), - timestamp: currentTime, - }; - - return response; - } catch (error) { - throw error; - } -} - -// Helper functions for command execution with prioritization - Using from utils.js now -function safeExec( - command, - args, - priority, - callback, - timeout = main.COMMAND_TIMEOUT, -) { - return utils.safeExec(command, args, priority, callback, timeout); -} - -// Helper functions for handling checks -function runCheck(checkFunction, priority, callback) { - // Default to highest priority execution if priority is not provided or invalid - let schedulingDelay = main.COMMAND_SCHEDULING.P0_PRIORITY; - - // If priority is a string, try to get the corresponding delay value - if ( - typeof priority === 'string' && - main.COMMAND_SCHEDULING[priority] !== undefined - ) { - schedulingDelay = main.COMMAND_SCHEDULING[priority]; - } - - const executeCheck = async () => { - try { - const result = await checkFunction(); - if (callback && typeof callback === 'function') { - callback(result); - } - return result; - } catch (error) { - if (callback && typeof callback === 'function') { - callback({ error }); - } - return { error }; - } - }; - - if (callback && typeof callback === 'function') { - setTimeout(executeCheck, schedulingDelay); - return; - } else { - return executeCheck(); - } -} - -function runAsyncTask(taskFunction, priority) { - // Default to highest priority execution if priority is not provided or invalid - let schedulingDelay = main.COMMAND_SCHEDULING.P0_PRIORITY; - - // If priority is a string, try to get the corresponding delay value - if ( - typeof priority === 'string' && - main.COMMAND_SCHEDULING[priority] !== undefined - ) { - schedulingDelay = main.COMMAND_SCHEDULING[priority]; - } - - setTimeout(async () => { - try { - await taskFunction(); - } catch (error) { - console.error('Async task error:', error); - } - }, schedulingDelay); -} - -// Helper Functions for UI and formatting -function createStatus(state, message, color) { - return { - state, - message: _(message), - color: main.STATUS_COLORS[color], - }; -} - -function formatDiagnosticOutput(output) { - if (typeof output !== 'string') return ''; - return output - .trim() - .replace(/\x1b\[[0-9;]*m/g, '') - .replace(/\r\n/g, '\n') - .replace(/\r/g, '\n'); -} - -function copyToClipboard(text, button) { - const textarea = document.createElement('textarea'); - textarea.value = text; - document.body.appendChild(textarea); - textarea.select(); - try { - document.execCommand('copy'); - const originalText = button.textContent; - button.textContent = _('Copied!'); - setTimeout( - () => (button.textContent = originalText), - main.BUTTON_FEEDBACK_TIMEOUT, - ); - } catch (err) { - ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message)); - } - document.body.removeChild(textarea); -} - -// IP masking function -function maskIP(ip) { - if (!ip) return ''; - const parts = ip.split('.'); - if (parts.length !== 4) return ip; - return ['XX', 'XX', 'XX', parts[3]].join('.'); -} - -// Status Check Functions -async function checkFakeIP() { - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), main.FETCH_TIMEOUT); - - try { - const response = await cachedFetch( - `https://${main.FAKEIP_CHECK_DOMAIN}/check`, - { signal: controller.signal }, - ); - const data = await response.json(); - clearTimeout(timeoutId); - - if (data.fakeip === true) { - return createStatus('working', 'working', 'SUCCESS'); - } else { - return createStatus('not_working', 'not working', 'ERROR'); - } - } catch (fetchError) { - clearTimeout(timeoutId); - const message = - fetchError.name === 'AbortError' ? 'timeout' : 'check error'; - return createStatus('error', message, 'WARNING'); - } - } catch (error) { - return createStatus('error', 'check error', 'WARNING'); - } -} - -async function checkFakeIPCLI() { - try { - return new Promise((resolve) => { - safeExec( - 'nslookup', - ['-timeout=2', main.FAKEIP_CHECK_DOMAIN, '127.0.0.42'], - 'P0_PRIORITY', - (result) => { - if (result.stdout && result.stdout.includes('198.18')) { - resolve(createStatus('working', 'working on router', 'SUCCESS')); - } else { - resolve( - createStatus('not_working', 'not working on router', 'ERROR'), - ); - } - }, - ); - }); - } catch (error) { - return createStatus('error', 'CLI check error', 'WARNING'); - } -} - -function checkDNSAvailability() { - return new Promise(async (resolve) => { - try { - safeExec( - '/usr/bin/podkop', - ['check_dns_available'], - 'P0_PRIORITY', - (dnsStatusResult) => { - if (!dnsStatusResult || !dnsStatusResult.stdout) { - return resolve({ - remote: createStatus('error', 'DNS check timeout', 'WARNING'), - local: createStatus('error', 'DNS check timeout', 'WARNING'), - }); - } - - try { - const dnsStatus = JSON.parse(dnsStatusResult.stdout); - - const remoteStatus = dnsStatus.is_available - ? createStatus( - 'available', - `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, - 'SUCCESS', - ) - : createStatus( - 'unavailable', - `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, - 'ERROR', - ); - - const localStatus = dnsStatus.local_dns_working - ? createStatus('available', 'Router DNS working', 'SUCCESS') - : createStatus('unavailable', 'Router DNS not working', 'ERROR'); - - return resolve({ - remote: remoteStatus, - local: localStatus, - }); - } catch (parseError) { - return resolve({ - remote: createStatus('error', 'DNS check parse error', 'WARNING'), - local: createStatus('error', 'DNS check parse error', 'WARNING'), - }); - } - }, - ); - } catch (error) { - return resolve({ - remote: createStatus('error', 'DNS check error', 'WARNING'), - local: createStatus('error', 'DNS check error', 'WARNING'), - }); - } - }); -} - -async function checkBypass() { - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), main.FETCH_TIMEOUT); - - try { - const response1 = await cachedFetch( - `https://${main.FAKEIP_CHECK_DOMAIN}/check`, - { signal: controller.signal }, - ); - const data1 = await response1.json(); - - const response2 = await cachedFetch( - `https://${main.IP_CHECK_DOMAIN}/check`, - { signal: controller.signal }, - ); - const data2 = await response2.json(); - - clearTimeout(timeoutId); - - if (data1.IP && data2.IP) { - if (data1.IP !== data2.IP) { - return createStatus('working', 'working', 'SUCCESS'); - } else { - return createStatus( - 'not_working', - 'same IP for both domains', - 'ERROR', - ); - } - } else { - return createStatus('error', 'check error (no IP)', 'WARNING'); - } - } catch (fetchError) { - clearTimeout(timeoutId); - const message = - fetchError.name === 'AbortError' ? 'timeout' : 'check error'; - return createStatus('error', message, 'WARNING'); - } - } catch (error) { - return createStatus('error', 'check error', 'WARNING'); - } -} - -function showConfigModal(command, title) { - // Create and show modal immediately with loading state - const modalContent = E('div', { class: 'panel-body' }, [ - E( - 'div', - { - class: 'panel-body', - style: - 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; ' + - 'font-family: monospace; white-space: pre-wrap; word-wrap: break-word; ' + - 'line-height: 1.5; font-size: 14px;', - }, - [ - E( - 'pre', - { - id: 'modal-content-pre', - style: 'margin: 0;', - }, - _('Loading...'), - ), - ], - ), - E( - 'div', - { - class: 'right', - style: 'margin-top: 1em;', - }, - [ - E( - 'button', - { - class: 'btn', - id: 'copy-button', - click: (ev) => - copyToClipboard( - '```txt\n' + - document.getElementById('modal-content-pre').innerText + - '\n```', - ev.target, - ), - }, - _('Copy to Clipboard'), - ), - E( - 'button', - { - class: 'btn', - click: ui.hideModal, - }, - _('Close'), - ), - ], - ), - ]); - - ui.showModal(_(title), modalContent); - - // Function to update modal content - const updateModalContent = (content) => { - const pre = document.getElementById('modal-content-pre'); - if (pre) { - pre.textContent = content; - } - }; - - try { - let formattedOutput = ''; - - if (command === 'global_check') { - safeExec('/usr/bin/podkop', [command, `${main.PODKOP_LUCI_APP_VERSION}`], 'P0_PRIORITY', (res) => { - formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - - try { - const controller = new AbortController(); - const timeoutId = setTimeout( - () => controller.abort(), - main.FETCH_TIMEOUT, - ); - - cachedFetch(`https://${main.FAKEIP_CHECK_DOMAIN}/check`, { - signal: controller.signal, - }) - .then((response) => response.json()) - .then((data) => { - clearTimeout(timeoutId); - - if (data.fakeip === true) { - formattedOutput += - '\n✅ ' + _('FakeIP is working in browser!') + '\n'; - } else { - formattedOutput += - '\n❌ ' + _('FakeIP is not working in browser') + '\n'; - formattedOutput += - _('Check DNS server on current device (PC, phone)') + '\n'; - formattedOutput += _('Its must be router!') + '\n'; - } - - // Bypass check - cachedFetch(`https://${main.FAKEIP_CHECK_DOMAIN}/check`, { - signal: controller.signal, - }) - .then((bypassResponse) => bypassResponse.json()) - .then((bypassData) => { - cachedFetch(`https://${main.IP_CHECK_DOMAIN}/check`, { - signal: controller.signal, - }) - .then((bypassResponse2) => bypassResponse2.json()) - .then((bypassData2) => { - formattedOutput += '━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'; - - if ( - bypassData.IP && - bypassData2.IP && - bypassData.IP !== bypassData2.IP - ) { - formattedOutput += - '✅ ' + _('Proxy working correctly') + '\n'; - formattedOutput += - _('Direct IP: ') + maskIP(bypassData.IP) + '\n'; - formattedOutput += - _('Proxy IP: ') + maskIP(bypassData2.IP) + '\n'; - } else if (bypassData.IP === bypassData2.IP) { - formattedOutput += - '❌ ' + - _('Proxy is not working - same IP for both domains') + - '\n'; - formattedOutput += - _('IP: ') + maskIP(bypassData.IP) + '\n'; - } else { - formattedOutput += - '❌ ' + _('Proxy check failed') + '\n'; - } - - updateModalContent(formattedOutput); - }) - .catch((error) => { - formattedOutput += - '\n❌ ' + - _('Check failed: ') + - (error.name === 'AbortError' - ? _('timeout') - : error.message) + - '\n'; - updateModalContent(formattedOutput); - }); - }) - .catch((error) => { - formattedOutput += - '\n❌ ' + - _('Check failed: ') + - (error.name === 'AbortError' - ? _('timeout') - : error.message) + - '\n'; - updateModalContent(formattedOutput); - }); - }) - .catch((error) => { - formattedOutput += - '\n❌ ' + - _('Check failed: ') + - (error.name === 'AbortError' ? _('timeout') : error.message) + - '\n'; - updateModalContent(formattedOutput); - }); - } catch (error) { - formattedOutput += - '\n❌ ' + _('Check failed: ') + error.message + '\n'; - updateModalContent(formattedOutput); - } - }); - } else { - safeExec('/usr/bin/podkop', [command], 'P0_PRIORITY', (res) => { - formattedOutput = formatDiagnosticOutput(res.stdout || _('No output')); - updateModalContent(formattedOutput); - }); - } - } catch (error) { - updateModalContent(_('Error: ') + error.message); - } -} - -// Button Factory -const ButtonFactory = { - createButton: function (config) { - return E( - 'button', - { - class: `btn ${config.additionalClass || ''}`.trim(), - click: config.onClick, - style: config.style || '', - }, - _(config.label), - ); - }, - - createActionButton: function (config) { - return this.createButton({ - label: config.label, - additionalClass: `cbi-button-${config.type || ''}`, - onClick: () => - safeExec('/usr/bin/podkop', [config.action], 'P0_PRIORITY').then( - () => config.reload && location.reload(), - ), - style: config.style, - }); - }, - - createInitActionButton: function (config) { - return this.createButton({ - label: config.label, - additionalClass: `cbi-button-${config.type || ''}`, - onClick: () => - safeExec('/etc/init.d/podkop', [config.action], 'P0_PRIORITY').then( - () => config.reload && location.reload(), - ), - style: config.style, - }); - }, - - createModalButton: function (config) { - return this.createButton({ - label: config.label, - onClick: () => showConfigModal(config.command, config.title), - additionalClass: `cbi-button-${config.type || ''}`, - style: config.style, - }); - }, -}; - -// Create a loading placeholder for status text -function createLoadingStatusText() { - return E('span', { class: 'loading-indicator' }, _('Loading...')); -} - -// Create the status section with buttons loaded immediately but status indicators loading asynchronously -let createStatusSection = async function () { - // Get initial podkop status - let initialPodkopStatus = { enabled: false }; - try { - const result = await fs.exec('/usr/bin/podkop', ['get_status']); - if (result && result.stdout) { - const status = JSON.parse(result.stdout); - initialPodkopStatus.enabled = status.enabled === 1; - } - } catch (e) { - console.error('Error getting initial podkop status:', e); - } - - return E('div', { class: 'cbi-section' }, [ - E('div', { class: 'table', style: 'display: flex; gap: 20px;' }, [ - // Podkop Status Panel - E( - 'div', - { - id: 'podkop-status-panel', - class: 'panel', - style: 'flex: 1; padding: 15px;', - }, - [ - E('div', { class: 'panel-heading' }, [ - E('strong', {}, _('Podkop Status')), - E('br'), - E('span', { id: 'podkop-status-text' }, createLoadingStatusText()), - ]), - E( - 'div', - { - class: 'panel-body', - style: 'display: flex; flex-direction: column; gap: 8px;', - }, - [ - ButtonFactory.createActionButton({ - label: 'Restart Podkop', - type: 'apply', - action: 'restart', - reload: true, - }), - ButtonFactory.createActionButton({ - label: 'Stop Podkop', - type: 'apply', - action: 'stop', - reload: true, - }), - // Autostart button - create with initial state - ButtonFactory.createInitActionButton({ - label: initialPodkopStatus.enabled - ? 'Disable Autostart' - : 'Enable Autostart', - type: initialPodkopStatus.enabled ? 'remove' : 'apply', - action: initialPodkopStatus.enabled ? 'disable' : 'enable', - reload: true, - }), - ButtonFactory.createModalButton({ - label: _('Global check'), - command: 'global_check', - title: _('Click here for all the info'), - }), - ButtonFactory.createModalButton({ - label: 'View Logs', - command: 'check_logs', - title: 'Podkop Logs', - }), - ButtonFactory.createModalButton({ - label: _('Update Lists'), - command: 'list_update', - title: _('Lists Update Results'), - }), - ], - ), - ], - ), - - // Sing-box Status Panel - E( - 'div', - { - id: 'singbox-status-panel', - class: 'panel', - style: 'flex: 1; padding: 15px;', - }, - [ - E('div', { class: 'panel-heading' }, [ - E('strong', {}, _('Sing-box Status')), - E('br'), - E('span', { id: 'singbox-status-text' }, createLoadingStatusText()), - ]), - E( - 'div', - { - class: 'panel-body', - style: 'display: flex; flex-direction: column; gap: 8px;', - }, - [ - ButtonFactory.createModalButton({ - label: 'Show Config', - command: 'show_sing_box_config', - title: 'Sing-box Configuration', - }), - ButtonFactory.createModalButton({ - label: 'View Logs', - command: 'check_sing_box_logs', - title: 'Sing-box Logs', - }), - ButtonFactory.createModalButton({ - label: 'Check Connections', - command: 'check_sing_box_connections', - title: 'Active Connections', - }), - ButtonFactory.createModalButton({ - label: _('Check NFT Rules'), - command: 'check_nft', - title: _('NFT Rules'), - }), - ButtonFactory.createModalButton({ - label: _('Check DNSMasq'), - command: 'check_dnsmasq', - title: _('DNSMasq Configuration'), - }), - ], - ), - ], - ), - - // FakeIP Status Panel - E( - 'div', - { - id: 'fakeip-status-panel', - class: 'panel', - style: 'flex: 1; padding: 15px;', - }, - [ - E('div', { class: 'panel-heading' }, [ - E('strong', {}, _('FakeIP Status')), - ]), - E( - 'div', - { - class: 'panel-body', - style: 'display: flex; flex-direction: column; gap: 8px;', - }, - [ - E('div', { style: 'margin-bottom: 5px;' }, [ - E('div', {}, [ - E( - 'span', - { id: 'fakeip-browser-status' }, - createLoadingStatusText(), - ), - ]), - E('div', {}, [ - E( - 'span', - { id: 'fakeip-router-status' }, - createLoadingStatusText(), - ), - ]), - ]), - E('div', { style: 'margin-bottom: 5px;' }, [ - E('div', {}, [ - E('strong', {}, _('DNS Status')), - E('br'), - E( - 'span', - { id: 'dns-remote-status' }, - createLoadingStatusText(), - ), - E('br'), - E( - 'span', - { id: 'dns-local-status' }, - createLoadingStatusText(), - ), - ]), - ]), - E('div', { style: 'margin-bottom: 5px;' }, [ - E('div', {}, [ - E('strong', { id: 'config-name-text' }, _('Main config')), - E('br'), - E('span', { id: 'bypass-status' }, createLoadingStatusText()), - ]), - ]), - ], - ), - ], - ), - - // Version Information Panel - E( - 'div', - { - id: 'version-info-panel', - class: 'panel', - style: 'flex: 1; padding: 15px;', - }, - [ - E('div', { class: 'panel-heading' }, [ - E('strong', {}, _('Version Information')), - ]), - E('div', { class: 'panel-body' }, [ - E( - 'div', - { - style: - 'margin-top: 10px; font-family: monospace; white-space: pre-wrap;', - }, - [ - E('strong', {}, _('Podkop: ')), - E('span', { id: 'podkop-version' }, _('Loading...')), - '\n', - E('strong', {}, _('LuCI App: ')), - E('span', { id: 'luci-version' }, _('Loading...')), - '\n', - E('strong', {}, _('Sing-box: ')), - E('span', { id: 'singbox-version' }, _('Loading...')), - '\n', - E('strong', {}, _('OpenWrt Version: ')), - E('span', { id: 'openwrt-version' }, _('Loading...')), - '\n', - E('strong', {}, _('Device Model: ')), - E('span', { id: 'device-model' }, _('Loading...')), - ], - ), - ]), - ], - ), - ]), - ]); -}; - -// Global variables for tracking state -let diagnosticsUpdateTimer = null; -let isInitialCheck = true; -showConfigModal.busy = false; - -function startDiagnosticsUpdates() { - if (diagnosticsUpdateTimer) { - clearInterval(diagnosticsUpdateTimer); - } - - // Immediately update when started - updateDiagnostics(); - - // Then set up periodic updates - diagnosticsUpdateTimer = setInterval( - updateDiagnostics, - main.DIAGNOSTICS_UPDATE_INTERVAL, - ); -} - -function stopDiagnosticsUpdates() { - if (diagnosticsUpdateTimer) { - clearInterval(diagnosticsUpdateTimer); - diagnosticsUpdateTimer = null; - } -} - -// Update individual text element with new content -function updateTextElement(elementId, content) { - const element = document.getElementById(elementId); - if (element) { - element.innerHTML = ''; - element.appendChild(content); - } -} - -async function updateDiagnostics() { - // Podkop Status check - safeExec('/usr/bin/podkop', ['get_status'], 'P0_PRIORITY', (result) => { - try { - const parsedPodkopStatus = JSON.parse( - result.stdout || '{"enabled":0,"status":"error"}', - ); - - // Update Podkop status text - updateTextElement( - 'podkop-status-text', - E( - 'span', - { - style: `color: ${parsedPodkopStatus.enabled ? main.STATUS_COLORS.SUCCESS : main.STATUS_COLORS.ERROR}`, - }, - [ - parsedPodkopStatus.enabled - ? '✔ Autostart enabled' - : '✘ Autostart disabled', - ], - ), - ); - - // Update autostart button - const autostartButton = parsedPodkopStatus.enabled - ? ButtonFactory.createInitActionButton({ - label: 'Disable Autostart', - type: 'remove', - action: 'disable', - reload: true, - }) - : ButtonFactory.createInitActionButton({ - label: 'Enable Autostart', - type: 'apply', - action: 'enable', - reload: true, - }); - - // Find the autostart button and replace it - const panel = document.getElementById('podkop-status-panel'); - if (panel) { - const buttons = panel.querySelectorAll('.cbi-button'); - if (buttons.length >= 3) { - buttons[2].parentNode.replaceChild(autostartButton, buttons[2]); - } - } - } catch (error) { - updateTextElement( - 'podkop-status-text', - E('span', { style: `color: ${main.STATUS_COLORS.ERROR}` }, '✘ Error'), - ); - } - }); - - // Sing-box Status check - safeExec( - '/usr/bin/podkop', - ['get_sing_box_status'], - 'P0_PRIORITY', - (result) => { - try { - const parsedSingboxStatus = JSON.parse( - result.stdout || '{"running":0,"enabled":0,"status":"error"}', - ); - - // Update Sing-box status text - updateTextElement( - 'singbox-status-text', - E( - 'span', - { - style: `color: ${ - parsedSingboxStatus.running && !parsedSingboxStatus.enabled - ? main.STATUS_COLORS.SUCCESS - : main.STATUS_COLORS.ERROR - }`, - }, - [ - parsedSingboxStatus.running && !parsedSingboxStatus.enabled - ? '✔ running' - : '✘ ' + parsedSingboxStatus.status, - ], - ), - ); - } catch (error) { - updateTextElement( - 'singbox-status-text', - E('span', { style: `color: ${main.STATUS_COLORS.ERROR}` }, '✘ Error'), - ); - } - }, - ); - - // Version Information checks - safeExec('/usr/bin/podkop', ['show_version'], 'P2_PRIORITY', (result) => { - updateTextElement( - 'podkop-version', - document.createTextNode( - result.stdout ? result.stdout.trim() : _('Unknown'), - ), - ); - }); - - updateTextElement( - 'luci-version', - document.createTextNode( - `${main.PODKOP_LUCI_APP_VERSION}` - ) - ); - - safeExec( - '/usr/bin/podkop', - ['show_sing_box_version'], - 'P2_PRIORITY', - (result) => { - updateTextElement( - 'singbox-version', - document.createTextNode( - result.stdout ? result.stdout.trim() : _('Unknown'), - ), - ); - }, - ); - - safeExec('/usr/bin/podkop', ['show_system_info'], 'P2_PRIORITY', (result) => { - if (result.stdout) { - updateTextElement( - 'openwrt-version', - document.createTextNode(result.stdout.split('\n')[1].trim()), - ); - updateTextElement( - 'device-model', - document.createTextNode(result.stdout.split('\n')[4].trim()), - ); - } else { - updateTextElement( - 'openwrt-version', - document.createTextNode(_('Unknown')), - ); - updateTextElement('device-model', document.createTextNode(_('Unknown'))); - } - }); - - // FakeIP and DNS status checks - runCheck(checkFakeIP, 'P3_PRIORITY', (result) => { - updateTextElement( - 'fakeip-browser-status', - E( - 'span', - { - style: `color: ${result.error ? main.STATUS_COLORS.WARNING : result.color}`, - }, - [ - result.error - ? '! ' - : result.state === 'working' - ? '✔ ' - : result.state === 'not_working' - ? '✘ ' - : '! ', - result.error - ? 'check error' - : result.state === 'working' - ? _('works in browser') - : _('does not work in browser'), - ], - ), - ); - }); - - runCheck(checkFakeIPCLI, 'P8_PRIORITY', (result) => { - updateTextElement( - 'fakeip-router-status', - E( - 'span', - { - style: `color: ${result.error ? main.STATUS_COLORS.WARNING : result.color}`, - }, - [ - result.error - ? '! ' - : result.state === 'working' - ? '✔ ' - : result.state === 'not_working' - ? '✘ ' - : '! ', - result.error - ? 'check error' - : result.state === 'working' - ? _('works on router') - : _('does not work on router'), - ], - ), - ); - }); - - runCheck(checkDNSAvailability, 'P4_PRIORITY', (result) => { - if (result.error) { - updateTextElement( - 'dns-remote-status', - E( - 'span', - { style: `color: ${main.STATUS_COLORS.WARNING}` }, - '! DNS check error', - ), - ); - updateTextElement( - 'dns-local-status', - E( - 'span', - { style: `color: ${main.STATUS_COLORS.WARNING}` }, - '! DNS check error', - ), - ); - } else { - updateTextElement( - 'dns-remote-status', - E('span', { style: `color: ${result.remote.color}` }, [ - result.remote.state === 'available' - ? '✔ ' - : result.remote.state === 'unavailable' - ? '✘ ' - : '! ', - result.remote.message, - ]), - ); - - updateTextElement( - 'dns-local-status', - E('span', { style: `color: ${result.local.color}` }, [ - result.local.state === 'available' - ? '✔ ' - : result.local.state === 'unavailable' - ? '✘ ' - : '! ', - result.local.message, - ]), - ); - } - }); - - runCheck( - checkBypass, - 'P1_PRIORITY', - (result) => { - updateTextElement( - 'bypass-status', - E( - 'span', - { - style: `color: ${result.error ? main.STATUS_COLORS.WARNING : result.color}`, - }, - [ - result.error - ? '! ' - : result.state === 'working' - ? '✔ ' - : result.state === 'not_working' - ? '✘ ' - : '! ', - result.error ? 'check error' : result.message, - ], - ), - ); - }, - 'P1_PRIORITY', - ); - - // Config name - runAsyncTask(async () => { - try { - let configName = _('Main config'); - const data = await uci.load('podkop'); - const proxyString = uci.get('podkop', 'main', 'proxy_string'); - - if (proxyString) { - const activeConfig = proxyString - .split('\n') - .map((line) => line.trim()) - .find((line) => line && !line.startsWith('//')); - - if (activeConfig) { - if (activeConfig.includes('#')) { - const label = activeConfig.split('#').pop(); - if (label && label.trim()) { - configName = _('Config: ') + decodeURIComponent(label); - } - } - } - } - - updateTextElement( - 'config-name-text', - document.createTextNode(configName), - ); - } catch (e) { - console.error('Error getting config name from UCI:', e); - } - }, 'P1_PRIORITY'); -} - -function createDiagnosticsSection(mainSection) { - let o = mainSection.tab('diagnostics', _('Diagnostics')); - - o = mainSection.taboption('diagnostics', form.DummyValue, '_status'); - o.rawhtml = true; - o.cfgvalue = () => - E('div', { - id: 'diagnostics-status', - 'data-loading': 'true', - }); -} - -function setupDiagnosticsEventHandlers(node) { - const titleDiv = E('h2', { class: 'cbi-map-title' }, _('Podkop')); - node.insertBefore(titleDiv, node.firstChild); - - // Function to initialize diagnostics - function initDiagnostics(container) { - if (container && container.hasAttribute('data-loading')) { - container.innerHTML = ''; - showConfigModal.busy = false; - createStatusSection().then((section) => { - container.appendChild(section); - startDiagnosticsUpdates(); - // Start error polling when diagnostics tab is active - utils.startErrorPolling(); - }); - } - } - - document.addEventListener('visibilitychange', function () { - const diagnosticsContainer = document.getElementById('diagnostics-status'); - const diagnosticsTab = document.querySelector( - '.cbi-tab[data-tab="diagnostics"]', - ); - - if ( - document.hidden || - !diagnosticsTab || - !diagnosticsTab.classList.contains('cbi-tab-active') - ) { - stopDiagnosticsUpdates(); - // Don't stop error polling here - it's managed in podkop.js for all tabs - } else if ( - diagnosticsContainer && - diagnosticsContainer.hasAttribute('data-loading') - ) { - startDiagnosticsUpdates(); - // Ensure error polling is running when diagnostics tab is active - utils.startErrorPolling(); - } - }); - - setTimeout(() => { - const diagnosticsContainer = document.getElementById('diagnostics-status'); - const diagnosticsTab = document.querySelector( - '.cbi-tab[data-tab="diagnostics"]', - ); - const otherTabs = document.querySelectorAll( - '.cbi-tab:not([data-tab="diagnostics"])', - ); - - // Check for direct page load case - const noActiveTabsExist = !Array.from(otherTabs).some((tab) => - tab.classList.contains('cbi-tab-active'), - ); - - if ( - diagnosticsContainer && - diagnosticsTab && - (diagnosticsTab.classList.contains('cbi-tab-active') || noActiveTabsExist) - ) { - initDiagnostics(diagnosticsContainer); - } - - const tabs = node.querySelectorAll('.cbi-tabmenu'); - if (tabs.length > 0) { - tabs[0].addEventListener('click', function (e) { - const tab = e.target.closest('.cbi-tab'); - if (tab) { - const tabName = tab.getAttribute('data-tab'); - if (tabName === 'diagnostics') { - const container = document.getElementById('diagnostics-status'); - container.setAttribute('data-loading', 'true'); - initDiagnostics(container); - } else { - stopDiagnosticsUpdates(); - // Don't stop error polling - it should continue on all tabs - } - } - }); - } - }, main.DIAGNOSTICS_INITIAL_DELAY); - - node.classList.add('fade-in'); - return node; -} - -return baseclass.extend({ - createDiagnosticsSection, - setupDiagnosticsEventHandlers, -}); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 2f5f206..b173c39 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -247,10 +247,30 @@ var GlobalStyles = ` display: none; } -#cbi-podkop-main-_status > div { +#cbi-podkop-dashboard-_mount_node > div { width: 100%; } +#cbi-podkop-dashboard > h3 { + display: none; +} + +#cbi-podkop-settings > h3 { + display: none; +} + +#cbi-podkop-section > h3:nth-child(1) { + display: none; +} + +.cbi-section-remove { + margin-bottom: -32px; +} + +.cbi-value { + margin-bottom: 20px !important; +} + /* Dashboard styles */ .pdk_dashboard-page { 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 c84ff91..74f8766 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,82 +1,68 @@ 'use strict'; 'require view'; 'require form'; +'require baseclass'; 'require network'; -'require view.podkop.configSection as configSection'; -'require view.podkop.diagnosticTab as diagnosticTab'; -'require view.podkop.additionalTab as additionalTab'; -'require view.podkop.dashboardTab as dashboardTab'; -'require view.podkop.utils as utils'; 'require view.podkop.main as main'; -const EntryNode = { +// Settings content +'require view.podkop.settings as settings'; + +// Sections content +'require view.podkop.section as section'; + +// Dashboard content +'require view.podkop.dashboard as dashboard'; + + +const EntryPoint = { async render() { main.injectGlobalStyles(); - const podkopFormMap = new form.Map('podkop', '', null, ['main', 'extra']); + const podkopMap = new form.Map('podkop', _('Podkop Settings'), _('Configuration for Podkop service')); + // Enable tab views + podkopMap.tabbed = true; - // Main Section - const mainSection = podkopFormMap.section(form.TypedSection, 'main'); - mainSection.anonymous = true; - configSection.createConfigSection(mainSection); + // Settings tab + 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']; }; - // Additional Settings Tab (main section) - additionalTab.createAdditionalSection(mainSection); + // Render settings content + settings.createSettingsContent(settingsSection); - // Diagnostics Tab (main section) - diagnosticTab.createDiagnosticsSection(mainSection); - const podkopFormMapPromise = podkopFormMap.render().then((node) => { - // Set up diagnostics event handlers - diagnosticTab.setupDiagnosticsEventHandlers(node); - // Start critical error polling for all tabs - utils.startErrorPolling(); + // Sections tab + const sectionsSection = podkopMap.section(form.TypedSection, 'section', _('Sections')); + sectionsSection.anonymous = false; + sectionsSection.addremove = true; + sectionsSection.template = 'cbi/simpleform'; - // Add event listener to keep error polling active when switching tabs - const tabs = node.querySelectorAll('.cbi-tabmenu'); - if (tabs.length > 0) { - tabs[0].addEventListener('click', function (e) { - const tab = e.target.closest('.cbi-tab'); - if (tab) { - // Ensure error polling continues when switching tabs - utils.startErrorPolling(); - } - }); - } + // Render section content + section.createSectionContent(sectionsSection); - // Add visibility change handler to manage error polling - document.addEventListener('visibilitychange', function () { - if (document.hidden) { - utils.stopErrorPolling(); - } else { - utils.startErrorPolling(); - } - }); - return node; - }); + // Dashboard tab + const dashboardSection = podkopMap.section(form.TypedSection, 'dashboard', _('Dashboard')); + dashboardSection.anonymous = true; + dashboardSection.addremove = false; + // dashboardSection.title = ''; + dashboardSection.cfgsections = function () { return ['dashboard']; }; + + // Render dashboard content + dashboard.createDashboardContent(dashboardSection); - // Extra Section - const extraSection = podkopFormMap.section( - form.TypedSection, - 'extra', - _('Extra configurations'), - ); - extraSection.anonymous = false; - extraSection.addremove = true; - extraSection.addbtntitle = _('Add Section'); - extraSection.multiple = true; - configSection.createConfigSection(extraSection); - // Initial dashboard render - dashboardTab.createDashboardSection(mainSection); // Inject core service main.coreService(); - return podkopFormMapPromise; - }, -}; + return podkopMap.render(); + } +} -return view.extend(EntryNode); + +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 new file mode 100644 index 0000000..085a318 --- /dev/null +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js @@ -0,0 +1,586 @@ +'use strict'; +'require form'; +'require baseclass'; +'require tools.widgets as widgets'; +'require view.podkop.main as main'; + +function createSectionContent(section) { + let o = section.option( + form.ListValue, + 'mode', + _('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('mode', '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'; + o.validate = function (section_id, value) { + // Optional + if (!value || value.length === 0) { + return true; + } + + try { + const activeConfigs = main.splitProxyString(value); + + 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.', + ); + } + + 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:// 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, + 'ss_uot', + _('Shadowsocks UDP over TCP'), + _('Apply for SS2022'), + ); + o.default = '0'; + o.depends('mode', 'proxy'); + o.rmempty = false; + + o = section.option( + widgets.DeviceSelect, + 'interface', + _('Network Interface'), + _('Select network interface for VPN connection'), + ); + o.depends('mode', '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('mode', '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.Flag, + 'community_lists_enabled', + _('Community Lists'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.DynamicList, + 'community_lists', + _('Service List'), + _('Select predefined service 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.depends('community_lists_enabled', '1'); + o.rmempty = false; + + o = section.option( + form.ListValue, + 'user_domain_list_type', + _('User Domain List Type'), + _('Select how to add your 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 (example: sub.example.com or 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 comma, space or newline. You can add comments after //', + ), + ); + 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'); + } + + return true; + }; + + o = section.option( + form.Flag, + 'local_domain_lists_enabled', + _('Local Domain Lists'), + _('Use the list from the router filesystem'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.DynamicList, + 'local_domain_lists', + _('Local Domain List Paths'), + _('Enter the list file path'), + ); + o.placeholder = '/path/file.lst'; + o.depends('local_domain_lists_enabled', '1'); + o.rmempty = false; + o.validate = function (section_id, value) { + // Optional + if (!value || value.length === 0) { + return true; + } + + const validation = main.validatePath(value); + + if (validation.valid) { + return true; + } + + return validation.message; + }; + + o = section.option( + form.Flag, + 'remote_domain_lists_enabled', + _('Remote Domain Lists'), + _('Download and use domain lists from remote URLs'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.DynamicList, + 'remote_domain_lists', + _('Remote Domain URLs'), + _('Enter full URLs starting with http:// or https://'), + ); + o.placeholder = 'URL'; + o.depends('remote_domain_lists_enabled', '1'); + o.rmempty = false; + o.validate = function (section_id, value) { + // Optional + if (!value || value.length === 0) { + return true; + } + + const validation = main.validateUrl(value); + + if (validation.valid) { + return true; + } + + return validation.message; + }; + + o = section.option( + form.Flag, + 'local_subnet_lists_enabled', + _('Local Subnet Lists'), + _('Use the list from the router filesystem'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.DynamicList, + 'local_subnet_lists', + _('Local Subnet List Paths'), + _('Enter the list file path'), + ); + o.placeholder = '/path/file.lst'; + o.depends('local_subnet_lists_enabled', '1'); + o.rmempty = false; + o.validate = function (section_id, value) { + // Optional + if (!value || value.length === 0) { + return true; + } + + const validation = main.validatePath(value); + + if (validation.valid) { + return true; + } + + return validation.message; + }; + + o = section.option( + form.ListValue, + 'user_subnet_list_type', + _('User Subnet List Type'), + _('Select how to add your 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.DynamicList, + 'user_subnets', + _('User Subnets'), + _( + 'Enter subnets in CIDR notation (example: 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 validation = main.validateSubnet(value); + + if (validation.valid) { + return true; + } + + return validation.message; + }; + + o = section.option( + form.TextValue, + 'user_subnets_text', + _('User Subnets List'), + _( + 'Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //', + ), + ); + 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; + } + + const subnets = main.parseValueList(value); + + if (!subnets.length) { + return _( + 'At least one valid subnet or IP must be specified. Comments-only content is not allowed.', + ); + } + + const { valid, results } = main.bulkValidate(subnets, main.validateSubnet); + + 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'); + } + + return true; + }; + + o = section.option( + form.Flag, + 'remote_subnet_lists_enabled', + _('Remote Subnet Lists'), + _('Download and use subnet lists from remote URLs'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.DynamicList, + 'remote_subnet_lists', + _('Remote Subnet URLs'), + _('Enter full URLs starting with http:// or https://'), + ); + o.placeholder = 'URL'; + o.depends('remote_subnet_lists_enabled', '1'); + o.rmempty = false; + o.validate = function (section_id, value) { + // Optional + if (!value || value.length === 0) { + return true; + } + + const validation = main.validateUrl(value); + + if (validation.valid) { + return true; + } + + return validation.message; + }; + + o = section.option( + form.Flag, + 'all_traffic_from_ip_enabled', + _('IP for full redirection'), + _( + 'Specify local IP addresses whose traffic will always use the configured route', + ), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.DynamicList, + 'all_traffic_ip', + _('Local IPs'), + _('Enter valid IPv4 addresses'), + ); + o.placeholder = 'IP'; + o.depends('all_traffic_from_ip_enabled', '1'); + o.rmempty = false; + o.validate = function (section_id, value) { + // Optional + if (!value || value.length === 0) { + return true; + } + + const validation = main.validateSubnet(value); + + if (validation.valid) { + return true; + } + + return validation.message; + }; + + o = section.option( + form.Flag, + 'socks5', + _('Mixed enable'), + _('Browser port: 2080'), + ); + o.default = '0'; + o.rmempty = false; +} + +const EntryPoint = { + 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 new file mode 100644 index 0000000..0aeb3c7 --- /dev/null +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js @@ -0,0 +1,283 @@ +'use strict'; +'require form'; +'require baseclass'; +'require tools.widgets as widgets'; +'require view.podkop.main as main'; + +function createSettingsContent(section) { + let o = section.option( + form.Flag, + 'yacd', + _('Yacd enable'), + `${main.getBaseUrl()}:9090/ui`, + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'exclude_ntp', + _('Exclude NTP'), + _('Allows you to exclude NTP protocol traffic from the tunnel'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'quic_disable', + _('QUIC disable'), + _('For issues with the video stream'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.ListValue, + 'update_interval', + _('List Update Frequency'), + _('Select how often the lists will be updated'), + ); + Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { + o.value(key, _(label)); + }); + o.default = '1d'; + o.rmempty = false; + + 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); + + if (validation.valid) { + return true; + } + + 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); + + if (validation.valid) { + return true; + } + + 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'); + } + + const ttl = parseInt(value); + if (isNaN(ttl) || ttl < 0) { + return _('TTL must be a positive number'); + } + + return true; + }; + + 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'); + } + + if (!value.startsWith('/')) { + return _('Path must be absolute (start with /)'); + } + + 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)'); + } + + return true; + }; + + o = section.option( + widgets.DeviceSelect, + 'iface', + _('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); + + // If no device is found, allow the value + if (!device) { + return true; + } + + // 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'); + + // Allow only non-wireless devices + return !isWireless; + }; + + o = section.option( + form.Flag, + 'mon_restart_ifaces', + _('Interface monitoring'), + _('Interface monitoring for bad WAN'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + widgets.NetworkSelect, + 'restart_ifaces', + _('Interface for monitoring'), + _('Select the WAN interfaces to be monitored'), + ); + o.depends('mon_restart_ifaces', '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; + } + + // Otherwise allow it + return true; + }; + + o = section.option( + form.Value, + 'procd_reload_delay', + _('Interface Monitoring Delay'), + _('Delay in milliseconds before reloading podkop after interface UP'), + ); + o.depends('mon_restart_ifaces', '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, + 'dont_touch_dhcp', + _('Dont touch my DHCP!'), + _('Podkop will not change the DHCP config'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'detour', + _('Proxy download of lists'), + _('Downloading all lists via main Proxy/VPN'), + ); + o.default = '0'; + o.rmempty = false; + + +} + +const EntryPoint = { + createSettingsContent, +} + +return baseclass.extend(EntryPoint); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/utils.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/utils.js deleted file mode 100644 index f358670..0000000 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/utils.js +++ /dev/null @@ -1,163 +0,0 @@ -'use strict'; -'require baseclass'; -'require ui'; -'require fs'; -'require view.podkop.main as main'; - -// Flag to track if this is the first error check -let isInitialCheck = true; - -// Set to track which errors we've already seen -const lastErrorsSet = new Set(); - -// Timer for periodic error polling -let errorPollTimer = null; - -// Helper function to fetch errors from the podkop command -async function getPodkopErrors() { - return new Promise((resolve) => { - safeExec('/usr/bin/podkop', ['check_logs'], 'P0_PRIORITY', (result) => { - if (!result || !result.stdout) return resolve([]); - - const logs = result.stdout.split('\n'); - const errors = logs.filter((log) => log.includes('[critical]')); - - resolve(errors); - }); - }); -} - -// Show error notification to the user -function showErrorNotification(error, isMultiple = false) { - const notificationContent = E('div', { class: 'alert-message error' }, [ - E('pre', { class: 'error-log' }, error), - ]); - - ui.addNotification(null, notificationContent); -} - -// Helper function for command execution with prioritization -function safeExec( - command, - args, - priority, - callback, - timeout = main.COMMAND_TIMEOUT, -) { - // Default to highest priority execution if priority is not provided or invalid - let schedulingDelay = main.COMMAND_SCHEDULING.P0_PRIORITY; - - // If priority is a string, try to get the corresponding delay value - if ( - typeof priority === 'string' && - main.COMMAND_SCHEDULING[priority] !== undefined - ) { - schedulingDelay = main.COMMAND_SCHEDULING[priority]; - } - - const executeCommand = async () => { - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeout); - - const result = await Promise.race([ - fs.exec(command, args), - new Promise((_, reject) => { - controller.signal.addEventListener('abort', () => { - reject(new Error('Command execution timed out')); - }); - }), - ]); - - clearTimeout(timeoutId); - - if (callback && typeof callback === 'function') { - callback(result); - } - - return result; - } catch (error) { - console.warn( - `Command execution failed or timed out: ${command} ${args.join(' ')}`, - ); - const errorResult = { stdout: '', stderr: error.message, error: error }; - - if (callback && typeof callback === 'function') { - callback(errorResult); - } - - return errorResult; - } - }; - - if (callback && typeof callback === 'function') { - setTimeout(executeCommand, schedulingDelay); - return; - } else { - return executeCommand(); - } -} - -// Check for critical errors and show notifications -async function checkForCriticalErrors() { - try { - const errors = await getPodkopErrors(); - - if (errors && errors.length > 0) { - // Filter out errors we've already seen - const newErrors = errors.filter((error) => !lastErrorsSet.has(error)); - - if (newErrors.length > 0) { - // On initial check, just store errors without showing notifications - if (!isInitialCheck) { - // Show each new error as a notification - newErrors.forEach((error) => { - showErrorNotification(error, newErrors.length > 1); - }); - } - - // Add new errors to our set of seen errors - newErrors.forEach((error) => lastErrorsSet.add(error)); - } - } - - // After first check, mark as no longer initial - isInitialCheck = false; - } catch (error) { - console.error('Error checking for critical messages:', error); - } -} - -// Start polling for errors at regular intervals -function startErrorPolling() { - if (errorPollTimer) { - clearInterval(errorPollTimer); - } - - // Reset initial check flag to make sure we show errors - isInitialCheck = false; - - // Immediately check for errors on start - checkForCriticalErrors(); - - // Then set up periodic checks - errorPollTimer = setInterval( - checkForCriticalErrors, - main.ERROR_POLL_INTERVAL, - ); -} - -// Stop polling for errors -function stopErrorPolling() { - if (errorPollTimer) { - clearInterval(errorPollTimer); - errorPollTimer = null; - } -} - -return baseclass.extend({ - startErrorPolling, - stopErrorPolling, - checkForCriticalErrors, - safeExec, -}); From 9e0135983f9a6310d3606149aeab92d700c1ba16 Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 8 Oct 2025 23:33:56 +0300 Subject: [PATCH 002/121] fix: change yacd url for option --- .../htdocs/luci-static/resources/view/podkop/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0aeb3c7..2675f38 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 @@ -9,7 +9,7 @@ function createSettingsContent(section) { form.Flag, 'yacd', _('Yacd enable'), - `${main.getBaseUrl()}:9090/ui`, + `${main.getClashApiUrl()}/ui`, ); o.default = '0'; o.rmempty = false; From 34404f6e40a2c54bc162b870ef3026a71118587d Mon Sep 17 00:00:00 2001 From: divocat Date: Thu, 9 Oct 2025 22:19:16 +0300 Subject: [PATCH 003/121] feat: reorder section & settings tabs --- .../resources/view/podkop/podkop.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) 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 74f8766..5cc6b4c 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 @@ -23,6 +23,14 @@ const EntryPoint = { // Enable tab views podkopMap.tabbed = true; + // Sections tab + const sectionsSection = podkopMap.section(form.TypedSection, 'section', _('Sections')); + sectionsSection.anonymous = false; + sectionsSection.addremove = true; + sectionsSection.template = 'cbi/simpleform'; + + // Render section content + section.createSectionContent(sectionsSection); // Settings tab const settingsSection = podkopMap.section(form.TypedSection, 'settings', _('Settings')); @@ -35,16 +43,6 @@ const EntryPoint = { settings.createSettingsContent(settingsSection); - // Sections tab - const sectionsSection = podkopMap.section(form.TypedSection, 'section', _('Sections')); - sectionsSection.anonymous = false; - sectionsSection.addremove = true; - sectionsSection.template = 'cbi/simpleform'; - - // Render section content - section.createSectionContent(sectionsSection); - - // Dashboard tab const dashboardSection = podkopMap.section(form.TypedSection, 'dashboard', _('Dashboard')); dashboardSection.anonymous = true; From 9f1505db48fe947a716c29bee41b85ab28f70e60 Mon Sep 17 00:00:00 2001 From: divocat Date: Thu, 9 Oct 2025 22:36:08 +0300 Subject: [PATCH 004/121] fix: reorder options on settings tab --- .../resources/view/podkop/settings.js | 99 +++++++++---------- 1 file changed, 49 insertions(+), 50 deletions(-) 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 2675f38..c3eb46a 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 @@ -6,45 +6,6 @@ function createSettingsContent(section) { let o = section.option( - form.Flag, - 'yacd', - _('Yacd enable'), - `${main.getClashApiUrl()}/ui`, - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.Flag, - 'exclude_ntp', - _('Exclude NTP'), - _('Allows you to exclude NTP protocol traffic from the tunnel'), - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.Flag, - 'quic_disable', - _('QUIC disable'), - _('For issues with the video stream'), - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.ListValue, - 'update_interval', - _('List Update Frequency'), - _('Select how often the lists will be updated'), - ); - Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { - o.value(key, _(label)); - }); - o.default = '1d'; - o.rmempty = false; - - o = section.option( form.ListValue, 'dns_type', _('DNS Protocol Type'), @@ -170,6 +131,7 @@ function createSettingsContent(section) { return true; }; + o = section.option( widgets.DeviceSelect, 'iface', @@ -207,6 +169,54 @@ function createSettingsContent(section) { return !isWireless; }; + o = section.option( + form.ListValue, + 'update_interval', + _('List Update Frequency'), + _('Select how often the lists will be updated'), + ); + Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { + o.value(key, _(label)); + }); + o.default = '1d'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'quic_disable', + _('QUIC disable'), + _('For issues with the video stream'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'yacd', + _('Yacd enable'), + `${main.getClashApiUrl()}/ui`, + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'exclude_ntp', + _('Exclude NTP'), + _('Allows you to exclude NTP protocol traffic from the tunnel'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'detour', + _('Proxy download of lists'), + _('Downloading all lists via main Proxy/VPN'), + ); + o.default = '0'; + o.rmempty = false; + o = section.option( form.Flag, 'mon_restart_ifaces', @@ -263,17 +273,6 @@ function createSettingsContent(section) { ); o.default = '0'; o.rmempty = false; - - o = section.option( - form.Flag, - 'detour', - _('Proxy download of lists'), - _('Downloading all lists via main Proxy/VPN'), - ); - o.default = '0'; - o.rmempty = false; - - } const EntryPoint = { From 3042a8641210bd979f2cba08d80c77112554f224 Mon Sep 17 00:00:00 2001 From: divocat Date: Thu, 9 Oct 2025 22:56:14 +0300 Subject: [PATCH 005/121] feat: init diagnostic tab --- .../podkop/tabs/dashboard/renderDashboard.ts | 1 + .../src/podkop/tabs/diagnostic/index.ts | 2 ++ .../diagnostic/initDiagnosticController.ts | 7 ++++++ .../tabs/diagnostic/renderDiagnostic.ts | 10 ++++++++ fe-app-podkop/src/podkop/tabs/index.ts | 1 + .../resources/view/podkop/diagnostic.js | 22 +++++++++++++++++ .../luci-static/resources/view/podkop/main.js | 24 ++++++++++++++++++- .../resources/view/podkop/podkop.js | 13 +++++++++- 8 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/index.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts create mode 100644 luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnostic.js diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/renderDashboard.ts b/fe-app-podkop/src/podkop/tabs/dashboard/renderDashboard.ts index b4151e2..c647cc6 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/renderDashboard.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/renderDashboard.ts @@ -47,6 +47,7 @@ export function renderDashboard() { }, onTestLatency: () => {}, onChooseOutbound: () => {}, + latencyFetching: false, }), ), ], diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts new file mode 100644 index 0000000..01db02e --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts @@ -0,0 +1,2 @@ +export * from './renderDiagnostic'; +export * from './initDiagnosticController'; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts new file mode 100644 index 0000000..e1e82e2 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts @@ -0,0 +1,7 @@ +import { onMount } from '../../../helpers'; + +export async function initDiagnosticController(): Promise { + onMount('diagnostic-status').then(() => { + console.log('diagnostic controller initialized.'); + }); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts new file mode 100644 index 0000000..5e8f7fc --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts @@ -0,0 +1,10 @@ +export function renderDiagnostic() { + return E( + 'div', + { + id: 'diagnostic-status', + class: 'pdk_diagnostic-page', + }, + 'Not implemented yet', + ); +} diff --git a/fe-app-podkop/src/podkop/tabs/index.ts b/fe-app-podkop/src/podkop/tabs/index.ts index b58b6c9..b49ac00 100644 --- a/fe-app-podkop/src/podkop/tabs/index.ts +++ b/fe-app-podkop/src/podkop/tabs/index.ts @@ -1 +1,2 @@ export * from './dashboard'; +export * from './diagnostic'; 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 new file mode 100644 index 0000000..e94cae0 --- /dev/null +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/diagnostic.js @@ -0,0 +1,22 @@ +'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.initDiagnosticController(); + return main.renderDiagnostic(); + }; +} + +const EntryPoint = { + createDiagnosticContent, +}; + +return baseclass.extend(EntryPoint); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index b173c39..8f7e7b7 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1503,7 +1503,8 @@ function renderDashboard() { onTestLatency: () => { }, onChooseOutbound: () => { - } + }, + latencyFetching: false }) ) ] @@ -1971,6 +1972,25 @@ async function initDashboardController() { connectToClashSockets(); }); } + +// src/podkop/tabs/diagnostic/renderDiagnostic.ts +function renderDiagnostic() { + return E( + "div", + { + id: "diagnostic-status", + class: "pdk_diagnostic-page" + }, + "Not implemented yet" + ); +} + +// src/podkop/tabs/diagnostic/initDiagnosticController.ts +async function initDiagnosticController() { + onMount("diagnostic-status").then(() => { + console.log("diagnostic controller initialized."); + }); +} return baseclass.extend({ ALLOWED_WITH_RUSSIA_INSIDE, BOOTSTRAP_DNS_SERVER_OPTIONS, @@ -2010,6 +2030,7 @@ return baseclass.extend({ getProxyUrlName, getSingboxStatus, initDashboardController, + initDiagnosticController, injectGlobalStyles, maskIP, onMount, @@ -2017,6 +2038,7 @@ return baseclass.extend({ parseValueList, preserveScrollForPage, renderDashboard, + renderDiagnostic, splitProxyString, triggerLatencyGroupTest, triggerLatencyProxyTest, 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 5cc6b4c..c1656bb 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 @@ -14,6 +14,9 @@ // Dashboard content 'require view.podkop.dashboard as dashboard'; +// Diagnostic content +'require view.podkop.diagnostic as diagnostic'; + const EntryPoint = { async render() { @@ -43,11 +46,19 @@ const EntryPoint = { settings.createSettingsContent(settingsSection); + // Diagnostic tab + const diagnosticSection = podkopMap.section(form.TypedSection, 'diagnostic', _('Diagnostics')); + diagnosticSection.anonymous = true; + diagnosticSection.addremove = false; + diagnosticSection.cfgsections = function () { return ['diagnostic']; }; + + // Render diagnostic content + diagnostic.createDiagnosticContent(diagnosticSection); + // Dashboard tab const dashboardSection = podkopMap.section(form.TypedSection, 'dashboard', _('Dashboard')); dashboardSection.anonymous = true; dashboardSection.addremove = false; - // dashboardSection.title = ''; dashboardSection.cfgsections = function () { return ['dashboard']; }; // Render dashboard content From e662f25f53185da62b0d656eec218674c58ee2a4 Mon Sep 17 00:00:00 2001 From: divocat Date: Thu, 9 Oct 2025 23:09:54 +0300 Subject: [PATCH 006/121] fix: reorder options on settings tab --- .../resources/view/podkop/settings.js | 195 +++++++++--------- 1 file changed, 97 insertions(+), 98 deletions(-) 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 c3eb46a..bf2476c 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 @@ -82,56 +82,6 @@ function createSettingsContent(section) { return true; }; - 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'); - } - - if (!value.startsWith('/')) { - return _('Path must be absolute (start with /)'); - } - - 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)'); - } - - return true; - }; - - o = section.option( widgets.DeviceSelect, 'iface', @@ -169,54 +119,6 @@ function createSettingsContent(section) { return !isWireless; }; - o = section.option( - form.ListValue, - 'update_interval', - _('List Update Frequency'), - _('Select how often the lists will be updated'), - ); - Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { - o.value(key, _(label)); - }); - o.default = '1d'; - o.rmempty = false; - - o = section.option( - form.Flag, - 'quic_disable', - _('QUIC disable'), - _('For issues with the video stream'), - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.Flag, - 'yacd', - _('Yacd enable'), - `${main.getClashApiUrl()}/ui`, - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.Flag, - 'exclude_ntp', - _('Exclude NTP'), - _('Allows you to exclude NTP protocol traffic from the tunnel'), - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.Flag, - 'detour', - _('Proxy download of lists'), - _('Downloading all lists via main Proxy/VPN'), - ); - o.default = '0'; - o.rmempty = false; - o = section.option( form.Flag, 'mon_restart_ifaces', @@ -265,6 +167,45 @@ function createSettingsContent(section) { return true; }; + o = section.option( + form.ListValue, + 'update_interval', + _('List Update Frequency'), + _('Select how often the lists will be updated'), + ); + Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { + o.value(key, _(label)); + }); + o.default = '1d'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'yacd', + _('Yacd enable'), + `${main.getClashApiUrl()}/ui`, + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'quic_disable', + _('QUIC disable'), + _('For issues with the video stream'), + ); + o.default = '0'; + o.rmempty = false; + + o = section.option( + form.Flag, + 'detour', + _('Proxy download of lists'), + _('Downloading all lists via main Proxy/VPN'), + ); + o.default = '0'; + o.rmempty = false; + o = section.option( form.Flag, 'dont_touch_dhcp', @@ -273,6 +214,64 @@ function createSettingsContent(section) { ); 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.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.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)'); + } + + return true; + }; + + o = section.option( + form.Flag, + 'exclude_ntp', + _('Exclude NTP'), + _('Allows you to exclude NTP protocol traffic from the tunnel'), + ); + o.default = '0'; + o.rmempty = false; } const EntryPoint = { From d2f0de39d93710736de82dd06a4d9918a39e2cd5 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 10:24:50 +0500 Subject: [PATCH 007/121] refactor: remove legacy migration logic; make migration() a no-op --- podkop/files/usr/bin/podkop | 74 +------------------------------------ 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 847e1b2..ac922f7 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -194,79 +194,7 @@ restart() { # Migrations and validation funcs migration() { - # list migrate - local CONFIG="/etc/config/podkop" - - if grep -q "ru_inside" $CONFIG; then - log "Deprecated list found: ru_inside" - sed -i '/ru_inside/d' $CONFIG - fi - - if grep -q "list domain_list 'ru_outside'" $CONFIG; then - log "Deprecated list found: sru_outside" - sed -i '/ru_outside/d' $CONFIG - fi - - if grep -q "list domain_list 'ua'" $CONFIG; then - log "Deprecated list found: ua" - sed -i '/ua/d' $CONFIG - fi - - # Subnet list - if grep -q "list subnets" $CONFIG; then - log "Deprecated second section found" - sed -i '/list subnets/d' $CONFIG - fi - - # second remove - if grep -q "config second 'second'" $CONFIG; then - log "Deprecated second section found" - sed -i '/second/d' $CONFIG - fi - - # cron update - if grep -qE "^\s*option update_interval '[0-9*/,-]+( [0-9*/,-]+){4}'" $CONFIG; then - log "Deprecated update_interval" - sed -i "s|^\(\s*option update_interval\) '[0-9*/,-]\+\( [0-9*/,-]\+\)\{4\}'|\1 '1d'|" $CONFIG - fi - - # dnsmasq https - if grep -q "^filter-rr=HTTPS" "/etc/dnsmasq.conf"; then - log "Found and removed filter-rr=HTTPS in dnsmasq config" - sed -i '/^filter-rr=HTTPS/d' "/etc/dnsmasq.conf" - fi - - # dhcp use-application-dns.net - if grep -q "use-application-dns.net" "/etc/config/dhcp"; then - log "Found and removed use-application-dns.net in dhcp config" - sed -i '/use-application-dns/d' "/etc/config/dhcp" - fi - - # corntab init.d - (crontab -l | grep -v "/etc/init.d/podkop list_update") | crontab - - - migration_rename_config_key "$CONFIG" "option" "domain_list_enabled" "community_lists_enabled" - migration_rename_config_key "$CONFIG" "list" "domain_list" "community_lists" - - migration_rename_config_key "$CONFIG" "option" "custom_domains_list_type" "user_domain_list_type" - migration_rename_config_key "$CONFIG" "option" "custom_domains_text" "user_domains_text" - migration_rename_config_key "$CONFIG" "list" "custom_domains" "user_domains" - - migration_rename_config_key "$CONFIG" "option" "custom_subnets_list_enabled" "user_subnet_list_type" - migration_rename_config_key "$CONFIG" "option" "custom_subnets_text" "user_subnets_text" - migration_rename_config_key "$CONFIG" "list" "custom_subnets" "user_subnets" - - migration_rename_config_key "$CONFIG" "option" "custom_local_domains_list_enabled" "local_domain_lists_enabled" - migration_rename_config_key "$CONFIG" "list" "custom_local_domains" "local_domain_lists" - - migration_rename_config_key "$CONFIG" "option" "custom_download_domains_list_enabled" "remote_domain_lists_enabled" - migration_rename_config_key "$CONFIG" "list" "custom_download_domains" "remote_domain_lists" - - migration_rename_config_key "$CONFIG" "option" "custom_download_subnets_list_enabled" "remote_subnet_lists_enabled" - migration_rename_config_key "$CONFIG" "list" "custom_download_subnets" "remote_subnet_lists" - - migration_rename_config_key "$CONFIG" "option" "cache_file" "cache_path" - migration_add_new_option "podkop" "main" "config_path" "/etc/sing-box/config.json" && config_load "$PODKOP_CONFIG" + : } validate_service() { From 8a80df9dc0137ea67ed0651383724e4a3bdb9636 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 10:38:59 +0500 Subject: [PATCH 008/121] refactor: Pass 'section' to config_foreach so outbound handler iterates only the correct sections --- podkop/files/usr/bin/podkop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index ac922f7..0cd2a8f 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -571,7 +571,7 @@ sing_box_configure_outbounds() { config=$(sing_box_cm_add_direct_outbound "$config" "$SB_DIRECT_OUTBOUND_TAG") - config_foreach configure_outbound_handler + config_foreach configure_outbound_handler "section" } configure_outbound_handler() { From ba91c180e8de6f3149bdc8d8f32af15d7ece3b41 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 13:22:30 +0500 Subject: [PATCH 009/121] refactor: switch UCI lookups from 'main' to 'settings', add routing_excluded_ips and relocate update_interval in UI --- .../resources/view/podkop/settings.js | 47 ++++++++++---- podkop/files/etc/init.d/podkop | 10 +-- podkop/files/usr/bin/podkop | 62 +++++++++---------- 3 files changed, 71 insertions(+), 48 deletions(-) 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 bf2476c..4430945 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 @@ -167,18 +167,6 @@ function createSettingsContent(section) { return true; }; - o = section.option( - form.ListValue, - 'update_interval', - _('List Update Frequency'), - _('Select how often the lists will be updated'), - ); - Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { - o.value(key, _(label)); - }); - o.default = '1d'; - o.rmempty = false; - o = section.option( form.Flag, 'yacd', @@ -197,6 +185,18 @@ function createSettingsContent(section) { o.default = '0'; o.rmempty = false; + o = section.option( + form.ListValue, + 'update_interval', + _('List Update Frequency'), + _('Select how often the lists will be updated'), + ); + Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { + o.value(key, _(label)); + }); + o.default = '1d'; + o.rmempty = false; + o = section.option( form.Flag, 'detour', @@ -272,6 +272,29 @@ function createSettingsContent(section) { ); 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; + } + + const validation = main.validateIPV4(value); + + if (validation.valid) { + return true; + } + + return validation.message; + }; } const EntryPoint = { diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index 1e4c73d..49fbde9 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -10,8 +10,8 @@ config_load "$NAME" start_service() { echo "Start podkop" - config_get mon_restart_ifaces "main" "mon_restart_ifaces" - config_get restart_ifaces "main" "restart_ifaces" + config_get mon_restart_ifaces "settings" "mon_restart_ifaces" + config_get restart_ifaces "settings" "restart_ifaces" procd_open_instance procd_set_param command /usr/bin/podkop start @@ -32,9 +32,9 @@ reload_service() { service_triggers() { echo "service_triggers start" - config_get mon_restart_ifaces "main" "mon_restart_ifaces" - config_get restart_ifaces "main" "restart_ifaces" - config_get procd_reload_delay "main" "procd_reload_delay" "2000" + config_get mon_restart_ifaces "settings" "mon_restart_ifaces" + config_get restart_ifaces "settings" "restart_ifaces" + config_get procd_reload_delay "settings" "procd_reload_delay" "2000" PROCD_RELOAD_DELAY=$procd_reload_delay diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 0cd2a8f..53b3d0e 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -71,7 +71,7 @@ check_requirements() { log "Detected https-dns-proxy in dhcp config. Edit /etc/config/dhcp" "warn" fi - local proxy_string interface outbound_json urltest_proxy_links dont_touch_dhcp + local proxy_string interface outbound_json urltest_proxy_links config_get proxy_string "main" "proxy_string" config_get interface "main" "interface" config_get outbound_json "main" "outbound_json" @@ -113,7 +113,7 @@ start_main() { /etc/init.d/sing-box start local exclude_ntp - config_get_bool exclude_ntp "main" "exclude_ntp" "0" + config_get_bool exclude_ntp "settings" "exclude_ntp" "0" if [ "$exclude_ntp" -eq 1 ]; then log "NTP traffic exclude for proxy" nft insert rule inet "$NFT_TABLE_NAME" mangle udp dport 123 return @@ -126,11 +126,11 @@ start_main() { start() { start_main - config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" 0 + config_get_bool dont_touch_dhcp "settings" "dont_touch_dhcp" 0 if [ "$dont_touch_dhcp" -eq 0 ]; then dnsmasq_add_resolver fi - uci_set "podkop" "main" "shutdown_correctly" 0 + uci_set "podkop" "settings" "shutdown_correctly" 0 uci commit "podkop" && config_load "$PODKOP_CONFIG" } @@ -171,12 +171,12 @@ stop_main() { stop() { local dont_touch_dhcp - config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" 0 + config_get_bool dont_touch_dhcp "settings" "dont_touch_dhcp" 0 if [ "$dont_touch_dhcp" -eq 0 ]; then dnsmasq_restore fi stop_main - uci_set "podkop" "main" "shutdown_correctly" 1 + uci_set "podkop" "settings" "shutdown_correctly" 1 uci commit "podkop" && config_load "$PODKOP_CONFIG" } @@ -252,7 +252,7 @@ nft_init_interfaces_set() { nft_create_ifname_set "$NFT_TABLE_NAME" "$NFT_INTERFACE_SET_NAME" local interface_list - config_get interface_list "main" "iface" "br-lan" + config_get interface_list "settings" "iface" "br-lan" for interface in $interface_list; do nft add element inet "$NFT_TABLE_NAME" "$NFT_INTERFACE_SET_NAME" "{ $interface }" @@ -322,7 +322,7 @@ backup_dnsmasq_config_option() { dnsmasq_add_resolver() { local shutdown_correctly - config_get shutdown_correctly "main" "shutdown_correctly" + config_get shutdown_correctly "settings" "shutdown_correctly" if [ "$shutdown_correctly" -eq 0 ]; then log "Previous shutdown of podkop was not correct, reconfiguration of dnsmasq is not required" return 0 @@ -354,7 +354,7 @@ dnsmasq_add_resolver() { dnsmasq_restore() { log "Restoring the dnsmasq configuration" local shutdown_correctly - config_get shutdown_correctly "main" "shutdown_correctly" + config_get shutdown_correctly "settings" "shutdown_correctly" if [ "$shutdown_correctly" -eq 1 ]; then log "Previous shutdown of podkop was correct, reconfiguration of dnsmasq is not required" return 0 @@ -408,7 +408,7 @@ add_cron_job() { config_get community_lists_enabled "$section" "community_lists_enabled" config_get remote_domain_lists_enabled "$section" "remote_domain_lists_enabled" config_get remote_subnet_lists_enabled "$section" "remote_subnet_lists_enabled" - config_get update_interval "main" "update_interval" + config_get update_interval "settings" "update_interval" case "$update_interval" in "1h") @@ -469,7 +469,7 @@ list_update() { fi for i in $(seq 1 60); do - config_get_bool detour "main" "detour" "0" + config_get_bool detour "settings" "detour" "0" if [ "$detour" -eq 1 ]; then if http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" curl -s -m 3 https://github.com > /dev/null; then echolog "✅ GitHub connection check passed (via proxy)" @@ -522,7 +522,7 @@ sing_box_uci() { log "sing-box service user has been changed to root" fi - config_get sing_box_config_path "main" "config_path" + config_get sing_box_config_path "settings" "config_path" sing_box_conffile=$(uci get "sing-box.main.conffile") log "sing-box config path: $sing_box_config_path" "debug" log "sing-box service conffile: $sing_box_conffile" "debug" @@ -689,9 +689,9 @@ sing_box_configure_dns() { log "Adding DNS Servers" "debug" local dns_type dns_server bootstrap_dns_server dns_domain_resolver dns_server_address - config_get dns_type "main" "dns_type" "doh" - config_get dns_server "main" "dns_server" "1.1.1.1" - config_get bootstrap_dns_server "main" "bootstrap_dns_server" "77.88.8.8" + config_get dns_type "settings" "dns_type" "doh" + config_get dns_server "settings" "dns_server" "1.1.1.1" + config_get bootstrap_dns_server "settings" "bootstrap_dns_server" "77.88.8.8" dns_server_address="$(url_get_host "$dns_server")" if ! is_ipv4 "$dns_server_address"; then @@ -704,7 +704,7 @@ sing_box_configure_dns() { log "Adding DNS Rules" local rewrite_ttl service_domains - config_get rewrite_ttl "main" "dns_rewrite_ttl" "60" + config_get rewrite_ttl "settings" "dns_rewrite_ttl" "60" config=$(sing_box_cm_add_dns_reject_rule "$config" "query_type" "HTTPS") config=$(sing_box_cm_add_dns_reject_rule "$config" "domain_suffix" '"use-application-dns.net"') @@ -731,7 +731,7 @@ sing_box_configure_route() { config=$(sing_box_cm_add_hijack_dns_route_rule "$config" "protocol" "dns") local quic_disable - config_get_bool quic_disable "main" "quic_disable" 0 + config_get_bool quic_disable "settings" "quic_disable" 0 if [ "$quic_disable" -eq 1 ]; then config=$(sing_box_cf_add_single_key_reject_rule "$config" "$SB_TPROXY_INBOUND_TAG" "protocol" "quic") fi @@ -745,12 +745,12 @@ sing_box_configure_route() { configure_common_reject_route_rule - local exclude_from_ip_enabled - config_get_bool exclude_from_ip_enabled "main" "exclude_from_ip_enabled" 0 - if [ "$exclude_from_ip_enabled" -eq 1 ]; then + local routing_excluded_ips + config_get_bool routing_excluded_ips "settings" "routing_excluded_ips" + if [ -n "$routing_excluded_ips" ]; then rule_tag="$(gen_id)" config=$(sing_box_cm_add_route_rule "$config" "$rule_tag" "$SB_TPROXY_INBOUND_TAG" "$SB_DIRECT_OUTBOUND_TAG") - config_list_foreach "main" "exclude_traffic_ip" exclude_source_ip_from_routing_handler "$rule_tag" + config_list_foreach "settings" "routing_excluded_ips" exclude_source_ip_from_routing_handler "$rule_tag" fi config_foreach configure_routing_for_section_lists @@ -909,7 +909,7 @@ configure_community_list_handler() { format="binary" url="$SRS_MAIN_URL/$tag.srs" detour="$(get_download_detour_tag)" - config_get update_interval "main" "update_interval" "1d" + config_get update_interval "settings" "update_interval" "1d" config=$(sing_box_cm_add_remote_ruleset "$config" "$ruleset_tag" "$format" "$url" "$detour" "$update_interval") config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag") @@ -1027,7 +1027,7 @@ configure_remote_domain_or_subnet_list_handler() { ruleset_tag=$(get_ruleset_tag "$section" "$basename" "remote-$type") format="$(get_ruleset_format_by_file_extension "$file_extension")" detour="$(get_download_detour_tag)" - config_get update_interval "main" "update_interval" "1d" + config_get update_interval "settings" "update_interval" "1d" config=$(sing_box_cm_add_remote_ruleset "$config" "$ruleset_tag" "$format" "$url" "$detour" "$update_interval") config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag") @@ -1050,11 +1050,11 @@ sing_box_configure_experimental() { log "Configuring cache database" local cache_file - config_get cache_file "main" "cache_path" "/tmp/sing-box/cache.db" + config_get cache_file "settings" "cache_path" "/tmp/sing-box/cache.db" config=$(sing_box_cm_configure_cache_file "$config" true "$cache_file" true) local yacd_enabled external_controller_ui - config_get_bool yacd_enabled "main" "yacd" 0 + config_get_bool yacd_enabled "settings" "yacd" 0 log "Configuring Clash API" if [ "$yacd_enabled" -eq 1 ]; then log "YACD is enabled, enabling Clash API with downloadable YACD" "debug" @@ -1094,7 +1094,7 @@ sing_box_additional_inbounds() { sing_box_save_config() { local sing_box_config_path temp_file_path current_config_hash temp_config_hash - config_get sing_box_config_path "main" "config_path" + config_get sing_box_config_path "settings" "config_path" temp_file_path="$(mktemp)" log "Save sing-box temporary config to $temp_file_path" "debug" @@ -1340,7 +1340,7 @@ import_subnets_from_remote_srs_file() { ## Support functions get_service_proxy_address() { local detour - config_get_bool detour "main" "detour" 0 + config_get_bool detour "settings" "detour" 0 if [ "$detour" -eq 1 ]; then echo "$SB_SERVICE_MIXED_INBOUND_ADDRESS:$SB_SERVICE_MIXED_INBOUND_PORT" else @@ -1349,7 +1349,7 @@ get_service_proxy_address() { } get_download_detour_tag() { - config_get_bool detour "main" "detour" 0 + config_get_bool detour "settings" "detour" 0 if [ "${detour:-0}" -eq 1 ]; then echo "$SB_MAIN_OUTBOUND_TAG" else @@ -1409,7 +1409,7 @@ nft_list_all_traffic_from_ip() { # Diagnotics check_proxy() { local sing_box_config_path - config_get sing_box_config_path "main" "config_path" + config_get sing_box_config_path "settings" "config_path" if ! command -v sing-box > /dev/null 2>&1; then nolog "sing-box is not installed" @@ -1567,7 +1567,7 @@ check_github() { "$SUBNETS_TWITTER" "$SUBNETS_META" "$SUBNETS_DISCORD"; do local list_name=$(basename "$url") - config_get_bool detour "main" "detour" "0" + config_get_bool detour "settings" "detour" "0" if [ "$detour" -eq 1 ]; then http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -q -O /dev/null "$url" else @@ -1652,7 +1652,7 @@ check_logs() { show_sing_box_config() { local sing_box_config_path - config_get sing_box_config_path "main" "config_path" + config_get sing_box_config_path "settings" "config_path" nolog "Current sing-box configuration:" if [ ! -f "$sing_box_config_path" ]; then From d3847db3137bee59b4eb277c0b8f2c3353387b5a Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 14:21:30 +0500 Subject: [PATCH 010/121] feat: Add mixed proxy per section with UI port option and sing-box integration --- .../resources/view/podkop/section.js | 22 +++++-- podkop/files/usr/bin/podkop | 58 ++++++++++--------- 2 files changed, 48 insertions(+), 32 deletions(-) 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 085a318..3dde984 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 @@ -311,7 +311,7 @@ function createSectionContent(section) { ); } - const { valid, results } = main.bulkValidate(domains, row => main.validateDomain(row, true)); + const {valid, results} = main.bulkValidate(domains, row => main.validateDomain(row, true)); if (!valid) { const errors = results @@ -488,7 +488,7 @@ function createSectionContent(section) { ); } - const { valid, results } = main.bulkValidate(subnets, main.validateSubnet); + const {valid, results} = main.bulkValidate(subnets, main.validateSubnet); if (!valid) { const errors = results @@ -571,12 +571,24 @@ function createSectionContent(section) { o = section.option( form.Flag, - 'socks5', - _('Mixed enable'), - _('Browser port: 2080'), + '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 = { diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 53b3d0e..2d9f55e 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -90,7 +90,7 @@ start_main() { migration - config_foreach process_validate_service + config_foreach process_validate_service "section" br_netfilter_disable @@ -109,7 +109,7 @@ start_main() { # sing-box sing_box_init_config - config_foreach add_cron_job + config_foreach add_cron_job "section" /etc/init.d/sing-box start local exclude_ntp @@ -493,9 +493,9 @@ list_update() { echolog "📥 Downloading and processing lists..." - config_foreach import_community_subnet_lists - config_foreach import_domains_from_remote_domain_lists - config_foreach import_subnets_from_remote_subnet_lists + config_foreach import_community_subnet_lists "section" + config_foreach import_domains_from_remote_domain_lists "section" + config_foreach import_subnets_from_remote_subnet_lists "section" if [ $? -eq 0 ]; then echolog "✅ Lists update completed successfully" @@ -719,13 +719,8 @@ sing_box_configure_route() { config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" true "$SB_DNS_SERVER_TAG") - local sniff_inbounds mixed_inbound_enabled - config_get_bool mixed_inbound_enabled "main" "socks5" 0 - if [ "$mixed_inbound_enabled" -eq 1 ]; then - sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG,$SB_MIXED_INBOUND_TAG") - else - sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG") - fi + local sniff_inbounds + sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG,$SB_MIXED_INBOUND_TAG") config=$(sing_box_cm_sniff_route_rule "$config" "inbound" "$sniff_inbounds") config=$(sing_box_cm_add_hijack_dns_route_rule "$config" "protocol" "dns") @@ -741,7 +736,7 @@ sing_box_configure_route() { ) config=$(sing_box_cf_override_domain_port "$config" "$FAKEIP_TEST_DOMAIN" 8443) - config_foreach include_source_ips_in_routing_handler + config_foreach include_source_ips_in_routing_handler "section" configure_common_reject_route_rule @@ -753,7 +748,7 @@ sing_box_configure_route() { config_list_foreach "settings" "routing_excluded_ips" exclude_source_ip_from_routing_handler "$rule_tag" fi - config_foreach configure_routing_for_section_lists + config_foreach configure_routing_for_section_lists "section" } include_source_ips_in_routing_handler() { @@ -1069,19 +1064,6 @@ sing_box_configure_experimental() { sing_box_additional_inbounds() { log "Configure the additional inbounds of a sing-box JSON configuration" - local mixed_inbound_enabled - config_get_bool mixed_inbound_enabled "main" "socks5" 0 - if [ "$mixed_inbound_enabled" -eq 1 ]; then - config=$( - sing_box_cf_add_mixed_inbound_and_route_rule \ - "$config" \ - "$SB_MIXED_INBOUND_TAG" \ - "$SB_MIXED_INBOUND_ADDRESS" \ - "$SB_MIXED_INBOUND_PORT" \ - "$SB_MAIN_OUTBOUND_TAG" - ) - fi - config=$( sing_box_cf_add_mixed_inbound_and_route_rule \ "$config" \ @@ -1090,6 +1072,28 @@ sing_box_additional_inbounds() { "$SB_SERVICE_MIXED_INBOUND_PORT" \ "$SB_MAIN_OUTBOUND_TAG" ) + + config_foreach configure_section_mixed_proxy "section" +} + +configure_section_mixed_proxy() { + local section="$1" + + local mixed_inbound_enabled mixed_proxy_port mixed_inbound_tag mixed_outbound_tag + config_get_bool mixed_inbound_enabled "$section" "mixed_proxy_enabled" 0 + config_get mixed_proxy_port "$section" "mixed_proxy_port" + if [ "$mixed_inbound_enabled" -eq 1 ]; then + mixed_inbound_tag="$(get_inbound_tag_by_section "$section-mixed")" + mixed_outbound_tag="$(get_outbound_tag_by_section "$section")" + config=$( + sing_box_cf_add_mixed_inbound_and_route_rule \ + "$config" \ + "$mixed_inbound_tag" \ + "$SB_MIXED_INBOUND_ADDRESS" \ + "$mixed_proxy_port" \ + "$mixed_outbound_tag" + ) + fi } sing_box_save_config() { From 9be0eb3e571fcd163ea9715f343ff2511d2f80f2 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 14:36:38 +0500 Subject: [PATCH 011/121] refactor: rename all_traffic_ip to fully_routed_ips, remove all_traffic_from_ip_enabled flag, update handlers --- .../resources/view/podkop/section.js | 22 +++++-------------- podkop/files/usr/bin/podkop | 8 +++---- 2 files changed, 9 insertions(+), 21 deletions(-) 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 3dde984..8d41053 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 @@ -534,25 +534,13 @@ function createSectionContent(section) { return validation.message; }; - o = section.option( - form.Flag, - 'all_traffic_from_ip_enabled', - _('IP for full redirection'), - _( - 'Specify local IP addresses whose traffic will always use the configured route', - ), - ); - o.default = '0'; - o.rmempty = false; - o = section.option( form.DynamicList, - 'all_traffic_ip', - _('Local IPs'), - _('Enter valid IPv4 addresses'), + 'fully_routed_ips', + _('Fully Routed IPs'), + _('Specify local IP addresses whose traffic will always be routed through the configured route'), ); o.placeholder = 'IP'; - o.depends('all_traffic_from_ip_enabled', '1'); o.rmempty = false; o.validate = function (section_id, value) { // Optional @@ -573,7 +561,7 @@ function createSectionContent(section) { form.Flag, 'mixed_proxy_enabled', _('Enable Mixed Proxy'), - _('Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies.'), + _('Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies'), ); o.default = '0'; o.rmempty = false; @@ -584,7 +572,7 @@ function createSectionContent(section) { _('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.' + 'Make sure the selected port is not used by another service' ), ); o.rmempty = false; diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 2d9f55e..91ce5f1 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -754,15 +754,15 @@ sing_box_configure_route() { include_source_ips_in_routing_handler() { local section="$1" - local all_traffic_from_ip_enabled rule_tag - config_get all_traffic_from_ip_enabled "$section" "all_traffic_from_ip_enabled" 0 - if [ "$all_traffic_from_ip_enabled" -eq 1 ]; then + local fully_routed_ips rule_tag + config_get fully_routed_ips "$section" "fully_routed_ips" + if [ -n "$fully_routed_ips" ]; then rule_tag="$(gen_id)" config=$( sing_box_cm_add_route_rule \ "$config" "$rule_tag" "$SB_TPROXY_INBOUND_TAG" "$(get_outbound_tag_by_section "$section")" ) - config_list_foreach "$section" "all_traffic_ip" include_source_ip_in_routing_handler "$rule_tag" + config_list_foreach "$section" "fully_routed_ips" include_source_ip_in_routing_handler "$rule_tag" fi } From ba69e3eaccaa338fbdf37bf9e2e61940970b6363 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 16:45:28 +0500 Subject: [PATCH 012/121] refactor: use list presence instead of *_enabled flags, simplify UI texts/placeholders, remove mixed inbound tag --- .../resources/view/podkop/section.js | 242 +++++++----------- podkop/files/usr/bin/podkop | 110 ++++---- podkop/files/usr/lib/constants.sh | 2 - 3 files changed, 145 insertions(+), 209 deletions(-) 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 8d41053..b338a5a 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 @@ -42,8 +42,7 @@ function createSectionContent(section) { 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'; + 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'; o.validate = function (section_id, value) { // Optional if (!value || value.length === 0) { @@ -54,15 +53,11 @@ function createSectionContent(section) { const activeConfigs = main.splitProxyString(value); if (!activeConfigs.length) { - return _( - 'No active configuration found. One configuration is required.', - ); + return _('No active configuration found. One configuration is required.'); } if (activeConfigs.length > 1) { - return _( - 'Multiply active configurations found. Please leave one configuration.', - ); + return _('Multiply active configurations found. Please leave one configuration.'); } const validation = main.validateProxyUrl(activeConfigs[0]); @@ -224,33 +219,24 @@ function createSectionContent(section) { return validation.message; }; - o = section.option( - form.Flag, - 'community_lists_enabled', - _('Community Lists'), - ); - o.default = '0'; - o.rmempty = false; - o = section.option( form.DynamicList, 'community_lists', - _('Service List'), - _('Select predefined service for routing') + + _('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.depends('community_lists_enabled', '1'); - o.rmempty = false; + o.rmempty = true; o = section.option( form.ListValue, 'user_domain_list_type', _('User Domain List Type'), - _('Select how to add your custom domains'), + _('Select the list type for adding custom domains'), ); o.value('disabled', _('Disabled')); o.value('dynamic', _('Dynamic List')); @@ -262,9 +248,7 @@ function createSectionContent(section) { form.DynamicList, 'user_domains', _('User Domains'), - _( - 'Enter domain names without protocols (example: sub.example.com or example.com)', - ), + _('Enter domain names without protocols, e.g. example.com or sub.example.com'), ); o.placeholder = 'Domains list'; o.depends('user_domain_list_type', 'dynamic'); @@ -288,12 +272,9 @@ function createSectionContent(section) { form.TextValue, 'user_domains_text', _('User Domains List'), - _( - 'Enter domain names separated by comma, space or newline. You can add comments after //', - ), + _('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.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; @@ -306,9 +287,7 @@ function createSectionContent(section) { const domains = main.parseValueList(value); if (!domains.length) { - return _( - 'At least one valid domain must be specified. Comments-only content is not allowed.', - ); + 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)); @@ -324,110 +303,11 @@ function createSectionContent(section) { return true; }; - o = section.option( - form.Flag, - 'local_domain_lists_enabled', - _('Local Domain Lists'), - _('Use the list from the router filesystem'), - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.DynamicList, - 'local_domain_lists', - _('Local Domain List Paths'), - _('Enter the list file path'), - ); - o.placeholder = '/path/file.lst'; - o.depends('local_domain_lists_enabled', '1'); - o.rmempty = false; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validatePath(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = section.option( - form.Flag, - 'remote_domain_lists_enabled', - _('Remote Domain Lists'), - _('Download and use domain lists from remote URLs'), - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.DynamicList, - 'remote_domain_lists', - _('Remote Domain URLs'), - _('Enter full URLs starting with http:// or https://'), - ); - o.placeholder = 'URL'; - o.depends('remote_domain_lists_enabled', '1'); - o.rmempty = false; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validateUrl(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - - o = section.option( - form.Flag, - 'local_subnet_lists_enabled', - _('Local Subnet Lists'), - _('Use the list from the router filesystem'), - ); - o.default = '0'; - o.rmempty = false; - - o = section.option( - form.DynamicList, - 'local_subnet_lists', - _('Local Subnet List Paths'), - _('Enter the list file path'), - ); - o.placeholder = '/path/file.lst'; - o.depends('local_subnet_lists_enabled', '1'); - o.rmempty = false; - o.validate = function (section_id, value) { - // Optional - if (!value || value.length === 0) { - return true; - } - - const validation = main.validatePath(value); - - if (validation.valid) { - return true; - } - - return validation.message; - }; - o = section.option( form.ListValue, 'user_subnet_list_type', _('User Subnet List Type'), - _('Select how to add your custom subnets'), + _('Select the list type for adding custom subnets'), ); o.value('disabled', _('Disabled')); o.value('dynamic', _('Dynamic List')); @@ -439,9 +319,7 @@ function createSectionContent(section) { form.DynamicList, 'user_subnets', _('User Subnets'), - _( - 'Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses', - ), + _('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'); @@ -466,7 +344,8 @@ function createSectionContent(section) { 'user_subnets_text', _('User Subnets List'), _( - 'Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //', + 'Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. ' + + 'You can add comments using //' ), ); o.placeholder = @@ -483,9 +362,7 @@ function createSectionContent(section) { const subnets = main.parseValueList(value); if (!subnets.length) { - return _( - 'At least one valid subnet or IP must be specified. Comments-only content is not allowed.', - ); + return _('At least one valid subnet or IP must be specified. Comments-only content is not allowed.'); } const {valid, results} = main.bulkValidate(subnets, main.validateSubnet); @@ -502,23 +379,82 @@ function createSectionContent(section) { }; o = section.option( - form.Flag, - 'remote_subnet_lists_enabled', - _('Remote Subnet Lists'), - _('Download and use subnet lists from remote URLs'), + form.DynamicList, + 'local_domain_lists', + _('Local Domain Lists'), + _('Specify the path to the list file located on the router filesystem'), ); - o.default = '0'; - o.rmempty = false; + 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); + + if (validation.valid) { + return true; + } + + return validation.message; + }; + + 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.validatePath(value); + + if (validation.valid) { + return true; + } + + 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; + } + + const validation = main.validateUrl(value); + + if (validation.valid) { + return true; + } + + return validation.message; + }; o = section.option( form.DynamicList, 'remote_subnet_lists', - _('Remote Subnet URLs'), - _('Enter full URLs starting with http:// or https://'), + _('Remote Subnet Lists'), + _('Specify remote URLs to download and use subnet lists'), ); - o.placeholder = 'URL'; - o.depends('remote_subnet_lists_enabled', '1'); - o.rmempty = false; + o.placeholder = 'https://example.com/subnets.srs'; + o.rmempty = true; o.validate = function (section_id, value) { // Optional if (!value || value.length === 0) { @@ -538,10 +474,10 @@ function createSectionContent(section) { form.DynamicList, 'fully_routed_ips', _('Fully Routed IPs'), - _('Specify local IP addresses whose traffic will always be routed through the configured route'), + _('Specify local IP addresses or subnets whose traffic will always be routed through the configured route'), ); - o.placeholder = 'IP'; - o.rmempty = false; + 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) { diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 91ce5f1..0aab46e 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -211,9 +211,10 @@ validate_service() { } process_validate_service() { - local community_lists_enabled - config_get_bool community_lists_enabled "$section" "community_lists_enabled" 0 - if [ "$community_lists_enabled" -eq 1 ]; then + local section="$1" + local community_lists + config_get community_lists "$section" "community_lists" + if [ -n "$community_lists" ]; then config_list_foreach "$section" "community_lists" validate_service fi } @@ -404,10 +405,10 @@ dnsmasq_restore() { add_cron_job() { ## Future: make a check so that it doesn't recreate many times - local community_lists_enabled remote_domain_lists_enabled remote_subnet_lists_enabled update_interval - config_get community_lists_enabled "$section" "community_lists_enabled" - config_get remote_domain_lists_enabled "$section" "remote_domain_lists_enabled" - config_get remote_subnet_lists_enabled "$section" "remote_subnet_lists_enabled" + local community_lists remote_domain_lists remote_subnet_lists update_interval + config_get community_lists "$section" "community_lists" + config_get remote_domain_lists "$section" "remote_domain_lists" + config_get remote_subnet_lists "$section" "remote_subnet_lists" config_get update_interval "settings" "update_interval" case "$update_interval" in @@ -432,9 +433,9 @@ add_cron_job() { ;; esac - if [ "$community_lists_enabled" -eq 1 ] || - [ "$remote_domain_lists_enabled" -eq 1 ] || - [ "$remote_subnet_lists_enabled" -eq 1 ]; then + if [ -n "$community_lists" ] || + [ -n "$remote_domain_lists" ] || + [ -n "$remote_subnet_lists" ]; then remove_cron_job crontab -l | { cat @@ -720,7 +721,7 @@ sing_box_configure_route() { config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" true "$SB_DNS_SERVER_TAG") local sniff_inbounds - sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG,$SB_MIXED_INBOUND_TAG") + sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG") config=$(sing_box_cm_sniff_route_rule "$config" "inbound" "$sniff_inbounds") config=$(sing_box_cm_add_hijack_dns_route_rule "$config" "protocol" "dns") @@ -803,20 +804,21 @@ exclude_source_ip_from_routing_handler() { configure_routing_for_section_lists() { local section="$1" + log "Configuring routing for '$section' section" if ! section_has_enabled_lists "$section"; then log "Section '$section' does not have any enabled list, skipping..." "warn" return 0 fi - local community_lists_enabled user_domain_list_type local_domain_lists_enabled remote_domain_lists_enabled \ - user_subnet_list_type local_subnet_lists_enabled remote_subnet_lists_enabled section_mode_type route_rule_tag - config_get_bool community_lists_enabled "$section" "community_lists_enabled" 0 + local community_lists user_domain_list_type user_subnet_list_type local_domain_lists local_subnet_lists \ + remote_domain_lists remote_subnet_lists section_mode_type route_rule_tag + config_get_bool community_lists "$section" "community_lists" config_get user_domain_list_type "$section" "user_domain_list_type" "disabled" - config_get_bool local_domain_lists_enabled "$section" "local_domain_lists_enabled" 0 - config_get_bool remote_domain_lists_enabled "$section" "remote_domain_lists_enabled" 0 config_get user_subnet_list_type "$section" "user_subnet_list_type" "disabled" - config_get_bool local_subnet_lists_enabled "$section" "local_subnet_lists_enabled" 0 - config_get_bool remote_subnet_lists_enabled "$section" "remote_subnet_lists_enabled" 0 + config_get_bool local_domain_lists "$section" "local_domain_lists" + config_get_bool local_subnet_lists "$section" "local_subnet_lists" + config_get_bool remote_domain_lists "$section" "remote_domain_lists" + config_get_bool remote_subnet_lists "$section" "remote_subnet_lists" config_get section_mode_type "$section" "mode" if [ "$section_mode_type" = "block" ]; then @@ -827,7 +829,7 @@ configure_routing_for_section_lists() { config=$(sing_box_cm_add_route_rule "$config" "$route_rule_tag" "$SB_TPROXY_INBOUND_TAG" "$outbound_tag") fi - if [ "$community_lists_enabled" -eq 1 ]; then + if [ -n "$community_lists" ]; then log "Processing community list routing rules for '$section' section" config_list_foreach "$section" "community_lists" configure_community_list_handler "$section" "$route_rule_tag" fi @@ -838,30 +840,30 @@ configure_routing_for_section_lists() { configure_user_domain_or_subnets_list "$section" "domains" "$route_rule_tag" fi - if [ "$local_domain_lists_enabled" -eq 1 ]; then - log "Processing local domains routing rules for '$section' section" - configure_local_domain_or_subnet_lists "$section" "domains" "$route_rule_tag" - fi - - if [ "$remote_domain_lists_enabled" -eq 1 ]; then - log "Processing remote domains routing rules for '$section' section" - prepare_common_ruleset "$section" "domains" "$route_rule_tag" - config_list_foreach "$section" "remote_domain_lists" configure_remote_domain_or_subnet_list_handler \ - "domains" "$section" "$route_rule_tag" - fi - if [ "$user_subnet_list_type" != "disabled" ]; then log "Processing user subnets routing rules for '$section' section" prepare_common_ruleset "$section" "subnets" "$route_rule_tag" configure_user_domain_or_subnets_list "$section" "subnets" "$route_rule_tag" fi - if [ "$local_subnet_lists_enabled" -eq 1 ]; then + if [ -n "$local_domain_lists" ]; then + log "Processing local domains routing rules for '$section' section" + configure_local_domain_or_subnet_lists "$section" "domains" "$route_rule_tag" + fi + + if [ -n "$local_subnet_lists" ]; then log "Processing local subnets routing rules for '$section' section" configure_local_domain_or_subnet_lists "$section" "subnets" "$route_rule_tag" fi - if [ "$remote_subnet_lists_enabled" -eq 1 ]; then + if [ -n "$remote_domain_lists" ]; then + log "Processing remote domains routing rules for '$section' section" + prepare_common_ruleset "$section" "domains" "$route_rule_tag" + config_list_foreach "$section" "remote_domain_lists" configure_remote_domain_or_subnet_list_handler \ + "domains" "$section" "$route_rule_tag" + fi + + if [ -n "$remote_subnet_lists" ]; then log "Processing remote subnets routing rules for '$section' section" prepare_common_ruleset "$section" "subnets" "$route_rule_tag" config_list_foreach "$section" "remote_subnet_lists" configure_remote_domain_or_subnet_list_handler \ @@ -1130,9 +1132,9 @@ sing_box_config_check() { import_community_subnet_lists() { local section="$1" - local community_lists_enabled - config_get_bool community_lists_enabled "$section" "community_lists_enabled" 0 - if [ "$community_lists_enabled" -eq 1 ]; then + local community_lists + config_get community_lists "$section" "community_lists" + if [ -n "$community_lists" ]; then log "Importing community subnet lists for '$section' section" config_list_foreach "$section" "community_lists" import_community_service_subnet_list_handler fi @@ -1198,9 +1200,9 @@ import_community_service_subnet_list_handler() { import_domains_from_remote_domain_lists() { local section="$1" - local remote_domain_lists_enabled - config_get remote_domain_lists_enabled "$section" "remote_domain_lists_enabled" - if [ "$remote_domain_lists_enabled" -eq 1 ]; then + local remote_domain_lists + config_get remote_domain_lists "$section" "remote_domain_lists" + if [ -n "$remote_domain_lists" ]; then log "Importing domains from remote domain lists for '$section' section" config_list_foreach "$section" "remote_domain_lists" import_domains_from_remote_domain_list_handler "$section" fi @@ -1227,9 +1229,9 @@ import_domains_from_remote_domain_list_handler() { import_subnets_from_remote_subnet_lists() { local section="$1" - - config_get remote_subnet_lists_enabled "$section" "remote_subnet_lists_enabled" - if [ "$remote_subnet_lists_enabled" -eq 1 ]; then + local remote_subnet_lists + config_get remote_subnet_lists "$section" "remote_subnet_lists" + if [ -n "$remote_subnet_lists" ]; then log "Importing subnets from remote subnet lists for '$section' section" config_list_foreach "$section" "remote_subnet_lists" import_subnets_from_remote_subnet_list_handler "$section" fi @@ -1375,24 +1377,24 @@ block_section_exists() { section_has_enabled_lists() { local section="$1" - local community_lists_enabled user_domain_list_type local_domain_lists_enabled remote_domain_lists_enabled \ - user_subnet_list_type local_subnet_lists_enabled remote_subnet_lists_enabled + local community_lists user_domain_list_type user_subnet_list_type local_domain_lists local_subnet_lists \ + remote_domain_lists remote_subnet_lists - config_get_bool community_lists_enabled "$section" "community_lists_enabled" 0 + config_get_bool community_lists "$section" "community_lists" config_get user_domain_list_type "$section" "user_domain_list_type" "disabled" - config_get_bool local_domain_lists_enabled "$section" "local_domain_lists_enabled" 0 - config_get_bool remote_domain_lists_enabled "$section" "remote_domain_lists_enabled" 0 config_get user_subnet_list_type "$section" "user_subnet_list_type" "disabled" - config_get_bool local_subnet_lists_enabled "$section" "local_subnet_lists_enabled" 0 - config_get_bool remote_subnet_lists_enabled "$section" "remote_subnet_lists_enabled" 0 + config_get_bool local_domain_lists "$section" "local_domain_lists" + config_get_bool local_subnet_lists "$section" "local_subnet_lists" + config_get_bool remote_domain_lists "$section" "remote_domain_lists" + config_get_bool remote_subnet_lists "$section" "remote_subnet_lists" - if [ "$community_lists_enabled" -ne 0 ] || + if [ -n "$community_lists" ] || [ "$user_domain_list_type" != "disabled" ] || - [ "$local_domain_lists_enabled" -ne 0 ] || - [ "$remote_domain_lists_enabled" -ne 0 ] || [ "$user_subnet_list_type" != "disabled" ] || - [ "$local_subnet_lists_enabled" -ne 0 ] || - [ "$remote_subnet_lists_enabled" -ne 0 ]; then + [ -n "$local_domain_lists" ] || + [ -n "$local_subnet_lists" ] || + [ -n "$remote_domain_lists" ] || + [ -n "$remote_subnet_lists" ]; then return 0 else return 1 diff --git a/podkop/files/usr/lib/constants.sh b/podkop/files/usr/lib/constants.sh index 42e4156..621ffe9 100644 --- a/podkop/files/usr/lib/constants.sh +++ b/podkop/files/usr/lib/constants.sh @@ -38,9 +38,7 @@ SB_TPROXY_INBOUND_PORT=1602 SB_DNS_INBOUND_TAG="dns-in" SB_DNS_INBOUND_ADDRESS="127.0.0.42" SB_DNS_INBOUND_PORT=53 -SB_MIXED_INBOUND_TAG="mixed-in" SB_MIXED_INBOUND_ADDRESS="0.0.0.0" # TODO(ampetelin): maybe to determine address? -SB_MIXED_INBOUND_PORT=2080 SB_SERVICE_MIXED_INBOUND_TAG="service-mixed-in" SB_SERVICE_MIXED_INBOUND_ADDRESS="127.0.0.1" SB_SERVICE_MIXED_INBOUND_PORT=4534 From 095b3c6fa9ab0014c1737122028962080b350f0a Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 16:57:54 +0500 Subject: [PATCH 013/121] chore: improve wording and capitalization of settings UI labels and descriptions --- .../resources/view/podkop/settings.js | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) 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 4430945..b4b96f8 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 @@ -42,9 +42,7 @@ function createSettingsContent(section) { form.Value, 'bootstrap_dns_server', _('Bootstrap DNS server'), - _( - 'The DNS server used to look up the IP address of an upstream 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)); @@ -122,8 +120,8 @@ function createSettingsContent(section) { o = section.option( form.Flag, 'mon_restart_ifaces', - _('Interface monitoring'), - _('Interface monitoring for bad WAN'), + _('Interface Monitoring'), + _('Interface monitoring for Bad WAN'), ); o.default = '0'; o.rmempty = false; @@ -170,7 +168,7 @@ function createSettingsContent(section) { o = section.option( form.Flag, 'yacd', - _('Yacd enable'), + _('Enable YACD'), `${main.getClashApiUrl()}/ui`, ); o.default = '0'; @@ -179,8 +177,8 @@ function createSettingsContent(section) { o = section.option( form.Flag, 'quic_disable', - _('QUIC disable'), - _('For issues with the video stream'), + _('Disable QUIC'), + _('Disable the QUIC protocol to improve compatibility or fix issues with video streaming'), ); o.default = '0'; o.rmempty = false; @@ -189,7 +187,7 @@ function createSettingsContent(section) { form.ListValue, 'update_interval', _('List Update Frequency'), - _('Select how often the lists will be updated'), + _('Select how often the domain or subnet lists are updated automatically'), ); Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => { o.value(key, _(label)); @@ -200,7 +198,7 @@ function createSettingsContent(section) { o = section.option( form.Flag, 'detour', - _('Proxy download of lists'), + _('Download Lists via Proxy/VPN'), _('Downloading all lists via main Proxy/VPN'), ); o.default = '0'; @@ -209,8 +207,8 @@ function createSettingsContent(section) { o = section.option( form.Flag, 'dont_touch_dhcp', - _('Dont touch my DHCP!'), - _('Podkop will not change the DHCP config'), + _('Dont Touch My DHCP!'), + _('Podkop will not modify your DHCP configuration'), ); o.default = '0'; o.rmempty = false; @@ -219,9 +217,7 @@ function createSettingsContent(section) { form.ListValue, 'config_path', _('Config File Path'), - _( - 'Select path for sing-box config file. Change this ONLY if you know what you are doing', - ), + _('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)'); @@ -232,9 +228,7 @@ function createSettingsContent(section) { 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', - ), + _('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( @@ -268,7 +262,7 @@ function createSettingsContent(section) { form.Flag, 'exclude_ntp', _('Exclude NTP'), - _('Allows you to exclude NTP protocol traffic from the tunnel'), + _('Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN'), ); o.default = '0'; o.rmempty = false; From 687334bf8da66a3977ca2cc028a2d54c3ac4bcd5 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 17:25:06 +0500 Subject: [PATCH 014/121] refactor: rename config key 'mode' to 'connection_type' --- .../resources/view/podkop/section.js | 2 +- podkop/files/usr/bin/podkop | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) 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 b338a5a..7d86ff7 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 @@ -7,7 +7,7 @@ function createSectionContent(section) { let o = section.option( form.ListValue, - 'mode', + 'connection_type', _('Connection Type'), _('Select between VPN and Proxy connection methods for traffic routing'), ); diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 0aab46e..82f2d0e 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -578,11 +578,11 @@ sing_box_configure_outbounds() { configure_outbound_handler() { local section="$1" - local connection_mode - config_get connection_mode "$section" "mode" - case "$connection_mode" in + local connection_type + config_get connection_type "$section" "connection_type" + case "$connection_type" in proxy) - log "Configuring outbound in proxy connection mode for the $section section" + log "Configuring outbound in proxy connection type for the $section section" local proxy_config_type config_get proxy_config_type "$section" "proxy_config_type" @@ -645,7 +645,7 @@ configure_outbound_handler() { esac ;; vpn) - log "Configuring outbound in VPN connection mode for the $section section" + log "Configuring outbound in VPN connection type for the $section section" local interface_name domain_resolver_enabled domain_resolver_dns_type domain_resolver_dns_server \ domain_resolver_dns_server_address outbound_tag domain_resolver_tag dns_domain_resolver @@ -675,10 +675,10 @@ configure_outbound_handler() { config=$(sing_box_cm_add_interface_outbound "$config" "$outbound_tag" "$interface_name" "$domain_resolver_tag") ;; block) - log "Connection mode 'block' detected for the $section section – no outbound will be created (handled via reject route rules)" + log "Connection type 'block' detected for the $section section – no outbound will be created (handled via reject route rules)" ;; *) - log "Unknown connection mode '$connection_mode' for the $section section. Aborted." "fatal" + log "Unknown connection type '$connection_type' for the $section section. Aborted." "fatal" exit 1 ;; esac @@ -811,7 +811,7 @@ configure_routing_for_section_lists() { fi local community_lists user_domain_list_type user_subnet_list_type local_domain_lists local_subnet_lists \ - remote_domain_lists remote_subnet_lists section_mode_type route_rule_tag + remote_domain_lists remote_subnet_lists section_connection_type route_rule_tag config_get_bool community_lists "$section" "community_lists" config_get user_domain_list_type "$section" "user_domain_list_type" "disabled" config_get user_subnet_list_type "$section" "user_subnet_list_type" "disabled" @@ -819,9 +819,9 @@ configure_routing_for_section_lists() { config_get_bool local_subnet_lists "$section" "local_subnet_lists" config_get_bool remote_domain_lists "$section" "remote_domain_lists" config_get_bool remote_subnet_lists "$section" "remote_subnet_lists" - config_get section_mode_type "$section" "mode" + config_get section_connection_type "$section" "connection_type" - if [ "$section_mode_type" = "block" ]; then + if [ "$section_connection_type" = "block" ]; then route_rule_tag="$SB_REJECT_RULE_TAG" else route_rule_tag="$(gen_id)" @@ -1364,11 +1364,11 @@ get_download_detour_tag() { } get_block_sections() { - uci show podkop | grep "\.mode='block'" | cut -d'.' -f2 + uci show podkop | grep "\.connection_type='block'" | cut -d'.' -f2 } block_section_exists() { - if uci show podkop | grep -q "\.mode='block'"; then + if uci show podkop | grep -q "\.connection_type='block'"; then return 0 else return 1 From 036808917d77f88707628019005432028b316822 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 17:26:33 +0500 Subject: [PATCH 015/121] refactor: rename 'iface' to 'source_network_interfaces' --- .../htdocs/luci-static/resources/view/podkop/settings.js | 2 +- podkop/files/usr/bin/podkop | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 b4b96f8..bfc5fbf 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 @@ -82,7 +82,7 @@ function createSettingsContent(section) { o = section.option( widgets.DeviceSelect, - 'iface', + 'source_network_interfaces', _('Source Network Interface'), _('Select the network interface from which the traffic will originate'), ); diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 82f2d0e..cfeeeb9 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -252,10 +252,10 @@ route_table_rule_mark() { nft_init_interfaces_set() { nft_create_ifname_set "$NFT_TABLE_NAME" "$NFT_INTERFACE_SET_NAME" - local interface_list - config_get interface_list "settings" "iface" "br-lan" + local source_network_interfaces + config_get source_network_interfaces "settings" "source_network_interfaces" "br-lan" - for interface in $interface_list; do + for interface in $source_network_interfaces; do nft add element inet "$NFT_TABLE_NAME" "$NFT_INTERFACE_SET_NAME" "{ $interface }" done } From 1e6b555bfafae9ddec9c8dc2aafbf71b5084f57c Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 19:29:16 +0500 Subject: [PATCH 016/121] refactor: rename 'yacd' to 'enable_yacd' --- .../htdocs/luci-static/resources/view/podkop/settings.js | 2 +- podkop/files/usr/bin/podkop | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 bfc5fbf..31b6b5e 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 @@ -167,7 +167,7 @@ function createSettingsContent(section) { o = section.option( form.Flag, - 'yacd', + 'enable_yacd', _('Enable YACD'), `${main.getClashApiUrl()}/ui`, ); diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index cfeeeb9..4342d72 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1050,10 +1050,10 @@ sing_box_configure_experimental() { config_get cache_file "settings" "cache_path" "/tmp/sing-box/cache.db" config=$(sing_box_cm_configure_cache_file "$config" true "$cache_file" true) - local yacd_enabled external_controller_ui - config_get_bool yacd_enabled "settings" "yacd" 0 + local enable_yacd external_controller_ui + config_get_bool enable_yacd "settings" "enable_yacd" 0 log "Configuring Clash API" - if [ "$yacd_enabled" -eq 1 ]; then + if [ "$enable_yacd" -eq 1 ]; then log "YACD is enabled, enabling Clash API with downloadable YACD" "debug" local external_controller_ui="ui" config=$(sing_box_cm_configure_clash_api "$config" "$SB_CLASH_API_CONTROLLER" "$external_controller_ui") From e3557f374e1928b7997aafce9da8f75717a40daf Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 19:30:21 +0500 Subject: [PATCH 017/121] refactor: rename 'quic_disabled' to 'disable_quic' --- .../htdocs/luci-static/resources/view/podkop/settings.js | 2 +- podkop/files/usr/bin/podkop | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 31b6b5e..9cf20cc 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 @@ -176,7 +176,7 @@ function createSettingsContent(section) { o = section.option( form.Flag, - 'quic_disable', + 'disable_quic', _('Disable QUIC'), _('Disable the QUIC protocol to improve compatibility or fix issues with video streaming'), ); diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 4342d72..e8f56eb 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -726,9 +726,9 @@ sing_box_configure_route() { config=$(sing_box_cm_add_hijack_dns_route_rule "$config" "protocol" "dns") - local quic_disable - config_get_bool quic_disable "settings" "quic_disable" 0 - if [ "$quic_disable" -eq 1 ]; then + local disable_quic + config_get_bool disable_quic "settings" "disable_quic" 0 + if [ "$disable_quic" -eq 1 ]; then config=$(sing_box_cf_add_single_key_reject_rule "$config" "$SB_TPROXY_INBOUND_TAG" "protocol" "quic") fi From 35d94418373516379822dc4d7f5af7d47cc33d18 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 19:33:37 +0500 Subject: [PATCH 018/121] refactor: rename 'detour' to 'download_lists_via_proxy' --- .../resources/view/podkop/settings.js | 2 +- podkop/files/usr/bin/podkop | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) 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 9cf20cc..086926e 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 @@ -197,7 +197,7 @@ function createSettingsContent(section) { o = section.option( form.Flag, - 'detour', + 'download_lists_via_proxy', _('Download Lists via Proxy/VPN'), _('Downloading all lists via main Proxy/VPN'), ); diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index e8f56eb..b7e5f87 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -470,8 +470,8 @@ list_update() { fi for i in $(seq 1 60); do - config_get_bool detour "settings" "detour" "0" - if [ "$detour" -eq 1 ]; then + config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" "0" + if [ "$download_lists_via_proxy" -eq 1 ]; then if http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" curl -s -m 3 https://github.com > /dev/null; then echolog "✅ GitHub connection check passed (via proxy)" break @@ -1177,7 +1177,7 @@ import_community_service_subnet_list_handler() { *) return 0 ;; esac - local tmpfile detour http_proxy_address subnets + local tmpfile http_proxy_address subnets tmpfile=$(mktemp) http_proxy_address="$(get_service_proxy_address)" @@ -1345,9 +1345,9 @@ import_subnets_from_remote_srs_file() { ## Support functions get_service_proxy_address() { - local detour - config_get_bool detour "settings" "detour" 0 - if [ "$detour" -eq 1 ]; then + local download_lists_via_proxy + config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" 0 + if [ "$download_lists_via_proxy" -eq 1 ]; then echo "$SB_SERVICE_MIXED_INBOUND_ADDRESS:$SB_SERVICE_MIXED_INBOUND_PORT" else echo "" @@ -1355,8 +1355,8 @@ get_service_proxy_address() { } get_download_detour_tag() { - config_get_bool detour "settings" "detour" 0 - if [ "${detour:-0}" -eq 1 ]; then + config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" 0 + if [ "$download_lists_via_proxy" -eq 1 ]; then echo "$SB_MAIN_OUTBOUND_TAG" else echo "" @@ -1573,8 +1573,8 @@ check_github() { "$SUBNETS_TWITTER" "$SUBNETS_META" "$SUBNETS_DISCORD"; do local list_name=$(basename "$url") - config_get_bool detour "settings" "detour" "0" - if [ "$detour" -eq 1 ]; then + config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" "0" + if [ "$download_lists_via_proxy" -eq 1 ]; then http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -q -O /dev/null "$url" else wget -q -O /dev/null "$url" From 458fd9251a6785150dfb02ee636165811174ed38 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 19:35:14 +0500 Subject: [PATCH 019/121] refactor: rename 'mon_restart_ifaces' to 'enable_badwan_interface_monitoring' --- .../resources/view/podkop/settings.js | 6 +-- podkop/files/etc/init.d/podkop | 40 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) 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 086926e..78180ef 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 @@ -119,7 +119,7 @@ function createSettingsContent(section) { o = section.option( form.Flag, - 'mon_restart_ifaces', + 'enable_badwan_interface_monitoring', _('Interface Monitoring'), _('Interface monitoring for Bad WAN'), ); @@ -132,7 +132,7 @@ function createSettingsContent(section) { _('Interface for monitoring'), _('Select the WAN interfaces to be monitored'), ); - o.depends('mon_restart_ifaces', '1'); + 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'] @@ -155,7 +155,7 @@ function createSettingsContent(section) { _('Interface Monitoring Delay'), _('Delay in milliseconds before reloading podkop after interface UP'), ); - o.depends('mon_restart_ifaces', '1'); + o.depends('enable_badwan_interface_monitoring', '1'); o.default = '2000'; o.rmempty = false; o.validate = function (section_id, value) { diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index 49fbde9..469fdf0 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -10,15 +10,15 @@ config_load "$NAME" start_service() { echo "Start podkop" - config_get mon_restart_ifaces "settings" "mon_restart_ifaces" - config_get restart_ifaces "settings" "restart_ifaces" + config_get enable_badwan_interface_monitoring "settings" "enable_badwan_interface_monitoring" + config_get restart_ifaces "settings" "restart_ifaces" - procd_open_instance - procd_set_param command /usr/bin/podkop start - [ "$mon_restart_ifaces" = "1" ] && [ -n "$restart_ifaces" ] && procd_set_param netdev $restart_ifaces - procd_set_param stdout 1 - procd_set_param stderr 1 - procd_close_instance + procd_open_instance + procd_set_param command /usr/bin/podkop start + [ "$enable_badwan_interface_monitoring" = "1" ] && [ -n "$restart_ifaces" ] && procd_set_param netdev $restart_ifaces + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance } stop_service() { @@ -32,19 +32,19 @@ reload_service() { service_triggers() { echo "service_triggers start" - config_get mon_restart_ifaces "settings" "mon_restart_ifaces" - config_get restart_ifaces "settings" "restart_ifaces" - config_get procd_reload_delay "settings" "procd_reload_delay" "2000" + config_get enable_badwan_interface_monitoring "settings" "enable_badwan_interface_monitoring" + config_get restart_ifaces "settings" "restart_ifaces" + config_get procd_reload_delay "settings" "procd_reload_delay" "2000" PROCD_RELOAD_DELAY=$procd_reload_delay - procd_open_trigger - procd_add_config_trigger "config.change" "$NAME" "$initscript" restart 'on_config_change' + procd_open_trigger + procd_add_config_trigger "config.change" "$NAME" "$initscript" restart 'on_config_change' - if [ "$mon_restart_ifaces" = "1" ]; then - for iface in $restart_ifaces; do - procd_add_interface_trigger "interface.*.up" "$iface" /etc/init.d/podkop reload - done - fi - procd_close_trigger -} \ No newline at end of file + if [ "$enable_badwan_interface_monitoring" = "1" ]; then + for iface in $restart_ifaces; do + procd_add_interface_trigger "interface.*.up" "$iface" /etc/init.d/podkop reload + done + fi + procd_close_trigger +} From f128bc4ec7eb13bf9611701f0f3b90c04fd54bf8 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 19:43:46 +0500 Subject: [PATCH 020/121] refactor: rename 'restart_ifaces' to 'badwan_monitored_interfaces' --- .../luci-static/resources/view/podkop/settings.js | 4 ++-- podkop/files/etc/init.d/podkop | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) 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 78180ef..f43a9b6 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 @@ -128,8 +128,8 @@ function createSettingsContent(section) { o = section.option( widgets.NetworkSelect, - 'restart_ifaces', - _('Interface for monitoring'), + 'badwan_monitored_interfaces', + _('Monitored Interfaces'), _('Select the WAN interfaces to be monitored'), ); o.depends('enable_badwan_interface_monitoring', '1'); diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index 469fdf0..309f566 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -1,4 +1,5 @@ #!/bin/sh /etc/rc.common +# shellcheck disable=SC2034,SC2154 START=99 USE_PROCD=1 @@ -11,11 +12,12 @@ start_service() { echo "Start podkop" config_get enable_badwan_interface_monitoring "settings" "enable_badwan_interface_monitoring" - config_get restart_ifaces "settings" "restart_ifaces" + config_get badwan_monitored_interfaces "settings" "badwan_monitored_interfaces" procd_open_instance procd_set_param command /usr/bin/podkop start - [ "$enable_badwan_interface_monitoring" = "1" ] && [ -n "$restart_ifaces" ] && procd_set_param netdev $restart_ifaces + [ "$enable_badwan_interface_monitoring" = "1" ] && [ -n "$badwan_monitored_interfaces" ] && + procd_set_param netdev "$badwan_monitored_interfaces" procd_set_param stdout 1 procd_set_param stderr 1 procd_close_instance @@ -33,7 +35,7 @@ service_triggers() { echo "service_triggers start" config_get enable_badwan_interface_monitoring "settings" "enable_badwan_interface_monitoring" - config_get restart_ifaces "settings" "restart_ifaces" + config_get badwan_monitored_interfaces "settings" "badwan_monitored_interfaces" config_get procd_reload_delay "settings" "procd_reload_delay" "2000" PROCD_RELOAD_DELAY=$procd_reload_delay @@ -42,7 +44,7 @@ service_triggers() { procd_add_config_trigger "config.change" "$NAME" "$initscript" restart 'on_config_change' if [ "$enable_badwan_interface_monitoring" = "1" ]; then - for iface in $restart_ifaces; do + for iface in $badwan_monitored_interfaces; do procd_add_interface_trigger "interface.*.up" "$iface" /etc/init.d/podkop reload done fi From ca5a3a79fe04d4a09290dbdcd29c85123ea672a1 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 19:44:41 +0500 Subject: [PATCH 021/121] refactor: rename 'procd_reload_delay' to 'badwan_reload_delay' --- .../htdocs/luci-static/resources/view/podkop/settings.js | 2 +- podkop/files/etc/init.d/podkop | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 f43a9b6..ed7dc5e 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 @@ -151,7 +151,7 @@ function createSettingsContent(section) { o = section.option( form.Value, - 'procd_reload_delay', + 'badwan_reload_delay', _('Interface Monitoring Delay'), _('Delay in milliseconds before reloading podkop after interface UP'), ); diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop index 309f566..73825ef 100755 --- a/podkop/files/etc/init.d/podkop +++ b/podkop/files/etc/init.d/podkop @@ -36,9 +36,9 @@ service_triggers() { config_get enable_badwan_interface_monitoring "settings" "enable_badwan_interface_monitoring" config_get badwan_monitored_interfaces "settings" "badwan_monitored_interfaces" - config_get procd_reload_delay "settings" "procd_reload_delay" "2000" + config_get badwan_reload_delay "settings" "badwan_reload_delay" "2000" - PROCD_RELOAD_DELAY=$procd_reload_delay + PROCD_RELOAD_DELAY=$badwan_reload_delay procd_open_trigger procd_add_config_trigger "config.change" "$NAME" "$initscript" restart 'on_config_change' From f95d801d4417f862de209a73d629c9c6c2ab570e Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 19:48:55 +0500 Subject: [PATCH 022/121] refactor: rename 'ss_uot' to 'enable_shadowsocks_udp_over_tcp' --- .../htdocs/luci-static/resources/view/podkop/section.js | 2 +- podkop/files/usr/bin/podkop | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 7d86ff7..16fcd6c 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 @@ -120,7 +120,7 @@ function createSectionContent(section) { o = section.option( form.Flag, - 'ss_uot', + 'enable_shadowsocks_udp_over_tcp', _('Shadowsocks UDP over TCP'), _('Apply for SS2022'), ); diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index b7e5f87..3d4d7d7 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -591,7 +591,7 @@ configure_outbound_handler() { log "Detected proxy configuration type: url" "debug" local proxy_string udp_over_tcp config_get proxy_string "$section" "proxy_string" - config_get udp_over_tcp "$section" "ss_uot" + config_get udp_over_tcp "$section" "enable_shadowsocks_udp_over_tcp" # Extract the first non-comment line as the active configuration active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1) @@ -612,7 +612,7 @@ configure_outbound_handler() { local urltest_proxy_links udp_over_tcp i urltest_tag selector_tag outbound_tag outbound_tags \ urltest_outbounds selector_outbounds config_get urltest_proxy_links "$section" "urltest_proxy_links" - config_get udp_over_tcp "$section" "ss_uot" + config_get udp_over_tcp "$section" "enable_shadowsocks_udp_over_tcp" if [ -z "$urltest_proxy_links" ]; then log "URLTest proxy links is not set. Aborted." "fatal" From ee93c26098fb841129088180c50ce9362223aa9d Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 19:55:22 +0500 Subject: [PATCH 023/121] fix: Use connection_type instead of mode for option dependencies in podkop section.js --- .../htdocs/luci-static/resources/view/podkop/section.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 16fcd6c..d6e4a96 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 @@ -26,7 +26,7 @@ function createSectionContent(section) { o.value('outbound', _('Outbound Config')); o.value('urltest', _('URLTest')); o.default = 'url'; - o.depends('mode', 'proxy'); + o.depends('connection_type', 'proxy'); o = section.option( form.TextValue, @@ -125,7 +125,7 @@ function createSectionContent(section) { _('Apply for SS2022'), ); o.default = '0'; - o.depends('mode', 'proxy'); + o.depends('connection_type', 'proxy'); o.rmempty = false; o = section.option( @@ -134,7 +134,7 @@ function createSectionContent(section) { _('Network Interface'), _('Select network interface for VPN connection'), ); - o.depends('mode', 'vpn'); + o.depends('connection_type', 'vpn'); o.noaliases = true; o.nobridges = false; o.noinactive = false; @@ -182,7 +182,7 @@ function createSectionContent(section) { ); o.default = '0'; o.rmempty = false; - o.depends('mode', 'vpn'); + o.depends('connection_type', 'vpn'); o = section.option( form.ListValue, From 7bfb673b493fe074fe49034eb5c0bc6815444d3b Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Fri, 10 Oct 2025 20:05:35 +0500 Subject: [PATCH 024/121] refactor: restructure podkop config --- podkop/files/etc/config/podkop | 84 +++++++++++++++------------------- 1 file changed, 37 insertions(+), 47 deletions(-) diff --git a/podkop/files/etc/config/podkop b/podkop/files/etc/config/podkop index 6e2269e..406c2c7 100644 --- a/podkop/files/etc/config/podkop +++ b/podkop/files/etc/config/podkop @@ -1,47 +1,37 @@ -config main 'main' - option mode 'proxy' - #option interface '' - option proxy_config_type 'url' - #option outbound_json '' - option proxy_string '' - option community_lists_enabled '1' - list community_lists 'russia_inside' - option user_domain_list_type 'disabled' - #list user_domains '' - #option user_domains_text '' - option local_domain_lists_enabled '0' - #list local_domain_lists '' - option remote_domain_lists_enabled '0' - #list remote_domain_lists '' - option user_subnet_list_type 'disable' - #list user_subnets '' - #option user_subnets_text '' - option local_subnet_lists_enabled '0' - #list local_subnet_lists '' - option remote_subnet_lists_enabled '0' - #list remote_subnet_lists '' - option all_traffic_from_ip_enabled '0' - #list all_traffic_ip '' - option exclude_from_ip_enabled '0' - #list exclude_traffic_ip '' - option yacd '0' - option socks5 '0' - option exclude_ntp '0' - option quic_disable '0' - option dont_touch_dhcp '0' - option update_interval '1d' - option dns_type 'udp' - option dns_server '8.8.8.8' - option split_dns_enabled '1' - option split_dns_type 'udp' - option split_dns_server '1.1.1.1' - option dns_rewrite_ttl '60' - option config_path '/etc/sing-box/config.json' - option cache_path '/tmp/sing-box/cache.db' - list iface 'br-lan' - option mon_restart_ifaces '0' - #list restart_ifaces 'wan' - option procd_reload_delay '2000' - option ss_uot '0' - option detour '0' - option shutdown_correctly '1' \ No newline at end of file +config settings 'settings' + option dns_type 'udp' + option dns_server '8.8.8.8' + option bootstrap_dns_server '77.88.8.8' + option dns_rewrite_ttl '60' + list source_network_interfaces 'br-lan' + option enable_badwan_interface_monitoring '0' + #list badwan_monitored_interfaces 'wan' + #option badwan_reload_delay '2000' + option enable_yacd '0' + option disable_quic '0' + option update_interval '1d' + option download_lists_via_proxy '0' + option dont_touch_dhcp '0' + option config_path '/etc/sing-box/config.json' + option cache_path '/tmp/sing-box/cache.db' + option exclude_ntp '0' + option shutdown_correctly '0' + #list routing_excluded_ips '192.168.1.3' + +config section 'main' + option connection_type 'proxy' + option proxy_config_type 'url' + option proxy_string '' + option enable_shadowsocks_udp_over_tcp '0' + #list community_lists 'russia_inside' + #option user_domain_list_type 'dynamic' + #list user_domains '2ip.ru' + #option user_subnet_list_type 'dynamic' + #list user_subnets '1.1.1.1' + #list local_domain_lists '/tmp/domains.lst' + #list local_subnet_lists '/tmp/subnets.lst' + #list remote_domain_lists 'https://example.com/domains.srs' + #list remote_subnet_lists 'https://example.com/subnets.srs' + #list fully_routed_ips '192.168.1.2' + #option mixed_proxy_enabled '1' + #option mixed_proxy_port '2080' \ No newline at end of file From b90f520c688caf0acf5bf5f72519c7566a108a4f Mon Sep 17 00:00:00 2001 From: divocat Date: Fri, 10 Oct 2025 20:39:28 +0300 Subject: [PATCH 025/121] feat: add bulk watch for fe/bin/lib directories --- fe-app-podkop/.env.example | 16 +++++++ fe-app-podkop/watch-upload.js | 80 ++++++++++++++++++++--------------- 2 files changed, 63 insertions(+), 33 deletions(-) create mode 100644 fe-app-podkop/.env.example diff --git a/fe-app-podkop/.env.example b/fe-app-podkop/.env.example new file mode 100644 index 0000000..b82d0b6 --- /dev/null +++ b/fe-app-podkop/.env.example @@ -0,0 +1,16 @@ +SFTP_HOST=192.168.160.129 +SFTP_PORT=22 +SFTP_USER=root +SFTP_PASS= + +# you can use key if needed +# SFTP_PRIVATE_KEY=~/.ssh/id_rsa + +LOCAL_DIR_FE=../luci-app-podkop/htdocs/luci-static/resources/view/podkop +REMOTE_DIR_FE=/www/luci-static/resources/view/podkop + +LOCAL_DIR_BIN=../podkop/files/usr/bin/ +REMOTE_DIR_BIN=/usr/bin/ + +LOCAL_DIR_LIB=../podkop/files/usr/lib/ +REMOTE_DIR_LIB=/usr/lib/podkop/ diff --git a/fe-app-podkop/watch-upload.js b/fe-app-podkop/watch-upload.js index db0b1ea..b05ea19 100644 --- a/fe-app-podkop/watch-upload.js +++ b/fe-app-podkop/watch-upload.js @@ -16,67 +16,81 @@ const config = { : { password: process.env.SFTP_PASS }), }; -const localDir = path.resolve(process.env.LOCAL_DIR || './dist'); -const remoteDir = process.env.REMOTE_DIR || '/www/luci-static/mypkg'; +const syncDirs = [ + { + local: path.resolve(process.env.LOCAL_DIR_FE ?? '../luci-app-podkop/htdocs/luci-static/resources/view/podkop'), + remote: process.env.REMOTE_DIR_FE ?? '/www/luci-static/resources/view/podkop', + }, + { + local: path.resolve(process.env.LOCAL_DIR_BIN ?? '../podkop/files/usr/bin/'), + remote: process.env.REMOTE_DIR_BIN ?? '/usr/bin/', + }, + { + local: path.resolve(process.env.LOCAL_DIR_LIB ?? '../podkop/files/usr/lib/'), + remote: process.env.REMOTE_DIR_LIB ?? '/usr/lib/podkop/', + }, +]; -async function uploadFile(filePath) { - const relativePath = path.relative(localDir, filePath); - const remotePath = path.posix.join(remoteDir, relativePath); +async function uploadFile(filePath, baseDir, remoteBase) { + const relativePath = path.relative(baseDir, filePath); + const remotePath = path.posix.join(remoteBase, relativePath); - console.log(`Uploading: ${relativePath} -> ${remotePath}`); + console.log(`↑ Uploading: ${relativePath} -> ${remotePath}`); try { await sftp.fastPut(filePath, remotePath); - console.log(`Uploaded: ${relativePath}`); + console.log(`✓ Uploaded: ${relativePath}`); } catch (err) { - console.error(`Failed: ${relativePath}: ${err.message}`); + console.error(`✗ Failed: ${relativePath}: ${err.message}`); } } -async function deleteFile(filePath) { - const relativePath = path.relative(localDir, filePath); - const remotePath = path.posix.join(remoteDir, relativePath); +async function deleteFile(filePath, baseDir, remoteBase) { + const relativePath = path.relative(baseDir, filePath); + const remotePath = path.posix.join(remoteBase, relativePath); - console.log(`Removing: ${relativePath}`); + console.log(`⨯ Removing: ${relativePath}`); try { await sftp.delete(remotePath); - console.log(`Removed: ${relativePath}`); + console.log(`✓ Removed: ${relativePath}`); } catch (err) { - console.warn(`Could not delete ${relativePath}: ${err.message}`); + console.warn(`⚠ Could not delete ${relativePath}: ${err.message}`); } } async function uploadAllFiles() { - console.log('Uploading all files from', localDir); - - const files = await glob(`${localDir}/**/*`, { nodir: true }); - for (const file of files) { - await uploadFile(file); + for (const { local, remote } of syncDirs) { + console.log(`📂 Uploading all from ${local}`); + const files = await glob(`${local}/**/*`, { nodir: true }); + for (const file of files) { + await uploadFile(file, local, remote); + } } - - console.log('Initial upload complete!'); + console.log('✅ Initial upload complete!'); } async function main() { await sftp.connect(config); - console.log(`Connected to ${config.host}`); + console.log(`🔌 Connected to ${config.host}`); await uploadAllFiles(); - chokidar - .watch(localDir, { ignoreInitial: true }) - .on('all', async (event, filePath) => { - if (event === 'add' || event === 'change') { - await uploadFile(filePath); - } else if (event === 'unlink') { - await deleteFile(filePath); - } - }); + for (const { local, remote } of syncDirs) { + chokidar.watch(local, { ignoreInitial: true }).on('all', async (event, filePath) => { + if (event === 'add' || event === 'change') { + await uploadFile(filePath, local, remote); + } else if (event === 'unlink') { + await deleteFile(filePath, local, remote); + } + }); + } process.on('SIGINT', async () => { - console.log('Disconnecting...'); + console.log('👋 Disconnecting...'); await sftp.end(); process.exit(); }); } -main().catch(console.error); +main().catch((err) => { + console.error('💥 Fatal:', err); +}); From 1bdd49e1982190900c2e00c603c02794d8283a54 Mon Sep 17 00:00:00 2001 From: divocat Date: Fri, 10 Oct 2025 20:49:44 +0300 Subject: [PATCH 026/121] fix: adapt dashboard for new sections structure --- .../src/podkop/methods/getDashboardSections.ts | 7 ++++--- fe-app-podkop/src/podkop/types.ts | 12 ++++++------ .../htdocs/luci-static/resources/view/podkop/main.js | 7 ++++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts b/fe-app-podkop/src/podkop/methods/getDashboardSections.ts index 28a5573..78d5c77 100644 --- a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts +++ b/fe-app-podkop/src/podkop/methods/getDashboardSections.ts @@ -10,6 +10,7 @@ interface IGetDashboardSectionsResponse { export async function getDashboardSections(): Promise { const configSections = await getConfigSections(); + console.log('configSections', configSections) const clashProxies = await getClashProxies(); if (!clashProxies.success) { @@ -27,9 +28,9 @@ export async function getDashboardSections(): Promise section.mode !== 'block') + .filter((section) => section.connection_type !== 'block' && section[".type"] !== 'settings') .map((section) => { - if (section.mode === 'proxy') { + if (section.connection_type === 'proxy') { if (section.proxy_config_type === 'url') { const outbound = proxies.find( (proxy) => proxy.code === `${section['.name']}-out`, @@ -122,7 +123,7 @@ export async function getDashboardSections(): Promise proxy.code === `${section['.name']}-out`, ); diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index 531f648..e6203e6 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -16,30 +16,30 @@ export namespace Podkop { } export interface ConfigProxyUrlTestSection { - mode: 'proxy'; + connection_type: 'proxy'; proxy_config_type: 'urltest'; urltest_proxy_links: string[]; } export interface ConfigProxyUrlSection { - mode: 'proxy'; + connection_type: 'proxy'; proxy_config_type: 'url'; proxy_string: string; } export interface ConfigProxyOutboundSection { - mode: 'proxy'; + connection_type: 'proxy'; proxy_config_type: 'outbound'; outbound_json: string; } export interface ConfigVpnSection { - mode: 'vpn'; + connection_type: 'vpn'; interface: string; } export interface ConfigBlockSection { - mode: 'block'; + connection_type: 'block'; } export type ConfigBaseSection = @@ -51,6 +51,6 @@ export namespace Podkop { export type ConfigSection = ConfigBaseSection & { '.name': string; - '.type': 'main' | 'extra'; + '.type': 'settings' | 'section'; }; } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 8f7e7b7..532b06d 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -943,6 +943,7 @@ async function getConfigSections() { // src/podkop/methods/getDashboardSections.ts async function getDashboardSections() { const configSections = await getConfigSections(); + console.log("configSections", configSections); const clashProxies = await getClashProxies(); if (!clashProxies.success) { return { @@ -956,8 +957,8 @@ async function getDashboardSections() { value }) ); - const data = configSections.filter((section) => section.mode !== "block").map((section) => { - if (section.mode === "proxy") { + const data = configSections.filter((section) => section.connection_type !== "block" && section[".type"] !== "settings").map((section) => { + if (section.connection_type === "proxy") { if (section.proxy_config_type === "url") { const outbound = proxies.find( (proxy) => proxy.code === `${section[".name"]}-out` @@ -1032,7 +1033,7 @@ async function getDashboardSections() { }; } } - if (section.mode === "vpn") { + if (section.connection_type === "vpn") { const outbound = proxies.find( (proxy) => proxy.code === `${section[".name"]}-out` ); From 3d1232786895804a258bab442786f183acdabe0c Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sat, 11 Oct 2025 00:32:33 +0300 Subject: [PATCH 027/121] Switch DNS check to dig. New checks and output format for check_dns_available --- podkop/files/usr/bin/podkop | 134 ++++++++++++++---------------------- 1 file changed, 52 insertions(+), 82 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 3d4d7d7..d3ff813 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1792,12 +1792,15 @@ get_status() { } check_dns_available() { - local dns_type=$(uci get podkop.main.dns_type 2> /dev/null) - local dns_server=$(uci get podkop.main.dns_server 2> /dev/null) - local is_available=0 - local status="unavailable" - local local_dns_working=0 - local local_dns_status="unavailable" + local dns_type dns_server bootstrap_dns_server + config_get dns_type "settings" "dns_type" + config_get dns_server "settings" "dns_server" + config_get bootstrap_dns_server "settings" "bootstrap_dns_server" + + local dns_status=0 + local local_dns_status=0 + local bootstrap_dns_status=0 + local dhcp_has_dns_server=0 # Mask NextDNS ID if present local display_dns_server="$dns_server" @@ -1810,73 +1813,59 @@ check_dns_available() { fi if [ "$dns_type" = "doh" ]; then - # Generate random DNS query ID (2 bytes) - local random_id=$(head -c2 /dev/urandom | hexdump -ve '1/1 "%.2x"' 2> /dev/null) - if [ $? -ne 0 ]; then - error_message="Failed to generate random ID" - status="internal error" - else - # Create DNS wire format query for google.com A record with random ID - local dns_query=$(printf "\x${random_id:0:2}\x${random_id:2:2}\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com\x00\x00\x01\x00\x01" | base64 2> /dev/null) - if [ $? -ne 0 ]; then - error_message="Failed to generate DNS query" - status="internal error" - else - # Try POST method first (RFC 8484 compliant) with shorter timeout - local result=$(echo "$dns_query" | base64 -d 2> /dev/null | curl -H "Content-Type: application/dns-message" \ - -H "Accept: application/dns-message" \ - --data-binary @- \ - --max-time 2 \ - --connect-timeout 1 \ - -s \ - "https://$dns_server/dns-query" 2> /dev/null) - - if [ $? -eq 0 ] && [ -n "$result" ]; then - is_available=1 - status="available" - else - # Try GET method as fallback with shorter timeout - local dns_query_no_padding=$(echo "$dns_query" | tr -d '=' 2> /dev/null) - result=$(curl -H "accept: application/dns-message" \ - --max-time 2 \ - --connect-timeout 1 \ - -s \ - "https://$dns_server/dns-query?dns=$dns_query_no_padding" 2> /dev/null) - - if [ $? -eq 0 ] && [ -n "$result" ]; then - is_available=1 - status="available" - else - error_message="DoH server not responding" - fi - fi - fi + # Check if dns_server already contains a path + local doh_path="/dns-query" + if echo "$dns_server" | grep -q "/"; then + # Path is already present, extract it + doh_path="/$(echo "$dns_server" | cut -d'/' -f2-)" + dns_server="$(echo "$dns_server" | cut -d'/' -f1)" + fi + + if dig @"$dns_server" google.com +https="$doh_path" +timeout=2 +tries=1 > /dev/null 2>&1; then + dns_status=1 fi elif [ "$dns_type" = "dot" ]; then - (nc "$dns_server" 853 < /dev/null > /dev/null 2>&1) & - pid=$! - sleep 2 - if kill -0 $pid 2> /dev/null; then - kill $pid 2> /dev/null - wait $pid 2> /dev/null - else - is_available=1 - status="available" + if dig @"$dns_server" google.com +tls +timeout=2 +tries=1 > /dev/null 2>&1; then + dns_status=1 fi elif [ "$dns_type" = "udp" ]; then - if nslookup -timeout=2 itdog.info $dns_server > /dev/null 2>&1; then - is_available=1 - status="available" + if dig @"$dns_server" google.com +timeout=2 +tries=1 > /dev/null 2>&1; then + dns_status=1 fi fi # Check if local DNS resolver is working - if nslookup -timeout=2 $FAKEIP_TEST_DOMAIN 127.0.0.1 > /dev/null 2>&1; then - local_dns_working=1 - local_dns_status="available" + if dig @127.0.0.1 "$FAKEIP_TEST_DOMAIN" +timeout=2 +tries=1 > /dev/null 2>&1; then + local_dns_status=1 fi - echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"is_available\":$is_available,\"status\":\"$status\",\"local_dns_working\":$local_dns_working,\"local_dns_status\":\"$local_dns_status\"}" + # Check bootstrap DNS server + if [ -n "$bootstrap_dns_server" ]; then + if dig @"$bootstrap_dns_server" google.com +timeout=2 +tries=1 > /dev/null 2>&1; then + bootstrap_dns_status=1 + fi + fi + + # Check if /etc/config/dhcp has server 127.0.0.42 + config_load dhcp + config_foreach check_dhcp_has_podkop_dns dnsmasq + config_load "$PODKOP_CONFIG" + + echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"dns_status\":$dns_status,\"local_dns_status\":$local_dns_status,\"bootstrap_dns_server\":\"$bootstrap_dns_server\",\"bootstrap_dns_status\":$bootstrap_dns_status,\"dhcp_has_dns_server\":$dhcp_has_dns_server}" | jq . +} + +check_dhcp_has_podkop_dns() { + local server_list + config_get server_list "$1" "server" + + if [ -n "$server_list" ]; then + for server in $server_list; do + if [ "$server" = "127.0.0.42" ]; then + dhcp_has_dns_server=1 + return 0 + fi + done + fi } print_global() { @@ -1884,16 +1873,6 @@ print_global() { echo "$message" } -find_working_resolver() { - for resolver in $DNS_RESOLVERS; do - if nslookup -timeout=2 "$FAKEIP_TEST_DOMAIN" "$resolver" > /dev/null 2>&1; then - echo "$resolver" - return 0 - fi - done - return 1 -} - global_check() { local PODKOP_LUCI_VERSION="Unknown" [ -n "$1" ] && PODKOP_LUCI_VERSION="$1" @@ -2015,15 +1994,6 @@ global_check() { print_global "➡️ DNS resolution: system DNS server" nslookup -timeout=2 $FAKEIP_TEST_DOMAIN - local working_resolver - working_resolver=$(find_working_resolver) - if [ -z "$working_resolver" ]; then - print_global "❌ No working external resolver found" - else - print_global "➡️ DNS resolution: external resolver ($working_resolver)" - nslookup -timeout=2 $FAKEIP_TEST_DOMAIN $working_resolver - fi - print_global "➡️ DNS resolution: sing-box DNS server (127.0.0.42)" local result result=$(nslookup -timeout=2 $FAKEIP_TEST_DOMAIN 127.0.0.42 2>&1) From a33b53743f8fae79616d4b47144cda3f5dc2b400 Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sat, 11 Oct 2025 00:33:20 +0300 Subject: [PATCH 028/121] Switch to sing-box-tiny. Add bind-dig depends --- podkop/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/podkop/Makefile b/podkop/Makefile index b4f4b84..7eebfb8 100644 --- a/podkop/Makefile +++ b/podkop/Makefile @@ -14,7 +14,7 @@ include $(INCLUDE_DIR)/package.mk define Package/podkop SECTION:=net CATEGORY:=Network - DEPENDS:=+sing-box +curl +jq +kmod-nft-tproxy +coreutils-base64 + DEPENDS:=+sing-box-tiny +curl +jq +kmod-nft-tproxy +coreutils-base64 +bind-dig CONFLICTS:=https-dns-proxy nextdns luci-app-passwall luci-app-passwall2 TITLE:=Domain routing app URL:=https://podkop.net From 63d56e736d97af7448e111c4d7e1b0804ea20ebb Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sat, 11 Oct 2025 14:35:14 +0300 Subject: [PATCH 029/121] Added init.d dir for sync --- fe-app-podkop/watch-upload.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fe-app-podkop/watch-upload.js b/fe-app-podkop/watch-upload.js index b05ea19..0f57832 100644 --- a/fe-app-podkop/watch-upload.js +++ b/fe-app-podkop/watch-upload.js @@ -29,6 +29,10 @@ const syncDirs = [ local: path.resolve(process.env.LOCAL_DIR_LIB ?? '../podkop/files/usr/lib/'), remote: process.env.REMOTE_DIR_LIB ?? '/usr/lib/podkop/', }, + { + local: path.resolve(process.env.LOCAL_DIR_INIT ?? '../podkop/files/etc/init.d/'), + remote: process.env.REMOTE_DIR_INIT ?? '/etc/init.d/', + } ]; async function uploadFile(filePath, baseDir, remoteBase) { From 791cc1c9459d0871178971fc8fced4c487ac0217 Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sat, 11 Oct 2025 14:36:04 +0300 Subject: [PATCH 030/121] Diagnostics: add check_nft_rules --- podkop/files/usr/bin/podkop | 75 +++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index d3ff813..2ebcb7c 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1868,6 +1868,77 @@ check_dhcp_has_podkop_dns() { fi } +check_nft_rules() { + local table_exist=0 + local rules_mangle_exist=0 + local rules_mangle_counters=0 + local rules_mangle_output_exist=0 + local rules_mangle_output_counters=0 + local rules_proxy_exist=0 + local rules_proxy_counters=0 + local rules_other_mark_exist=0 + + # Check if PodkopTable exists + if nft list table inet "$NFT_TABLE_NAME" > /dev/null 2>&1; then + table_exist=1 + + # Check mangle chain rules + if nft list chain inet "$NFT_TABLE_NAME" mangle > /dev/null 2>&1; then + local mangle_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle) + if echo "$mangle_output" | grep -q "counter"; then + rules_mangle_exist=1 + + if echo "$mangle_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then + rules_mangle_counters=1 + fi + fi + fi + + # Check mangle_output chain rules + if nft list chain inet "$NFT_TABLE_NAME" mangle_output > /dev/null 2>&1; then + local mangle_output_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle_output) + if echo "$mangle_output_output" | grep -q "counter"; then + rules_mangle_output_exist=1 + + if echo "$mangle_output_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then + rules_mangle_output_counters=1 + fi + fi + fi + + # Check proxy chain rules + if nft list chain inet "$NFT_TABLE_NAME" proxy > /dev/null 2>&1; then + local proxy_output=$(nft list chain inet "$NFT_TABLE_NAME" proxy) + if echo "$proxy_output" | grep -q "counter"; then + rules_proxy_exist=1 + + if echo "$proxy_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then + rules_proxy_counters=1 + fi + fi + fi + fi + + # Check for other mark rules outside PodkopTable + nft list tables 2>/dev/null | while read -r keyword family table_name; do + [ -z "$table_name" ] && continue + + [ "$table_name" = "$NFT_TABLE_NAME" ] && continue + + if nft list table "$family" "$table_name" 2>/dev/null | grep -q "meta mark set"; then + touch /tmp/podkop/mark_check.$$ + break + fi + done + + if [ -f /tmp/podkop/mark_check.$$ ]; then + rules_other_mark_exist=1 + rm -f /tmp/podkop/mark_check.$$ + fi + + echo "{\"table_exist\":$table_exist,\"rules_mangle_exist\":$rules_mangle_exist,\"rules_mangle_counters\":$rules_mangle_counters,\"rules_mangle_output_exist\":$rules_mangle_output_exist,\"rules_mangle_output_counters\":$rules_mangle_output_counters,\"rules_proxy_exist\":$rules_proxy_exist,\"rules_proxy_counters\":$rules_proxy_counters,\"rules_other_mark_exist\":$rules_other_mark_exist}" | jq . +} + print_global() { local message="$1" echo "$message" @@ -2026,6 +2097,7 @@ Available commands: list_update Update domain lists check_proxy Check proxy connectivity check_nft Check NFT rules + check_nft_rules Check NFT rules status check_github Check GitHub connectivity check_logs Show podkop logs from system journal check_sing_box_connections Show active sing-box connections @@ -2068,6 +2140,9 @@ check_proxy) check_nft) check_nft ;; +check_nft_rules) + check_nft_rules + ;; check_github) check_github ;; From d041334d8881184fea56956be9bf4f864c2be563 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 11 Oct 2025 17:48:53 +0300 Subject: [PATCH 031/121] feat: add getDNSCheck & getNftRulesCheck js methods --- .../src/podkop/methods/getDNSCheck.ts | 24 ++++++++++ .../podkop/methods/getDashboardSections.ts | 6 ++- .../src/podkop/methods/getNftRulesCheck.ts | 24 ++++++++++ fe-app-podkop/src/podkop/methods/index.ts | 2 + fe-app-podkop/src/podkop/types.ts | 35 +++++++++++++++ .../luci-static/resources/view/podkop/main.js | 45 ++++++++++++++++++- 6 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 fe-app-podkop/src/podkop/methods/getDNSCheck.ts create mode 100644 fe-app-podkop/src/podkop/methods/getNftRulesCheck.ts diff --git a/fe-app-podkop/src/podkop/methods/getDNSCheck.ts b/fe-app-podkop/src/podkop/methods/getDNSCheck.ts new file mode 100644 index 0000000..8744230 --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/getDNSCheck.ts @@ -0,0 +1,24 @@ +import { executeShellCommand } from '../../helpers'; +import { Podkop } from '../types'; + +export async function getDNSCheck(): Promise< + Podkop.MethodResponse +> { + const response = await executeShellCommand({ + command: '/usr/bin/podkop', + args: ['check_dns_available'], + timeout: 10000, + }); + + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) as Podkop.DnsCheckResult, + }; + } + + return { + success: false, + error: '', + }; +} diff --git a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts b/fe-app-podkop/src/podkop/methods/getDashboardSections.ts index 78d5c77..b6619d0 100644 --- a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts +++ b/fe-app-podkop/src/podkop/methods/getDashboardSections.ts @@ -10,7 +10,6 @@ interface IGetDashboardSectionsResponse { export async function getDashboardSections(): Promise { const configSections = await getConfigSections(); - console.log('configSections', configSections) const clashProxies = await getClashProxies(); if (!clashProxies.success) { @@ -28,7 +27,10 @@ export async function getDashboardSections(): Promise section.connection_type !== 'block' && section[".type"] !== 'settings') + .filter( + (section) => + section.connection_type !== 'block' && section['.type'] !== 'settings', + ) .map((section) => { if (section.connection_type === 'proxy') { if (section.proxy_config_type === 'url') { diff --git a/fe-app-podkop/src/podkop/methods/getNftRulesCheck.ts b/fe-app-podkop/src/podkop/methods/getNftRulesCheck.ts new file mode 100644 index 0000000..f5eded4 --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/getNftRulesCheck.ts @@ -0,0 +1,24 @@ +import { executeShellCommand } from '../../helpers'; +import { Podkop } from '../types'; + +export async function getNftRulesCheck(): Promise< + Podkop.MethodResponse +> { + const response = await executeShellCommand({ + command: '/usr/bin/podkop', + args: ['check_nft_rules'], + timeout: 10000, + }); + + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) as Podkop.NftRulesCheckResult, + }; + } + + return { + success: false, + error: '', + }; +} diff --git a/fe-app-podkop/src/podkop/methods/index.ts b/fe-app-podkop/src/podkop/methods/index.ts index 6b2c1f3..98851a9 100644 --- a/fe-app-podkop/src/podkop/methods/index.ts +++ b/fe-app-podkop/src/podkop/methods/index.ts @@ -2,3 +2,5 @@ export * from './getConfigSections'; export * from './getDashboardSections'; export * from './getPodkopStatus'; export * from './getSingboxStatus'; +export * from './getDNSCheck'; +export * from './getNftRulesCheck'; diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index e6203e6..1e51a0f 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -53,4 +53,39 @@ export namespace Podkop { '.name': string; '.type': 'settings' | 'section'; }; + + export interface MethodSuccessResponse { + success: true; + data: T; + } + + export interface MethodFailureResponse { + success: false; + error: string; + } + + export type MethodResponse = + | MethodSuccessResponse + | MethodFailureResponse; + + export interface DnsCheckResult { + dns_type: 'udp' | 'doh' | 'dot'; + dns_server: string; + dns_status: 0 | 1; + local_dns_status: 0 | 1; + bootstrap_dns_server: string; + bootstrap_dns_status: 0 | 1; + dhcp_has_dns_server: 0 | 1; + } + + export interface NftRulesCheckResult { + table_exist: 0 | 1; + rules_mangle_exist: 0 | 1; + rules_mangle_counters: 0 | 1; + rules_mangle_output_exist: 0 | 1; + rules_mangle_output_counters: 0 | 1; + rules_proxy_exist: 0 | 1; + rules_proxy_counters: 0 | 1; + rules_other_mark_exist: 0 | 1; + } } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 532b06d..06e499f 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -943,7 +943,6 @@ async function getConfigSections() { // src/podkop/methods/getDashboardSections.ts async function getDashboardSections() { const configSections = await getConfigSections(); - console.log("configSections", configSections); const clashProxies = await getClashProxies(); if (!clashProxies.success) { return { @@ -957,7 +956,9 @@ async function getDashboardSections() { value }) ); - const data = configSections.filter((section) => section.connection_type !== "block" && section[".type"] !== "settings").map((section) => { + const data = configSections.filter( + (section) => section.connection_type !== "block" && section[".type"] !== "settings" + ).map((section) => { if (section.connection_type === "proxy") { if (section.proxy_config_type === "url") { const outbound = proxies.find( @@ -1091,6 +1092,44 @@ async function getSingboxStatus() { return { running: 0, enabled: 0, status: "unknown" }; } +// src/podkop/methods/getDNSCheck.ts +async function getDNSCheck() { + const response = await executeShellCommand({ + command: "/usr/bin/podkop", + args: ["check_dns_available"], + timeout: 1e4 + }); + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) + }; + } + return { + success: false, + error: "" + }; +} + +// src/podkop/methods/getNftRulesCheck.ts +async function getNftRulesCheck() { + const response = await executeShellCommand({ + command: "/usr/bin/podkop", + args: ["check_nft_rules"], + timeout: 1e4 + }); + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) + }; + } + return { + success: false, + error: "" + }; +} + // src/podkop/services/tab.service.ts var TabService = class _TabService { constructor() { @@ -2026,7 +2065,9 @@ return baseclass.extend({ getClashVersion, getClashWsUrl, getConfigSections, + getDNSCheck, getDashboardSections, + getNftRulesCheck, getPodkopStatus, getProxyUrlName, getSingboxStatus, From fd0b981186c48bf3cf75dca6c631cde46463c4a2 Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sat, 11 Oct 2025 18:54:31 +0300 Subject: [PATCH 032/121] Fix check_nft_rules. Add check_sing_box func --- podkop/files/usr/bin/podkop | 83 +++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 2ebcb7c..7f2f1c7 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1878,6 +1878,16 @@ check_nft_rules() { local rules_proxy_counters=0 local rules_other_mark_exist=0 + # Generate traffic through PodkopTable + curl -m 3 -s "http://ip.podkop.fyi/check" > /dev/null 2>&1 & + local pid1=$! + curl -m 3 -s "http://fakeip.podkop.fyi/check" > /dev/null 2>&1 & + local pid2=$! + + wait $pid1 2>/dev/null + wait $pid2 2>/dev/null + sleep 1 + # Check if PodkopTable exists if nft list table inet "$NFT_TABLE_NAME" > /dev/null 2>&1; then table_exist=1 @@ -1926,19 +1936,82 @@ check_nft_rules() { [ "$table_name" = "$NFT_TABLE_NAME" ] && continue if nft list table "$family" "$table_name" 2>/dev/null | grep -q "meta mark set"; then - touch /tmp/podkop/mark_check.$$ + touch /tmp/podkop_mark_check.$$ break fi done - if [ -f /tmp/podkop/mark_check.$$ ]; then + if [ -f /tmp/podkop_mark_check.$$ ]; then rules_other_mark_exist=1 - rm -f /tmp/podkop/mark_check.$$ + rm -f /tmp/podkop_mark_check.$$ fi echo "{\"table_exist\":$table_exist,\"rules_mangle_exist\":$rules_mangle_exist,\"rules_mangle_counters\":$rules_mangle_counters,\"rules_mangle_output_exist\":$rules_mangle_output_exist,\"rules_mangle_output_counters\":$rules_mangle_output_counters,\"rules_proxy_exist\":$rules_proxy_exist,\"rules_proxy_counters\":$rules_proxy_counters,\"rules_other_mark_exist\":$rules_other_mark_exist}" | jq . } +check_sing_box() { + local sing_box_installed=0 + local sing_box_version_ok=0 + local sing_box_service_exist=0 + local sing_box_autostart_disabled=0 + local sing_box_process_running=0 + local sing_box_ports_listening=0 + + # Check if sing-box is installed + if command -v sing-box > /dev/null 2>&1; then + sing_box_installed=1 + + # Check version (must be >= 1.12.4) + local version=$(sing-box version 2>/dev/null | head -n 1 | awk '{print $3}') + if [ -n "$version" ]; then + version=$(echo "$version" | sed 's/^v//') + local major=$(echo "$version" | cut -d. -f1) + local minor=$(echo "$version" | cut -d. -f2) + local patch=$(echo "$version" | cut -d. -f3) + + # Compare version: must be >= 1.12.4 + if [ "$major" -gt 1 ] || \ + [ "$major" -eq 1 ] && [ "$minor" -gt 12 ] || \ + [ "$major" -eq 1 ] && [ "$minor" -eq 12 ] && [ "$patch" -ge 4 ]; then + sing_box_version_ok=1 + fi + fi + fi + + # Check if service exists and is enabled + if [ -f /etc/init.d/sing-box ]; then + sing_box_service_exist=1 + + if ! /etc/init.d/sing-box enabled 2>/dev/null; then + sing_box_autostart_disabled=1 + fi + fi + + # Check if process is running + if pgrep "sing-box" > /dev/null 2>&1; then + sing_box_process_running=1 + fi + + # Check if sing-box is listening on required ports + local port_53_ok=0 + local port_1602_ok=0 + + if netstat -ln 2>/dev/null | grep -q "127.0.0.42:53"; then + port_53_ok=1 + fi + + if netstat -ln 2>/dev/null | grep -q "127.0.0.1:1602"; then + port_1602_ok=1 + fi + + # Both ports must be listening + if [ "$port_53_ok" == "1" ] && [ "$port_1602_ok" == "1" ]; then + sing_box_ports_listening=1 + fi + + echo "{\"sing_box_installed\":$sing_box_installed,\"sing_box_version_ok\":$sing_box_version_ok,\"sing_box_service_exist\":$sing_box_service_exist,\"sing_box_autostart_disabled\":$sing_box_autostart_disabled,\"sing_box_process_running\":$sing_box_process_running,\"sing_box_ports_listening\":$sing_box_ports_listening}" | jq . +} + print_global() { local message="$1" echo "$message" @@ -2098,6 +2171,7 @@ Available commands: check_proxy Check proxy connectivity check_nft Check NFT rules check_nft_rules Check NFT rules status + check_sing_box Check sing-box installation and status check_github Check GitHub connectivity check_logs Show podkop logs from system journal check_sing_box_connections Show active sing-box connections @@ -2143,6 +2217,9 @@ check_nft) check_nft_rules) check_nft_rules ;; +check_sing_box) + check_sing_box + ;; check_github) check_github ;; From 5486dfb0a4fc78e234f9ed8f904e8aa596947e35 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 11 Oct 2025 20:17:24 +0300 Subject: [PATCH 033/121] feat: add getSingBoxCheck js method --- .../src/podkop/methods/getSingBoxCheck.ts | 24 ++++++++++++++++ ...etSingboxStatus.ts => getSingBoxStatus.ts} | 2 +- fe-app-podkop/src/podkop/methods/index.ts | 3 +- .../tabs/dashboard/initDashboardController.ts | 4 +-- fe-app-podkop/src/podkop/types.ts | 9 ++++++ .../luci-static/resources/view/podkop/main.js | 28 ++++++++++++++++--- 6 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 fe-app-podkop/src/podkop/methods/getSingBoxCheck.ts rename fe-app-podkop/src/podkop/methods/{getSingboxStatus.ts => getSingBoxStatus.ts} (90%) diff --git a/fe-app-podkop/src/podkop/methods/getSingBoxCheck.ts b/fe-app-podkop/src/podkop/methods/getSingBoxCheck.ts new file mode 100644 index 0000000..6ad35cc --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/getSingBoxCheck.ts @@ -0,0 +1,24 @@ +import { executeShellCommand } from '../../helpers'; +import { Podkop } from '../types'; + +export async function getSingBoxCheck(): Promise< + Podkop.MethodResponse +> { + const response = await executeShellCommand({ + command: '/usr/bin/podkop', + args: ['check_sing_box'], + timeout: 10000, + }); + + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) as Podkop.SingBoxCheckResult, + }; + } + + return { + success: false, + error: '', + }; +} diff --git a/fe-app-podkop/src/podkop/methods/getSingboxStatus.ts b/fe-app-podkop/src/podkop/methods/getSingBoxStatus.ts similarity index 90% rename from fe-app-podkop/src/podkop/methods/getSingboxStatus.ts rename to fe-app-podkop/src/podkop/methods/getSingBoxStatus.ts index 41735f5..51d2f62 100644 --- a/fe-app-podkop/src/podkop/methods/getSingboxStatus.ts +++ b/fe-app-podkop/src/podkop/methods/getSingBoxStatus.ts @@ -1,6 +1,6 @@ import { executeShellCommand } from '../../helpers'; -export async function getSingboxStatus(): Promise<{ +export async function getSingBoxStatus(): Promise<{ running: number; enabled: number; status: string; diff --git a/fe-app-podkop/src/podkop/methods/index.ts b/fe-app-podkop/src/podkop/methods/index.ts index 98851a9..2577428 100644 --- a/fe-app-podkop/src/podkop/methods/index.ts +++ b/fe-app-podkop/src/podkop/methods/index.ts @@ -1,6 +1,7 @@ export * from './getConfigSections'; export * from './getDashboardSections'; export * from './getPodkopStatus'; -export * from './getSingboxStatus'; +export * from './getSingBoxStatus'; export * from './getDNSCheck'; export * from './getNftRulesCheck'; +export * from './getSingBoxCheck'; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts index d5c9526..fc7b5c9 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts @@ -1,7 +1,7 @@ import { getDashboardSections, getPodkopStatus, - getSingboxStatus, + getSingBoxStatus, } from '../../methods'; import { getClashApiUrl, @@ -52,7 +52,7 @@ async function fetchServicesInfo() { try { const [podkop, singbox] = await Promise.all([ getPodkopStatus(), - getSingboxStatus(), + getSingBoxStatus(), ]); store.set({ diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index 1e51a0f..558f99b 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -88,4 +88,13 @@ export namespace Podkop { rules_proxy_counters: 0 | 1; rules_other_mark_exist: 0 | 1; } + + export interface SingBoxCheckResult { + sing_box_installed: 0 | 1; + sing_box_version_ok: 0 | 1; + sing_box_service_exist: 0 | 1; + sing_box_autostart_disabled: 0 | 1; + sing_box_process_running: 0 | 1; + sing_box_ports_listening: 0 | 1; + } } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 06e499f..1ae5e68 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1079,8 +1079,8 @@ async function getPodkopStatus() { return { enabled: 0, status: "unknown" }; } -// src/podkop/methods/getSingboxStatus.ts -async function getSingboxStatus() { +// src/podkop/methods/getSingBoxStatus.ts +async function getSingBoxStatus() { const response = await executeShellCommand({ command: "/usr/bin/podkop", args: ["get_sing_box_status"], @@ -1130,6 +1130,25 @@ async function getNftRulesCheck() { }; } +// src/podkop/methods/getSingBoxCheck.ts +async function getSingBoxCheck() { + const response = await executeShellCommand({ + command: "/usr/bin/podkop", + args: ["check_sing_box"], + timeout: 1e4 + }); + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) + }; + } + return { + success: false, + error: "" + }; +} + // src/podkop/services/tab.service.ts var TabService = class _TabService { constructor() { @@ -1691,7 +1710,7 @@ async function fetchServicesInfo() { try { const [podkop, singbox] = await Promise.all([ getPodkopStatus(), - getSingboxStatus() + getSingBoxStatus() ]); store.set({ servicesInfoWidget: { @@ -2070,7 +2089,8 @@ return baseclass.extend({ getNftRulesCheck, getPodkopStatus, getProxyUrlName, - getSingboxStatus, + getSingBoxCheck, + getSingBoxStatus, initDashboardController, initDiagnosticController, injectGlobalStyles, From 4334643e8e70dd74d46d52e74f182ffb7d23a8b1 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 11 Oct 2025 23:09:31 +0300 Subject: [PATCH 034/121] feat: implement base of diagnostics --- fe-app-podkop/src/helpers/index.ts | 1 + fe-app-podkop/src/helpers/svgEl.ts | 18 + fe-app-podkop/src/icons/index.ts | 5 + .../src/icons/renderLoaderCircleIcon24.ts | 34 + .../src/icons/renderShieldAlertIcon24.ts | 27 + .../src/icons/renderShieldCheckIcon24.ts | 26 + fe-app-podkop/src/icons/renderShieldIcon24.ts | 25 + .../src/icons/renderShieldXIcon24.ts | 27 + .../tabs/diagnostic/checks/runDnsCheck.ts | 82 +++ .../tabs/diagnostic/checks/runNftCheck.ts | 119 +++ .../tabs/diagnostic/checks/runSingBoxCheck.ts | 103 +++ .../diagnostic/initDiagnosticController.ts | 50 +- .../tabs/diagnostic/renderCheckSection.ts | 167 +++++ .../tabs/diagnostic/renderDiagnostic.ts | 45 +- .../tabs/diagnostic/updateDiagnosticsCheck.ts | 10 + fe-app-podkop/src/store.ts | 14 + fe-app-podkop/src/styles.ts | 89 +++ .../luci-static/resources/view/podkop/main.js | 688 +++++++++++++++++- 18 files changed, 1518 insertions(+), 12 deletions(-) create mode 100644 fe-app-podkop/src/helpers/svgEl.ts create mode 100644 fe-app-podkop/src/icons/index.ts create mode 100644 fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderShieldAlertIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderShieldCheckIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderShieldIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderShieldXIcon24.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts diff --git a/fe-app-podkop/src/helpers/index.ts b/fe-app-podkop/src/helpers/index.ts index 9b48e5b..b82ff09 100644 --- a/fe-app-podkop/src/helpers/index.ts +++ b/fe-app-podkop/src/helpers/index.ts @@ -10,3 +10,4 @@ export * from './getClashApiUrl'; export * from './splitProxyString'; export * from './preserveScrollForPage'; export * from './parseQueryString'; +export * from './svgEl'; diff --git a/fe-app-podkop/src/helpers/svgEl.ts b/fe-app-podkop/src/helpers/svgEl.ts new file mode 100644 index 0000000..f7ebfd4 --- /dev/null +++ b/fe-app-podkop/src/helpers/svgEl.ts @@ -0,0 +1,18 @@ +export function svgEl( + tag: K, + attrs: Partial> = {}, + children: (SVGElement | null | undefined)[] = [], +): SVGElementTagNameMap[K] { + const NS = 'http://www.w3.org/2000/svg'; + const el = document.createElementNS(NS, tag); + + for (const [k, v] of Object.entries(attrs)) { + if (v != null) el.setAttribute(k, String(v)); + } + + (Array.isArray(children) ? children : [children]) + .filter(Boolean) + .forEach((ch) => el.appendChild(ch as SVGElement)); + + return el; +} diff --git a/fe-app-podkop/src/icons/index.ts b/fe-app-podkop/src/icons/index.ts new file mode 100644 index 0000000..2bfb2f5 --- /dev/null +++ b/fe-app-podkop/src/icons/index.ts @@ -0,0 +1,5 @@ +export * from './renderLoaderCircleIcon24'; +export * from './renderShieldAlertIcon24'; +export * from './renderShieldCheckIcon24'; +export * from './renderShieldIcon24'; +export * from './renderShieldXIcon24'; diff --git a/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts b/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts new file mode 100644 index 0000000..43e2030 --- /dev/null +++ b/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts @@ -0,0 +1,34 @@ +import { svgEl } from '../helpers'; + +export function renderLoaderCircleIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + width: '24', + height: '24', + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-loader-circle lucide-rotate', + }, + [ + svgEl('path', { + d: 'M21 12a9 9 0 1 1-6.219-8.56', + }), + svgEl('animateTransform', { + attributeName: 'transform', + attributeType: 'XML', + type: 'rotate', + from: '0 12 12', + to: '360 12 12', + dur: '1s', + repeatCount: 'indefinite', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderShieldAlertIcon24.ts b/fe-app-podkop/src/icons/renderShieldAlertIcon24.ts new file mode 100644 index 0000000..7397794 --- /dev/null +++ b/fe-app-podkop/src/icons/renderShieldAlertIcon24.ts @@ -0,0 +1,27 @@ +import { svgEl } from '../helpers'; + +export function renderShieldAlertIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + width: '24', + height: '24', + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-shield-alert', + }, + [ + svgEl('path', { + d: 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z', + }), + svgEl('path', { d: 'M12 8v4' }), + svgEl('path', { d: 'M12 16h.01' }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderShieldCheckIcon24.ts b/fe-app-podkop/src/icons/renderShieldCheckIcon24.ts new file mode 100644 index 0000000..37f4046 --- /dev/null +++ b/fe-app-podkop/src/icons/renderShieldCheckIcon24.ts @@ -0,0 +1,26 @@ +import { svgEl } from '../helpers'; + +export function renderShieldCheckIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + width: '24', + height: '24', + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-shield-check', + }, + [ + svgEl('path', { + d: 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z', + }), + svgEl('path', { d: 'm9 12 2 2 4-4' }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderShieldIcon24.ts b/fe-app-podkop/src/icons/renderShieldIcon24.ts new file mode 100644 index 0000000..afaaae1 --- /dev/null +++ b/fe-app-podkop/src/icons/renderShieldIcon24.ts @@ -0,0 +1,25 @@ +import { svgEl } from '../helpers'; + +export function renderShieldIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + width: '24', + height: '24', + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-shield', + }, + [ + svgEl('path', { + d: 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderShieldXIcon24.ts b/fe-app-podkop/src/icons/renderShieldXIcon24.ts new file mode 100644 index 0000000..ffbf708 --- /dev/null +++ b/fe-app-podkop/src/icons/renderShieldXIcon24.ts @@ -0,0 +1,27 @@ +import { svgEl } from '../helpers'; + +export function renderShieldXIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + width: '24', + height: '24', + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-shield-x', + }, + [ + svgEl('path', { + d: 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z', + }), + svgEl('path', { d: 'm14.5 9.5-5 5' }), + svgEl('path', { d: 'm9.5 9.5 5 5' }), + ], + ); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts new file mode 100644 index 0000000..fb5a2a8 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -0,0 +1,82 @@ +import { getDNSCheck } from '../../../methods'; +import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; + +export async function runDnsCheck() { + const code = 'dns_check'; + + updateDiagnosticsCheck({ + code, + title: _('DNS checks'), + description: _('Checking dns, please wait'), + state: 'loading', + items: [], + }); + + const dnsChecks = await getDNSCheck(); + + if (!dnsChecks.success) { + updateDiagnosticsCheck({ + code, + title: _('DNS checks'), + description: _('Cannot receive DNS checks result'), + state: 'error', + items: [], + }); + + throw new Error('DNS checks failed'); + } + + const data = dnsChecks.data; + + const allGood = + Boolean(data.local_dns_status) && + Boolean(data.bootstrap_dns_status) && + Boolean(data.dns_status); + + const atLeastOneGood = + Boolean(data.local_dns_status) || + Boolean(data.bootstrap_dns_status) || + Boolean(data.dns_status); + + console.log('dnsChecks', dnsChecks); + + function getStatus() { + if (allGood) { + return 'success'; + } + + if (atLeastOneGood) { + return 'warning'; + } + + return 'error'; + } + + updateDiagnosticsCheck({ + code, + title: _('DNS checks'), + description: _('DNS checks passed'), + state: getStatus(), + items: [ + { + state: data.bootstrap_dns_status ? 'success' : 'error', + key: _('Bootsrap DNS'), + value: data.bootstrap_dns_server, + }, + { + state: data.dns_status ? 'success' : 'error', + key: _('Main DNS'), + value: `${data.dns_server} [${data.dns_type}]`, + }, + { + state: data.local_dns_status ? 'success' : 'error', + key: _('Local DNS'), + value: data.local_dns_status ? _('Enabled') : _('Failed'), + }, + ], + }); + + if (!atLeastOneGood) { + throw new Error('DNS checks failed'); + } +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts new file mode 100644 index 0000000..d59eeee --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -0,0 +1,119 @@ +import { getNftRulesCheck } from '../../../methods'; +import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; + +export async function runNftCheck() { + const code = 'nft_check'; + + updateDiagnosticsCheck({ + code, + title: _('Nftables checks'), + description: _('Checking nftables, please wait'), + state: 'loading', + items: [], + }); + + const nftablesChecks = await getNftRulesCheck(); + + if (!nftablesChecks.success) { + updateDiagnosticsCheck({ + code, + title: _('Nftables checks'), + description: _('Cannot receive nftables checks result'), + state: 'error', + items: [], + }); + + throw new Error('Nftables checks failed'); + } + + const data = nftablesChecks.data; + + const allGood = + Boolean(data.table_exist) && + Boolean(data.rules_mangle_exist) && + Boolean(data.rules_mangle_counters) && + Boolean(data.rules_mangle_output_exist) && + Boolean(data.rules_mangle_output_counters) && + Boolean(data.rules_proxy_exist) && + Boolean(data.rules_proxy_counters) && + Boolean(data.rules_other_mark_exist); + + const atLeastOneGood = + Boolean(data.table_exist) || + Boolean(data.rules_mangle_exist) || + Boolean(data.rules_mangle_counters) || + Boolean(data.rules_mangle_output_exist) || + Boolean(data.rules_mangle_output_counters) || + Boolean(data.rules_proxy_exist) || + Boolean(data.rules_proxy_counters) || + Boolean(data.rules_other_mark_exist); + + console.log('nftablesChecks', nftablesChecks); + + function getStatus() { + if (allGood) { + return 'success'; + } + + if (atLeastOneGood) { + return 'warning'; + } + + return 'error'; + } + + updateDiagnosticsCheck({ + code, + title: _('Nftables checks'), + description: allGood + ? _('Nftables checks passed') + : _('Nftables checks partially passed'), + state: getStatus(), + items: [ + { + state: data.table_exist ? 'success' : 'error', + key: _('Table exist'), + value: data.table_exist ? _('Yes') : _('No'), + }, + { + state: data.rules_mangle_exist ? 'success' : 'error', + key: _('Rules mangle exist'), + value: data.rules_mangle_exist ? _('Yes') : _('No'), + }, + { + state: data.rules_mangle_counters ? 'success' : 'error', + key: _('Rules mangle counters'), + value: data.rules_mangle_counters ? _('Yes') : _('No'), + }, + { + state: data.rules_mangle_output_exist ? 'success' : 'error', + key: _('Rules mangle output exist'), + value: data.rules_mangle_output_exist ? _('Yes') : _('No'), + }, + { + state: data.rules_mangle_output_counters ? 'success' : 'error', + key: _('Rules mangle output counters'), + value: data.rules_mangle_output_counters ? _('Yes') : _('No'), + }, + { + state: data.rules_proxy_exist ? 'success' : 'error', + key: _('Rules proxy exist'), + value: data.rules_proxy_exist ? _('Yes') : _('No'), + }, + { + state: data.rules_proxy_counters ? 'success' : 'error', + key: _('Rules proxy counters'), + value: data.rules_proxy_counters ? _('Yes') : _('No'), + }, + { + state: data.rules_other_mark_exist ? 'warning' : 'success', + key: _('Rules other mark exist'), + value: data.rules_other_mark_exist ? _('Yes') : _('No'), + }, + ], + }); + + if (!atLeastOneGood) { + throw new Error('Nftables checks failed'); + } +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts new file mode 100644 index 0000000..6f4adce --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts @@ -0,0 +1,103 @@ +import { getSingBoxCheck } from '../../../methods'; +import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; + +export async function runSingBoxCheck() { + const code = 'sing_box_check'; + + updateDiagnosticsCheck({ + code, + title: _('Sing-box checks'), + description: _('Checking sing-box, please wait'), + state: 'loading', + items: [], + }); + + const singBoxChecks = await getSingBoxCheck(); + + if (!singBoxChecks.success) { + updateDiagnosticsCheck({ + code, + title: _('Sing-box checks'), + description: _('Cannot receive Sing-box checks result'), + state: 'error', + items: [], + }); + + throw new Error('Sing-box checks failed'); + } + + const data = singBoxChecks.data; + + const allGood = + Boolean(data.sing_box_installed) && + Boolean(data.sing_box_version_ok) && + Boolean(data.sing_box_service_exist) && + Boolean(data.sing_box_autostart_disabled) && + Boolean(data.sing_box_process_running) && + Boolean(data.sing_box_ports_listening); + + const atLeastOneGood = + Boolean(data.sing_box_installed) || + Boolean(data.sing_box_version_ok) || + Boolean(data.sing_box_service_exist) || + Boolean(data.sing_box_autostart_disabled) || + Boolean(data.sing_box_process_running) || + Boolean(data.sing_box_ports_listening); + + console.log('singBoxChecks', singBoxChecks); + + function getStatus() { + if (allGood) { + return 'success'; + } + + if (atLeastOneGood) { + return 'warning'; + } + + return 'error'; + } + + updateDiagnosticsCheck({ + code, + title: _('Sing-box checks'), + description: _('Sing-box checks passed'), + state: getStatus(), + items: [ + { + state: data.sing_box_installed ? 'success' : 'error', + key: _('Sing-box installed'), + value: data.sing_box_installed ? _('Yes') : _('No'), + }, + { + state: data.sing_box_version_ok ? 'success' : 'error', + key: _('Sing-box version >= 1.12.4'), + value: data.sing_box_version_ok ? _('Yes') : _('No'), + }, + { + state: data.sing_box_service_exist ? 'success' : 'error', + key: _('Sing-box service exist'), + value: data.sing_box_service_exist ? _('Yes') : _('No'), + }, + { + state: data.sing_box_autostart_disabled ? 'success' : 'error', + key: _('Sing-box autostart disabled'), + value: data.sing_box_autostart_disabled ? _('Yes') : _('No'), + }, + { + state: data.sing_box_process_running ? 'success' : 'error', + key: _('Sing-box process running'), + value: data.sing_box_process_running ? _('Yes') : _('No'), + }, + { + state: data.sing_box_ports_listening ? 'success' : 'error', + key: _('Sing-box listening ports'), + value: data.sing_box_ports_listening ? _('Yes') : _('No'), + }, + ], + }); + + if (!atLeastOneGood) { + throw new Error('Sing-box checks failed'); + } +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts index e1e82e2..f6afc55 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts @@ -1,7 +1,55 @@ -import { onMount } from '../../../helpers'; +import { onMount, preserveScrollForPage } from '../../../helpers'; +import { store, StoreType } from '../../../store'; +import { renderCheckSection } from './renderCheckSection'; +import { runDnsCheck } from './checks/runDnsCheck'; +import { runSingBoxCheck } from './checks/runSingBoxCheck'; +import { runNftCheck } from './checks/runNftCheck'; + +async function renderDiagnosticsChecks() { + console.log('renderDiagnosticsChecks'); + const diagnosticsChecks = store.get().diagnosticsChecks; + const container = document.getElementById('pdk_diagnostic-page-checks'); + + const renderedDiagnosticsChecks = diagnosticsChecks.map((check) => + renderCheckSection(check), + ); + + return preserveScrollForPage(() => { + container!.replaceChildren(...renderedDiagnosticsChecks); + }); +} + +async function onStoreUpdate( + next: StoreType, + prev: StoreType, + diff: Partial, +) { + if (diff.diagnosticsChecks) { + renderDiagnosticsChecks(); + } +} + +async function runChecks() { + await runDnsCheck(); + + await runSingBoxCheck(); + + await runNftCheck(); +} export async function initDiagnosticController(): Promise { onMount('diagnostic-status').then(() => { console.log('diagnostic controller initialized.'); + // Remove old listener + store.unsubscribe(onStoreUpdate); + + // Clear store + store.reset(); + + // Add new listener + store.subscribe(onStoreUpdate); + + // TMP run checks on mount + runChecks(); }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts new file mode 100644 index 0000000..1c15522 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts @@ -0,0 +1,167 @@ +import { + renderLoaderCircleIcon24, + renderShieldAlertIcon24, + renderShieldCheckIcon24, + renderShieldIcon24, + renderShieldXIcon24, +} from '../../../icons'; +import { IDiagnosticsChecksStoreItem } from '../../../store'; + +type IRenderCheckSectionProps = IDiagnosticsChecksStoreItem; + +function renderCheckSummary(items: IRenderCheckSectionProps['items']) { + if (!items.length) { + return E('div', {}, ''); + } + + const renderedItems = items.map((item) => + E( + 'div', + { + class: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}`, + }, + [E('b', {}, item.key), E('div', {}, item.value)], + ), + ); + + return E('div', { class: 'pdk_diagnostic_alert__summary' }, renderedItems); +} + +function renderLoadingState(props: IRenderCheckSectionProps) { + const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); + iconWrap.appendChild(renderLoaderCircleIcon24()); + + return E( + 'div', + { class: 'pdk_diagnostic_alert pdk_diagnostic_alert--loading' }, + [ + iconWrap, + E('div', { class: 'pdk_diagnostic_alert__content' }, [ + E('b', { class: 'pdk_diagnostic_alert__title' }, props.title), + E( + 'div', + { class: 'pdk_diagnostic_alert__description' }, + props.description, + ), + ]), + E('div', {}, ''), + renderCheckSummary(props.items), + ], + ); +} + +function renderWarningState(props: IRenderCheckSectionProps) { + const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); + iconWrap.appendChild(renderShieldAlertIcon24()); + + return E( + 'div', + { class: 'pdk_diagnostic_alert pdk_diagnostic_alert--warning' }, + [ + iconWrap, + E('div', { class: 'pdk_diagnostic_alert__content' }, [ + E('b', { class: 'pdk_diagnostic_alert__title' }, props.title), + E( + 'div', + { class: 'pdk_diagnostic_alert__description' }, + props.description, + ), + ]), + E('div', {}, ''), + renderCheckSummary(props.items), + ], + ); +} + +function renderErrorState(props: IRenderCheckSectionProps) { + const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); + iconWrap.appendChild(renderShieldXIcon24()); + + return E( + 'div', + { class: 'pdk_diagnostic_alert pdk_diagnostic_alert--error' }, + [ + iconWrap, + E('div', { class: 'pdk_diagnostic_alert__content' }, [ + E('b', { class: 'pdk_diagnostic_alert__title' }, props.title), + E( + 'div', + { class: 'pdk_diagnostic_alert__description' }, + props.description, + ), + ]), + E('div', {}, ''), + renderCheckSummary(props.items), + ], + ); +} + +function renderSuccessState(props: IRenderCheckSectionProps) { + const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); + iconWrap.appendChild(renderShieldCheckIcon24()); + + return E( + 'div', + { class: 'pdk_diagnostic_alert pdk_diagnostic_alert--success' }, + [ + iconWrap, + E('div', { class: 'pdk_diagnostic_alert__content' }, [ + E('b', { class: 'pdk_diagnostic_alert__title' }, props.title), + E( + 'div', + { class: 'pdk_diagnostic_alert__description' }, + props.description, + ), + ]), + E('div', {}, ''), + renderCheckSummary(props.items), + ], + ); +} + +function renderSkippedState(props: IRenderCheckSectionProps) { + const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); + iconWrap.appendChild(renderShieldIcon24()); + + return E( + 'div', + { class: 'pdk_diagnostic_alert pdk_diagnostic_alert--skipped' }, + [ + iconWrap, + E('div', { class: 'pdk_diagnostic_alert__content' }, [ + E('b', { class: 'pdk_diagnostic_alert__title' }, props.title), + E( + 'div', + { class: 'pdk_diagnostic_alert__description' }, + props.description, + ), + ]), + E('div', {}, ''), + renderCheckSummary(props.items), + ], + ); +} + +export function renderCheckSection(props: IRenderCheckSectionProps) { + if (props.state === 'loading') { + return renderLoadingState(props); + } + + if (props.state === 'warning') { + return renderWarningState(props); + } + + if (props.state === 'error') { + return renderErrorState(props); + } + + if (props.state === 'success') { + return renderSuccessState(props); + } + + if (props.state === 'skipped') { + return renderSkippedState(props); + } + + return E('div', {}, 'Not implement yet'); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts index 5e8f7fc..ef5fdac 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts @@ -1,10 +1,45 @@ export function renderDiagnostic() { return E( 'div', - { - id: 'diagnostic-status', - class: 'pdk_diagnostic-page', - }, - 'Not implemented yet', + { id: 'diagnostic-status', class: 'pdk_diagnostic-page' }, + E( + 'div', + { + class: 'pdk_diagnostic-page__checks', + id: 'pdk_diagnostic-page-checks', + }, + // [ + // renderCheckSection({ + // state: 'loading', + // title: _('DNS Checks'), + // description: _('Checking, please wait'), + // items: [], + // }), + // renderCheckSection({ + // state: 'warning', + // title: _('DNS Checks'), + // description: _('Some checks was failed'), + // items: [], + // }), + // renderCheckSection({ + // state: 'error', + // title: _('DNS Checks'), + // description: _('Checks was failed'), + // items: [], + // }), + // renderCheckSection({ + // state: 'success', + // title: _('DNS Checks'), + // description: _('Checks was passed'), + // items: [], + // }), + // renderCheckSection({ + // state: 'skipped', + // title: _('DNS Checks'), + // description: _('Checks was skipped'), + // items: [], + // }), + // ], + ), ); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts new file mode 100644 index 0000000..d8dad2c --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts @@ -0,0 +1,10 @@ +import { IDiagnosticsChecksStoreItem, store } from '../../../store'; + +export function updateDiagnosticsCheck(check: IDiagnosticsChecksStoreItem) { + const diagnosticsChecks = store.get().diagnosticsChecks; + const other = diagnosticsChecks.filter((item) => item.code !== check.code); + + store.set({ + diagnosticsChecks: [...other, check], + }); +} diff --git a/fe-app-podkop/src/store.ts b/fe-app-podkop/src/store.ts index 4591353..5e07548 100644 --- a/fe-app-podkop/src/store.ts +++ b/fe-app-podkop/src/store.ts @@ -112,6 +112,18 @@ class Store> { } } +export interface IDiagnosticsChecksStoreItem { + code: string; + title: string; + description: string; + state: 'loading' | 'warning' | 'success' | 'error' | 'skipped'; + items: Array<{ + state: 'error' | 'warning' | 'success'; + key: string; + value: string; + }>; +} + export interface StoreType { tabService: { current: string; @@ -143,6 +155,7 @@ export interface StoreType { data: Podkop.OutboundGroup[]; latencyFetching: boolean; }; + diagnosticsChecks: Array; } const initialStore: StoreType = { @@ -176,6 +189,7 @@ const initialStore: StoreType = { latencyFetching: false, data: [], }, + diagnosticsChecks: [], }; export const store = new Store(initialStore); diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index 6c3f706..5ed936c 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -40,6 +40,10 @@ export const GlobalStyles = ` display: none; } +#cbi-podkop-diagnostic > h3 { + display: none; +} + .cbi-section-remove { margin-bottom: -32px; } @@ -194,4 +198,89 @@ export const GlobalStyles = ` left: 150%; } } + +/* Lucide spinner animate */ +.lucide-rotate { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +#cbi-podkop-diagnostic-_mount_node > div { + width: 100%; +} + +.pdk_diagnostic-page__checks { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic_alert { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + + display: grid; + grid-template-columns: 24px 1fr; + grid-column-gap: 10px; + align-items: center; + padding: 10px; +} + +.pdk_diagnostic_alert--loading { + border: 2px var(--primary-color-high, dodgerblue) solid; +} + +.pdk_diagnostic_alert--warning { + border: 2px var(--warn-color-medium, orange) solid; + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic_alert--error { + border: 2px var(--error-color-medium, red) solid; + color: var(--error-color-medium, red); +} + +.pdk_diagnostic_alert--success { + border: 2px var(--success-color-medium, green) solid; + color: var(--success-color-medium, green); +} + +.pdk_diagnostic_alert--skipped {} + +.pdk_diagnostic_alert__icon {} + +.pdk_diagnostic_alert__content {} + +.pdk_diagnostic_alert__title { + display: block; +} + +.pdk_diagnostic_alert__description {} + +.pdk_diagnostic_alert__summary { + margin-top: 10px; +} + +.pdk_diagnostic_alert__summary__item { + display: grid; + grid-template-columns: auto 1fr; + grid-column-gap: 10px; +} + +.pdk_diagnostic_alert__summary__item--error { + color: var(--error-color-medium, red); +} + +.pdk_diagnostic_alert__summary__item--warning { + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic_alert__summary__item--success { + color: var(--success-color-medium, green); +} + `; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 1ae5e68..5fee549 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -263,6 +263,10 @@ var GlobalStyles = ` display: none; } +#cbi-podkop-diagnostic > h3 { + display: none; +} + .cbi-section-remove { margin-bottom: -32px; } @@ -417,6 +421,91 @@ var GlobalStyles = ` left: 150%; } } + +/* Lucide spinner animate */ +.lucide-rotate { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +#cbi-podkop-diagnostic-_mount_node > div { + width: 100%; +} + +.pdk_diagnostic-page__checks { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic_alert { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + + display: grid; + grid-template-columns: 24px 1fr; + grid-column-gap: 10px; + align-items: center; + padding: 10px; +} + +.pdk_diagnostic_alert--loading { + border: 2px var(--primary-color-high, dodgerblue) solid; +} + +.pdk_diagnostic_alert--warning { + border: 2px var(--warn-color-medium, orange) solid; + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic_alert--error { + border: 2px var(--error-color-medium, red) solid; + color: var(--error-color-medium, red); +} + +.pdk_diagnostic_alert--success { + border: 2px var(--success-color-medium, green) solid; + color: var(--success-color-medium, green); +} + +.pdk_diagnostic_alert--skipped {} + +.pdk_diagnostic_alert__icon {} + +.pdk_diagnostic_alert__content {} + +.pdk_diagnostic_alert__title { + display: block; +} + +.pdk_diagnostic_alert__description {} + +.pdk_diagnostic_alert__summary { + margin-top: 10px; +} + +.pdk_diagnostic_alert__summary__item { + display: grid; + grid-template-columns: auto 1fr; + grid-column-gap: 10px; +} + +.pdk_diagnostic_alert__summary__item--error { + color: var(--error-color-medium, red); +} + +.pdk_diagnostic_alert__summary__item--warning { + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic_alert__summary__item--success { + color: var(--success-color-medium, green); +} + `; // src/helpers/injectGlobalStyles.ts @@ -668,6 +757,17 @@ function parseQueryString(query) { ); } +// src/helpers/svgEl.ts +function svgEl(tag, attrs = {}, children = []) { + const NS = "http://www.w3.org/2000/svg"; + const el = document.createElementNS(NS, tag); + for (const [k, v] of Object.entries(attrs)) { + if (v != null) el.setAttribute(k, String(v)); + } + (Array.isArray(children) ? children : [children]).filter(Boolean).forEach((ch) => el.appendChild(ch)); + return el; +} + // src/validators/validateVlessUrl.ts function validateVlessUrl(url) { try { @@ -1329,7 +1429,8 @@ var initialStore = { failed: false, latencyFetching: false, data: [] - } + }, + diagnosticsChecks: [] }; var store = new Store(initialStore); @@ -2036,18 +2137,592 @@ async function initDashboardController() { function renderDiagnostic() { return E( "div", - { - id: "diagnostic-status", - class: "pdk_diagnostic-page" - }, - "Not implemented yet" + { id: "diagnostic-status", class: "pdk_diagnostic-page" }, + E( + "div", + { + class: "pdk_diagnostic-page__checks", + id: "pdk_diagnostic-page-checks" + } + // [ + // renderCheckSection({ + // state: 'loading', + // title: _('DNS Checks'), + // description: _('Checking, please wait'), + // items: [], + // }), + // renderCheckSection({ + // state: 'warning', + // title: _('DNS Checks'), + // description: _('Some checks was failed'), + // items: [], + // }), + // renderCheckSection({ + // state: 'error', + // title: _('DNS Checks'), + // description: _('Checks was failed'), + // items: [], + // }), + // renderCheckSection({ + // state: 'success', + // title: _('DNS Checks'), + // description: _('Checks was passed'), + // items: [], + // }), + // renderCheckSection({ + // state: 'skipped', + // title: _('DNS Checks'), + // description: _('Checks was skipped'), + // items: [], + // }), + // ], + ) ); } +// src/icons/renderLoaderCircleIcon24.ts +function renderLoaderCircleIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-loader-circle lucide-rotate" + }, + [ + svgEl("path", { + d: "M21 12a9 9 0 1 1-6.219-8.56" + }), + svgEl("animateTransform", { + attributeName: "transform", + attributeType: "XML", + type: "rotate", + from: "0 12 12", + to: "360 12 12", + dur: "1s", + repeatCount: "indefinite" + }) + ] + ); +} + +// src/icons/renderShieldAlertIcon24.ts +function renderShieldAlertIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-shield-alert" + }, + [ + svgEl("path", { + d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" + }), + svgEl("path", { d: "M12 8v4" }), + svgEl("path", { d: "M12 16h.01" }) + ] + ); +} + +// src/icons/renderShieldCheckIcon24.ts +function renderShieldCheckIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-shield-check" + }, + [ + svgEl("path", { + d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" + }), + svgEl("path", { d: "m9 12 2 2 4-4" }) + ] + ); +} + +// src/icons/renderShieldIcon24.ts +function renderShieldIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-shield" + }, + [ + svgEl("path", { + d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" + }) + ] + ); +} + +// src/icons/renderShieldXIcon24.ts +function renderShieldXIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-shield-x" + }, + [ + svgEl("path", { + d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" + }), + svgEl("path", { d: "m14.5 9.5-5 5" }), + svgEl("path", { d: "m9.5 9.5 5 5" }) + ] + ); +} + +// src/podkop/tabs/diagnostic/renderCheckSection.ts +function renderCheckSummary(items) { + if (!items.length) { + return E("div", {}, ""); + } + const renderedItems = items.map( + (item) => E( + "div", + { + class: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}` + }, + [E("b", {}, item.key), E("div", {}, item.value)] + ) + ); + return E("div", { class: "pdk_diagnostic_alert__summary" }, renderedItems); +} +function renderLoadingState3(props) { + const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); + iconWrap.appendChild(renderLoaderCircleIcon24()); + return E( + "div", + { class: "pdk_diagnostic_alert pdk_diagnostic_alert--loading" }, + [ + iconWrap, + E("div", { class: "pdk_diagnostic_alert__content" }, [ + E("b", { class: "pdk_diagnostic_alert__title" }, props.title), + E( + "div", + { class: "pdk_diagnostic_alert__description" }, + props.description + ) + ]), + E("div", {}, ""), + renderCheckSummary(props.items) + ] + ); +} +function renderWarningState(props) { + const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); + iconWrap.appendChild(renderShieldAlertIcon24()); + return E( + "div", + { class: "pdk_diagnostic_alert pdk_diagnostic_alert--warning" }, + [ + iconWrap, + E("div", { class: "pdk_diagnostic_alert__content" }, [ + E("b", { class: "pdk_diagnostic_alert__title" }, props.title), + E( + "div", + { class: "pdk_diagnostic_alert__description" }, + props.description + ) + ]), + E("div", {}, ""), + renderCheckSummary(props.items) + ] + ); +} +function renderErrorState(props) { + const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); + iconWrap.appendChild(renderShieldXIcon24()); + return E( + "div", + { class: "pdk_diagnostic_alert pdk_diagnostic_alert--error" }, + [ + iconWrap, + E("div", { class: "pdk_diagnostic_alert__content" }, [ + E("b", { class: "pdk_diagnostic_alert__title" }, props.title), + E( + "div", + { class: "pdk_diagnostic_alert__description" }, + props.description + ) + ]), + E("div", {}, ""), + renderCheckSummary(props.items) + ] + ); +} +function renderSuccessState(props) { + const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); + iconWrap.appendChild(renderShieldCheckIcon24()); + return E( + "div", + { class: "pdk_diagnostic_alert pdk_diagnostic_alert--success" }, + [ + iconWrap, + E("div", { class: "pdk_diagnostic_alert__content" }, [ + E("b", { class: "pdk_diagnostic_alert__title" }, props.title), + E( + "div", + { class: "pdk_diagnostic_alert__description" }, + props.description + ) + ]), + E("div", {}, ""), + renderCheckSummary(props.items) + ] + ); +} +function renderSkippedState(props) { + const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); + iconWrap.appendChild(renderShieldIcon24()); + return E( + "div", + { class: "pdk_diagnostic_alert pdk_diagnostic_alert--skipped" }, + [ + iconWrap, + E("div", { class: "pdk_diagnostic_alert__content" }, [ + E("b", { class: "pdk_diagnostic_alert__title" }, props.title), + E( + "div", + { class: "pdk_diagnostic_alert__description" }, + props.description + ) + ]), + E("div", {}, ""), + renderCheckSummary(props.items) + ] + ); +} +function renderCheckSection(props) { + if (props.state === "loading") { + return renderLoadingState3(props); + } + if (props.state === "warning") { + return renderWarningState(props); + } + if (props.state === "error") { + return renderErrorState(props); + } + if (props.state === "success") { + return renderSuccessState(props); + } + if (props.state === "skipped") { + return renderSkippedState(props); + } + return E("div", {}, "Not implement yet"); +} + +// src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts +function updateDiagnosticsCheck(check) { + const diagnosticsChecks = store.get().diagnosticsChecks; + const other = diagnosticsChecks.filter((item) => item.code !== check.code); + store.set({ + diagnosticsChecks: [...other, check] + }); +} + +// src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +async function runDnsCheck() { + const code = "dns_check"; + updateDiagnosticsCheck({ + code, + title: _("DNS checks"), + description: _("Checking dns, please wait"), + state: "loading", + items: [] + }); + const dnsChecks = await getDNSCheck(); + if (!dnsChecks.success) { + updateDiagnosticsCheck({ + code, + title: _("DNS checks"), + description: _("Cannot receive DNS checks result"), + state: "error", + items: [] + }); + throw new Error("DNS checks failed"); + } + const data = dnsChecks.data; + const allGood = Boolean(data.local_dns_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); + const atLeastOneGood = Boolean(data.local_dns_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); + console.log("dnsChecks", dnsChecks); + function getStatus() { + if (allGood) { + return "success"; + } + if (atLeastOneGood) { + return "warning"; + } + return "error"; + } + updateDiagnosticsCheck({ + code, + title: _("DNS checks"), + description: _("DNS checks passed"), + state: getStatus(), + items: [ + { + state: data.bootstrap_dns_status ? "success" : "error", + key: _("Bootsrap DNS"), + value: data.bootstrap_dns_server + }, + { + state: data.dns_status ? "success" : "error", + key: _("Main DNS"), + value: `${data.dns_server} [${data.dns_type}]` + }, + { + state: data.local_dns_status ? "success" : "error", + key: _("Local DNS"), + value: data.local_dns_status ? _("Enabled") : _("Failed") + } + ] + }); + if (!atLeastOneGood) { + throw new Error("DNS checks failed"); + } +} + +// src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +async function runSingBoxCheck() { + const code = "sing_box_check"; + updateDiagnosticsCheck({ + code, + title: _("Sing-box checks"), + description: _("Checking sing-box, please wait"), + state: "loading", + items: [] + }); + const singBoxChecks = await getSingBoxCheck(); + if (!singBoxChecks.success) { + updateDiagnosticsCheck({ + code, + title: _("Sing-box checks"), + description: _("Cannot receive Sing-box checks result"), + state: "error", + items: [] + }); + throw new Error("Sing-box checks failed"); + } + const data = singBoxChecks.data; + const allGood = Boolean(data.sing_box_installed) && Boolean(data.sing_box_version_ok) && Boolean(data.sing_box_service_exist) && Boolean(data.sing_box_autostart_disabled) && Boolean(data.sing_box_process_running) && Boolean(data.sing_box_ports_listening); + const atLeastOneGood = Boolean(data.sing_box_installed) || Boolean(data.sing_box_version_ok) || Boolean(data.sing_box_service_exist) || Boolean(data.sing_box_autostart_disabled) || Boolean(data.sing_box_process_running) || Boolean(data.sing_box_ports_listening); + console.log("singBoxChecks", singBoxChecks); + function getStatus() { + if (allGood) { + return "success"; + } + if (atLeastOneGood) { + return "warning"; + } + return "error"; + } + updateDiagnosticsCheck({ + code, + title: _("Sing-box checks"), + description: _("Sing-box checks passed"), + state: getStatus(), + items: [ + { + state: data.sing_box_installed ? "success" : "error", + key: _("Sing-box installed"), + value: data.sing_box_installed ? _("Yes") : _("No") + }, + { + state: data.sing_box_version_ok ? "success" : "error", + key: _("Sing-box version >= 1.12.4"), + value: data.sing_box_version_ok ? _("Yes") : _("No") + }, + { + state: data.sing_box_service_exist ? "success" : "error", + key: _("Sing-box service exist"), + value: data.sing_box_service_exist ? _("Yes") : _("No") + }, + { + state: data.sing_box_autostart_disabled ? "success" : "error", + key: _("Sing-box autostart disabled"), + value: data.sing_box_autostart_disabled ? _("Yes") : _("No") + }, + { + state: data.sing_box_process_running ? "success" : "error", + key: _("Sing-box process running"), + value: data.sing_box_process_running ? _("Yes") : _("No") + }, + { + state: data.sing_box_ports_listening ? "success" : "error", + key: _("Sing-box listening ports"), + value: data.sing_box_ports_listening ? _("Yes") : _("No") + } + ] + }); + if (!atLeastOneGood) { + throw new Error("Sing-box checks failed"); + } +} + +// src/podkop/tabs/diagnostic/checks/runNftCheck.ts +async function runNftCheck() { + const code = "nft_check"; + updateDiagnosticsCheck({ + code, + title: _("Nftables checks"), + description: _("Checking nftables, please wait"), + state: "loading", + items: [] + }); + const nftablesChecks = await getNftRulesCheck(); + if (!nftablesChecks.success) { + updateDiagnosticsCheck({ + code, + title: _("Nftables checks"), + description: _("Cannot receive nftables checks result"), + state: "error", + items: [] + }); + throw new Error("Nftables checks failed"); + } + const data = nftablesChecks.data; + const allGood = Boolean(data.table_exist) && Boolean(data.rules_mangle_exist) && Boolean(data.rules_mangle_counters) && Boolean(data.rules_mangle_output_exist) && Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && Boolean(data.rules_other_mark_exist); + const atLeastOneGood = Boolean(data.table_exist) || Boolean(data.rules_mangle_exist) || Boolean(data.rules_mangle_counters) || Boolean(data.rules_mangle_output_exist) || Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || Boolean(data.rules_other_mark_exist); + console.log("nftablesChecks", nftablesChecks); + function getStatus() { + if (allGood) { + return "success"; + } + if (atLeastOneGood) { + return "warning"; + } + return "error"; + } + updateDiagnosticsCheck({ + code, + title: _("Nftables checks"), + description: allGood ? _("Nftables checks passed") : _("Nftables checks partially passed"), + state: getStatus(), + items: [ + { + state: data.table_exist ? "success" : "error", + key: _("Table exist"), + value: data.table_exist ? _("Yes") : _("No") + }, + { + state: data.rules_mangle_exist ? "success" : "error", + key: _("Rules mangle exist"), + value: data.rules_mangle_exist ? _("Yes") : _("No") + }, + { + state: data.rules_mangle_counters ? "success" : "error", + key: _("Rules mangle counters"), + value: data.rules_mangle_counters ? _("Yes") : _("No") + }, + { + state: data.rules_mangle_output_exist ? "success" : "error", + key: _("Rules mangle output exist"), + value: data.rules_mangle_output_exist ? _("Yes") : _("No") + }, + { + state: data.rules_mangle_output_counters ? "success" : "error", + key: _("Rules mangle output counters"), + value: data.rules_mangle_output_counters ? _("Yes") : _("No") + }, + { + state: data.rules_proxy_exist ? "success" : "error", + key: _("Rules proxy exist"), + value: data.rules_proxy_exist ? _("Yes") : _("No") + }, + { + state: data.rules_proxy_counters ? "success" : "error", + key: _("Rules proxy counters"), + value: data.rules_proxy_counters ? _("Yes") : _("No") + }, + { + state: data.rules_other_mark_exist ? "warning" : "success", + key: _("Rules other mark exist"), + value: data.rules_other_mark_exist ? _("Yes") : _("No") + } + ] + }); + if (!atLeastOneGood) { + throw new Error("Nftables checks failed"); + } +} + // src/podkop/tabs/diagnostic/initDiagnosticController.ts +async function renderDiagnosticsChecks() { + console.log("renderDiagnosticsChecks"); + const diagnosticsChecks = store.get().diagnosticsChecks; + const container = document.getElementById("pdk_diagnostic-page-checks"); + const renderedDiagnosticsChecks = diagnosticsChecks.map( + (check) => renderCheckSection(check) + ); + return preserveScrollForPage(() => { + container.replaceChildren(...renderedDiagnosticsChecks); + }); +} +async function onStoreUpdate2(next, prev, diff) { + if (diff.diagnosticsChecks) { + renderDiagnosticsChecks(); + } +} +async function runChecks() { + await runDnsCheck(); + await runSingBoxCheck(); + await runNftCheck(); +} async function initDiagnosticController() { onMount("diagnostic-status").then(() => { console.log("diagnostic controller initialized."); + store.unsubscribe(onStoreUpdate2); + store.reset(); + store.subscribe(onStoreUpdate2); + runChecks(); }); } return baseclass.extend({ @@ -2102,6 +2777,7 @@ return baseclass.extend({ renderDashboard, renderDiagnostic, splitProxyString, + svgEl, triggerLatencyGroupTest, triggerLatencyProxyTest, triggerProxySelector, From e3e0b2d4e410f9ef120dc95e4abed1627a3d63a1 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 11 Oct 2025 23:21:53 +0300 Subject: [PATCH 035/121] feat: implement fake ip check mock --- .../podkop/tabs/diagnostic/checks/runFakeIPCheck.ts | 13 +++++++++++++ .../tabs/diagnostic/initDiagnosticController.ts | 3 +++ .../luci-static/resources/view/podkop/main.js | 13 +++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts new file mode 100644 index 0000000..1c16ff5 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -0,0 +1,13 @@ +import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; + +export async function runFakeIPCheck() { + const code = 'fake_ip_check'; + + updateDiagnosticsCheck({ + code, + title: _('Fake IP checks'), + description: _('Not implemented yet'), + state: 'skipped', + items: [], + }); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts index f6afc55..8f1531b 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts @@ -4,6 +4,7 @@ import { renderCheckSection } from './renderCheckSection'; import { runDnsCheck } from './checks/runDnsCheck'; import { runSingBoxCheck } from './checks/runSingBoxCheck'; import { runNftCheck } from './checks/runNftCheck'; +import { runFakeIPCheck } from './checks/runFakeIPCheck'; async function renderDiagnosticsChecks() { console.log('renderDiagnosticsChecks'); @@ -35,6 +36,8 @@ async function runChecks() { await runSingBoxCheck(); await runNftCheck(); + + await runFakeIPCheck(); } export async function initDiagnosticController(): Promise { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 5fee549..9662104 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2694,6 +2694,18 @@ async function runNftCheck() { } } +// src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +async function runFakeIPCheck() { + const code = "fake_ip_check"; + updateDiagnosticsCheck({ + code, + title: _("Fake IP checks"), + description: _("Not implemented yet"), + state: "skipped", + items: [] + }); +} + // src/podkop/tabs/diagnostic/initDiagnosticController.ts async function renderDiagnosticsChecks() { console.log("renderDiagnosticsChecks"); @@ -2715,6 +2727,7 @@ async function runChecks() { await runDnsCheck(); await runSingBoxCheck(); await runNftCheck(); + await runFakeIPCheck(); } async function initDiagnosticController() { onMount("diagnostic-status").then(() => { From 55df0f283dcddb07c36271050c21d9d50ee53e03 Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sun, 12 Oct 2025 14:55:57 +0300 Subject: [PATCH 036/121] Added clash_api func. Some fixes --- podkop/files/usr/bin/podkop | 164 +++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 20 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 7f2f1c7..e9e8775 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1722,7 +1722,8 @@ show_version() { } show_sing_box_version() { - local version=$(sing-box version | head -n 1 | awk '{print $3}') + local version + version=$(sing-box version | head -n 1 | awk '{print $3}') echo "$version" } @@ -1753,7 +1754,8 @@ get_sing_box_status() { fi # Check DNS configuration - local dns_server=$(uci get dhcp.@dnsmasq[0].server 2> /dev/null) + local dns_server + dns_server=$(uci get dhcp.@dnsmasq[0].server 2> /dev/null) if [ "$dns_server" = "127.0.0.42" ]; then dns_configured=1 fi @@ -1801,14 +1803,17 @@ check_dns_available() { local local_dns_status=0 local bootstrap_dns_status=0 local dhcp_has_dns_server=0 + local domain="google.com" # Mask NextDNS ID if present local display_dns_server="$dns_server" if echo "$dns_server" | grep -q "\.dns\.nextdns\.io$"; then - local nextdns_id=$(echo "$dns_server" | cut -d'.' -f1) + local nextdns_id + nextdns_id=$(echo "$dns_server" | cut -d'.' -f1) display_dns_server="$(echo "$nextdns_id" | sed 's/./*/g').dns.nextdns.io" elif echo "$dns_server" | grep -q "^dns\.nextdns\.io/"; then - local masked_path=$(echo "$dns_server" | cut -d'/' -f2- | sed 's/./*/g') + local masked_path + masked_path=$(echo "$dns_server" | cut -d'/' -f2- | sed 's/./*/g') display_dns_server="dns.nextdns.io/$masked_path" fi @@ -1820,28 +1825,28 @@ check_dns_available() { doh_path="/$(echo "$dns_server" | cut -d'/' -f2-)" dns_server="$(echo "$dns_server" | cut -d'/' -f1)" fi - - if dig @"$dns_server" google.com +https="$doh_path" +timeout=2 +tries=1 > /dev/null 2>&1; then + + if dig @"$dns_server" "$domain" +https="$doh_path" +timeout=2 +tries=1 > /dev/null 2>&1; then dns_status=1 fi elif [ "$dns_type" = "dot" ]; then - if dig @"$dns_server" google.com +tls +timeout=2 +tries=1 > /dev/null 2>&1; then + if dig @"$dns_server" "$domain" +tls +timeout=2 +tries=1 > /dev/null 2>&1; then dns_status=1 fi elif [ "$dns_type" = "udp" ]; then - if dig @"$dns_server" google.com +timeout=2 +tries=1 > /dev/null 2>&1; then + if dig @"$dns_server" "$domain" +timeout=2 +tries=1 > /dev/null 2>&1; then dns_status=1 fi fi # Check if local DNS resolver is working - if dig @127.0.0.1 "$FAKEIP_TEST_DOMAIN" +timeout=2 +tries=1 > /dev/null 2>&1; then + if dig @127.0.0.1 "$domain" +timeout=2 +tries=1 > /dev/null 2>&1; then local_dns_status=1 fi # Check bootstrap DNS server if [ -n "$bootstrap_dns_server" ]; then - if dig @"$bootstrap_dns_server" google.com +timeout=2 +tries=1 > /dev/null 2>&1; then + if dig @"$bootstrap_dns_server" "$domain" +timeout=2 +tries=1 > /dev/null 2>&1; then bootstrap_dns_status=1 fi fi @@ -1894,7 +1899,8 @@ check_nft_rules() { # Check mangle chain rules if nft list chain inet "$NFT_TABLE_NAME" mangle > /dev/null 2>&1; then - local mangle_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle) + local mangle_output + mangle_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle) if echo "$mangle_output" | grep -q "counter"; then rules_mangle_exist=1 @@ -1906,7 +1912,8 @@ check_nft_rules() { # Check mangle_output chain rules if nft list chain inet "$NFT_TABLE_NAME" mangle_output > /dev/null 2>&1; then - local mangle_output_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle_output) + local mangle_output_output + mangle_output_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle_output) if echo "$mangle_output_output" | grep -q "counter"; then rules_mangle_output_exist=1 @@ -1918,7 +1925,8 @@ check_nft_rules() { # Check proxy chain rules if nft list chain inet "$NFT_TABLE_NAME" proxy > /dev/null 2>&1; then - local proxy_output=$(nft list chain inet "$NFT_TABLE_NAME" proxy) + local proxy_output + proxy_output=$(nft list chain inet "$NFT_TABLE_NAME" proxy) if echo "$proxy_output" | grep -q "counter"; then rules_proxy_exist=1 @@ -1930,7 +1938,7 @@ check_nft_rules() { fi # Check for other mark rules outside PodkopTable - nft list tables 2>/dev/null | while read -r keyword family table_name; do + nft list tables 2>/dev/null | while read -r _ family table_name; do [ -z "$table_name" ] && continue [ "$table_name" = "$NFT_TABLE_NAME" ] && continue @@ -1962,12 +1970,16 @@ check_sing_box() { sing_box_installed=1 # Check version (must be >= 1.12.4) - local version=$(sing-box version 2>/dev/null | head -n 1 | awk '{print $3}') + local version + version=$(sing-box version 2>/dev/null | head -n 1 | awk '{print $3}') if [ -n "$version" ]; then version=$(echo "$version" | sed 's/^v//') - local major=$(echo "$version" | cut -d. -f1) - local minor=$(echo "$version" | cut -d. -f2) - local patch=$(echo "$version" | cut -d. -f3) + local major + local minor + local patch + major=$(echo "$version" | cut -d. -f1) + minor=$(echo "$version" | cut -d. -f2) + patch=$(echo "$version" | cut -d. -f3) # Compare version: must be >= 1.12.4 if [ "$major" -gt 1 ] || \ @@ -1978,7 +1990,7 @@ check_sing_box() { fi fi - # Check if service exists and is enabled + # Check if service exists if [ -f /etc/init.d/sing-box ]; then sing_box_service_exist=1 @@ -2005,13 +2017,121 @@ check_sing_box() { fi # Both ports must be listening - if [ "$port_53_ok" == "1" ] && [ "$port_1602_ok" == "1" ]; then + if [ "$port_53_ok" = "1" ] && [ "$port_1602_ok" = "1" ]; then sing_box_ports_listening=1 fi echo "{\"sing_box_installed\":$sing_box_installed,\"sing_box_version_ok\":$sing_box_version_ok,\"sing_box_service_exist\":$sing_box_service_exist,\"sing_box_autostart_disabled\":$sing_box_autostart_disabled,\"sing_box_process_running\":$sing_box_process_running,\"sing_box_ports_listening\":$sing_box_ports_listening}" | jq . } +####################################### +# Clash API interface for managing proxies and groups +# Arguments: +# $1 - Action: get_proxies, get_proxy_latency, get_group_latency, set_group_proxy +# $2 - Proxy/Group tag (required for latency and set operations) +# $3 - Timeout in ms (optional, defaults: 2000 for proxy, 5000 for group) or target proxy tag for set_group_proxy +# Outputs: +# JSON formatted response +# Usage: +# clash_api get_proxies +# clash_api get_proxy_latency [timeout] +# clash_api get_group_latency [timeout] +# clash_api set_group_proxy +####################################### + +clash_api() { + local CLASH_URL="127.0.0.1:9090" + local TEST_URL="https://www.gstatic.com/generate_204" + local action="$1" + + case "$action" in + get_proxies) + curl -s "$CLASH_URL/proxies" | jq . + ;; + + get_proxy_latency) + local proxy_tag="$2" + local timeout="${3:-2000}" + + if [ -z "$proxy_tag" ]; then + echo '{"error":"proxy_tag required"}' | jq . + return 1 + fi + + curl -G -s "$CLASH_URL/proxies/$proxy_tag/delay" \ + --data-urlencode "url=$TEST_URL" \ + --data-urlencode "timeout=$timeout" | jq . + ;; + + get_group_latency) + local group_tag="$2" + local timeout="${3:-5000}" + + if [ -z "$group_tag" ]; then + echo '{"error":"group_tag required"}' | jq . + return 1 + fi + + curl -G -s "$CLASH_URL/group/$group_tag/delay" \ + --data-urlencode "url=$TEST_URL" \ + --data-urlencode "timeout=$timeout" | jq . + ;; + + set_group_proxy) + local group_tag="$2" + local proxy_tag="$3" + + if [ -z "$group_tag" ] || [ -z "$proxy_tag" ]; then + echo '{"error":"group_tag and proxy_tag required"}' | jq . + return 1 + fi + + local response + response=$(curl -X PUT -s -w "\n%{http_code}" "$CLASH_URL/proxies/$group_tag" \ + --data-raw "{\"name\":\"$proxy_tag\"}") + + local http_code + local body + http_code=$(echo "$response" | tail -n 1) + body=$(echo "$response" | sed '$d') + + case "$http_code" in + 204) + echo "{\"success\":true,\"group\":\"$group_tag\",\"proxy\":\"$proxy_tag\"}" | jq . + ;; + 404) + echo "{\"success\":false,\"error\":\"group_not_found\",\"message\":\"$group_tag does not exist\"}" | jq . + return 1 + ;; + 400) + if echo "$body" | grep -q "not found"; then + echo "{\"success\":false,\"error\":\"proxy_not_found\",\"message\":\"$proxy_tag not found in group $group_tag\"}" | jq . + else + echo '{"success":false,"error":"bad_request","message":"Invalid request"}' | jq . + fi + return 1 + ;; + *) + if [ -n "$body" ]; then + local body_json + body_json=$(echo "$body" | jq -c .) + echo "{\"success\":false,\"http_code\":$http_code,\"body\":$body_json}" | jq . + else + echo "{\"success\":false,\"http_code\":$http_code}" | jq . + fi + return 1 + ;; + esac + ;; + + *) + echo '{"error":"unknown action","available":["get_proxies","get_proxy_latency","get_group_latency","set_group_proxy"]}' | jq . + return 1 + ;; + esac +} + + print_global() { local message="$1" echo "$message" @@ -2177,6 +2297,7 @@ Available commands: check_sing_box_connections Show active sing-box connections check_sing_box_logs Show sing-box logs check_dnsmasq Check DNSMasq configuration + clash_api Clash API interface for managing proxies and groups show_config Display current podkop configuration show_version Show podkop version show_sing_box_config Show sing-box configuration @@ -2235,6 +2356,9 @@ check_sing_box_logs) check_dnsmasq) check_dnsmasq ;; +clash_api) + clash_api "$2" "$3" "$4" + ;; show_config) show_config ;; From 7a2868b630db9c655d23b227caf83a763eb8ae6e Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sun, 12 Oct 2025 16:25:03 +0300 Subject: [PATCH 037/121] Add CI for shellcheck --- .github/workflows/shellcheck.yml | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/shellcheck.yml diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..5138d01 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,48 @@ +name: Differential ShellCheck + +on: + push: + branches: + - main + - 'rc/**' + paths: + - '**.sh' + - 'podkop/files/usr/bin/**' + - 'podkop/files/usr/lib/**' + - '.github/workflows/shellcheck.yml' + pull_request: + branches: + - main + - 'rc/**' + paths: + - '**.sh' + - 'podkop/files/usr/bin/**' + - 'podkop/files/usr/lib/**' + - '.github/workflows/shellcheck.yml' + +permissions: + contents: read + +jobs: + shellcheck: + name: Differential ShellCheck + runs-on: ubuntu-latest + + permissions: + contents: read + security-events: write + + steps: + - name: Checkout code + uses: actions/checkout@v5.0.0 + with: + fetch-depth: 0 + + - name: Differential ShellCheck + uses: redhat-plumbers-in-action/differential-shellcheck@v5.5.5 + with: + severity: error + scan-directory: | + podkop/files/usr/bin/** + podkop/files/usr/lib/** + token: ${{ secrets.GITHUB_TOKEN }} From f20e205b7222efad43d2b5eb43706a7e9f148577 Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sun, 12 Oct 2025 18:49:09 +0300 Subject: [PATCH 038/121] shellcheck fix --- install.sh | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/install.sh b/install.sh index c84051b..8235045 100755 --- a/install.sh +++ b/install.sh @@ -1,4 +1,5 @@ #!/bin/sh +# shellcheck shell=dash REPO="https://api.github.com/repos/itdoginfo/podkop/releases/latest" DOWNLOAD_DIR="/tmp/podkop" @@ -74,7 +75,7 @@ main() { msg "Installed podkop..." fi - if command -v curl &> /dev/null; then + if command -v curl >/dev/null 2>&1; then check_response=$(curl -s "https://api.github.com/repos/itdoginfo/podkop/releases/latest") if echo "$check_response" | grep -q 'API rate limit '; then @@ -90,8 +91,7 @@ main() { grep_url_pattern='https://[^"[:space:]]*\.ipk' fi - download_success=0 - while read -r url; do + wget -qO- "$REPO" | grep -o "$grep_url_pattern" | while read -r url; do filename=$(basename "$url") filepath="$DOWNLOAD_DIR/$filename" @@ -101,7 +101,6 @@ main() { if wget -q -O "$filepath" "$url"; then if [ -s "$filepath" ]; then msg "$filename successfully downloaded" - download_success=1 break fi fi @@ -113,15 +112,22 @@ main() { if [ $attempt -eq $COUNT ]; then msg "Failed to download $filename after $COUNT attempts" fi - done < <(wget -qO- "$REPO" | grep -o "$grep_url_pattern") + done - if [ $download_success -eq 0 ]; then + # Check if any files were downloaded + if ! ls "$DOWNLOAD_DIR"/*podkop* >/dev/null 2>&1; then msg "No packages were downloaded successfully" exit 1 fi for pkg in podkop luci-app-podkop; do - file=$(ls "$DOWNLOAD_DIR" | grep "^$pkg" | head -n 1) + file="" + for f in "$DOWNLOAD_DIR"/"$pkg"*; do + if [ -f "$f" ]; then + file=$(basename "$f") + break + fi + done if [ -n "$file" ]; then msg "Installing $file" pkg_install "$DOWNLOAD_DIR/$file" @@ -129,7 +135,13 @@ main() { fi done - ru=$(ls "$DOWNLOAD_DIR" | grep "luci-i18n-podkop-ru" | head -n 1) + ru="" + for f in "$DOWNLOAD_DIR"/luci-i18n-podkop-ru*; do + if [ -f "$f" ]; then + ru=$(basename "$f") + break + fi + done if [ -n "$ru" ]; then if pkg_is_installed luci-i18n-podkop-ru; then msg "Upgraded ru translation..." @@ -219,7 +231,7 @@ sing_box() { sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}') required_version="1.12.4" - if [ "$(echo -e "$sing_box_version\n$required_version" | sort -V | head -n 1)" != "$required_version" ]; then + if [ "$(printf '%s\n%s\n' "$sing_box_version" "$required_version" | sort -V | head -n 1)" != "$required_version" ]; then msg "sing-box version $sing_box_version is older than required $required_version" msg "Removing old version..." service podkop stop From 44894f32578a0e6a342a9b89687231e2c744caf6 Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Sun, 12 Oct 2025 18:53:22 +0300 Subject: [PATCH 039/121] Fix path --- .github/workflows/shellcheck.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 5138d01..41cbebc 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -6,7 +6,7 @@ on: - main - 'rc/**' paths: - - '**.sh' + - 'install.sh' - 'podkop/files/usr/bin/**' - 'podkop/files/usr/lib/**' - '.github/workflows/shellcheck.yml' @@ -15,7 +15,7 @@ on: - main - 'rc/**' paths: - - '**.sh' + - 'install.sh' - 'podkop/files/usr/bin/**' - 'podkop/files/usr/lib/**' - '.github/workflows/shellcheck.yml' @@ -26,7 +26,7 @@ permissions: jobs: shellcheck: name: Differential ShellCheck - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read @@ -42,7 +42,8 @@ jobs: uses: redhat-plumbers-in-action/differential-shellcheck@v5.5.5 with: severity: error - scan-directory: | - podkop/files/usr/bin/** - podkop/files/usr/lib/** + include-path: | + podkop/files/usr/bin/podkop + podkop/files/usr/lib/**.sh + install.sh token: ${{ secrets.GITHUB_TOKEN }} From a7150f7143f9704cb5ee9047adf5aa88ca9d1919 Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 13 Oct 2025 21:05:48 +0300 Subject: [PATCH 040/121] feat: add download_lists_via_proxy_section --- .../resources/view/podkop/settings.js | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) 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 ed7dc5e..6c26ad9 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,5 +1,6 @@ 'use strict'; 'require form'; +'require uci'; 'require baseclass'; 'require tools.widgets as widgets'; 'require view.podkop.main as main'; @@ -204,6 +205,33 @@ function createSettingsContent(section) { 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.rmempty = false; + o.depends('download_lists_via_proxy', '1'); + + o.load = function () { + const sections = this.map?.data?.state?.values?.podkop ?? {}; + + 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); + } + } + + return Promise.resolve(); + }; + o = section.option( form.Flag, 'dont_touch_dhcp', From 0fba31c10a6239b8d49d442b7beb1880dfde3cae Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 13 Oct 2025 21:32:01 +0300 Subject: [PATCH 041/121] feat: change icons for diagnostic --- fe-app-podkop/src/icons/index.ts | 8 +- .../src/icons/renderCircleAlertIcon24.ts | 39 +++++ ...ldIcon24.ts => renderCircleCheckIcon24.ts} | 11 +- ...rtIcon24.ts => renderCircleSlashIcon24.ts} | 18 +- ...dCheckIcon24.ts => renderCircleXIcon24.ts} | 17 +- .../src/icons/renderShieldXIcon24.ts | 27 --- .../tabs/diagnostic/renderCheckSection.ts | 16 +- .../luci-static/resources/view/podkop/main.js | 164 +++++++++++------- 8 files changed, 180 insertions(+), 120 deletions(-) create mode 100644 fe-app-podkop/src/icons/renderCircleAlertIcon24.ts rename fe-app-podkop/src/icons/{renderShieldIcon24.ts => renderCircleCheckIcon24.ts} (61%) rename fe-app-podkop/src/icons/{renderShieldAlertIcon24.ts => renderCircleSlashIcon24.ts} (50%) rename fe-app-podkop/src/icons/{renderShieldCheckIcon24.ts => renderCircleXIcon24.ts} (56%) delete mode 100644 fe-app-podkop/src/icons/renderShieldXIcon24.ts diff --git a/fe-app-podkop/src/icons/index.ts b/fe-app-podkop/src/icons/index.ts index 2bfb2f5..184bcb8 100644 --- a/fe-app-podkop/src/icons/index.ts +++ b/fe-app-podkop/src/icons/index.ts @@ -1,5 +1,5 @@ export * from './renderLoaderCircleIcon24'; -export * from './renderShieldAlertIcon24'; -export * from './renderShieldCheckIcon24'; -export * from './renderShieldIcon24'; -export * from './renderShieldXIcon24'; +export * from './renderCircleAlertIcon24'; +export * from './renderCircleCheckIcon24'; +export * from './renderCircleSlashIcon24'; +export * from './renderCircleXIcon24'; diff --git a/fe-app-podkop/src/icons/renderCircleAlertIcon24.ts b/fe-app-podkop/src/icons/renderCircleAlertIcon24.ts new file mode 100644 index 0000000..8e6820b --- /dev/null +++ b/fe-app-podkop/src/icons/renderCircleAlertIcon24.ts @@ -0,0 +1,39 @@ +import { svgEl } from '../helpers'; + +export function renderCircleAlertIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + width: '24', + height: '24', + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-circle-alert-icon lucide-circle-alert', + }, + [ + svgEl('circle', { + cx: '12', + cy: '12', + r: '10', + }), + svgEl('line', { + x1: '12', + y1: '8', + x2: '12', + y2: '12', + }), + svgEl('line', { + x1: '12', + y1: '16', + x2: '12.01', + y2: '16', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderShieldIcon24.ts b/fe-app-podkop/src/icons/renderCircleCheckIcon24.ts similarity index 61% rename from fe-app-podkop/src/icons/renderShieldIcon24.ts rename to fe-app-podkop/src/icons/renderCircleCheckIcon24.ts index afaaae1..4ccf018 100644 --- a/fe-app-podkop/src/icons/renderShieldIcon24.ts +++ b/fe-app-podkop/src/icons/renderCircleCheckIcon24.ts @@ -1,6 +1,6 @@ import { svgEl } from '../helpers'; -export function renderShieldIcon24() { +export function renderCircleCheckIcon24() { const NS = 'http://www.w3.org/2000/svg'; return svgEl( 'svg', @@ -14,11 +14,16 @@ export function renderShieldIcon24() { 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', - class: 'lucide lucide-shield', + class: 'lucide lucide-circle-check-icon lucide-circle-check', }, [ + svgEl('circle', { + cx: '12', + cy: '12', + r: '10', + }), svgEl('path', { - d: 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z', + d: 'M9 12l2 2 4-4', }), ], ); diff --git a/fe-app-podkop/src/icons/renderShieldAlertIcon24.ts b/fe-app-podkop/src/icons/renderCircleSlashIcon24.ts similarity index 50% rename from fe-app-podkop/src/icons/renderShieldAlertIcon24.ts rename to fe-app-podkop/src/icons/renderCircleSlashIcon24.ts index 7397794..e082c11 100644 --- a/fe-app-podkop/src/icons/renderShieldAlertIcon24.ts +++ b/fe-app-podkop/src/icons/renderCircleSlashIcon24.ts @@ -1,6 +1,6 @@ import { svgEl } from '../helpers'; -export function renderShieldAlertIcon24() { +export function renderCircleSlashIcon24() { const NS = 'http://www.w3.org/2000/svg'; return svgEl( 'svg', @@ -14,14 +14,20 @@ export function renderShieldAlertIcon24() { 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', - class: 'lucide lucide-shield-alert', + class: 'lucide lucide-circle-slash-icon lucide-circle-slash', }, [ - svgEl('path', { - d: 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z', + svgEl('circle', { + cx: '12', + cy: '12', + r: '10', + }), + svgEl('line', { + x1: '9', + y1: '15', + x2: '15', + y2: '9', }), - svgEl('path', { d: 'M12 8v4' }), - svgEl('path', { d: 'M12 16h.01' }), ], ); } diff --git a/fe-app-podkop/src/icons/renderShieldCheckIcon24.ts b/fe-app-podkop/src/icons/renderCircleXIcon24.ts similarity index 56% rename from fe-app-podkop/src/icons/renderShieldCheckIcon24.ts rename to fe-app-podkop/src/icons/renderCircleXIcon24.ts index 37f4046..5695617 100644 --- a/fe-app-podkop/src/icons/renderShieldCheckIcon24.ts +++ b/fe-app-podkop/src/icons/renderCircleXIcon24.ts @@ -1,6 +1,6 @@ import { svgEl } from '../helpers'; -export function renderShieldCheckIcon24() { +export function renderCircleXIcon24() { const NS = 'http://www.w3.org/2000/svg'; return svgEl( 'svg', @@ -14,13 +14,20 @@ export function renderShieldCheckIcon24() { 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', - class: 'lucide lucide-shield-check', + class: 'lucide lucide-circle-x-icon lucide-circle-x', }, [ - svgEl('path', { - d: 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z', + svgEl('circle', { + cx: '12', + cy: '12', + r: '10', + }), + svgEl('path', { + d: 'M15 9L9 15', + }), + svgEl('path', { + d: 'M9 9L15 15', }), - svgEl('path', { d: 'm9 12 2 2 4-4' }), ], ); } diff --git a/fe-app-podkop/src/icons/renderShieldXIcon24.ts b/fe-app-podkop/src/icons/renderShieldXIcon24.ts deleted file mode 100644 index ffbf708..0000000 --- a/fe-app-podkop/src/icons/renderShieldXIcon24.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { svgEl } from '../helpers'; - -export function renderShieldXIcon24() { - const NS = 'http://www.w3.org/2000/svg'; - return svgEl( - 'svg', - { - xmlns: NS, - width: '24', - height: '24', - viewBox: '0 0 24 24', - fill: 'none', - stroke: 'currentColor', - 'stroke-width': '2', - 'stroke-linecap': 'round', - 'stroke-linejoin': 'round', - class: 'lucide lucide-shield-x', - }, - [ - svgEl('path', { - d: 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z', - }), - svgEl('path', { d: 'm14.5 9.5-5 5' }), - svgEl('path', { d: 'm9.5 9.5 5 5' }), - ], - ); -} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts index 1c15522..5128516 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts @@ -1,9 +1,9 @@ import { + renderCircleAlertIcon24, + renderCircleCheckIcon24, + renderCircleSlashIcon24, + renderCircleXIcon24, renderLoaderCircleIcon24, - renderShieldAlertIcon24, - renderShieldCheckIcon24, - renderShieldIcon24, - renderShieldXIcon24, } from '../../../icons'; import { IDiagnosticsChecksStoreItem } from '../../../store'; @@ -52,7 +52,7 @@ function renderLoadingState(props: IRenderCheckSectionProps) { function renderWarningState(props: IRenderCheckSectionProps) { const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); - iconWrap.appendChild(renderShieldAlertIcon24()); + iconWrap.appendChild(renderCircleAlertIcon24()); return E( 'div', @@ -75,7 +75,7 @@ function renderWarningState(props: IRenderCheckSectionProps) { function renderErrorState(props: IRenderCheckSectionProps) { const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); - iconWrap.appendChild(renderShieldXIcon24()); + iconWrap.appendChild(renderCircleXIcon24()); return E( 'div', @@ -98,7 +98,7 @@ function renderErrorState(props: IRenderCheckSectionProps) { function renderSuccessState(props: IRenderCheckSectionProps) { const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); - iconWrap.appendChild(renderShieldCheckIcon24()); + iconWrap.appendChild(renderCircleCheckIcon24()); return E( 'div', @@ -121,7 +121,7 @@ function renderSuccessState(props: IRenderCheckSectionProps) { function renderSkippedState(props: IRenderCheckSectionProps) { const iconWrap = E('span', { class: 'pdk_diagnostic_alert__icon' }); - iconWrap.appendChild(renderShieldIcon24()); + iconWrap.appendChild(renderCircleSlashIcon24()); return E( 'div', diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 9662104..1a4e2b2 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2214,8 +2214,8 @@ function renderLoaderCircleIcon24() { ); } -// src/icons/renderShieldAlertIcon24.ts -function renderShieldAlertIcon24() { +// src/icons/renderCircleAlertIcon24.ts +function renderCircleAlertIcon24() { const NS = "http://www.w3.org/2000/svg"; return svgEl( "svg", @@ -2229,71 +2229,32 @@ function renderShieldAlertIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-shield-alert" + class: "lucide lucide-circle-alert-icon lucide-circle-alert" }, [ - svgEl("path", { - d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" + svgEl("circle", { + cx: "12", + cy: "12", + r: "10" }), - svgEl("path", { d: "M12 8v4" }), - svgEl("path", { d: "M12 16h.01" }) - ] - ); -} - -// src/icons/renderShieldCheckIcon24.ts -function renderShieldCheckIcon24() { - const NS = "http://www.w3.org/2000/svg"; - return svgEl( - "svg", - { - xmlns: NS, - width: "24", - height: "24", - viewBox: "0 0 24 24", - fill: "none", - stroke: "currentColor", - "stroke-width": "2", - "stroke-linecap": "round", - "stroke-linejoin": "round", - class: "lucide lucide-shield-check" - }, - [ - svgEl("path", { - d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" + svgEl("line", { + x1: "12", + y1: "8", + x2: "12", + y2: "12" }), - svgEl("path", { d: "m9 12 2 2 4-4" }) - ] - ); -} - -// src/icons/renderShieldIcon24.ts -function renderShieldIcon24() { - const NS = "http://www.w3.org/2000/svg"; - return svgEl( - "svg", - { - xmlns: NS, - width: "24", - height: "24", - viewBox: "0 0 24 24", - fill: "none", - stroke: "currentColor", - "stroke-width": "2", - "stroke-linecap": "round", - "stroke-linejoin": "round", - class: "lucide lucide-shield" - }, - [ - svgEl("path", { - d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" + svgEl("line", { + x1: "12", + y1: "16", + x2: "12.01", + y2: "16" }) ] ); } -// src/icons/renderShieldXIcon24.ts -function renderShieldXIcon24() { +// src/icons/renderCircleCheckIcon24.ts +function renderCircleCheckIcon24() { const NS = "http://www.w3.org/2000/svg"; return svgEl( "svg", @@ -2307,14 +2268,83 @@ function renderShieldXIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-shield-x" + class: "lucide lucide-circle-check-icon lucide-circle-check" }, [ - svgEl("path", { - d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" + svgEl("circle", { + cx: "12", + cy: "12", + r: "10" }), - svgEl("path", { d: "m14.5 9.5-5 5" }), - svgEl("path", { d: "m9.5 9.5 5 5" }) + svgEl("path", { + d: "M9 12l2 2 4-4" + }) + ] + ); +} + +// src/icons/renderCircleSlashIcon24.ts +function renderCircleSlashIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-circle-slash-icon lucide-circle-slash" + }, + [ + svgEl("circle", { + cx: "12", + cy: "12", + r: "10" + }), + svgEl("line", { + x1: "9", + y1: "15", + x2: "15", + y2: "9" + }) + ] + ); +} + +// src/icons/renderCircleXIcon24.ts +function renderCircleXIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + width: "24", + height: "24", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-circle-x-icon lucide-circle-x" + }, + [ + svgEl("circle", { + cx: "12", + cy: "12", + r: "10" + }), + svgEl("path", { + d: "M15 9L9 15" + }), + svgEl("path", { + d: "M9 9L15 15" + }) ] ); } @@ -2358,7 +2388,7 @@ function renderLoadingState3(props) { } function renderWarningState(props) { const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); - iconWrap.appendChild(renderShieldAlertIcon24()); + iconWrap.appendChild(renderCircleAlertIcon24()); return E( "div", { class: "pdk_diagnostic_alert pdk_diagnostic_alert--warning" }, @@ -2379,7 +2409,7 @@ function renderWarningState(props) { } function renderErrorState(props) { const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); - iconWrap.appendChild(renderShieldXIcon24()); + iconWrap.appendChild(renderCircleXIcon24()); return E( "div", { class: "pdk_diagnostic_alert pdk_diagnostic_alert--error" }, @@ -2400,7 +2430,7 @@ function renderErrorState(props) { } function renderSuccessState(props) { const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); - iconWrap.appendChild(renderShieldCheckIcon24()); + iconWrap.appendChild(renderCircleCheckIcon24()); return E( "div", { class: "pdk_diagnostic_alert pdk_diagnostic_alert--success" }, @@ -2421,7 +2451,7 @@ function renderSuccessState(props) { } function renderSkippedState(props) { const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" }); - iconWrap.appendChild(renderShieldIcon24()); + iconWrap.appendChild(renderCircleSlashIcon24()); return E( "div", { class: "pdk_diagnostic_alert pdk_diagnostic_alert--skipped" }, From aea6fd9453ad6c61ff86638d6711748281233145 Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 13 Oct 2025 21:49:38 +0300 Subject: [PATCH 042/121] feat: change dns check output --- fe-app-podkop/src/helpers/index.ts | 1 + fe-app-podkop/src/helpers/insertIf.ts | 7 +++++ .../tabs/diagnostic/checks/runDnsCheck.ts | 21 +++++++++----- fe-app-podkop/src/store.ts | 12 ++++---- .../luci-static/resources/view/podkop/main.js | 29 ++++++++++++++----- 5 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 fe-app-podkop/src/helpers/insertIf.ts diff --git a/fe-app-podkop/src/helpers/index.ts b/fe-app-podkop/src/helpers/index.ts index b82ff09..3ec39ab 100644 --- a/fe-app-podkop/src/helpers/index.ts +++ b/fe-app-podkop/src/helpers/index.ts @@ -11,3 +11,4 @@ export * from './splitProxyString'; export * from './preserveScrollForPage'; export * from './parseQueryString'; export * from './svgEl'; +export * from './insertIf'; diff --git a/fe-app-podkop/src/helpers/insertIf.ts b/fe-app-podkop/src/helpers/insertIf.ts new file mode 100644 index 0000000..cfb93f3 --- /dev/null +++ b/fe-app-podkop/src/helpers/insertIf.ts @@ -0,0 +1,7 @@ +export function insertIf(condition: boolean, elements: Array) { + return condition ? elements : ([] as Array); +} + +export function insertIfObj(condition: boolean, object: T) { + return condition ? object : ({} as T); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index fb5a2a8..6d9ef0b 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -1,5 +1,7 @@ import { getDNSCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; +import { insertIf } from '../../../../helpers'; +import { IDiagnosticsChecksItem } from '../../../../store'; export async function runDnsCheck() { const code = 'dns_check'; @@ -58,20 +60,25 @@ export async function runDnsCheck() { description: _('DNS checks passed'), state: getStatus(), items: [ - { - state: data.bootstrap_dns_status ? 'success' : 'error', - key: _('Bootsrap DNS'), - value: data.bootstrap_dns_server, - }, + ...insertIf( + data.dns_type === 'doh' || data.dns_type === 'dot', + [ + { + state: data.bootstrap_dns_status ? 'success' : 'error', + key: _('Bootsrap DNS'), + value: `${data.bootstrap_dns_server} ${data.bootstrap_dns_status ? '✅' : '❌'}`, + }, + ], + ), { state: data.dns_status ? 'success' : 'error', key: _('Main DNS'), - value: `${data.dns_server} [${data.dns_type}]`, + value: `${data.dns_server} [${data.dns_type}] ${data.dns_status ? '✅' : '❌'}`, }, { state: data.local_dns_status ? 'success' : 'error', key: _('Local DNS'), - value: data.local_dns_status ? _('Enabled') : _('Failed'), + value: data.local_dns_status ? '✅' : '❌', }, ], }); diff --git a/fe-app-podkop/src/store.ts b/fe-app-podkop/src/store.ts index 5e07548..4b734cd 100644 --- a/fe-app-podkop/src/store.ts +++ b/fe-app-podkop/src/store.ts @@ -112,16 +112,18 @@ class Store> { } } +export interface IDiagnosticsChecksItem { + state: 'error' | 'warning' | 'success'; + key: string; + value: string; +} + export interface IDiagnosticsChecksStoreItem { code: string; title: string; description: string; state: 'loading' | 'warning' | 'success' | 'error' | 'skipped'; - items: Array<{ - state: 'error' | 'warning' | 'success'; - key: string; - value: string; - }>; + items: Array; } export interface StoreType { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 1a4e2b2..77dadcd 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -768,6 +768,14 @@ function svgEl(tag, attrs = {}, children = []) { return el; } +// src/helpers/insertIf.ts +function insertIf(condition, elements) { + return condition ? elements : []; +} +function insertIfObj(condition, object) { + return condition ? object : {}; +} + // src/validators/validateVlessUrl.ts function validateVlessUrl(url) { try { @@ -2538,20 +2546,25 @@ async function runDnsCheck() { description: _("DNS checks passed"), state: getStatus(), items: [ - { - state: data.bootstrap_dns_status ? "success" : "error", - key: _("Bootsrap DNS"), - value: data.bootstrap_dns_server - }, + ...insertIf( + data.dns_type === "doh" || data.dns_type === "dot", + [ + { + state: data.bootstrap_dns_status ? "success" : "error", + key: _("Bootsrap DNS"), + value: `${data.bootstrap_dns_server} ${data.bootstrap_dns_status ? "\u2705" : "\u274C"}` + } + ] + ), { state: data.dns_status ? "success" : "error", key: _("Main DNS"), - value: `${data.dns_server} [${data.dns_type}]` + value: `${data.dns_server} [${data.dns_type}] ${data.dns_status ? "\u2705" : "\u274C"}` }, { state: data.local_dns_status ? "success" : "error", key: _("Local DNS"), - value: data.local_dns_status ? _("Enabled") : _("Failed") + value: data.local_dns_status ? "\u2705" : "\u274C" } ] }); @@ -2812,6 +2825,8 @@ return baseclass.extend({ initDashboardController, initDiagnosticController, injectGlobalStyles, + insertIf, + insertIfObj, maskIP, onMount, parseQueryString, From 74edbcf07f14502450bc5f025b435c91b36b6e9a Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 13 Oct 2025 22:40:49 +0300 Subject: [PATCH 043/121] feat: update diagnostics checks --- .../methods => api}/createBaseApiRequest.ts | 20 +- fe-app-podkop/src/api/index.ts | 2 + fe-app-podkop/src/api/types.ts | 9 + fe-app-podkop/src/clash/methods/getConfig.ts | 4 +- .../src/clash/methods/getGroupDelay.ts | 4 +- fe-app-podkop/src/clash/methods/getProxies.ts | 4 +- fe-app-podkop/src/clash/methods/getVersion.ts | 4 +- fe-app-podkop/src/clash/methods/index.ts | 1 - .../src/clash/methods/triggerLatencyTest.ts | 3 +- .../src/clash/methods/triggerProxySelector.ts | 3 +- fe-app-podkop/src/clash/types.ts | 10 - fe-app-podkop/src/fakeip/index.ts | 1 + .../src/fakeip/methods/getFakeIpCheck.ts | 23 ++ .../src/fakeip/methods/getIpCheck.ts | 23 ++ fe-app-podkop/src/fakeip/methods/index.ts | 2 + fe-app-podkop/src/icons/index.ts | 3 + fe-app-podkop/src/icons/renderCheckIcon24.ts | 23 ++ .../src/icons/renderTriangleAlertIcon24.ts | 25 +++ fe-app-podkop/src/icons/renderXIcon24.ts | 19 ++ .../tabs/diagnostic/checks/runDnsCheck.ts | 6 +- .../tabs/diagnostic/checks/runFakeIPCheck.ts | 18 +- .../tabs/diagnostic/checks/runNftCheck.ts | 24 ++- .../tabs/diagnostic/checks/runSingBoxCheck.ts | 12 +- .../tabs/diagnostic/renderCheckSection.ts | 33 ++- fe-app-podkop/src/styles.ts | 7 +- .../luci-static/resources/view/podkop/main.js | 198 +++++++++++++++--- 26 files changed, 400 insertions(+), 81 deletions(-) rename fe-app-podkop/src/{clash/methods => api}/createBaseApiRequest.ts (53%) create mode 100644 fe-app-podkop/src/api/index.ts create mode 100644 fe-app-podkop/src/api/types.ts create mode 100644 fe-app-podkop/src/fakeip/index.ts create mode 100644 fe-app-podkop/src/fakeip/methods/getFakeIpCheck.ts create mode 100644 fe-app-podkop/src/fakeip/methods/getIpCheck.ts create mode 100644 fe-app-podkop/src/fakeip/methods/index.ts create mode 100644 fe-app-podkop/src/icons/renderCheckIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderTriangleAlertIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderXIcon24.ts diff --git a/fe-app-podkop/src/clash/methods/createBaseApiRequest.ts b/fe-app-podkop/src/api/createBaseApiRequest.ts similarity index 53% rename from fe-app-podkop/src/clash/methods/createBaseApiRequest.ts rename to fe-app-podkop/src/api/createBaseApiRequest.ts index 601a433..c175678 100644 --- a/fe-app-podkop/src/clash/methods/createBaseApiRequest.ts +++ b/fe-app-podkop/src/api/createBaseApiRequest.ts @@ -1,10 +1,26 @@ -import { IBaseApiResponse } from '../types'; +import { IBaseApiResponse } from './types'; +import { withTimeout } from '../helpers'; export async function createBaseApiRequest( fetchFn: () => Promise, + options?: { + timeoutMs?: number; + operationName?: string; + timeoutMessage?: string; + }, ): Promise> { + const wrappedFn = () => + options?.timeoutMs && options?.operationName + ? withTimeout( + fetchFn(), + options.timeoutMs, + options.operationName, + options.timeoutMessage, + ) + : fetchFn(); + try { - const response = await fetchFn(); + const response = await wrappedFn(); if (!response.ok) { return { diff --git a/fe-app-podkop/src/api/index.ts b/fe-app-podkop/src/api/index.ts new file mode 100644 index 0000000..2068b3f --- /dev/null +++ b/fe-app-podkop/src/api/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './createBaseApiRequest'; diff --git a/fe-app-podkop/src/api/types.ts b/fe-app-podkop/src/api/types.ts new file mode 100644 index 0000000..193eb59 --- /dev/null +++ b/fe-app-podkop/src/api/types.ts @@ -0,0 +1,9 @@ +export type IBaseApiResponse = + | { + success: true; + data: T; + } + | { + success: false; + message: string; + }; diff --git a/fe-app-podkop/src/clash/methods/getConfig.ts b/fe-app-podkop/src/clash/methods/getConfig.ts index 8f7135a..03cd5be 100644 --- a/fe-app-podkop/src/clash/methods/getConfig.ts +++ b/fe-app-podkop/src/clash/methods/getConfig.ts @@ -1,6 +1,6 @@ -import { ClashAPI, IBaseApiResponse } from '../types'; -import { createBaseApiRequest } from './createBaseApiRequest'; +import { ClashAPI } from '../types'; import { getClashApiUrl } from '../../helpers'; +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; export async function getClashConfig(): Promise< IBaseApiResponse diff --git a/fe-app-podkop/src/clash/methods/getGroupDelay.ts b/fe-app-podkop/src/clash/methods/getGroupDelay.ts index f160bec..5dda283 100644 --- a/fe-app-podkop/src/clash/methods/getGroupDelay.ts +++ b/fe-app-podkop/src/clash/methods/getGroupDelay.ts @@ -1,6 +1,6 @@ -import { ClashAPI, IBaseApiResponse } from '../types'; -import { createBaseApiRequest } from './createBaseApiRequest'; +import { ClashAPI } from '../types'; import { getClashApiUrl } from '../../helpers'; +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; export async function getClashGroupDelay( group: string, diff --git a/fe-app-podkop/src/clash/methods/getProxies.ts b/fe-app-podkop/src/clash/methods/getProxies.ts index e465c58..27ae08a 100644 --- a/fe-app-podkop/src/clash/methods/getProxies.ts +++ b/fe-app-podkop/src/clash/methods/getProxies.ts @@ -1,6 +1,6 @@ -import { ClashAPI, IBaseApiResponse } from '../types'; -import { createBaseApiRequest } from './createBaseApiRequest'; +import { ClashAPI } from '../types'; import { getClashApiUrl } from '../../helpers'; +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; export async function getClashProxies(): Promise< IBaseApiResponse diff --git a/fe-app-podkop/src/clash/methods/getVersion.ts b/fe-app-podkop/src/clash/methods/getVersion.ts index 119db9f..da2f97d 100644 --- a/fe-app-podkop/src/clash/methods/getVersion.ts +++ b/fe-app-podkop/src/clash/methods/getVersion.ts @@ -1,6 +1,6 @@ -import { ClashAPI, IBaseApiResponse } from '../types'; -import { createBaseApiRequest } from './createBaseApiRequest'; +import { ClashAPI } from '../types'; import { getClashApiUrl } from '../../helpers'; +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; export async function getClashVersion(): Promise< IBaseApiResponse diff --git a/fe-app-podkop/src/clash/methods/index.ts b/fe-app-podkop/src/clash/methods/index.ts index 1feccdb..9d04845 100644 --- a/fe-app-podkop/src/clash/methods/index.ts +++ b/fe-app-podkop/src/clash/methods/index.ts @@ -1,4 +1,3 @@ -export * from './createBaseApiRequest'; export * from './getConfig'; export * from './getGroupDelay'; export * from './getProxies'; diff --git a/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts b/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts index b7fffd9..fd937f7 100644 --- a/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts +++ b/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts @@ -1,6 +1,5 @@ -import { IBaseApiResponse } from '../types'; -import { createBaseApiRequest } from './createBaseApiRequest'; import { getClashApiUrl } from '../../helpers'; +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; export async function triggerLatencyGroupTest( tag: string, diff --git a/fe-app-podkop/src/clash/methods/triggerProxySelector.ts b/fe-app-podkop/src/clash/methods/triggerProxySelector.ts index 16d1f55..5b22b0c 100644 --- a/fe-app-podkop/src/clash/methods/triggerProxySelector.ts +++ b/fe-app-podkop/src/clash/methods/triggerProxySelector.ts @@ -1,6 +1,5 @@ -import { IBaseApiResponse } from '../types'; -import { createBaseApiRequest } from './createBaseApiRequest'; import { getClashApiUrl } from '../../helpers'; +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; export async function triggerProxySelector( selector: string, diff --git a/fe-app-podkop/src/clash/types.ts b/fe-app-podkop/src/clash/types.ts index a54a55f..9f4700c 100644 --- a/fe-app-podkop/src/clash/types.ts +++ b/fe-app-podkop/src/clash/types.ts @@ -1,13 +1,3 @@ -export type IBaseApiResponse = - | { - success: true; - data: T; - } - | { - success: false; - message: string; - }; - // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ClashAPI { export interface Version { diff --git a/fe-app-podkop/src/fakeip/index.ts b/fe-app-podkop/src/fakeip/index.ts new file mode 100644 index 0000000..4f1821b --- /dev/null +++ b/fe-app-podkop/src/fakeip/index.ts @@ -0,0 +1 @@ +export * from './methods'; diff --git a/fe-app-podkop/src/fakeip/methods/getFakeIpCheck.ts b/fe-app-podkop/src/fakeip/methods/getFakeIpCheck.ts new file mode 100644 index 0000000..44df28c --- /dev/null +++ b/fe-app-podkop/src/fakeip/methods/getFakeIpCheck.ts @@ -0,0 +1,23 @@ +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; +import { FAKEIP_CHECK_DOMAIN } from '../../constants'; + +interface IGetFakeIpCheckResponse { + fakeip: boolean; + IP: string; +} + +export async function getFakeIpCheck(): Promise< + IBaseApiResponse +> { + return createBaseApiRequest( + () => + fetch(`https://${FAKEIP_CHECK_DOMAIN}/check`, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }), + { + operationName: 'getFakeIpCheck', + timeoutMs: 5000, + }, + ); +} diff --git a/fe-app-podkop/src/fakeip/methods/getIpCheck.ts b/fe-app-podkop/src/fakeip/methods/getIpCheck.ts new file mode 100644 index 0000000..f54a054 --- /dev/null +++ b/fe-app-podkop/src/fakeip/methods/getIpCheck.ts @@ -0,0 +1,23 @@ +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; +import { IP_CHECK_DOMAIN } from '../../constants'; + +interface IGetIpCheckResponse { + fakeip: boolean; + IP: string; +} + +export async function getIpCheck(): Promise< + IBaseApiResponse +> { + return createBaseApiRequest( + () => + fetch(`https://${IP_CHECK_DOMAIN}/check`, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }), + { + operationName: 'getIpCheck', + timeoutMs: 5000, + }, + ); +} diff --git a/fe-app-podkop/src/fakeip/methods/index.ts b/fe-app-podkop/src/fakeip/methods/index.ts new file mode 100644 index 0000000..aab9ee5 --- /dev/null +++ b/fe-app-podkop/src/fakeip/methods/index.ts @@ -0,0 +1,2 @@ +export * from './getIpCheck'; +export * from './getFakeIpCheck'; diff --git a/fe-app-podkop/src/icons/index.ts b/fe-app-podkop/src/icons/index.ts index 184bcb8..73c766a 100644 --- a/fe-app-podkop/src/icons/index.ts +++ b/fe-app-podkop/src/icons/index.ts @@ -3,3 +3,6 @@ export * from './renderCircleAlertIcon24'; export * from './renderCircleCheckIcon24'; export * from './renderCircleSlashIcon24'; export * from './renderCircleXIcon24'; +export * from './renderCheckIcon24'; +export * from './renderXIcon24'; +export * from './renderTriangleAlertIcon24'; diff --git a/fe-app-podkop/src/icons/renderCheckIcon24.ts b/fe-app-podkop/src/icons/renderCheckIcon24.ts new file mode 100644 index 0000000..b1c9ab2 --- /dev/null +++ b/fe-app-podkop/src/icons/renderCheckIcon24.ts @@ -0,0 +1,23 @@ +import { svgEl } from '../helpers'; + +export function renderCheckIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-check-icon lucide-check', + }, + [ + svgEl('path', { + d: 'M20 6 9 17l-5-5', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderTriangleAlertIcon24.ts b/fe-app-podkop/src/icons/renderTriangleAlertIcon24.ts new file mode 100644 index 0000000..993b317 --- /dev/null +++ b/fe-app-podkop/src/icons/renderTriangleAlertIcon24.ts @@ -0,0 +1,25 @@ +import { svgEl } from '../helpers'; + +export function renderTriangleAlertIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-triangle-alert-icon lucide-triangle-alert', + }, + [ + svgEl('path', { + d: 'm21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3', + }), + svgEl('path', { d: 'M12 9v4' }), + svgEl('path', { d: 'M12 17h.01' }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderXIcon24.ts b/fe-app-podkop/src/icons/renderXIcon24.ts new file mode 100644 index 0000000..d8b6ce6 --- /dev/null +++ b/fe-app-podkop/src/icons/renderXIcon24.ts @@ -0,0 +1,19 @@ +import { svgEl } from '../helpers'; + +export function renderXIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-x-icon lucide-x', + }, + [svgEl('path', { d: 'M18 6 6 18' }), svgEl('path', { d: 'm6 6 12 12' })], + ); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index 6d9ef0b..6ac19a2 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -66,19 +66,19 @@ export async function runDnsCheck() { { state: data.bootstrap_dns_status ? 'success' : 'error', key: _('Bootsrap DNS'), - value: `${data.bootstrap_dns_server} ${data.bootstrap_dns_status ? '✅' : '❌'}`, + value: data.bootstrap_dns_server, }, ], ), { state: data.dns_status ? 'success' : 'error', key: _('Main DNS'), - value: `${data.dns_server} [${data.dns_type}] ${data.dns_status ? '✅' : '❌'}`, + value: `${data.dns_server} [${data.dns_type}]`, }, { state: data.local_dns_status ? 'success' : 'error', key: _('Local DNS'), - value: data.local_dns_status ? '✅' : '❌', + value: '', }, ], }); diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts index 1c16ff5..82b4eeb 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -8,6 +8,22 @@ export async function runFakeIPCheck() { title: _('Fake IP checks'), description: _('Not implemented yet'), state: 'skipped', - items: [], + items: [ + { + state: 'success', + key: 'success', + value: '', + }, + { + state: 'warning', + key: 'warning', + value: '', + }, + { + state: 'error', + key: 'error', + value: '', + }, + ], }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index d59eeee..713d474 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -1,9 +1,13 @@ import { getNftRulesCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; +import { getFakeIpCheck, getIpCheck } from '../../../../fakeip'; export async function runNftCheck() { const code = 'nft_check'; + await getFakeIpCheck(); + await getIpCheck(); + updateDiagnosticsCheck({ code, title: _('Nftables checks'), @@ -73,42 +77,42 @@ export async function runNftCheck() { { state: data.table_exist ? 'success' : 'error', key: _('Table exist'), - value: data.table_exist ? _('Yes') : _('No'), + value: '', }, { state: data.rules_mangle_exist ? 'success' : 'error', key: _('Rules mangle exist'), - value: data.rules_mangle_exist ? _('Yes') : _('No'), + value: '', }, { state: data.rules_mangle_counters ? 'success' : 'error', key: _('Rules mangle counters'), - value: data.rules_mangle_counters ? _('Yes') : _('No'), + value: '', }, { state: data.rules_mangle_output_exist ? 'success' : 'error', key: _('Rules mangle output exist'), - value: data.rules_mangle_output_exist ? _('Yes') : _('No'), + value: '', }, { state: data.rules_mangle_output_counters ? 'success' : 'error', key: _('Rules mangle output counters'), - value: data.rules_mangle_output_counters ? _('Yes') : _('No'), + value: '', }, { state: data.rules_proxy_exist ? 'success' : 'error', key: _('Rules proxy exist'), - value: data.rules_proxy_exist ? _('Yes') : _('No'), + value: '', }, { state: data.rules_proxy_counters ? 'success' : 'error', key: _('Rules proxy counters'), - value: data.rules_proxy_counters ? _('Yes') : _('No'), + value: '', }, { - state: data.rules_other_mark_exist ? 'warning' : 'success', - key: _('Rules other mark exist'), - value: data.rules_other_mark_exist ? _('Yes') : _('No'), + state: !data.rules_other_mark_exist ? 'success' : 'warning', + key: _('None other Mark rules'), + value: '', }, ], }); diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts index 6f4adce..c9e3f12 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts @@ -67,32 +67,32 @@ export async function runSingBoxCheck() { { state: data.sing_box_installed ? 'success' : 'error', key: _('Sing-box installed'), - value: data.sing_box_installed ? _('Yes') : _('No'), + value: '', }, { state: data.sing_box_version_ok ? 'success' : 'error', key: _('Sing-box version >= 1.12.4'), - value: data.sing_box_version_ok ? _('Yes') : _('No'), + value: '', }, { state: data.sing_box_service_exist ? 'success' : 'error', key: _('Sing-box service exist'), - value: data.sing_box_service_exist ? _('Yes') : _('No'), + value: '', }, { state: data.sing_box_autostart_disabled ? 'success' : 'error', key: _('Sing-box autostart disabled'), - value: data.sing_box_autostart_disabled ? _('Yes') : _('No'), + value: '', }, { state: data.sing_box_process_running ? 'success' : 'error', key: _('Sing-box process running'), - value: data.sing_box_process_running ? _('Yes') : _('No'), + value: '', }, { state: data.sing_box_ports_listening ? 'success' : 'error', key: _('Sing-box listening ports'), - value: data.sing_box_ports_listening ? _('Yes') : _('No'), + value: '', }, ], }); diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts index 5128516..e7d8098 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts @@ -1,9 +1,12 @@ import { + renderCheckIcon24, renderCircleAlertIcon24, renderCircleCheckIcon24, renderCircleSlashIcon24, renderCircleXIcon24, renderLoaderCircleIcon24, + renderTriangleAlertIcon24, + renderXIcon24, } from '../../../icons'; import { IDiagnosticsChecksStoreItem } from '../../../store'; @@ -14,15 +17,35 @@ function renderCheckSummary(items: IRenderCheckSectionProps['items']) { return E('div', {}, ''); } - const renderedItems = items.map((item) => - E( + const renderedItems = items.map((item) => { + function getIcon() { + const iconWrap = E('span', { + class: 'pdk_diagnostic_alert__summary__item__icon', + }); + + if (item.state === 'success') { + iconWrap.appendChild(renderCheckIcon24()); + } + + if (item.state === 'warning') { + iconWrap.appendChild(renderTriangleAlertIcon24()); + } + + if (item.state === 'error') { + iconWrap.appendChild(renderXIcon24()); + } + + return iconWrap; + } + + return E( 'div', { class: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}`, }, - [E('b', {}, item.key), E('div', {}, item.value)], - ), - ); + [getIcon(), E('b', {}, item.key), E('div', {}, item.value)], + ); + }); return E('div', { class: 'pdk_diagnostic_alert__summary' }, renderedItems); } diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index 5ed936c..08d7892 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -267,7 +267,7 @@ export const GlobalStyles = ` .pdk_diagnostic_alert__summary__item { display: grid; - grid-template-columns: auto 1fr; + grid-template-columns: 16px auto 1fr; grid-column-gap: 10px; } @@ -283,4 +283,9 @@ export const GlobalStyles = ` color: var(--success-color-medium, green); } +.pdk_diagnostic_alert__summary__item__icon { + width: 16px; + height: 16px; +} + `; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 77dadcd..50ff4c8 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -490,7 +490,7 @@ var GlobalStyles = ` .pdk_diagnostic_alert__summary__item { display: grid; - grid-template-columns: auto 1fr; + grid-template-columns: 16px auto 1fr; grid-column-gap: 10px; } @@ -506,6 +506,11 @@ var GlobalStyles = ` color: var(--success-color-medium, green); } +.pdk_diagnostic_alert__summary__item__icon { + width: 16px; + height: 16px; +} + `; // src/helpers/injectGlobalStyles.ts @@ -942,10 +947,16 @@ function validateProxyUrl(url) { }; } -// src/clash/methods/createBaseApiRequest.ts -async function createBaseApiRequest(fetchFn) { +// src/api/createBaseApiRequest.ts +async function createBaseApiRequest(fetchFn, options) { + const wrappedFn = () => options?.timeoutMs && options?.operationName ? withTimeout( + fetchFn(), + options.timeoutMs, + options.operationName, + options.timeoutMessage + ) : fetchFn(); try { - const response = await fetchFn(); + const response = await wrappedFn(); if (!response.ok) { return { success: false, @@ -2357,20 +2368,102 @@ function renderCircleXIcon24() { ); } +// src/icons/renderCheckIcon24.ts +function renderCheckIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-check-icon lucide-check" + }, + [ + svgEl("path", { + d: "M20 6 9 17l-5-5" + }) + ] + ); +} + +// src/icons/renderXIcon24.ts +function renderXIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-x-icon lucide-x" + }, + [svgEl("path", { d: "M18 6 6 18" }), svgEl("path", { d: "m6 6 12 12" })] + ); +} + +// src/icons/renderTriangleAlertIcon24.ts +function renderTriangleAlertIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-triangle-alert-icon lucide-triangle-alert" + }, + [ + svgEl("path", { + d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" + }), + svgEl("path", { d: "M12 9v4" }), + svgEl("path", { d: "M12 17h.01" }) + ] + ); +} + // src/podkop/tabs/diagnostic/renderCheckSection.ts function renderCheckSummary(items) { if (!items.length) { return E("div", {}, ""); } - const renderedItems = items.map( - (item) => E( + const renderedItems = items.map((item) => { + function getIcon() { + const iconWrap = E("span", { + class: "pdk_diagnostic_alert__summary__item__icon" + }); + if (item.state === "success") { + iconWrap.appendChild(renderCheckIcon24()); + } + if (item.state === "warning") { + iconWrap.appendChild(renderTriangleAlertIcon24()); + } + if (item.state === "error") { + iconWrap.appendChild(renderXIcon24()); + } + return iconWrap; + } + return E( "div", { class: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}` }, - [E("b", {}, item.key), E("div", {}, item.value)] - ) - ); + [getIcon(), E("b", {}, item.key), E("div", {}, item.value)] + ); + }); return E("div", { class: "pdk_diagnostic_alert__summary" }, renderedItems); } function renderLoadingState3(props) { @@ -2552,19 +2645,19 @@ async function runDnsCheck() { { state: data.bootstrap_dns_status ? "success" : "error", key: _("Bootsrap DNS"), - value: `${data.bootstrap_dns_server} ${data.bootstrap_dns_status ? "\u2705" : "\u274C"}` + value: data.bootstrap_dns_server } ] ), { state: data.dns_status ? "success" : "error", key: _("Main DNS"), - value: `${data.dns_server} [${data.dns_type}] ${data.dns_status ? "\u2705" : "\u274C"}` + value: `${data.dns_server} [${data.dns_type}]` }, { state: data.local_dns_status ? "success" : "error", key: _("Local DNS"), - value: data.local_dns_status ? "\u2705" : "\u274C" + value: "" } ] }); @@ -2616,32 +2709,32 @@ async function runSingBoxCheck() { { state: data.sing_box_installed ? "success" : "error", key: _("Sing-box installed"), - value: data.sing_box_installed ? _("Yes") : _("No") + value: "" }, { state: data.sing_box_version_ok ? "success" : "error", key: _("Sing-box version >= 1.12.4"), - value: data.sing_box_version_ok ? _("Yes") : _("No") + value: "" }, { state: data.sing_box_service_exist ? "success" : "error", key: _("Sing-box service exist"), - value: data.sing_box_service_exist ? _("Yes") : _("No") + value: "" }, { state: data.sing_box_autostart_disabled ? "success" : "error", key: _("Sing-box autostart disabled"), - value: data.sing_box_autostart_disabled ? _("Yes") : _("No") + value: "" }, { state: data.sing_box_process_running ? "success" : "error", key: _("Sing-box process running"), - value: data.sing_box_process_running ? _("Yes") : _("No") + value: "" }, { state: data.sing_box_ports_listening ? "success" : "error", key: _("Sing-box listening ports"), - value: data.sing_box_ports_listening ? _("Yes") : _("No") + value: "" } ] }); @@ -2650,9 +2743,39 @@ async function runSingBoxCheck() { } } +// src/fakeip/methods/getIpCheck.ts +async function getIpCheck() { + return createBaseApiRequest( + () => fetch(`https://${IP_CHECK_DOMAIN}/check`, { + method: "GET", + headers: { "Content-Type": "application/json" } + }), + { + operationName: "getIpCheck", + timeoutMs: 5e3 + } + ); +} + +// src/fakeip/methods/getFakeIpCheck.ts +async function getFakeIpCheck() { + return createBaseApiRequest( + () => fetch(`https://${FAKEIP_CHECK_DOMAIN}/check`, { + method: "GET", + headers: { "Content-Type": "application/json" } + }), + { + operationName: "getFakeIpCheck", + timeoutMs: 5e3 + } + ); +} + // src/podkop/tabs/diagnostic/checks/runNftCheck.ts async function runNftCheck() { const code = "nft_check"; + await getFakeIpCheck(); + await getIpCheck(); updateDiagnosticsCheck({ code, title: _("Nftables checks"), @@ -2693,42 +2816,42 @@ async function runNftCheck() { { state: data.table_exist ? "success" : "error", key: _("Table exist"), - value: data.table_exist ? _("Yes") : _("No") + value: "" }, { state: data.rules_mangle_exist ? "success" : "error", key: _("Rules mangle exist"), - value: data.rules_mangle_exist ? _("Yes") : _("No") + value: "" }, { state: data.rules_mangle_counters ? "success" : "error", key: _("Rules mangle counters"), - value: data.rules_mangle_counters ? _("Yes") : _("No") + value: "" }, { state: data.rules_mangle_output_exist ? "success" : "error", key: _("Rules mangle output exist"), - value: data.rules_mangle_output_exist ? _("Yes") : _("No") + value: "" }, { state: data.rules_mangle_output_counters ? "success" : "error", key: _("Rules mangle output counters"), - value: data.rules_mangle_output_counters ? _("Yes") : _("No") + value: "" }, { state: data.rules_proxy_exist ? "success" : "error", key: _("Rules proxy exist"), - value: data.rules_proxy_exist ? _("Yes") : _("No") + value: "" }, { state: data.rules_proxy_counters ? "success" : "error", key: _("Rules proxy counters"), - value: data.rules_proxy_counters ? _("Yes") : _("No") + value: "" }, { - state: data.rules_other_mark_exist ? "warning" : "success", - key: _("Rules other mark exist"), - value: data.rules_other_mark_exist ? _("Yes") : _("No") + state: !data.rules_other_mark_exist ? "success" : "warning", + key: _("None other Mark rules"), + value: "" } ] }); @@ -2745,7 +2868,23 @@ async function runFakeIPCheck() { title: _("Fake IP checks"), description: _("Not implemented yet"), state: "skipped", - items: [] + items: [ + { + state: "success", + key: "success", + value: "" + }, + { + state: "warning", + key: "warning", + value: "" + }, + { + state: "error", + key: "error", + value: "" + } + ] }); } @@ -2804,7 +2943,6 @@ return baseclass.extend({ UPDATE_INTERVAL_OPTIONS, bulkValidate, coreService, - createBaseApiRequest, executeShellCommand, getBaseUrl, getClashApiUrl, From 2e257e4adf972b5616ec57d3f5b2ffcf6e3cb89a Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Mon, 13 Oct 2025 22:43:55 +0300 Subject: [PATCH 044/121] feat: check_fakeip func --- podkop/files/usr/bin/podkop | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index e9e8775..e012bab 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1884,9 +1884,9 @@ check_nft_rules() { local rules_other_mark_exist=0 # Generate traffic through PodkopTable - curl -m 3 -s "http://ip.podkop.fyi/check" > /dev/null 2>&1 & + curl -m 3 -s "https://$CHECK_PROXY_IP_DOMAIN/check" > /dev/null 2>&1 & local pid1=$! - curl -m 3 -s "http://fakeip.podkop.fyi/check" > /dev/null 2>&1 & + curl -m 3 -s "https://$FAKEIP_TEST_DOMAIN/check" > /dev/null 2>&1 & local pid2=$! wait $pid1 2>/dev/null @@ -2024,6 +2024,10 @@ check_sing_box() { echo "{\"sing_box_installed\":$sing_box_installed,\"sing_box_version_ok\":$sing_box_version_ok,\"sing_box_service_exist\":$sing_box_service_exist,\"sing_box_autostart_disabled\":$sing_box_autostart_disabled,\"sing_box_process_running\":$sing_box_process_running,\"sing_box_ports_listening\":$sing_box_ports_listening}" | jq . } +check_fakeip() { + curl -m 3 -s "https://$FAKEIP_TEST_DOMAIN/check" | jq . +} + ####################################### # Clash API interface for managing proxies and groups # Arguments: @@ -2297,6 +2301,7 @@ Available commands: check_sing_box_connections Show active sing-box connections check_sing_box_logs Show sing-box logs check_dnsmasq Check DNSMasq configuration + check_fakeip Test FakeIP on router clash_api Clash API interface for managing proxies and groups show_config Display current podkop configuration show_version Show podkop version @@ -2356,6 +2361,9 @@ check_sing_box_logs) check_dnsmasq) check_dnsmasq ;; +check_fakeip) + check_fakeip + ;; clash_api) clash_api "$2" "$3" "$4" ;; From f7517e6794d2f48b9f44c9da73f682adaf6e4668 Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 13 Oct 2025 23:42:30 +0300 Subject: [PATCH 045/121] feat: change naming for rules_other_mark_exist --- .../src/podkop/tabs/diagnostic/checks/runNftCheck.ts | 4 +++- .../htdocs/luci-static/resources/view/podkop/main.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index 713d474..ddc233a 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -111,7 +111,9 @@ export async function runNftCheck() { }, { state: !data.rules_other_mark_exist ? 'success' : 'warning', - key: _('None other Mark rules'), + key: !data.rules_other_mark_exist + ? _('No other marking rules found') + : _('Additional marking rules found'), value: '', }, ], diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 50ff4c8..49df475 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2850,7 +2850,7 @@ async function runNftCheck() { }, { state: !data.rules_other_mark_exist ? "success" : "warning", - key: _("None other Mark rules"), + key: !data.rules_other_mark_exist ? _("No other marking rules found") : _("Additional marking rules found"), value: "" } ] From 85b1dc75f5c98f2e01eb1eac6c7c083b3c19f45c Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 00:26:17 +0300 Subject: [PATCH 046/121] feat: implement fakeip checks step --- .../src/podkop/methods/getFakeIPCheck.ts | 24 +++++ fe-app-podkop/src/podkop/methods/index.ts | 1 + .../tabs/diagnostic/checks/runFakeIPCheck.ts | 96 ++++++++++++++++--- .../tabs/diagnostic/checks/runNftCheck.ts | 6 +- fe-app-podkop/src/podkop/types.ts | 5 + .../luci-static/resources/view/podkop/main.js | 92 +++++++++++++++--- 6 files changed, 195 insertions(+), 29 deletions(-) create mode 100644 fe-app-podkop/src/podkop/methods/getFakeIPCheck.ts diff --git a/fe-app-podkop/src/podkop/methods/getFakeIPCheck.ts b/fe-app-podkop/src/podkop/methods/getFakeIPCheck.ts new file mode 100644 index 0000000..0e7bbe2 --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/getFakeIPCheck.ts @@ -0,0 +1,24 @@ +import { executeShellCommand } from '../../helpers'; +import { Podkop } from '../types'; + +export async function getFakeIPCheck(): Promise< + Podkop.MethodResponse +> { + const response = await executeShellCommand({ + command: '/usr/bin/podkop', + args: ['check_fakeip'], + timeout: 10000, + }); + + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) as Podkop.FakeIPCheckResult, + }; + } + + return { + success: false, + error: '', + }; +} diff --git a/fe-app-podkop/src/podkop/methods/index.ts b/fe-app-podkop/src/podkop/methods/index.ts index 2577428..70d753b 100644 --- a/fe-app-podkop/src/podkop/methods/index.ts +++ b/fe-app-podkop/src/podkop/methods/index.ts @@ -5,3 +5,4 @@ export * from './getSingBoxStatus'; export * from './getDNSCheck'; export * from './getNftRulesCheck'; export * from './getSingBoxCheck'; +export * from './getFakeIPCheck'; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts index 82b4eeb..55e0188 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -1,29 +1,101 @@ +import * as podkopMethods from '../../../methods'; +import * as fakeIPMethods from '../../../../fakeip/methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; +import { insertIf } from '../../../../helpers'; +import { IDiagnosticsChecksItem } from '../../../../store'; export async function runFakeIPCheck() { const code = 'fake_ip_check'; updateDiagnosticsCheck({ code, - title: _('Fake IP checks'), - description: _('Not implemented yet'), - state: 'skipped', + title: _('FakeIP checks'), + description: _('Checking FakeIP, please wait'), + state: 'loading', + items: [], + }); + + const routerFakeIPResponse = await podkopMethods.getFakeIPCheck(); + const checkFakeIPResponse = await fakeIPMethods.getFakeIpCheck(); + const checkIPResponse = await fakeIPMethods.getIpCheck(); + + console.log('runFakeIPCheck', { + routerFakeIPResponse, + checkFakeIPResponse, + checkIPResponse, + }); + + const checks = { + router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, + browserFakeIP: + checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, + differentIP: + checkFakeIPResponse.success && + checkIPResponse.success && + checkFakeIPResponse.data.IP !== checkIPResponse.data.IP, + }; + + console.log('checks', checks); + + const allGood = checks.router || checks.browserFakeIP || checks.differentIP; + const atLeastOneGood = + checks.router && checks.browserFakeIP && checks.differentIP; + + function getMeta(): { + description: string; + state: 'loading' | 'warning' | 'success' | 'error' | 'skipped'; + } { + if (allGood) { + return { + state: 'success', + description: _('FakeIP checks passed'), + }; + } + + if (atLeastOneGood) { + return { + state: 'warning', + description: _('FakeIP checks partially passed'), + }; + } + + return { + state: 'error', + description: _('FakeIP checks failed'), + }; + } + + const { state, description } = getMeta(); + + updateDiagnosticsCheck({ + code, + title: _('FakeIP checks'), + description, + state, items: [ { - state: 'success', - key: 'success', + state: checks.router ? 'success' : 'warning', + key: checks.router + ? _('Router DNS is routed through sing-box') + : _('Router DNS is not routed through sing-box'), value: '', }, { - state: 'warning', - key: 'warning', - value: '', - }, - { - state: 'error', - key: 'error', + state: checks.browserFakeIP ? 'success' : 'error', + key: checks.browserFakeIP + ? _('Browser is using FakeIP correctly') + : _('Browser is not using FakeIP'), value: '', }, + ...insertIf(checks.browserFakeIP, [ + { + state: checks.differentIP ? 'success' : 'error', + key: checks.differentIP + ? _('Proxy traffic is routed via FakeIP') + : _('Proxy traffic is not routed via FakeIP'), + value: '', + }, + ]), ], }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index ddc233a..eb3c93f 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -5,9 +5,6 @@ import { getFakeIpCheck, getIpCheck } from '../../../../fakeip'; export async function runNftCheck() { const code = 'nft_check'; - await getFakeIpCheck(); - await getIpCheck(); - updateDiagnosticsCheck({ code, title: _('Nftables checks'), @@ -16,6 +13,9 @@ export async function runNftCheck() { items: [], }); + await getFakeIpCheck(); + await getIpCheck(); + const nftablesChecks = await getNftRulesCheck(); if (!nftablesChecks.success) { diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index 558f99b..df38f94 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -97,4 +97,9 @@ export namespace Podkop { sing_box_process_running: 0 | 1; sing_box_ports_listening: 0 | 1; } + + export interface FakeIPCheckResult { + fakeip: boolean; + IP: string; + } } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 49df475..26f40fc 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1268,6 +1268,25 @@ async function getSingBoxCheck() { }; } +// src/podkop/methods/getFakeIPCheck.ts +async function getFakeIPCheck() { + const response = await executeShellCommand({ + command: "/usr/bin/podkop", + args: ["check_fakeip"], + timeout: 1e4 + }); + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) + }; + } + return { + success: false, + error: "" + }; +} + // src/podkop/services/tab.service.ts var TabService = class _TabService { constructor() { @@ -2774,8 +2793,6 @@ async function getFakeIpCheck() { // src/podkop/tabs/diagnostic/checks/runNftCheck.ts async function runNftCheck() { const code = "nft_check"; - await getFakeIpCheck(); - await getIpCheck(); updateDiagnosticsCheck({ code, title: _("Nftables checks"), @@ -2783,6 +2800,8 @@ async function runNftCheck() { state: "loading", items: [] }); + await getFakeIpCheck(); + await getIpCheck(); const nftablesChecks = await getNftRulesCheck(); if (!nftablesChecks.success) { updateDiagnosticsCheck({ @@ -2865,25 +2884,69 @@ async function runFakeIPCheck() { const code = "fake_ip_check"; updateDiagnosticsCheck({ code, - title: _("Fake IP checks"), - description: _("Not implemented yet"), - state: "skipped", + title: _("FakeIP checks"), + description: _("Checking FakeIP, please wait"), + state: "loading", + items: [] + }); + const routerFakeIPResponse = await getFakeIPCheck(); + const checkFakeIPResponse = await getFakeIpCheck(); + const checkIPResponse = await getIpCheck(); + console.log("runFakeIPCheck", { + routerFakeIPResponse, + checkFakeIPResponse, + checkIPResponse + }); + const checks = { + router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, + browserFakeIP: checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, + differentIP: checkFakeIPResponse.success && checkIPResponse.success && checkFakeIPResponse.data.IP !== checkIPResponse.data.IP + }; + console.log("checks", checks); + const allGood = checks.router || checks.browserFakeIP || checks.differentIP; + const atLeastOneGood = checks.router && checks.browserFakeIP && checks.differentIP; + function getMeta() { + if (allGood) { + return { + state: "success", + description: _("FakeIP checks passed") + }; + } + if (atLeastOneGood) { + return { + state: "warning", + description: _("FakeIP checks partially passed") + }; + } + return { + state: "error", + description: _("FakeIP checks failed") + }; + } + const { state, description } = getMeta(); + updateDiagnosticsCheck({ + code, + title: _("FakeIP checks"), + description, + state, items: [ { - state: "success", - key: "success", + state: checks.router ? "success" : "warning", + key: checks.router ? _("Router DNS is routed through sing-box") : _("Router DNS is not routed through sing-box"), value: "" }, { - state: "warning", - key: "warning", + state: checks.browserFakeIP ? "success" : "error", + key: checks.browserFakeIP ? _("Browser is using FakeIP correctly") : _("Browser is not using FakeIP"), value: "" }, - { - state: "error", - key: "error", - value: "" - } + ...insertIf(checks.browserFakeIP, [ + { + state: checks.differentIP ? "success" : "error", + key: checks.differentIP ? _("Proxy traffic is routed via FakeIP") : _("Proxy traffic is not routed via FakeIP"), + value: "" + } + ]) ] }); } @@ -2955,6 +3018,7 @@ return baseclass.extend({ getConfigSections, getDNSCheck, getDashboardSections, + getFakeIPCheck, getNftRulesCheck, getPodkopStatus, getProxyUrlName, From 45bd2d0499da7f751c6726cb0d09e6c4d18e479f Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Tue, 14 Oct 2025 14:20:05 +0300 Subject: [PATCH 047/121] Fix: log function combination --- podkop/files/usr/bin/podkop | 41 +++++++++++++++---------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index e012bab..e3be992 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1621,39 +1621,30 @@ check_sing_box_connections() { done } -check_sing_box_logs() { - nolog "Showing sing-box logs from system journal..." - - local logs=$(logread -e sing-box | tail -n 50) - if [ -z "$logs" ]; then - nolog "No sing-box logs found" - return 1 - fi - - echo "$logs" -} - check_logs() { - nolog "Showing podkop logs from system journal..." - if ! command -v logread > /dev/null 2>&1; then nolog "Error: logread command not found" return 1 fi - - # Get all logs first - local all_logs=$(logread) - - # Find the last occurrence of "Starting podkop" - local start_line=$(echo "$all_logs" | grep -n "podkop.*Starting podkop" | tail -n 1 | cut -d: -f1) - - if [ -z "$start_line" ]; then - nolog "No 'Starting podkop' message found in logs" + + local logs + logs=$(logread | grep -E "podkop|sing-box") + + if [ -z "$logs" ]; then + nolog "Logs not found" return 1 fi + ы + # Find the last occurrence of "Starting podkop" + local start_line + start_line=$(echo "$logs" | grep -n "podkop.*Starting podkop" | tail -n 1 | cut -d: -f1) - # Output all logs from the last start - echo "$all_logs" | tail -n +"$start_line" + if [ -n "$start_line" ]; then + echo "$logs" | tail -n +"$start_line" + else + nolog "No 'Starting podkop' message found, showing last 100 lines" + echo "$logs" | tail -n 100 + fi } show_sing_box_config() { From d39ee3a6664a447139ec6d68e90961cc52178d93 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 17:51:14 +0300 Subject: [PATCH 048/121] feat: add optional minified check displaying --- .../tabs/diagnostic/updateDiagnosticsCheck.ts | 14 ++++++++++++-- .../luci-static/resources/view/podkop/main.js | 9 +++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts index d8dad2c..a36697f 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts @@ -1,10 +1,20 @@ import { IDiagnosticsChecksStoreItem, store } from '../../../store'; -export function updateDiagnosticsCheck(check: IDiagnosticsChecksStoreItem) { +export function updateDiagnosticsCheck( + check: IDiagnosticsChecksStoreItem, + minified?: boolean, +) { const diagnosticsChecks = store.get().diagnosticsChecks; const other = diagnosticsChecks.filter((item) => item.code !== check.code); + const smallCheck: IDiagnosticsChecksStoreItem = { + ...check, + items: check.items.filter((item) => item.state !== 'success'), + }; + + const targetCheck = minified ? smallCheck : check; + store.set({ - diagnosticsChecks: [...other, check], + diagnosticsChecks: [...other, targetCheck], }); } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 26f40fc..fe29c15 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2610,11 +2610,16 @@ function renderCheckSection(props) { } // src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts -function updateDiagnosticsCheck(check) { +function updateDiagnosticsCheck(check, minified) { const diagnosticsChecks = store.get().diagnosticsChecks; const other = diagnosticsChecks.filter((item) => item.code !== check.code); + const smallCheck = { + ...check, + items: check.items.filter((item) => item.state !== "success") + }; + const targetCheck = minified ? smallCheck : check; store.set({ - diagnosticsChecks: [...other, check] + diagnosticsChecks: [...other, targetCheck] }); } From 3f6f03c8d1310ce04ccde62662db6a5166e938a0 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Tue, 14 Oct 2025 20:12:12 +0500 Subject: [PATCH 049/121] feat: honor download_lists_via_proxy setting and use its outbound section for service mixed inbound routing --- podkop/files/usr/bin/podkop | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index e3be992..5f833af 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1066,14 +1066,21 @@ sing_box_configure_experimental() { sing_box_additional_inbounds() { log "Configure the additional inbounds of a sing-box JSON configuration" - config=$( - sing_box_cf_add_mixed_inbound_and_route_rule \ - "$config" \ - "$SB_SERVICE_MIXED_INBOUND_TAG" \ - "$SB_SERVICE_MIXED_INBOUND_ADDRESS" \ - "$SB_SERVICE_MIXED_INBOUND_PORT" \ - "$SB_MAIN_OUTBOUND_TAG" - ) + local download_lists_via_proxy + config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" 0 + if [ "$download_lists_via_proxy" -eq 1 ]; then + local download_lists_via_proxy_section section_outbound_tag + config_get download_lists_via_proxy_section "settings" "download_lists_via_proxy_section" + section_outbound_tag="$(get_outbound_tag_by_section "$download_lists_via_proxy_section")" + config=$( + sing_box_cf_add_mixed_inbound_and_route_rule \ + "$config" \ + "$SB_SERVICE_MIXED_INBOUND_TAG" \ + "$SB_SERVICE_MIXED_INBOUND_ADDRESS" \ + "$SB_SERVICE_MIXED_INBOUND_PORT" \ + "$section_outbound_tag" + ) + fi config_foreach configure_section_mixed_proxy "section" } From 661ba648797cb301f711dcb3581fd4684eeeb972 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Tue, 14 Oct 2025 20:19:57 +0500 Subject: [PATCH 050/121] fix: replace config_get_bool with config_get for community/local/remote list options in podkop script --- podkop/files/usr/bin/podkop | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 5f833af..d3e91c8 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -812,13 +812,13 @@ configure_routing_for_section_lists() { local community_lists user_domain_list_type user_subnet_list_type local_domain_lists local_subnet_lists \ remote_domain_lists remote_subnet_lists section_connection_type route_rule_tag - config_get_bool community_lists "$section" "community_lists" + config_get community_lists "$section" "community_lists" config_get user_domain_list_type "$section" "user_domain_list_type" "disabled" config_get user_subnet_list_type "$section" "user_subnet_list_type" "disabled" - config_get_bool local_domain_lists "$section" "local_domain_lists" - config_get_bool local_subnet_lists "$section" "local_subnet_lists" - config_get_bool remote_domain_lists "$section" "remote_domain_lists" - config_get_bool remote_subnet_lists "$section" "remote_subnet_lists" + config_get local_domain_lists "$section" "local_domain_lists" + config_get local_subnet_lists "$section" "local_subnet_lists" + config_get remote_domain_lists "$section" "remote_domain_lists" + config_get remote_subnet_lists "$section" "remote_subnet_lists" config_get section_connection_type "$section" "connection_type" if [ "$section_connection_type" = "block" ]; then @@ -1387,13 +1387,13 @@ section_has_enabled_lists() { local community_lists user_domain_list_type user_subnet_list_type local_domain_lists local_subnet_lists \ remote_domain_lists remote_subnet_lists - config_get_bool community_lists "$section" "community_lists" + config_get community_lists "$section" "community_lists" config_get user_domain_list_type "$section" "user_domain_list_type" "disabled" config_get user_subnet_list_type "$section" "user_subnet_list_type" "disabled" - config_get_bool local_domain_lists "$section" "local_domain_lists" - config_get_bool local_subnet_lists "$section" "local_subnet_lists" - config_get_bool remote_domain_lists "$section" "remote_domain_lists" - config_get_bool remote_subnet_lists "$section" "remote_subnet_lists" + config_get local_domain_lists "$section" "local_domain_lists" + config_get local_subnet_lists "$section" "local_subnet_lists" + config_get remote_domain_lists "$section" "remote_domain_lists" + config_get remote_subnet_lists "$section" "remote_subnet_lists" if [ -n "$community_lists" ] || [ "$user_domain_list_type" != "disabled" ] || From fa152c3abfd1f7ff2e9f7f3bd671a60d8fa0a738 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Tue, 14 Oct 2025 20:22:46 +0500 Subject: [PATCH 051/121] feat: honor download_lists_via_proxy and use its outbound section as detour tag for rule set --- podkop/files/usr/bin/podkop | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index d3e91c8..01e0db9 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1364,7 +1364,10 @@ get_service_proxy_address() { get_download_detour_tag() { config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" 0 if [ "$download_lists_via_proxy" -eq 1 ]; then - echo "$SB_MAIN_OUTBOUND_TAG" + local download_lists_via_proxy_section section_outbound_tag + config_get download_lists_via_proxy_section "settings" "download_lists_via_proxy_section" + section_outbound_tag="$(get_outbound_tag_by_section "$download_lists_via_proxy_section")" + echo "$section_outbound_tag" else echo "" fi From dbf7e39599425c19c2bf8a486fe8d0d343020775 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 20:17:19 +0300 Subject: [PATCH 052/121] feat: implement some diagnostics widget --- .../tabs/dashboard/initDashboardController.ts | 8 +- .../tabs/diagnostic/checks/contstants.ts | 32 ++ .../tabs/diagnostic/checks/runDnsCheck.ts | 12 +- .../tabs/diagnostic/checks/runFakeIPCheck.ts | 9 +- .../tabs/diagnostic/checks/runNftCheck.ts | 12 +- .../tabs/diagnostic/checks/runSingBoxCheck.ts | 12 +- .../tabs/diagnostic/diagnostic.store.ts | 86 ++++ .../diagnostic/initDiagnosticController.ts | 112 ++++- .../tabs/diagnostic/renderAvailableActions.ts | 11 + .../tabs/diagnostic/renderDiagnostic.ts | 52 +-- .../diagnostic/renderDiagnosticRunAction.ts | 17 + .../tabs/diagnostic/renderSystemInfo.ts | 19 + fe-app-podkop/src/store.ts | 19 +- fe-app-podkop/src/styles.ts | 56 +++ .../luci-static/resources/view/podkop/main.js | 395 +++++++++++++++--- 15 files changed, 713 insertions(+), 139 deletions(-) create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/checks/contstants.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts index fc7b5c9..2054df4 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts @@ -431,7 +431,13 @@ export async function initDashboardController(): Promise { // Remove old listener store.unsubscribe(onStoreUpdate); // Clear store - store.reset(); + store.reset([ + 'bandwidthWidget', + 'trafficTotalWidget', + 'systemInfoWidget', + 'servicesInfoWidget', + 'sectionsWidget', + ]); // Add new listener store.subscribe(onStoreUpdate); diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/contstants.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/contstants.ts new file mode 100644 index 0000000..5c3b2d9 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/contstants.ts @@ -0,0 +1,32 @@ +export enum DIAGNOSTICS_CHECKS { + DNS = 'DNS', + SINGBOX = 'SINGBOX', + NFT = 'NFT', + FAKEIP = 'FAKEIP', +} + +export const DIAGNOSTICS_CHECKS_MAP: Record< + DIAGNOSTICS_CHECKS, + { order: number; title: string; code: DIAGNOSTICS_CHECKS } +> = { + [DIAGNOSTICS_CHECKS.DNS]: { + order: 1, + title: _('DNS checks'), + code: DIAGNOSTICS_CHECKS.DNS, + }, + [DIAGNOSTICS_CHECKS.SINGBOX]: { + order: 2, + title: _('Sing-box checks'), + code: DIAGNOSTICS_CHECKS.SINGBOX, + }, + [DIAGNOSTICS_CHECKS.NFT]: { + order: 3, + title: _('Nftables checks'), + code: DIAGNOSTICS_CHECKS.NFT, + }, + [DIAGNOSTICS_CHECKS.FAKEIP]: { + order: 4, + title: _('FakeIP checks'), + code: DIAGNOSTICS_CHECKS.FAKEIP, + }, +}; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index 6ac19a2..28c62af 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -2,13 +2,15 @@ import { getDNSCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; import { IDiagnosticsChecksItem } from '../../../../store'; +import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; export async function runDnsCheck() { - const code = 'dns_check'; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; updateDiagnosticsCheck({ + order, code, - title: _('DNS checks'), + title, description: _('Checking dns, please wait'), state: 'loading', items: [], @@ -18,8 +20,9 @@ export async function runDnsCheck() { if (!dnsChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _('DNS checks'), + title, description: _('Cannot receive DNS checks result'), state: 'error', items: [], @@ -55,8 +58,9 @@ export async function runDnsCheck() { } updateDiagnosticsCheck({ + order, code, - title: _('DNS checks'), + title, description: _('DNS checks passed'), state: getStatus(), items: [ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts index 55e0188..4b3548e 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -3,13 +3,15 @@ import * as fakeIPMethods from '../../../../fakeip/methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; import { IDiagnosticsChecksItem } from '../../../../store'; +import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; export async function runFakeIPCheck() { - const code = 'fake_ip_check'; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; updateDiagnosticsCheck({ + order, code, - title: _('FakeIP checks'), + title, description: _('Checking FakeIP, please wait'), state: 'loading', items: [], @@ -68,8 +70,9 @@ export async function runFakeIPCheck() { const { state, description } = getMeta(); updateDiagnosticsCheck({ + order, code, - title: _('FakeIP checks'), + title, description, state, items: [ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index eb3c93f..b7fa0c2 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -1,13 +1,15 @@ import { getNftRulesCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { getFakeIpCheck, getIpCheck } from '../../../../fakeip'; +import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; export async function runNftCheck() { - const code = 'nft_check'; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; updateDiagnosticsCheck({ + order, code, - title: _('Nftables checks'), + title, description: _('Checking nftables, please wait'), state: 'loading', items: [], @@ -20,8 +22,9 @@ export async function runNftCheck() { if (!nftablesChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _('Nftables checks'), + title, description: _('Cannot receive nftables checks result'), state: 'error', items: [], @@ -67,8 +70,9 @@ export async function runNftCheck() { } updateDiagnosticsCheck({ + order, code, - title: _('Nftables checks'), + title, description: allGood ? _('Nftables checks passed') : _('Nftables checks partially passed'), diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts index c9e3f12..80a8bd1 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts @@ -1,12 +1,14 @@ import { getSingBoxCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; +import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; export async function runSingBoxCheck() { - const code = 'sing_box_check'; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; updateDiagnosticsCheck({ + order, code, - title: _('Sing-box checks'), + title, description: _('Checking sing-box, please wait'), state: 'loading', items: [], @@ -16,8 +18,9 @@ export async function runSingBoxCheck() { if (!singBoxChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _('Sing-box checks'), + title, description: _('Cannot receive Sing-box checks result'), state: 'error', items: [], @@ -59,8 +62,9 @@ export async function runSingBoxCheck() { } updateDiagnosticsCheck({ + order, code, - title: _('Sing-box checks'), + title, description: _('Sing-box checks passed'), state: getStatus(), items: [ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts new file mode 100644 index 0000000..a39c141 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts @@ -0,0 +1,86 @@ +import { + DIAGNOSTICS_CHECKS, + DIAGNOSTICS_CHECKS_MAP, +} from './checks/contstants'; +import { StoreType } from '../../../store'; + +export const initialDiagnosticStore: Pick< + StoreType, + 'diagnosticsChecks' | 'diagnosticsRunAction' +> = { + diagnosticsRunAction: { loading: false }, + diagnosticsChecks: [ + { + code: DIAGNOSTICS_CHECKS.DNS, + title: DIAGNOSTICS_CHECKS_MAP.DNS.title, + order: DIAGNOSTICS_CHECKS_MAP.DNS.order, + description: _('Not running'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.SINGBOX, + title: DIAGNOSTICS_CHECKS_MAP.SINGBOX.title, + order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, + description: _('Not running'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.NFT, + title: DIAGNOSTICS_CHECKS_MAP.NFT.title, + order: DIAGNOSTICS_CHECKS_MAP.NFT.order, + description: _('Not running'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.FAKEIP, + title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title, + order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, + description: _('Not running'), + items: [], + state: 'skipped', + }, + ], +}; + +export const loadingDiagnosticsChecksStore: Pick< + StoreType, + 'diagnosticsChecks' +> = { + diagnosticsChecks: [ + { + code: DIAGNOSTICS_CHECKS.DNS, + title: DIAGNOSTICS_CHECKS_MAP.DNS.title, + order: DIAGNOSTICS_CHECKS_MAP.DNS.order, + description: _('Queued'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.SINGBOX, + title: DIAGNOSTICS_CHECKS_MAP.SINGBOX.title, + order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, + description: _('Queued'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.NFT, + title: DIAGNOSTICS_CHECKS_MAP.NFT.title, + order: DIAGNOSTICS_CHECKS_MAP.NFT.order, + description: _('Queued'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.FAKEIP, + title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title, + order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, + description: _('Queued'), + items: [], + state: 'skipped', + }, + ], +}; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts index 8f1531b..a8f43df 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts @@ -5,10 +5,16 @@ import { runDnsCheck } from './checks/runDnsCheck'; import { runSingBoxCheck } from './checks/runSingBoxCheck'; import { runNftCheck } from './checks/runNftCheck'; import { runFakeIPCheck } from './checks/runFakeIPCheck'; +import { renderDiagnosticRunAction } from './renderDiagnosticRunAction'; +import { renderAvailableActions } from './renderAvailableActions'; +import { renderSystemInfo } from './renderSystemInfo'; +import { loadingDiagnosticsChecksStore } from './diagnostic.store'; -async function renderDiagnosticsChecks() { +function renderDiagnosticsChecks() { console.log('renderDiagnosticsChecks'); - const diagnosticsChecks = store.get().diagnosticsChecks; + const diagnosticsChecks = store + .get() + .diagnosticsChecks.sort((a, b) => a.order - b.order); const container = document.getElementById('pdk_diagnostic-page-checks'); const renderedDiagnosticsChecks = diagnosticsChecks.map((check) => @@ -20,6 +26,69 @@ async function renderDiagnosticsChecks() { }); } +function renderDiagnosticRunActionWidget() { + console.log('renderDiagnosticRunActionWidget'); + + const { loading } = store.get().diagnosticsRunAction; + const container = document.getElementById('pdk_diagnostic-page-run-check'); + + const renderedAction = renderDiagnosticRunAction({ + loading, + click: () => runChecks(), + }); + + return preserveScrollForPage(() => { + container!.replaceChildren(renderedAction); + }); +} + +function renderDiagnosticAvailableActionsWidget() { + console.log('renderDiagnosticActionsWidget'); + + const container = document.getElementById('pdk_diagnostic-page-actions'); + + const renderedActions = renderAvailableActions(); + + return preserveScrollForPage(() => { + container!.replaceChildren(renderedActions); + }); +} + +function renderDiagnosticSystemInfoWidget() { + console.log('renderDiagnosticSystemInfoWidget'); + + const container = document.getElementById('pdk_diagnostic-page-system-info'); + + const renderedSystemInfo = renderSystemInfo({ + items: [ + { + key: 'Podkop', + value: '1', + }, + { + key: 'Luci App', + value: '1', + }, + { + key: 'Sing-box', + value: '1', + }, + { + key: 'OS', + value: '1', + }, + { + key: 'Device', + value: '1', + }, + ], + }); + + return preserveScrollForPage(() => { + container!.replaceChildren(renderedSystemInfo); + }); +} + async function onStoreUpdate( next: StoreType, prev: StoreType, @@ -28,16 +97,31 @@ async function onStoreUpdate( if (diff.diagnosticsChecks) { renderDiagnosticsChecks(); } + + if (diff.diagnosticsRunAction) { + renderDiagnosticRunActionWidget(); + } } async function runChecks() { - await runDnsCheck(); + try { + store.set({ + diagnosticsRunAction: { loading: true }, + diagnosticsChecks: loadingDiagnosticsChecksStore.diagnosticsChecks, + }); - await runSingBoxCheck(); + await runDnsCheck(); - await runNftCheck(); + await runSingBoxCheck(); - await runFakeIPCheck(); + await runNftCheck(); + + await runFakeIPCheck(); + } catch (e) { + console.log('runChecks - e', e); + } finally { + store.set({ diagnosticsRunAction: { loading: false } }); + } } export async function initDiagnosticController(): Promise { @@ -46,13 +130,19 @@ export async function initDiagnosticController(): Promise { // Remove old listener store.unsubscribe(onStoreUpdate); - // Clear store - store.reset(); - // Add new listener store.subscribe(onStoreUpdate); - // TMP run checks on mount - runChecks(); + // Initial checks render + renderDiagnosticsChecks(); + + // Initial run checks action render + renderDiagnosticRunActionWidget(); + + // Initial available actions render + renderDiagnosticAvailableActionsWidget(); + + // Initial system info render + renderDiagnosticSystemInfoWidget(); }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts new file mode 100644 index 0000000..a8e2ed0 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts @@ -0,0 +1,11 @@ +export function renderAvailableActions() { + return E('div', { class: 'pdk_diagnostic-page__right-bar__actions' }, [ + E('b', {}, 'Available actions'), + E('button', { class: 'btn' }, 'Restart podkop'), + E('button', { class: 'btn' }, 'Stop podkop'), + E('button', { class: 'btn' }, 'Disable podkop'), + E('button', { class: 'btn' }, 'Get global check'), + E('button', { class: 'btn' }, 'View logs'), + E('button', { class: 'btn' }, 'Show sing-box config'), + ]); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts index ef5fdac..b4a5766 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts @@ -1,45 +1,15 @@ export function renderDiagnostic() { - return E( - 'div', - { id: 'diagnostic-status', class: 'pdk_diagnostic-page' }, - E( - 'div', - { + return E('div', { id: 'diagnostic-status', class: 'pdk_diagnostic-page' }, [ + E('div', { class: 'pdk_diagnostic-page__left-bar' }, [ + E('div', { id: 'pdk_diagnostic-page-run-check' }), + E('div', { class: 'pdk_diagnostic-page__checks', id: 'pdk_diagnostic-page-checks', - }, - // [ - // renderCheckSection({ - // state: 'loading', - // title: _('DNS Checks'), - // description: _('Checking, please wait'), - // items: [], - // }), - // renderCheckSection({ - // state: 'warning', - // title: _('DNS Checks'), - // description: _('Some checks was failed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'error', - // title: _('DNS Checks'), - // description: _('Checks was failed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'success', - // title: _('DNS Checks'), - // description: _('Checks was passed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'skipped', - // title: _('DNS Checks'), - // description: _('Checks was skipped'), - // items: [], - // }), - // ], - ), - ); + }), + ]), + E('div', { class: 'pdk_diagnostic-page__right-bar' }, [ + E('div', { id: 'pdk_diagnostic-page-actions' }), + E('div', { id: 'pdk_diagnostic-page-system-info' }), + ]), + ]); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts new file mode 100644 index 0000000..f26af81 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts @@ -0,0 +1,17 @@ +interface IRenderDiagnosticRunActionProps { + loading: boolean; + click: () => void; +} + +export function renderDiagnosticRunAction({ + loading, + click, +}: IRenderDiagnosticRunActionProps) { + return E('div', { class: 'pdk_diagnostic-page__run_check_wrapper' }, [ + E( + 'button', + { class: 'btn', disabled: loading ? true : undefined, click }, + loading ? _('Running... please wait') : _('Run Diagnostic'), + ), + ]); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts new file mode 100644 index 0000000..b1b3bc5 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts @@ -0,0 +1,19 @@ +interface IRenderSystemInfoProps { + items: Array<{ key: string; value: string }>; +} + +export function renderSystemInfo({ items }: IRenderSystemInfoProps) { + return E('div', { class: 'pdk_diagnostic-page__right-bar__system-info' }, [ + E( + 'b', + { class: 'pdk_diagnostic-page__right-bar__system-info__title' }, + 'System information', + ), + ...items.map((item) => + E('div', { class: 'pdk_diagnostic-page__right-bar__system-info__row' }, [ + E('b', {}, item.key), + E('div', {}, item.value), + ]), + ), + ]); +} diff --git a/fe-app-podkop/src/store.ts b/fe-app-podkop/src/store.ts index 4b734cd..3bd35a9 100644 --- a/fe-app-podkop/src/store.ts +++ b/fe-app-podkop/src/store.ts @@ -1,4 +1,5 @@ import { Podkop } from './podkop/types'; +import { initialDiagnosticStore } from './podkop/tabs/diagnostic/diagnostic.store'; function jsonStableStringify(obj: T): string { return JSON.stringify(obj, (_, value) => { @@ -61,9 +62,17 @@ class Store> { this.listeners.forEach((cb) => cb(this.value, prev, diff)); } - reset(): void { + reset(keys?: K[]): void { const prev = this.value; - const next = structuredClone(this.initial); + const next = structuredClone(this.value); + + if (keys && keys.length > 0) { + keys.forEach((key) => { + next[key] = structuredClone(this.initial[key]); + }); + } else { + Object.assign(next, structuredClone(this.initial)); + } if (jsonEqual(prev, next)) return; @@ -119,6 +128,7 @@ export interface IDiagnosticsChecksItem { } export interface IDiagnosticsChecksStoreItem { + order: number; code: string; title: string; description: string; @@ -157,6 +167,9 @@ export interface StoreType { data: Podkop.OutboundGroup[]; latencyFetching: boolean; }; + diagnosticsRunAction: { + loading: boolean; + }; diagnosticsChecks: Array; } @@ -191,7 +204,7 @@ const initialStore: StoreType = { latencyFetching: false, data: [], }, - diagnosticsChecks: [], + ...initialDiagnosticStore, }; export const store = new Store(initialStore); diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index 08d7892..4b240bf 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -213,6 +213,62 @@ export const GlobalStyles = ` width: 100%; } +.pdk_diagnostic-page { + display: grid; + grid-template-columns: 2fr 1fr; + grid-column-gap: 10px; + align-items: start; +} + +.pdk_diagnostic-page__right-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__actions { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; + +} + +.pdk_diagnostic-page__right-bar__system-info { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__system-info__title { + +} + +.pdk_diagnostic-page__right-bar__system-info__row { + display: grid; + grid-template-columns: auto 1fr; + grid-column-gap: 5px; +} + +.pdk_diagnostic-page__left-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__run_check_wrapper {} + +.pdk_diagnostic-page__run_check_wrapper button { + width: 100%; +} + .pdk_diagnostic-page__checks { display: grid; grid-template-columns: 1fr; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index fe29c15..d1cd702 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -436,6 +436,62 @@ var GlobalStyles = ` width: 100%; } +.pdk_diagnostic-page { + display: grid; + grid-template-columns: 2fr 1fr; + grid-column-gap: 10px; + align-items: start; +} + +.pdk_diagnostic-page__right-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__actions { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; + +} + +.pdk_diagnostic-page__right-bar__system-info { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__system-info__title { + +} + +.pdk_diagnostic-page__right-bar__system-info__row { + display: grid; + grid-template-columns: auto 1fr; + grid-column-gap: 5px; +} + +.pdk_diagnostic-page__left-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__run_check_wrapper {} + +.pdk_diagnostic-page__run_check_wrapper button { + width: 100%; +} + .pdk_diagnostic-page__checks { display: grid; grid-template-columns: 1fr; @@ -1354,6 +1410,105 @@ var TabService = class _TabService { }; var TabServiceInstance = TabService.getInstance(); +// src/podkop/tabs/diagnostic/checks/contstants.ts +var DIAGNOSTICS_CHECKS_MAP = { + ["DNS" /* DNS */]: { + order: 1, + title: _("DNS checks"), + code: "DNS" /* DNS */ + }, + ["SINGBOX" /* SINGBOX */]: { + order: 2, + title: _("Sing-box checks"), + code: "SINGBOX" /* SINGBOX */ + }, + ["NFT" /* NFT */]: { + order: 3, + title: _("Nftables checks"), + code: "NFT" /* NFT */ + }, + ["FAKEIP" /* FAKEIP */]: { + order: 4, + title: _("FakeIP checks"), + code: "FAKEIP" /* FAKEIP */ + } +}; + +// src/podkop/tabs/diagnostic/diagnostic.store.ts +var initialDiagnosticStore = { + diagnosticsRunAction: { loading: false }, + diagnosticsChecks: [ + { + code: "DNS" /* DNS */, + title: DIAGNOSTICS_CHECKS_MAP.DNS.title, + order: DIAGNOSTICS_CHECKS_MAP.DNS.order, + description: _("Not running"), + items: [], + state: "skipped" + }, + { + code: "SINGBOX" /* SINGBOX */, + title: DIAGNOSTICS_CHECKS_MAP.SINGBOX.title, + order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, + description: _("Not running"), + items: [], + state: "skipped" + }, + { + code: "NFT" /* NFT */, + title: DIAGNOSTICS_CHECKS_MAP.NFT.title, + order: DIAGNOSTICS_CHECKS_MAP.NFT.order, + description: _("Not running"), + items: [], + state: "skipped" + }, + { + code: "FAKEIP" /* FAKEIP */, + title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title, + order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, + description: _("Not running"), + items: [], + state: "skipped" + } + ] +}; +var loadingDiagnosticsChecksStore = { + diagnosticsChecks: [ + { + code: "DNS" /* DNS */, + title: DIAGNOSTICS_CHECKS_MAP.DNS.title, + order: DIAGNOSTICS_CHECKS_MAP.DNS.order, + description: _("Queued"), + items: [], + state: "skipped" + }, + { + code: "SINGBOX" /* SINGBOX */, + title: DIAGNOSTICS_CHECKS_MAP.SINGBOX.title, + order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, + description: _("Queued"), + items: [], + state: "skipped" + }, + { + code: "NFT" /* NFT */, + title: DIAGNOSTICS_CHECKS_MAP.NFT.title, + order: DIAGNOSTICS_CHECKS_MAP.NFT.order, + description: _("Queued"), + items: [], + state: "skipped" + }, + { + code: "FAKEIP" /* FAKEIP */, + title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title, + order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, + description: _("Queued"), + items: [], + state: "skipped" + } + ] +}; + // src/store.ts function jsonStableStringify(obj) { return JSON.stringify(obj, (_2, value) => { @@ -1399,9 +1554,16 @@ var Store = class { } this.listeners.forEach((cb) => cb(this.value, prev, diff)); } - reset() { + reset(keys) { const prev = this.value; - const next = structuredClone(this.initial); + const next = structuredClone(this.value); + if (keys && keys.length > 0) { + keys.forEach((key) => { + next[key] = structuredClone(this.initial[key]); + }); + } else { + Object.assign(next, structuredClone(this.initial)); + } if (jsonEqual(prev, next)) return; this.value = next; this.lastHash = jsonStableStringify(next); @@ -1468,7 +1630,7 @@ var initialStore = { latencyFetching: false, data: [] }, - diagnosticsChecks: [] + ...initialDiagnosticStore }; var store = new Store(initialStore); @@ -2163,7 +2325,13 @@ async function onStoreUpdate(next, prev, diff) { async function initDashboardController() { onMount("dashboard-status").then(() => { store.unsubscribe(onStoreUpdate); - store.reset(); + store.reset([ + "bandwidthWidget", + "trafficTotalWidget", + "systemInfoWidget", + "servicesInfoWidget", + "sectionsWidget" + ]); store.subscribe(onStoreUpdate); fetchDashboardSections(); fetchServicesInfo(); @@ -2173,49 +2341,19 @@ async function initDashboardController() { // src/podkop/tabs/diagnostic/renderDiagnostic.ts function renderDiagnostic() { - return E( - "div", - { id: "diagnostic-status", class: "pdk_diagnostic-page" }, - E( - "div", - { + return E("div", { id: "diagnostic-status", class: "pdk_diagnostic-page" }, [ + E("div", { class: "pdk_diagnostic-page__left-bar" }, [ + E("div", { id: "pdk_diagnostic-page-run-check" }), + E("div", { class: "pdk_diagnostic-page__checks", id: "pdk_diagnostic-page-checks" - } - // [ - // renderCheckSection({ - // state: 'loading', - // title: _('DNS Checks'), - // description: _('Checking, please wait'), - // items: [], - // }), - // renderCheckSection({ - // state: 'warning', - // title: _('DNS Checks'), - // description: _('Some checks was failed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'error', - // title: _('DNS Checks'), - // description: _('Checks was failed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'success', - // title: _('DNS Checks'), - // description: _('Checks was passed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'skipped', - // title: _('DNS Checks'), - // description: _('Checks was skipped'), - // items: [], - // }), - // ], - ) - ); + }) + ]), + E("div", { class: "pdk_diagnostic-page__right-bar" }, [ + E("div", { id: "pdk_diagnostic-page-actions" }), + E("div", { id: "pdk_diagnostic-page-system-info" }) + ]) + ]); } // src/icons/renderLoaderCircleIcon24.ts @@ -2625,10 +2763,11 @@ function updateDiagnosticsCheck(check, minified) { // src/podkop/tabs/diagnostic/checks/runDnsCheck.ts async function runDnsCheck() { - const code = "dns_check"; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; updateDiagnosticsCheck({ + order, code, - title: _("DNS checks"), + title, description: _("Checking dns, please wait"), state: "loading", items: [] @@ -2636,8 +2775,9 @@ async function runDnsCheck() { const dnsChecks = await getDNSCheck(); if (!dnsChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _("DNS checks"), + title, description: _("Cannot receive DNS checks result"), state: "error", items: [] @@ -2658,8 +2798,9 @@ async function runDnsCheck() { return "error"; } updateDiagnosticsCheck({ + order, code, - title: _("DNS checks"), + title, description: _("DNS checks passed"), state: getStatus(), items: [ @@ -2692,10 +2833,11 @@ async function runDnsCheck() { // src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts async function runSingBoxCheck() { - const code = "sing_box_check"; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; updateDiagnosticsCheck({ + order, code, - title: _("Sing-box checks"), + title, description: _("Checking sing-box, please wait"), state: "loading", items: [] @@ -2703,8 +2845,9 @@ async function runSingBoxCheck() { const singBoxChecks = await getSingBoxCheck(); if (!singBoxChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _("Sing-box checks"), + title, description: _("Cannot receive Sing-box checks result"), state: "error", items: [] @@ -2725,8 +2868,9 @@ async function runSingBoxCheck() { return "error"; } updateDiagnosticsCheck({ + order, code, - title: _("Sing-box checks"), + title, description: _("Sing-box checks passed"), state: getStatus(), items: [ @@ -2797,10 +2941,11 @@ async function getFakeIpCheck() { // src/podkop/tabs/diagnostic/checks/runNftCheck.ts async function runNftCheck() { - const code = "nft_check"; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; updateDiagnosticsCheck({ + order, code, - title: _("Nftables checks"), + title, description: _("Checking nftables, please wait"), state: "loading", items: [] @@ -2810,8 +2955,9 @@ async function runNftCheck() { const nftablesChecks = await getNftRulesCheck(); if (!nftablesChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _("Nftables checks"), + title, description: _("Cannot receive nftables checks result"), state: "error", items: [] @@ -2832,8 +2978,9 @@ async function runNftCheck() { return "error"; } updateDiagnosticsCheck({ + order, code, - title: _("Nftables checks"), + title, description: allGood ? _("Nftables checks passed") : _("Nftables checks partially passed"), state: getStatus(), items: [ @@ -2886,10 +3033,11 @@ async function runNftCheck() { // src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts async function runFakeIPCheck() { - const code = "fake_ip_check"; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; updateDiagnosticsCheck({ + order, code, - title: _("FakeIP checks"), + title, description: _("Checking FakeIP, please wait"), state: "loading", items: [] @@ -2930,8 +3078,9 @@ async function runFakeIPCheck() { } const { state, description } = getMeta(); updateDiagnosticsCheck({ + order, code, - title: _("FakeIP checks"), + title, description, state, items: [ @@ -2956,10 +3105,54 @@ async function runFakeIPCheck() { }); } +// src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts +function renderDiagnosticRunAction({ + loading, + click +}) { + return E("div", { class: "pdk_diagnostic-page__run_check_wrapper" }, [ + E( + "button", + { class: "btn", disabled: loading ? true : void 0, click }, + loading ? _("Running... please wait") : _("Run Diagnostic") + ) + ]); +} + +// src/podkop/tabs/diagnostic/renderAvailableActions.ts +function renderAvailableActions() { + return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [ + E("b", {}, "Available actions"), + E("button", { class: "btn" }, "Restart podkop"), + E("button", { class: "btn" }, "Stop podkop"), + E("button", { class: "btn" }, "Disable podkop"), + E("button", { class: "btn" }, "Get global check"), + E("button", { class: "btn" }, "View logs"), + E("button", { class: "btn" }, "Show sing-box config") + ]); +} + +// src/podkop/tabs/diagnostic/renderSystemInfo.ts +function renderSystemInfo({ items }) { + return E("div", { class: "pdk_diagnostic-page__right-bar__system-info" }, [ + E( + "b", + { class: "pdk_diagnostic-page__right-bar__system-info__title" }, + "System information" + ), + ...items.map( + (item) => E("div", { class: "pdk_diagnostic-page__right-bar__system-info__row" }, [ + E("b", {}, item.key), + E("div", {}, item.value) + ]) + ) + ]); +} + // src/podkop/tabs/diagnostic/initDiagnosticController.ts -async function renderDiagnosticsChecks() { +function renderDiagnosticsChecks() { console.log("renderDiagnosticsChecks"); - const diagnosticsChecks = store.get().diagnosticsChecks; + const diagnosticsChecks = store.get().diagnosticsChecks.sort((a, b) => a.order - b.order); const container = document.getElementById("pdk_diagnostic-page-checks"); const renderedDiagnosticsChecks = diagnosticsChecks.map( (check) => renderCheckSection(check) @@ -2968,24 +3161,90 @@ async function renderDiagnosticsChecks() { container.replaceChildren(...renderedDiagnosticsChecks); }); } +function renderDiagnosticRunActionWidget() { + console.log("renderDiagnosticRunActionWidget"); + const { loading } = store.get().diagnosticsRunAction; + const container = document.getElementById("pdk_diagnostic-page-run-check"); + const renderedAction = renderDiagnosticRunAction({ + loading, + click: () => runChecks() + }); + return preserveScrollForPage(() => { + container.replaceChildren(renderedAction); + }); +} +function renderDiagnosticAvailableActionsWidget() { + console.log("renderDiagnosticActionsWidget"); + const container = document.getElementById("pdk_diagnostic-page-actions"); + const renderedActions = renderAvailableActions(); + return preserveScrollForPage(() => { + container.replaceChildren(renderedActions); + }); +} +function renderDiagnosticSystemInfoWidget() { + console.log("renderDiagnosticSystemInfoWidget"); + const container = document.getElementById("pdk_diagnostic-page-system-info"); + const renderedSystemInfo = renderSystemInfo({ + items: [ + { + key: "Podkop", + value: "1" + }, + { + key: "Luci App", + value: "1" + }, + { + key: "Sing-box", + value: "1" + }, + { + key: "OS", + value: "1" + }, + { + key: "Device", + value: "1" + } + ] + }); + return preserveScrollForPage(() => { + container.replaceChildren(renderedSystemInfo); + }); +} async function onStoreUpdate2(next, prev, diff) { if (diff.diagnosticsChecks) { renderDiagnosticsChecks(); } + if (diff.diagnosticsRunAction) { + renderDiagnosticRunActionWidget(); + } } async function runChecks() { - await runDnsCheck(); - await runSingBoxCheck(); - await runNftCheck(); - await runFakeIPCheck(); + try { + store.set({ + diagnosticsRunAction: { loading: true }, + diagnosticsChecks: loadingDiagnosticsChecksStore.diagnosticsChecks + }); + await runDnsCheck(); + await runSingBoxCheck(); + await runNftCheck(); + await runFakeIPCheck(); + } catch (e) { + console.log("runChecks - e", e); + } finally { + store.set({ diagnosticsRunAction: { loading: false } }); + } } async function initDiagnosticController() { onMount("diagnostic-status").then(() => { console.log("diagnostic controller initialized."); store.unsubscribe(onStoreUpdate2); - store.reset(); store.subscribe(onStoreUpdate2); - runChecks(); + renderDiagnosticsChecks(); + renderDiagnosticRunActionWidget(); + renderDiagnosticAvailableActionsWidget(); + renderDiagnosticSystemInfoWidget(); }); } return baseclass.extend({ From de3e67f999a001b3d3e80522aee9290128bcb77c Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 21:27:16 +0300 Subject: [PATCH 053/121] refactor: reorganize all methods --- fe-app-podkop/src/api/index.ts | 2 - fe-app-podkop/src/api/types.ts | 9 - fe-app-podkop/src/clash/index.ts | 2 - fe-app-podkop/src/clash/methods/getConfig.ts | 14 - .../src/clash/methods/getGroupDelay.ts | 20 - fe-app-podkop/src/clash/methods/getVersion.ts | 14 - fe-app-podkop/src/clash/methods/index.ts | 6 - .../src/clash/methods/triggerLatencyTest.ts | 34 -- fe-app-podkop/src/clash/types.ts | 43 --- fe-app-podkop/src/fakeip/index.ts | 1 - fe-app-podkop/src/fakeip/methods/index.ts | 2 - fe-app-podkop/src/main.ts | 1 - .../createBaseApiRequest.ts => podkop/api.ts} | 11 +- .../podkop/methods/clash/getGroupLatency.ts | 18 + .../methods/clash}/getProxies.ts | 6 +- .../podkop/methods/clash/getProxyLatency.ts | 18 + .../src/podkop/methods/clash/index.ts | 11 + .../methods/clash/setProxy.ts} | 4 +- .../methods/{ => custom}/getConfigSections.ts | 2 +- .../{ => custom}/getDashboardSections.ts | 8 +- .../src/podkop/methods/custom/index.ts | 7 + .../methods/fakeip}/getFakeIpCheck.ts | 2 +- .../methods/fakeip}/getIpCheck.ts | 2 +- .../src/podkop/methods/fakeip/index.ts | 7 + .../src/podkop/methods/getDNSCheck.ts | 24 -- .../src/podkop/methods/getFakeIPCheck.ts | 24 -- .../src/podkop/methods/getNftRulesCheck.ts | 24 -- .../src/podkop/methods/getPodkopStatus.ts | 21 -- .../src/podkop/methods/getSingBoxCheck.ts | 24 -- .../src/podkop/methods/getSingBoxStatus.ts | 23 -- fe-app-podkop/src/podkop/methods/index.ts | 12 +- .../podkop/methods/shell/callBaseMethod.ts | 24 ++ .../src/podkop/methods/shell/index.ts | 27 ++ .../tabs/dashboard/initDashboardController.ts | 53 ++- .../tabs/diagnostic/checks/runDnsCheck.ts | 4 +- .../tabs/diagnostic/checks/runFakeIPCheck.ts | 9 +- .../tabs/diagnostic/checks/runNftCheck.ts | 10 +- .../tabs/diagnostic/checks/runSingBoxCheck.ts | 4 +- fe-app-podkop/src/podkop/types.ts | 41 ++ .../luci-static/resources/view/podkop/main.js | 349 +++++++----------- 40 files changed, 355 insertions(+), 562 deletions(-) delete mode 100644 fe-app-podkop/src/api/index.ts delete mode 100644 fe-app-podkop/src/api/types.ts delete mode 100644 fe-app-podkop/src/clash/index.ts delete mode 100644 fe-app-podkop/src/clash/methods/getConfig.ts delete mode 100644 fe-app-podkop/src/clash/methods/getGroupDelay.ts delete mode 100644 fe-app-podkop/src/clash/methods/getVersion.ts delete mode 100644 fe-app-podkop/src/clash/methods/index.ts delete mode 100644 fe-app-podkop/src/clash/methods/triggerLatencyTest.ts delete mode 100644 fe-app-podkop/src/clash/types.ts delete mode 100644 fe-app-podkop/src/fakeip/index.ts delete mode 100644 fe-app-podkop/src/fakeip/methods/index.ts rename fe-app-podkop/src/{api/createBaseApiRequest.ts => podkop/api.ts} (87%) create mode 100644 fe-app-podkop/src/podkop/methods/clash/getGroupLatency.ts rename fe-app-podkop/src/{clash/methods => podkop/methods/clash}/getProxies.ts (68%) create mode 100644 fe-app-podkop/src/podkop/methods/clash/getProxyLatency.ts create mode 100644 fe-app-podkop/src/podkop/methods/clash/index.ts rename fe-app-podkop/src/{clash/methods/triggerProxySelector.ts => podkop/methods/clash/setProxy.ts} (80%) rename fe-app-podkop/src/podkop/methods/{ => custom}/getConfigSections.ts (79%) rename fe-app-podkop/src/podkop/methods/{ => custom}/getDashboardSections.ts (95%) create mode 100644 fe-app-podkop/src/podkop/methods/custom/index.ts rename fe-app-podkop/src/{fakeip/methods => podkop/methods/fakeip}/getFakeIpCheck.ts (90%) rename fe-app-podkop/src/{fakeip/methods => podkop/methods/fakeip}/getIpCheck.ts (90%) create mode 100644 fe-app-podkop/src/podkop/methods/fakeip/index.ts delete mode 100644 fe-app-podkop/src/podkop/methods/getDNSCheck.ts delete mode 100644 fe-app-podkop/src/podkop/methods/getFakeIPCheck.ts delete mode 100644 fe-app-podkop/src/podkop/methods/getNftRulesCheck.ts delete mode 100644 fe-app-podkop/src/podkop/methods/getPodkopStatus.ts delete mode 100644 fe-app-podkop/src/podkop/methods/getSingBoxCheck.ts delete mode 100644 fe-app-podkop/src/podkop/methods/getSingBoxStatus.ts create mode 100644 fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts create mode 100644 fe-app-podkop/src/podkop/methods/shell/index.ts diff --git a/fe-app-podkop/src/api/index.ts b/fe-app-podkop/src/api/index.ts deleted file mode 100644 index 2068b3f..0000000 --- a/fe-app-podkop/src/api/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './types'; -export * from './createBaseApiRequest'; diff --git a/fe-app-podkop/src/api/types.ts b/fe-app-podkop/src/api/types.ts deleted file mode 100644 index 193eb59..0000000 --- a/fe-app-podkop/src/api/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type IBaseApiResponse = - | { - success: true; - data: T; - } - | { - success: false; - message: string; - }; diff --git a/fe-app-podkop/src/clash/index.ts b/fe-app-podkop/src/clash/index.ts deleted file mode 100644 index c3b7574..0000000 --- a/fe-app-podkop/src/clash/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './types'; -export * from './methods'; diff --git a/fe-app-podkop/src/clash/methods/getConfig.ts b/fe-app-podkop/src/clash/methods/getConfig.ts deleted file mode 100644 index 03cd5be..0000000 --- a/fe-app-podkop/src/clash/methods/getConfig.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ClashAPI } from '../types'; -import { getClashApiUrl } from '../../helpers'; -import { createBaseApiRequest, IBaseApiResponse } from '../../api'; - -export async function getClashConfig(): Promise< - IBaseApiResponse -> { - return createBaseApiRequest(() => - fetch(`${getClashApiUrl()}/configs`, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }), - ); -} diff --git a/fe-app-podkop/src/clash/methods/getGroupDelay.ts b/fe-app-podkop/src/clash/methods/getGroupDelay.ts deleted file mode 100644 index 5dda283..0000000 --- a/fe-app-podkop/src/clash/methods/getGroupDelay.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ClashAPI } from '../types'; -import { getClashApiUrl } from '../../helpers'; -import { createBaseApiRequest, IBaseApiResponse } from '../../api'; - -export async function getClashGroupDelay( - group: string, - url = 'https://www.gstatic.com/generate_204', - timeout = 2000, -): Promise> { - const endpoint = `${getClashApiUrl()}/group/${group}/delay?url=${encodeURIComponent( - url, - )}&timeout=${timeout}`; - - return createBaseApiRequest(() => - fetch(endpoint, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }), - ); -} diff --git a/fe-app-podkop/src/clash/methods/getVersion.ts b/fe-app-podkop/src/clash/methods/getVersion.ts deleted file mode 100644 index da2f97d..0000000 --- a/fe-app-podkop/src/clash/methods/getVersion.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ClashAPI } from '../types'; -import { getClashApiUrl } from '../../helpers'; -import { createBaseApiRequest, IBaseApiResponse } from '../../api'; - -export async function getClashVersion(): Promise< - IBaseApiResponse -> { - return createBaseApiRequest(() => - fetch(`${getClashApiUrl()}/version`, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }), - ); -} diff --git a/fe-app-podkop/src/clash/methods/index.ts b/fe-app-podkop/src/clash/methods/index.ts deleted file mode 100644 index 9d04845..0000000 --- a/fe-app-podkop/src/clash/methods/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './getConfig'; -export * from './getGroupDelay'; -export * from './getProxies'; -export * from './getVersion'; -export * from './triggerProxySelector'; -export * from './triggerLatencyTest'; diff --git a/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts b/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts deleted file mode 100644 index fd937f7..0000000 --- a/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { getClashApiUrl } from '../../helpers'; -import { createBaseApiRequest, IBaseApiResponse } from '../../api'; - -export async function triggerLatencyGroupTest( - tag: string, - timeout: number = 5000, - url: string = 'https://www.gstatic.com/generate_204', -): Promise> { - return createBaseApiRequest(() => - fetch( - `${getClashApiUrl()}/group/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, - { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }, - ), - ); -} - -export async function triggerLatencyProxyTest( - tag: string, - timeout: number = 2000, - url: string = 'https://www.gstatic.com/generate_204', -): Promise> { - return createBaseApiRequest(() => - fetch( - `${getClashApiUrl()}/proxies/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, - { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }, - ), - ); -} diff --git a/fe-app-podkop/src/clash/types.ts b/fe-app-podkop/src/clash/types.ts deleted file mode 100644 index 9f4700c..0000000 --- a/fe-app-podkop/src/clash/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-namespace -export namespace ClashAPI { - export interface Version { - meta: boolean; - premium: boolean; - version: string; - } - - export interface Config { - port: number; - 'socks-port': number; - 'redir-port': number; - 'tproxy-port': number; - 'mixed-port': number; - 'allow-lan': boolean; - 'bind-address': string; - mode: 'Rule' | 'Global' | 'Direct'; - 'mode-list': string[]; - 'log-level': 'debug' | 'info' | 'warn' | 'error'; - ipv6: boolean; - tun: null | Record; - } - - export interface ProxyHistoryEntry { - time: string; - delay: number; - } - - export interface ProxyBase { - type: string; - name: string; - udp: boolean; - history: ProxyHistoryEntry[]; - now?: string; - all?: string[]; - } - - export interface Proxies { - proxies: Record; - } - - export type Delays = Record; -} diff --git a/fe-app-podkop/src/fakeip/index.ts b/fe-app-podkop/src/fakeip/index.ts deleted file mode 100644 index 4f1821b..0000000 --- a/fe-app-podkop/src/fakeip/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './methods'; diff --git a/fe-app-podkop/src/fakeip/methods/index.ts b/fe-app-podkop/src/fakeip/methods/index.ts deleted file mode 100644 index aab9ee5..0000000 --- a/fe-app-podkop/src/fakeip/methods/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './getIpCheck'; -export * from './getFakeIpCheck'; diff --git a/fe-app-podkop/src/main.ts b/fe-app-podkop/src/main.ts index 7d7e2b7..79b8260 100644 --- a/fe-app-podkop/src/main.ts +++ b/fe-app-podkop/src/main.ts @@ -5,6 +5,5 @@ export * from './validators'; export * from './helpers'; -export * from './clash'; export * from './podkop'; export * from './constants'; diff --git a/fe-app-podkop/src/api/createBaseApiRequest.ts b/fe-app-podkop/src/podkop/api.ts similarity index 87% rename from fe-app-podkop/src/api/createBaseApiRequest.ts rename to fe-app-podkop/src/podkop/api.ts index c175678..5214c54 100644 --- a/fe-app-podkop/src/api/createBaseApiRequest.ts +++ b/fe-app-podkop/src/podkop/api.ts @@ -1,4 +1,3 @@ -import { IBaseApiResponse } from './types'; import { withTimeout } from '../helpers'; export async function createBaseApiRequest( @@ -42,3 +41,13 @@ export async function createBaseApiRequest( }; } } + +export type IBaseApiResponse = + | { + success: true; + data: T; + } + | { + success: false; + message: string; + }; diff --git a/fe-app-podkop/src/podkop/methods/clash/getGroupLatency.ts b/fe-app-podkop/src/podkop/methods/clash/getGroupLatency.ts new file mode 100644 index 0000000..7b26ac7 --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/clash/getGroupLatency.ts @@ -0,0 +1,18 @@ +import { getClashApiUrl } from '../../../helpers'; +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; + +export async function getGroupLatency( + tag: string, + timeout: number = 5000, + url: string = 'https://www.gstatic.com/generate_204', +): Promise> { + return createBaseApiRequest(() => + fetch( + `${getClashApiUrl()}/group/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, + { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }, + ), + ); +} diff --git a/fe-app-podkop/src/clash/methods/getProxies.ts b/fe-app-podkop/src/podkop/methods/clash/getProxies.ts similarity index 68% rename from fe-app-podkop/src/clash/methods/getProxies.ts rename to fe-app-podkop/src/podkop/methods/clash/getProxies.ts index 27ae08a..2c8d90c 100644 --- a/fe-app-podkop/src/clash/methods/getProxies.ts +++ b/fe-app-podkop/src/podkop/methods/clash/getProxies.ts @@ -1,8 +1,8 @@ -import { ClashAPI } from '../types'; -import { getClashApiUrl } from '../../helpers'; +import { ClashAPI } from '../../types'; +import { getClashApiUrl } from '../../../helpers'; import { createBaseApiRequest, IBaseApiResponse } from '../../api'; -export async function getClashProxies(): Promise< +export async function getProxies(): Promise< IBaseApiResponse > { return createBaseApiRequest(() => diff --git a/fe-app-podkop/src/podkop/methods/clash/getProxyLatency.ts b/fe-app-podkop/src/podkop/methods/clash/getProxyLatency.ts new file mode 100644 index 0000000..6714efa --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/clash/getProxyLatency.ts @@ -0,0 +1,18 @@ +import { getClashApiUrl } from '../../../helpers'; +import { createBaseApiRequest, IBaseApiResponse } from '../../api'; + +export async function getProxyLatency( + tag: string, + timeout: number = 2000, + url: string = 'https://www.gstatic.com/generate_204', +): Promise> { + return createBaseApiRequest(() => + fetch( + `${getClashApiUrl()}/proxies/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, + { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }, + ), + ); +} diff --git a/fe-app-podkop/src/podkop/methods/clash/index.ts b/fe-app-podkop/src/podkop/methods/clash/index.ts new file mode 100644 index 0000000..d40db47 --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/clash/index.ts @@ -0,0 +1,11 @@ +import { getGroupLatency } from './getGroupLatency'; +import { getProxies } from './getProxies'; +import { getProxyLatency } from './getProxyLatency'; +import { setProxy } from './setProxy'; + +export const ClashMethods = { + getGroupLatency, + getProxies, + getProxyLatency, + setProxy, +}; diff --git a/fe-app-podkop/src/clash/methods/triggerProxySelector.ts b/fe-app-podkop/src/podkop/methods/clash/setProxy.ts similarity index 80% rename from fe-app-podkop/src/clash/methods/triggerProxySelector.ts rename to fe-app-podkop/src/podkop/methods/clash/setProxy.ts index 5b22b0c..4642230 100644 --- a/fe-app-podkop/src/clash/methods/triggerProxySelector.ts +++ b/fe-app-podkop/src/podkop/methods/clash/setProxy.ts @@ -1,7 +1,7 @@ -import { getClashApiUrl } from '../../helpers'; +import { getClashApiUrl } from '../../../helpers'; import { createBaseApiRequest, IBaseApiResponse } from '../../api'; -export async function triggerProxySelector( +export async function setProxy( selector: string, outbound: string, ): Promise> { diff --git a/fe-app-podkop/src/podkop/methods/getConfigSections.ts b/fe-app-podkop/src/podkop/methods/custom/getConfigSections.ts similarity index 79% rename from fe-app-podkop/src/podkop/methods/getConfigSections.ts rename to fe-app-podkop/src/podkop/methods/custom/getConfigSections.ts index d8883d4..bdb13c4 100644 --- a/fe-app-podkop/src/podkop/methods/getConfigSections.ts +++ b/fe-app-podkop/src/podkop/methods/custom/getConfigSections.ts @@ -1,4 +1,4 @@ -import { Podkop } from '../types'; +import { Podkop } from '../../types'; export async function getConfigSections(): Promise { return uci.load('podkop').then(() => uci.sections('podkop')); diff --git a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts b/fe-app-podkop/src/podkop/methods/custom/getDashboardSections.ts similarity index 95% rename from fe-app-podkop/src/podkop/methods/getDashboardSections.ts rename to fe-app-podkop/src/podkop/methods/custom/getDashboardSections.ts index b6619d0..96ff4b1 100644 --- a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts +++ b/fe-app-podkop/src/podkop/methods/custom/getDashboardSections.ts @@ -1,7 +1,7 @@ -import { Podkop } from '../types'; import { getConfigSections } from './getConfigSections'; -import { getClashProxies } from '../../clash'; -import { getProxyUrlName, splitProxyString } from '../../helpers'; +import { Podkop } from '../../types'; +import { ClashMethods } from '../clash'; +import { getProxyUrlName, splitProxyString } from '../../../helpers'; interface IGetDashboardSectionsResponse { success: boolean; @@ -10,7 +10,7 @@ interface IGetDashboardSectionsResponse { export async function getDashboardSections(): Promise { const configSections = await getConfigSections(); - const clashProxies = await getClashProxies(); + const clashProxies = await ClashMethods.getProxies(); if (!clashProxies.success) { return { diff --git a/fe-app-podkop/src/podkop/methods/custom/index.ts b/fe-app-podkop/src/podkop/methods/custom/index.ts new file mode 100644 index 0000000..7ade0fa --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/custom/index.ts @@ -0,0 +1,7 @@ +import { getConfigSections } from './getConfigSections'; +import { getDashboardSections } from './getDashboardSections'; + +export const CustomPodkopMethods = { + getConfigSections, + getDashboardSections, +}; diff --git a/fe-app-podkop/src/fakeip/methods/getFakeIpCheck.ts b/fe-app-podkop/src/podkop/methods/fakeip/getFakeIpCheck.ts similarity index 90% rename from fe-app-podkop/src/fakeip/methods/getFakeIpCheck.ts rename to fe-app-podkop/src/podkop/methods/fakeip/getFakeIpCheck.ts index 44df28c..4044ca0 100644 --- a/fe-app-podkop/src/fakeip/methods/getFakeIpCheck.ts +++ b/fe-app-podkop/src/podkop/methods/fakeip/getFakeIpCheck.ts @@ -1,5 +1,5 @@ +import { FAKEIP_CHECK_DOMAIN } from '../../../constants'; import { createBaseApiRequest, IBaseApiResponse } from '../../api'; -import { FAKEIP_CHECK_DOMAIN } from '../../constants'; interface IGetFakeIpCheckResponse { fakeip: boolean; diff --git a/fe-app-podkop/src/fakeip/methods/getIpCheck.ts b/fe-app-podkop/src/podkop/methods/fakeip/getIpCheck.ts similarity index 90% rename from fe-app-podkop/src/fakeip/methods/getIpCheck.ts rename to fe-app-podkop/src/podkop/methods/fakeip/getIpCheck.ts index f54a054..c25a60e 100644 --- a/fe-app-podkop/src/fakeip/methods/getIpCheck.ts +++ b/fe-app-podkop/src/podkop/methods/fakeip/getIpCheck.ts @@ -1,5 +1,5 @@ +import { IP_CHECK_DOMAIN } from '../../../constants'; import { createBaseApiRequest, IBaseApiResponse } from '../../api'; -import { IP_CHECK_DOMAIN } from '../../constants'; interface IGetIpCheckResponse { fakeip: boolean; diff --git a/fe-app-podkop/src/podkop/methods/fakeip/index.ts b/fe-app-podkop/src/podkop/methods/fakeip/index.ts new file mode 100644 index 0000000..4a95f16 --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/fakeip/index.ts @@ -0,0 +1,7 @@ +import { getFakeIpCheck } from './getFakeIpCheck'; +import { getIpCheck } from './getIpCheck'; + +export const RemoteFakeIPMethods = { + getFakeIpCheck, + getIpCheck, +}; diff --git a/fe-app-podkop/src/podkop/methods/getDNSCheck.ts b/fe-app-podkop/src/podkop/methods/getDNSCheck.ts deleted file mode 100644 index 8744230..0000000 --- a/fe-app-podkop/src/podkop/methods/getDNSCheck.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { executeShellCommand } from '../../helpers'; -import { Podkop } from '../types'; - -export async function getDNSCheck(): Promise< - Podkop.MethodResponse -> { - const response = await executeShellCommand({ - command: '/usr/bin/podkop', - args: ['check_dns_available'], - timeout: 10000, - }); - - if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) as Podkop.DnsCheckResult, - }; - } - - return { - success: false, - error: '', - }; -} diff --git a/fe-app-podkop/src/podkop/methods/getFakeIPCheck.ts b/fe-app-podkop/src/podkop/methods/getFakeIPCheck.ts deleted file mode 100644 index 0e7bbe2..0000000 --- a/fe-app-podkop/src/podkop/methods/getFakeIPCheck.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { executeShellCommand } from '../../helpers'; -import { Podkop } from '../types'; - -export async function getFakeIPCheck(): Promise< - Podkop.MethodResponse -> { - const response = await executeShellCommand({ - command: '/usr/bin/podkop', - args: ['check_fakeip'], - timeout: 10000, - }); - - if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) as Podkop.FakeIPCheckResult, - }; - } - - return { - success: false, - error: '', - }; -} diff --git a/fe-app-podkop/src/podkop/methods/getNftRulesCheck.ts b/fe-app-podkop/src/podkop/methods/getNftRulesCheck.ts deleted file mode 100644 index f5eded4..0000000 --- a/fe-app-podkop/src/podkop/methods/getNftRulesCheck.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { executeShellCommand } from '../../helpers'; -import { Podkop } from '../types'; - -export async function getNftRulesCheck(): Promise< - Podkop.MethodResponse -> { - const response = await executeShellCommand({ - command: '/usr/bin/podkop', - args: ['check_nft_rules'], - timeout: 10000, - }); - - if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) as Podkop.NftRulesCheckResult, - }; - } - - return { - success: false, - error: '', - }; -} diff --git a/fe-app-podkop/src/podkop/methods/getPodkopStatus.ts b/fe-app-podkop/src/podkop/methods/getPodkopStatus.ts deleted file mode 100644 index 666c41e..0000000 --- a/fe-app-podkop/src/podkop/methods/getPodkopStatus.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { executeShellCommand } from '../../helpers'; - -export async function getPodkopStatus(): Promise<{ - enabled: number; - status: string; -}> { - const response = await executeShellCommand({ - command: '/usr/bin/podkop', - args: ['get_status'], - timeout: 10000, - }); - - if (response.stdout) { - return JSON.parse(response.stdout.replace(/\n/g, '')) as { - enabled: number; - status: string; - }; - } - - return { enabled: 0, status: 'unknown' }; -} diff --git a/fe-app-podkop/src/podkop/methods/getSingBoxCheck.ts b/fe-app-podkop/src/podkop/methods/getSingBoxCheck.ts deleted file mode 100644 index 6ad35cc..0000000 --- a/fe-app-podkop/src/podkop/methods/getSingBoxCheck.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { executeShellCommand } from '../../helpers'; -import { Podkop } from '../types'; - -export async function getSingBoxCheck(): Promise< - Podkop.MethodResponse -> { - const response = await executeShellCommand({ - command: '/usr/bin/podkop', - args: ['check_sing_box'], - timeout: 10000, - }); - - if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) as Podkop.SingBoxCheckResult, - }; - } - - return { - success: false, - error: '', - }; -} diff --git a/fe-app-podkop/src/podkop/methods/getSingBoxStatus.ts b/fe-app-podkop/src/podkop/methods/getSingBoxStatus.ts deleted file mode 100644 index 51d2f62..0000000 --- a/fe-app-podkop/src/podkop/methods/getSingBoxStatus.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { executeShellCommand } from '../../helpers'; - -export async function getSingBoxStatus(): Promise<{ - running: number; - enabled: number; - status: string; -}> { - const response = await executeShellCommand({ - command: '/usr/bin/podkop', - args: ['get_sing_box_status'], - timeout: 10000, - }); - - if (response.stdout) { - return JSON.parse(response.stdout.replace(/\n/g, '')) as { - running: number; - enabled: number; - status: string; - }; - } - - return { running: 0, enabled: 0, status: 'unknown' }; -} diff --git a/fe-app-podkop/src/podkop/methods/index.ts b/fe-app-podkop/src/podkop/methods/index.ts index 70d753b..28101c6 100644 --- a/fe-app-podkop/src/podkop/methods/index.ts +++ b/fe-app-podkop/src/podkop/methods/index.ts @@ -1,8 +1,4 @@ -export * from './getConfigSections'; -export * from './getDashboardSections'; -export * from './getPodkopStatus'; -export * from './getSingBoxStatus'; -export * from './getDNSCheck'; -export * from './getNftRulesCheck'; -export * from './getSingBoxCheck'; -export * from './getFakeIPCheck'; +export * from './clash'; +export * from './custom'; +export * from './fakeip'; +export * from './shell'; diff --git a/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts b/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts new file mode 100644 index 0000000..55da36e --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts @@ -0,0 +1,24 @@ +import { executeShellCommand } from '../../../helpers'; +import { Podkop } from '../../types'; + +export async function callBaseMethod( + method: Podkop.AvailableMethods, +): Promise> { + const response = await executeShellCommand({ + command: '/usr/bin/podkop', + args: [method], + timeout: 10000, + }); + + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) as T, + }; + } + + return { + success: false, + error: '', + }; +} diff --git a/fe-app-podkop/src/podkop/methods/shell/index.ts b/fe-app-podkop/src/podkop/methods/shell/index.ts new file mode 100644 index 0000000..9b1cd93 --- /dev/null +++ b/fe-app-podkop/src/podkop/methods/shell/index.ts @@ -0,0 +1,27 @@ +import { callBaseMethod } from './callBaseMethod'; +import { Podkop } from '../../types'; + +export const PodkopShellMethods = { + checkDNSAvailable: async () => + callBaseMethod( + Podkop.AvailableMethods.CHECK_DNS_AVAILABLE, + ), + checkFakeIP: async () => + callBaseMethod( + Podkop.AvailableMethods.CHECK_FAKEIP, + ), + checkNftRules: async () => + callBaseMethod( + Podkop.AvailableMethods.CHECK_NFT_RULES, + ), + getStatus: async () => + callBaseMethod(Podkop.AvailableMethods.GET_STATUS), + checkSingBox: async () => + callBaseMethod( + Podkop.AvailableMethods.CHECK_SING_BOX, + ), + getSingBoxStatus: async () => + callBaseMethod( + Podkop.AvailableMethods.GET_SING_BOX_STATUS, + ), +}; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts index 2054df4..d9be183 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts @@ -1,24 +1,19 @@ -import { - getDashboardSections, - getPodkopStatus, - getSingBoxStatus, -} from '../../methods'; import { getClashApiUrl, getClashWsUrl, onMount, preserveScrollForPage, } from '../../../helpers'; -import { - triggerLatencyGroupTest, - triggerLatencyProxyTest, - triggerProxySelector, -} from '../../../clash'; import { store, StoreType } from '../../../store'; import { socket } from '../../../socket'; import { prettyBytes } from '../../../helpers/prettyBytes'; import { renderSections } from './renderSections'; import { renderWidget } from './renderWidget'; +import { + ClashMethods, + CustomPodkopMethods, + PodkopShellMethods, +} from '../../methods'; // Fetchers @@ -32,7 +27,7 @@ async function fetchDashboardSections() { }, }); - const { data, success } = await getDashboardSections(); + const { data, success } = await CustomPodkopMethods.getDashboardSections(); if (!success) { console.log('[fetchDashboardSections]: failed to fetch', getClashApiUrl()); @@ -49,22 +44,12 @@ async function fetchDashboardSections() { } async function fetchServicesInfo() { - try { - const [podkop, singbox] = await Promise.all([ - getPodkopStatus(), - getSingBoxStatus(), - ]); - - store.set({ - servicesInfoWidget: { - loading: false, - failed: false, - data: { singbox: singbox.running, podkop: podkop.enabled }, - }, - }); - } catch (err) { - console.log('[fetchServicesInfo]: failed to fetchServices', err); + const [podkop, singbox] = await Promise.all([ + PodkopShellMethods.getStatus(), + PodkopShellMethods.getSingBoxStatus(), + ]); + if (!podkop.success || !singbox.success) { store.set({ servicesInfoWidget: { loading: false, @@ -73,6 +58,16 @@ async function fetchServicesInfo() { }, }); } + + if (podkop.success && singbox.success) { + store.set({ + servicesInfoWidget: { + loading: false, + failed: false, + data: { singbox: singbox.data.running, podkop: podkop.data.enabled }, + }, + }); + } } async function connectToClashSockets() { @@ -155,7 +150,7 @@ async function connectToClashSockets() { // Handlers async function handleChooseOutbound(selector: string, tag: string) { - await triggerProxySelector(selector, tag); + await ClashMethods.setProxy(selector, tag); await fetchDashboardSections(); } @@ -167,7 +162,7 @@ async function handleTestGroupLatency(tag: string) { }, }); - await triggerLatencyGroupTest(tag); + await ClashMethods.getGroupLatency(tag); await fetchDashboardSections(); store.set({ @@ -186,7 +181,7 @@ async function handleTestProxyLatency(tag: string) { }, }); - await triggerLatencyProxyTest(tag); + await ClashMethods.getProxyLatency(tag); await fetchDashboardSections(); store.set({ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index 28c62af..18a0dac 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -1,8 +1,8 @@ -import { getDNSCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; import { IDiagnosticsChecksItem } from '../../../../store'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; +import { PodkopShellMethods } from '../../../methods'; export async function runDnsCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; @@ -16,7 +16,7 @@ export async function runDnsCheck() { items: [], }); - const dnsChecks = await getDNSCheck(); + const dnsChecks = await PodkopShellMethods.checkDNSAvailable(); if (!dnsChecks.success) { updateDiagnosticsCheck({ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts index 4b3548e..d2b87e3 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -1,9 +1,8 @@ -import * as podkopMethods from '../../../methods'; -import * as fakeIPMethods from '../../../../fakeip/methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; import { IDiagnosticsChecksItem } from '../../../../store'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; +import { PodkopShellMethods, RemoteFakeIPMethods } from '../../../methods'; export async function runFakeIPCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; @@ -17,9 +16,9 @@ export async function runFakeIPCheck() { items: [], }); - const routerFakeIPResponse = await podkopMethods.getFakeIPCheck(); - const checkFakeIPResponse = await fakeIPMethods.getFakeIpCheck(); - const checkIPResponse = await fakeIPMethods.getIpCheck(); + const routerFakeIPResponse = await PodkopShellMethods.checkFakeIP(); + const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); + const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); console.log('runFakeIPCheck', { routerFakeIPResponse, diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index b7fa0c2..cc0a4c9 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -1,7 +1,7 @@ -import { getNftRulesCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; -import { getFakeIpCheck, getIpCheck } from '../../../../fakeip'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; +import { RemoteFakeIPMethods } from '../../../methods/fakeip'; +import { PodkopShellMethods } from '../../../methods'; export async function runNftCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; @@ -15,10 +15,10 @@ export async function runNftCheck() { items: [], }); - await getFakeIpCheck(); - await getIpCheck(); + await RemoteFakeIPMethods.getFakeIpCheck(); + await RemoteFakeIPMethods.getIpCheck(); - const nftablesChecks = await getNftRulesCheck(); + const nftablesChecks = await PodkopShellMethods.checkNftRules(); if (!nftablesChecks.success) { updateDiagnosticsCheck({ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts index 80a8bd1..037e905 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts @@ -1,6 +1,6 @@ -import { getSingBoxCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; +import { PodkopShellMethods } from '../../../methods'; export async function runSingBoxCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; @@ -14,7 +14,7 @@ export async function runSingBoxCheck() { items: [], }); - const singBoxChecks = await getSingBoxCheck(); + const singBoxChecks = await PodkopShellMethods.checkSingBox(); if (!singBoxChecks.success) { updateDiagnosticsCheck({ diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index df38f94..32c0447 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -1,5 +1,35 @@ +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ClashAPI { + export interface ProxyHistoryEntry { + time: string; + delay: number; + } + + export interface ProxyBase { + type: string; + name: string; + udp: boolean; + history: ProxyHistoryEntry[]; + now?: string; + all?: string[]; + } + + export interface Proxies { + proxies: Record; + } +} + // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Podkop { + export enum AvailableMethods { + CHECK_DNS_AVAILABLE = 'check_dns_available', + CHECK_FAKEIP = 'check_fakeip', + CHECK_NFT_RULES = 'check_nft_rules', + GET_STATUS = 'get_status', + CHECK_SING_BOX = 'check_sing_box', + GET_SING_BOX_STATUS = 'get_sing_box_status', + } + export interface Outbound { code: string; displayName: string; @@ -102,4 +132,15 @@ export namespace Podkop { fakeip: boolean; IP: string; } + + export interface GetStatus { + enabled: number; + status: string; + } + + export interface GetSingBoxStatus { + running: number; + enabled: number; + status: string; + } } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index d1cd702..ac258f0 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1003,7 +1003,7 @@ function validateProxyUrl(url) { }; } -// src/api/createBaseApiRequest.ts +// src/podkop/api.ts async function createBaseApiRequest(fetchFn, options) { const wrappedFn = () => options?.timeoutMs && options?.operationName ? withTimeout( fetchFn(), @@ -1032,62 +1032,8 @@ async function createBaseApiRequest(fetchFn, options) { } } -// src/clash/methods/getConfig.ts -async function getClashConfig() { - return createBaseApiRequest( - () => fetch(`${getClashApiUrl()}/configs`, { - method: "GET", - headers: { "Content-Type": "application/json" } - }) - ); -} - -// src/clash/methods/getGroupDelay.ts -async function getClashGroupDelay(group, url = "https://www.gstatic.com/generate_204", timeout = 2e3) { - const endpoint = `${getClashApiUrl()}/group/${group}/delay?url=${encodeURIComponent( - url - )}&timeout=${timeout}`; - return createBaseApiRequest( - () => fetch(endpoint, { - method: "GET", - headers: { "Content-Type": "application/json" } - }) - ); -} - -// src/clash/methods/getProxies.ts -async function getClashProxies() { - return createBaseApiRequest( - () => fetch(`${getClashApiUrl()}/proxies`, { - method: "GET", - headers: { "Content-Type": "application/json" } - }) - ); -} - -// src/clash/methods/getVersion.ts -async function getClashVersion() { - return createBaseApiRequest( - () => fetch(`${getClashApiUrl()}/version`, { - method: "GET", - headers: { "Content-Type": "application/json" } - }) - ); -} - -// src/clash/methods/triggerProxySelector.ts -async function triggerProxySelector(selector, outbound) { - return createBaseApiRequest( - () => fetch(`${getClashApiUrl()}/proxies/${selector}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name: outbound }) - }) - ); -} - -// src/clash/methods/triggerLatencyTest.ts -async function triggerLatencyGroupTest(tag, timeout = 5e3, url = "https://www.gstatic.com/generate_204") { +// src/podkop/methods/clash/getGroupLatency.ts +async function getGroupLatency(tag, timeout = 5e3, url = "https://www.gstatic.com/generate_204") { return createBaseApiRequest( () => fetch( `${getClashApiUrl()}/group/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, @@ -1098,7 +1044,19 @@ async function triggerLatencyGroupTest(tag, timeout = 5e3, url = "https://www.gs ) ); } -async function triggerLatencyProxyTest(tag, timeout = 2e3, url = "https://www.gstatic.com/generate_204") { + +// src/podkop/methods/clash/getProxies.ts +async function getProxies() { + return createBaseApiRequest( + () => fetch(`${getClashApiUrl()}/proxies`, { + method: "GET", + headers: { "Content-Type": "application/json" } + }) + ); +} + +// src/podkop/methods/clash/getProxyLatency.ts +async function getProxyLatency(tag, timeout = 2e3, url = "https://www.gstatic.com/generate_204") { return createBaseApiRequest( () => fetch( `${getClashApiUrl()}/proxies/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, @@ -1110,15 +1068,34 @@ async function triggerLatencyProxyTest(tag, timeout = 2e3, url = "https://www.gs ); } -// src/podkop/methods/getConfigSections.ts +// src/podkop/methods/clash/setProxy.ts +async function setProxy(selector, outbound) { + return createBaseApiRequest( + () => fetch(`${getClashApiUrl()}/proxies/${selector}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: outbound }) + }) + ); +} + +// src/podkop/methods/clash/index.ts +var ClashMethods = { + getGroupLatency, + getProxies, + getProxyLatency, + setProxy +}; + +// src/podkop/methods/custom/getConfigSections.ts async function getConfigSections() { return uci.load("podkop").then(() => uci.sections("podkop")); } -// src/podkop/methods/getDashboardSections.ts +// src/podkop/methods/custom/getDashboardSections.ts async function getDashboardSections() { const configSections = await getConfigSections(); - const clashProxies = await getClashProxies(); + const clashProxies = await ClashMethods.getProxies(); if (!clashProxies.success) { return { success: false, @@ -1241,37 +1218,51 @@ async function getDashboardSections() { }; } -// src/podkop/methods/getPodkopStatus.ts -async function getPodkopStatus() { - const response = await executeShellCommand({ - command: "/usr/bin/podkop", - args: ["get_status"], - timeout: 1e4 - }); - if (response.stdout) { - return JSON.parse(response.stdout.replace(/\n/g, "")); - } - return { enabled: 0, status: "unknown" }; +// src/podkop/methods/custom/index.ts +var CustomPodkopMethods = { + getConfigSections, + getDashboardSections +}; + +// src/podkop/methods/fakeip/getFakeIpCheck.ts +async function getFakeIpCheck() { + return createBaseApiRequest( + () => fetch(`https://${FAKEIP_CHECK_DOMAIN}/check`, { + method: "GET", + headers: { "Content-Type": "application/json" } + }), + { + operationName: "getFakeIpCheck", + timeoutMs: 5e3 + } + ); } -// src/podkop/methods/getSingBoxStatus.ts -async function getSingBoxStatus() { - const response = await executeShellCommand({ - command: "/usr/bin/podkop", - args: ["get_sing_box_status"], - timeout: 1e4 - }); - if (response.stdout) { - return JSON.parse(response.stdout.replace(/\n/g, "")); - } - return { running: 0, enabled: 0, status: "unknown" }; +// src/podkop/methods/fakeip/getIpCheck.ts +async function getIpCheck() { + return createBaseApiRequest( + () => fetch(`https://${IP_CHECK_DOMAIN}/check`, { + method: "GET", + headers: { "Content-Type": "application/json" } + }), + { + operationName: "getIpCheck", + timeoutMs: 5e3 + } + ); } -// src/podkop/methods/getDNSCheck.ts -async function getDNSCheck() { +// src/podkop/methods/fakeip/index.ts +var RemoteFakeIPMethods = { + getFakeIpCheck, + getIpCheck +}; + +// src/podkop/methods/shell/callBaseMethod.ts +async function callBaseMethod(method) { const response = await executeShellCommand({ command: "/usr/bin/podkop", - args: ["check_dns_available"], + args: [method], timeout: 1e4 }); if (response.stdout) { @@ -1286,62 +1277,39 @@ async function getDNSCheck() { }; } -// src/podkop/methods/getNftRulesCheck.ts -async function getNftRulesCheck() { - const response = await executeShellCommand({ - command: "/usr/bin/podkop", - args: ["check_nft_rules"], - timeout: 1e4 - }); - if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) - }; - } - return { - success: false, - error: "" - }; -} +// src/podkop/types.ts +var Podkop; +((Podkop2) => { + let AvailableMethods; + ((AvailableMethods2) => { + AvailableMethods2["CHECK_DNS_AVAILABLE"] = "check_dns_available"; + AvailableMethods2["CHECK_FAKEIP"] = "check_fakeip"; + AvailableMethods2["CHECK_NFT_RULES"] = "check_nft_rules"; + AvailableMethods2["GET_STATUS"] = "get_status"; + AvailableMethods2["CHECK_SING_BOX"] = "check_sing_box"; + AvailableMethods2["GET_SING_BOX_STATUS"] = "get_sing_box_status"; + })(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})); +})(Podkop || (Podkop = {})); -// src/podkop/methods/getSingBoxCheck.ts -async function getSingBoxCheck() { - const response = await executeShellCommand({ - command: "/usr/bin/podkop", - args: ["check_sing_box"], - timeout: 1e4 - }); - if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) - }; - } - return { - success: false, - error: "" - }; -} - -// src/podkop/methods/getFakeIPCheck.ts -async function getFakeIPCheck() { - const response = await executeShellCommand({ - command: "/usr/bin/podkop", - args: ["check_fakeip"], - timeout: 1e4 - }); - if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) - }; - } - return { - success: false, - error: "" - }; -} +// src/podkop/methods/shell/index.ts +var PodkopShellMethods = { + checkDNSAvailable: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_DNS_AVAILABLE + ), + checkFakeIP: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_FAKEIP + ), + checkNftRules: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_NFT_RULES + ), + getStatus: async () => callBaseMethod(Podkop.AvailableMethods.GET_STATUS), + checkSingBox: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_SING_BOX + ), + getSingBoxStatus: async () => callBaseMethod( + Podkop.AvailableMethods.GET_SING_BOX_STATUS + ) +}; // src/podkop/services/tab.service.ts var TabService = class _TabService { @@ -1994,7 +1962,7 @@ async function fetchDashboardSections() { failed: false } }); - const { data, success } = await getDashboardSections(); + const { data, success } = await CustomPodkopMethods.getDashboardSections(); if (!success) { console.log("[fetchDashboardSections]: failed to fetch", getClashApiUrl()); } @@ -2008,20 +1976,11 @@ async function fetchDashboardSections() { }); } async function fetchServicesInfo() { - try { - const [podkop, singbox] = await Promise.all([ - getPodkopStatus(), - getSingBoxStatus() - ]); - store.set({ - servicesInfoWidget: { - loading: false, - failed: false, - data: { singbox: singbox.running, podkop: podkop.enabled } - } - }); - } catch (err) { - console.log("[fetchServicesInfo]: failed to fetchServices", err); + const [podkop, singbox] = await Promise.all([ + PodkopShellMethods.getStatus(), + PodkopShellMethods.getSingBoxStatus() + ]); + if (!podkop.success || !singbox.success) { store.set({ servicesInfoWidget: { loading: false, @@ -2030,6 +1989,15 @@ async function fetchServicesInfo() { } }); } + if (podkop.success && singbox.success) { + store.set({ + servicesInfoWidget: { + loading: false, + failed: false, + data: { singbox: singbox.data.running, podkop: podkop.data.enabled } + } + }); + } } async function connectToClashSockets() { socket.subscribe( @@ -2105,7 +2073,7 @@ async function connectToClashSockets() { ); } async function handleChooseOutbound(selector, tag) { - await triggerProxySelector(selector, tag); + await ClashMethods.setProxy(selector, tag); await fetchDashboardSections(); } async function handleTestGroupLatency(tag) { @@ -2115,7 +2083,7 @@ async function handleTestGroupLatency(tag) { latencyFetching: true } }); - await triggerLatencyGroupTest(tag); + await ClashMethods.getGroupLatency(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { @@ -2131,7 +2099,7 @@ async function handleTestProxyLatency(tag) { latencyFetching: true } }); - await triggerLatencyProxyTest(tag); + await ClashMethods.getProxyLatency(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { @@ -2772,7 +2740,7 @@ async function runDnsCheck() { state: "loading", items: [] }); - const dnsChecks = await getDNSCheck(); + const dnsChecks = await PodkopShellMethods.checkDNSAvailable(); if (!dnsChecks.success) { updateDiagnosticsCheck({ order, @@ -2842,7 +2810,7 @@ async function runSingBoxCheck() { state: "loading", items: [] }); - const singBoxChecks = await getSingBoxCheck(); + const singBoxChecks = await PodkopShellMethods.checkSingBox(); if (!singBoxChecks.success) { updateDiagnosticsCheck({ order, @@ -2911,34 +2879,6 @@ async function runSingBoxCheck() { } } -// src/fakeip/methods/getIpCheck.ts -async function getIpCheck() { - return createBaseApiRequest( - () => fetch(`https://${IP_CHECK_DOMAIN}/check`, { - method: "GET", - headers: { "Content-Type": "application/json" } - }), - { - operationName: "getIpCheck", - timeoutMs: 5e3 - } - ); -} - -// src/fakeip/methods/getFakeIpCheck.ts -async function getFakeIpCheck() { - return createBaseApiRequest( - () => fetch(`https://${FAKEIP_CHECK_DOMAIN}/check`, { - method: "GET", - headers: { "Content-Type": "application/json" } - }), - { - operationName: "getFakeIpCheck", - timeoutMs: 5e3 - } - ); -} - // src/podkop/tabs/diagnostic/checks/runNftCheck.ts async function runNftCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; @@ -2950,9 +2890,9 @@ async function runNftCheck() { state: "loading", items: [] }); - await getFakeIpCheck(); - await getIpCheck(); - const nftablesChecks = await getNftRulesCheck(); + await RemoteFakeIPMethods.getFakeIpCheck(); + await RemoteFakeIPMethods.getIpCheck(); + const nftablesChecks = await PodkopShellMethods.checkNftRules(); if (!nftablesChecks.success) { updateDiagnosticsCheck({ order, @@ -3042,9 +2982,9 @@ async function runFakeIPCheck() { state: "loading", items: [] }); - const routerFakeIPResponse = await getFakeIPCheck(); - const checkFakeIPResponse = await getFakeIpCheck(); - const checkIPResponse = await getIpCheck(); + const routerFakeIPResponse = await PodkopShellMethods.checkFakeIP(); + const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); + const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); console.log("runFakeIPCheck", { routerFakeIPResponse, checkFakeIPResponse, @@ -3254,6 +3194,8 @@ return baseclass.extend({ CACHE_TIMEOUT, COMMAND_SCHEDULING, COMMAND_TIMEOUT, + ClashMethods, + CustomPodkopMethods, DIAGNOSTICS_INITIAL_DELAY, DIAGNOSTICS_UPDATE_INTERVAL, DNS_SERVER_OPTIONS, @@ -3263,7 +3205,9 @@ return baseclass.extend({ FETCH_TIMEOUT, IP_CHECK_DOMAIN, PODKOP_LUCI_APP_VERSION, + PodkopShellMethods, REGIONAL_OPTIONS, + RemoteFakeIPMethods, STATUS_COLORS, TabService, TabServiceInstance, @@ -3273,21 +3217,9 @@ return baseclass.extend({ executeShellCommand, getBaseUrl, getClashApiUrl, - getClashConfig, - getClashGroupDelay, - getClashProxies, getClashUIUrl, - getClashVersion, getClashWsUrl, - getConfigSections, - getDNSCheck, - getDashboardSections, - getFakeIPCheck, - getNftRulesCheck, - getPodkopStatus, getProxyUrlName, - getSingBoxCheck, - getSingBoxStatus, initDashboardController, initDiagnosticController, injectGlobalStyles, @@ -3302,9 +3234,6 @@ return baseclass.extend({ renderDiagnostic, splitProxyString, svgEl, - triggerLatencyGroupTest, - triggerLatencyProxyTest, - triggerProxySelector, validateDNS, validateDomain, validateIPV4, From 33dfb8c3f04f7f2dd7ad764afa6425d8d6240359 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 21:32:06 +0300 Subject: [PATCH 054/121] refactor: reorganize services --- .../src/podkop/services/core.service.ts | 2 +- fe-app-podkop/src/podkop/services/index.ts | 2 + .../services/socket.service.ts} | 0 .../services/store.service.ts} | 8 +- .../tabs/dashboard/initDashboardController.ts | 3 +- .../tabs/diagnostic/checks/runDnsCheck.ts | 2 +- .../tabs/diagnostic/checks/runFakeIPCheck.ts | 2 +- .../tabs/diagnostic/checks/runNftCheck.ts | 3 +- .../tabs/diagnostic/diagnostic.store.ts | 2 +- .../diagnostic/initDiagnosticController.ts | 2 +- .../tabs/diagnostic/renderCheckSection.ts | 2 +- .../tabs/diagnostic/updateDiagnosticsCheck.ts | 2 +- .../luci-static/resources/view/podkop/main.js | 212 +++++++++--------- 13 files changed, 122 insertions(+), 120 deletions(-) rename fe-app-podkop/src/{socket.ts => podkop/services/socket.service.ts} (100%) rename fe-app-podkop/src/{store.ts => podkop/services/store.service.ts} (95%) diff --git a/fe-app-podkop/src/podkop/services/core.service.ts b/fe-app-podkop/src/podkop/services/core.service.ts index 4b7d827..ac9df68 100644 --- a/fe-app-podkop/src/podkop/services/core.service.ts +++ b/fe-app-podkop/src/podkop/services/core.service.ts @@ -1,5 +1,5 @@ import { TabServiceInstance } from './tab.service'; -import { store } from '../../store'; +import { store } from './store.service'; export function coreService() { TabServiceInstance.onChange((activeId, tabs) => { diff --git a/fe-app-podkop/src/podkop/services/index.ts b/fe-app-podkop/src/podkop/services/index.ts index 4b776d2..d6477f7 100644 --- a/fe-app-podkop/src/podkop/services/index.ts +++ b/fe-app-podkop/src/podkop/services/index.ts @@ -1,2 +1,4 @@ export * from './tab.service'; export * from './core.service'; +export * from './socket.service'; +export * from './store.service'; diff --git a/fe-app-podkop/src/socket.ts b/fe-app-podkop/src/podkop/services/socket.service.ts similarity index 100% rename from fe-app-podkop/src/socket.ts rename to fe-app-podkop/src/podkop/services/socket.service.ts diff --git a/fe-app-podkop/src/store.ts b/fe-app-podkop/src/podkop/services/store.service.ts similarity index 95% rename from fe-app-podkop/src/store.ts rename to fe-app-podkop/src/podkop/services/store.service.ts index 3bd35a9..5093ba7 100644 --- a/fe-app-podkop/src/store.ts +++ b/fe-app-podkop/src/podkop/services/store.service.ts @@ -1,5 +1,5 @@ -import { Podkop } from './podkop/types'; -import { initialDiagnosticStore } from './podkop/tabs/diagnostic/diagnostic.store'; +import { Podkop } from '../types'; +import { initialDiagnosticStore } from '../tabs/diagnostic/diagnostic.store'; function jsonStableStringify(obj: T): string { return JSON.stringify(obj, (_, value) => { @@ -29,7 +29,7 @@ function jsonEqual(a: A, b: B): boolean { type Listener = (next: T, prev: T, diff: Partial) => void; // eslint-disable-next-line -class Store> { +class StoreService> { private value: T; private readonly initial: T; private listeners = new Set>(); @@ -207,4 +207,4 @@ const initialStore: StoreType = { ...initialDiagnosticStore, }; -export const store = new Store(initialStore); +export const store = new StoreService(initialStore); diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts index d9be183..6915ba4 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts @@ -4,8 +4,6 @@ import { onMount, preserveScrollForPage, } from '../../../helpers'; -import { store, StoreType } from '../../../store'; -import { socket } from '../../../socket'; import { prettyBytes } from '../../../helpers/prettyBytes'; import { renderSections } from './renderSections'; import { renderWidget } from './renderWidget'; @@ -14,6 +12,7 @@ import { CustomPodkopMethods, PodkopShellMethods, } from '../../methods'; +import { socket, store, StoreType } from '../../services'; // Fetchers diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index 18a0dac..f33d11c 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -1,8 +1,8 @@ import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; -import { IDiagnosticsChecksItem } from '../../../../store'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { PodkopShellMethods } from '../../../methods'; +import { IDiagnosticsChecksItem } from '../../../services'; export async function runDnsCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts index d2b87e3..5983a84 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -1,8 +1,8 @@ import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; -import { IDiagnosticsChecksItem } from '../../../../store'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { PodkopShellMethods, RemoteFakeIPMethods } from '../../../methods'; +import { IDiagnosticsChecksItem } from '../../../services'; export async function runFakeIPCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index cc0a4c9..0fc8d87 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -1,7 +1,6 @@ import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; -import { RemoteFakeIPMethods } from '../../../methods/fakeip'; -import { PodkopShellMethods } from '../../../methods'; +import { RemoteFakeIPMethods, PodkopShellMethods } from '../../../methods'; export async function runNftCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts index a39c141..b34bbc0 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts @@ -2,7 +2,7 @@ import { DIAGNOSTICS_CHECKS, DIAGNOSTICS_CHECKS_MAP, } from './checks/contstants'; -import { StoreType } from '../../../store'; +import { StoreType } from '../../services'; export const initialDiagnosticStore: Pick< StoreType, diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts index a8f43df..94ea952 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts @@ -1,5 +1,4 @@ import { onMount, preserveScrollForPage } from '../../../helpers'; -import { store, StoreType } from '../../../store'; import { renderCheckSection } from './renderCheckSection'; import { runDnsCheck } from './checks/runDnsCheck'; import { runSingBoxCheck } from './checks/runSingBoxCheck'; @@ -9,6 +8,7 @@ import { renderDiagnosticRunAction } from './renderDiagnosticRunAction'; import { renderAvailableActions } from './renderAvailableActions'; import { renderSystemInfo } from './renderSystemInfo'; import { loadingDiagnosticsChecksStore } from './diagnostic.store'; +import { store, StoreType } from '../../services'; function renderDiagnosticsChecks() { console.log('renderDiagnosticsChecks'); diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts index e7d8098..5f83387 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts @@ -8,7 +8,7 @@ import { renderTriangleAlertIcon24, renderXIcon24, } from '../../../icons'; -import { IDiagnosticsChecksStoreItem } from '../../../store'; +import { IDiagnosticsChecksStoreItem } from '../../services'; type IRenderCheckSectionProps = IDiagnosticsChecksStoreItem; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts index a36697f..62e75f2 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts @@ -1,4 +1,4 @@ -import { IDiagnosticsChecksStoreItem, store } from '../../../store'; +import { IDiagnosticsChecksStoreItem, store } from '../../services'; export function updateDiagnosticsCheck( check: IDiagnosticsChecksStoreItem, diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index ac258f0..61b965f 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1477,7 +1477,7 @@ var loadingDiagnosticsChecksStore = { ] }; -// src/store.ts +// src/podkop/services/store.service.ts function jsonStableStringify(obj) { return JSON.stringify(obj, (_2, value) => { if (value && typeof value === "object" && !Array.isArray(value)) { @@ -1499,7 +1499,7 @@ function jsonEqual(a, b) { return false; } } -var Store = class { +var StoreService = class { constructor(initial) { this.listeners = /* @__PURE__ */ new Set(); this.lastHash = ""; @@ -1600,7 +1600,7 @@ var initialStore = { }, ...initialDiagnosticStore }; -var store = new Store(initialStore); +var store = new StoreService(initialStore); // src/podkop/services/core.service.ts function coreService() { @@ -1614,6 +1614,108 @@ function coreService() { }); } +// src/podkop/services/socket.service.ts +var SocketManager = class _SocketManager { + constructor() { + this.sockets = /* @__PURE__ */ new Map(); + this.listeners = /* @__PURE__ */ new Map(); + this.connected = /* @__PURE__ */ new Map(); + this.errorListeners = /* @__PURE__ */ new Map(); + } + static getInstance() { + if (!_SocketManager.instance) { + _SocketManager.instance = new _SocketManager(); + } + return _SocketManager.instance; + } + connect(url) { + if (this.sockets.has(url)) return; + const ws = new WebSocket(url); + this.sockets.set(url, ws); + this.connected.set(url, false); + this.listeners.set(url, /* @__PURE__ */ new Set()); + this.errorListeners.set(url, /* @__PURE__ */ new Set()); + ws.addEventListener("open", () => { + this.connected.set(url, true); + console.info(`Connected: ${url}`); + }); + ws.addEventListener("message", (event) => { + const handlers = this.listeners.get(url); + if (handlers) { + for (const handler of handlers) { + try { + handler(event.data); + } catch (err) { + console.error(`Handler error for ${url}:`, err); + } + } + } + }); + ws.addEventListener("close", () => { + this.connected.set(url, false); + console.warn(`Disconnected: ${url}`); + this.triggerError(url, "Connection closed"); + }); + ws.addEventListener("error", (err) => { + console.error(`Socket error for ${url}:`, err); + this.triggerError(url, err); + }); + } + subscribe(url, listener, onError) { + if (!this.sockets.has(url)) { + this.connect(url); + } + this.listeners.get(url)?.add(listener); + if (onError) { + this.errorListeners.get(url)?.add(onError); + } + } + unsubscribe(url, listener, onError) { + this.listeners.get(url)?.delete(listener); + if (onError) { + this.errorListeners.get(url)?.delete(onError); + } + } + // eslint-disable-next-line + send(url, data) { + const ws = this.sockets.get(url); + if (ws && this.connected.get(url)) { + ws.send(typeof data === "string" ? data : JSON.stringify(data)); + } else { + console.warn(`Cannot send: not connected to ${url}`); + this.triggerError(url, "Not connected"); + } + } + disconnect(url) { + const ws = this.sockets.get(url); + if (ws) { + ws.close(); + this.sockets.delete(url); + this.listeners.delete(url); + this.errorListeners.delete(url); + this.connected.delete(url); + } + } + disconnectAll() { + for (const url of this.sockets.keys()) { + this.disconnect(url); + } + } + triggerError(url, err) { + const handlers = this.errorListeners.get(url); + if (handlers) { + for (const cb of handlers) { + try { + cb(err); + } catch (e) { + console.error(`Error handler threw for ${url}:`, e); + } + } + } + } +}; +var socket = SocketManager.getInstance(); + // src/podkop/tabs/dashboard/renderSections.ts function renderFailedState() { return E( @@ -1839,108 +1941,6 @@ function renderDashboard() { ); } -// src/socket.ts -var SocketManager = class _SocketManager { - constructor() { - this.sockets = /* @__PURE__ */ new Map(); - this.listeners = /* @__PURE__ */ new Map(); - this.connected = /* @__PURE__ */ new Map(); - this.errorListeners = /* @__PURE__ */ new Map(); - } - static getInstance() { - if (!_SocketManager.instance) { - _SocketManager.instance = new _SocketManager(); - } - return _SocketManager.instance; - } - connect(url) { - if (this.sockets.has(url)) return; - const ws = new WebSocket(url); - this.sockets.set(url, ws); - this.connected.set(url, false); - this.listeners.set(url, /* @__PURE__ */ new Set()); - this.errorListeners.set(url, /* @__PURE__ */ new Set()); - ws.addEventListener("open", () => { - this.connected.set(url, true); - console.info(`Connected: ${url}`); - }); - ws.addEventListener("message", (event) => { - const handlers = this.listeners.get(url); - if (handlers) { - for (const handler of handlers) { - try { - handler(event.data); - } catch (err) { - console.error(`Handler error for ${url}:`, err); - } - } - } - }); - ws.addEventListener("close", () => { - this.connected.set(url, false); - console.warn(`Disconnected: ${url}`); - this.triggerError(url, "Connection closed"); - }); - ws.addEventListener("error", (err) => { - console.error(`Socket error for ${url}:`, err); - this.triggerError(url, err); - }); - } - subscribe(url, listener, onError) { - if (!this.sockets.has(url)) { - this.connect(url); - } - this.listeners.get(url)?.add(listener); - if (onError) { - this.errorListeners.get(url)?.add(onError); - } - } - unsubscribe(url, listener, onError) { - this.listeners.get(url)?.delete(listener); - if (onError) { - this.errorListeners.get(url)?.delete(onError); - } - } - // eslint-disable-next-line - send(url, data) { - const ws = this.sockets.get(url); - if (ws && this.connected.get(url)) { - ws.send(typeof data === "string" ? data : JSON.stringify(data)); - } else { - console.warn(`Cannot send: not connected to ${url}`); - this.triggerError(url, "Not connected"); - } - } - disconnect(url) { - const ws = this.sockets.get(url); - if (ws) { - ws.close(); - this.sockets.delete(url); - this.listeners.delete(url); - this.errorListeners.delete(url); - this.connected.delete(url); - } - } - disconnectAll() { - for (const url of this.sockets.keys()) { - this.disconnect(url); - } - } - triggerError(url, err) { - const handlers = this.errorListeners.get(url); - if (handlers) { - for (const cb of handlers) { - try { - cb(err); - } catch (e) { - console.error(`Error handler threw for ${url}:`, e); - } - } - } - } -}; -var socket = SocketManager.getInstance(); - // src/helpers/prettyBytes.ts function prettyBytes(n) { const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; @@ -3232,7 +3232,9 @@ return baseclass.extend({ preserveScrollForPage, renderDashboard, renderDiagnostic, + socket, splitProxyString, + store, svgEl, validateDNS, validateDomain, From 67ec5f30903a2d823cb38d18d55a3c4115d0b726 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 21:49:09 +0300 Subject: [PATCH 055/121] refactor: unify dynamic page structure --- .../src/podkop/tabs/dashboard/index.ts | 9 +- ...shboardController.ts => initController.ts} | 5 +- .../podkop/tabs/dashboard/partials/index.ts | 2 + .../{ => partials}/renderSections.ts | 4 +- .../dashboard/{ => partials}/renderWidget.ts | 0 .../{renderDashboard.ts => render.ts} | 5 +- .../tabs/diagnostic/checks/runDnsCheck.ts | 8 +- .../tabs/diagnostic/checks/runFakeIPCheck.ts | 6 +- .../tabs/diagnostic/checks/runNftCheck.ts | 8 +- .../tabs/diagnostic/checks/runSingBoxCheck.ts | 8 +- .../updateCheckStore.ts} | 4 +- .../src/podkop/tabs/diagnostic/index.ts | 9 +- ...gnosticController.ts => initController.ts} | 14 +- .../podkop/tabs/diagnostic/partials/index.ts | 4 + .../{ => partials}/renderAvailableActions.ts | 0 .../{ => partials}/renderCheckSection.ts | 4 +- .../renderRunAction.ts} | 2 +- .../{ => partials}/renderSystemInfo.ts | 0 .../tabs/diagnostic/renderDiagnostic.ts | 2 +- .../resources/view/podkop/dashboard.js | 4 +- .../resources/view/podkop/diagnostic.js | 4 +- .../luci-static/resources/view/podkop/main.js | 732 +++++++++--------- 22 files changed, 430 insertions(+), 404 deletions(-) rename fe-app-podkop/src/podkop/tabs/dashboard/{initDashboardController.ts => initController.ts} (98%) create mode 100644 fe-app-podkop/src/podkop/tabs/dashboard/partials/index.ts rename fe-app-podkop/src/podkop/tabs/dashboard/{ => partials}/renderSections.ts (97%) rename fe-app-podkop/src/podkop/tabs/dashboard/{ => partials}/renderWidget.ts (100%) rename fe-app-podkop/src/podkop/tabs/dashboard/{renderDashboard.ts => render.ts} (90%) rename fe-app-podkop/src/podkop/tabs/diagnostic/{updateDiagnosticsCheck.ts => checks/updateCheckStore.ts} (81%) rename fe-app-podkop/src/podkop/tabs/diagnostic/{initDiagnosticController.ts => initController.ts} (90%) create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/partials/index.ts rename fe-app-podkop/src/podkop/tabs/diagnostic/{ => partials}/renderAvailableActions.ts (100%) rename fe-app-podkop/src/podkop/tabs/diagnostic/{ => partials}/renderCheckSection.ts (98%) rename fe-app-podkop/src/podkop/tabs/diagnostic/{renderDiagnosticRunAction.ts => partials/renderRunAction.ts} (89%) rename fe-app-podkop/src/podkop/tabs/diagnostic/{ => partials}/renderSystemInfo.ts (100%) diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/index.ts b/fe-app-podkop/src/podkop/tabs/dashboard/index.ts index 898949a..105b142 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/index.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/index.ts @@ -1,2 +1,7 @@ -export * from './renderDashboard'; -export * from './initDashboardController'; +import { render } from './render'; +import { initController } from './initController'; + +export const DashboardTab = { + render, + initController, +}; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts similarity index 98% rename from fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts rename to fe-app-podkop/src/podkop/tabs/dashboard/initController.ts index 6915ba4..d10eb11 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts @@ -5,14 +5,13 @@ import { preserveScrollForPage, } from '../../../helpers'; import { prettyBytes } from '../../../helpers/prettyBytes'; -import { renderSections } from './renderSections'; -import { renderWidget } from './renderWidget'; import { ClashMethods, CustomPodkopMethods, PodkopShellMethods, } from '../../methods'; import { socket, store, StoreType } from '../../services'; +import { renderSections, renderWidget } from './partials'; // Fetchers @@ -420,7 +419,7 @@ async function onStoreUpdate( } } -export async function initDashboardController(): Promise { +export async function initController(): Promise { onMount('dashboard-status').then(() => { // Remove old listener store.unsubscribe(onStoreUpdate); diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/partials/index.ts b/fe-app-podkop/src/podkop/tabs/dashboard/partials/index.ts new file mode 100644 index 0000000..f797e31 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/dashboard/partials/index.ts @@ -0,0 +1,2 @@ +export * from './renderSections'; +export * from './renderWidget'; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/renderSections.ts b/fe-app-podkop/src/podkop/tabs/dashboard/partials/renderSections.ts similarity index 97% rename from fe-app-podkop/src/podkop/tabs/dashboard/renderSections.ts rename to fe-app-podkop/src/podkop/tabs/dashboard/partials/renderSections.ts index b8b77b0..2389379 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/renderSections.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/partials/renderSections.ts @@ -1,5 +1,5 @@ -import { Podkop } from '../../types'; -import { getClashApiUrl } from '../../../helpers'; +import { Podkop } from '../../../types'; +import { getClashApiUrl } from '../../../../helpers'; interface IRenderSectionsProps { loading: boolean; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/renderWidget.ts b/fe-app-podkop/src/podkop/tabs/dashboard/partials/renderWidget.ts similarity index 100% rename from fe-app-podkop/src/podkop/tabs/dashboard/renderWidget.ts rename to fe-app-podkop/src/podkop/tabs/dashboard/partials/renderWidget.ts diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/renderDashboard.ts b/fe-app-podkop/src/podkop/tabs/dashboard/render.ts similarity index 90% rename from fe-app-podkop/src/podkop/tabs/dashboard/renderDashboard.ts rename to fe-app-podkop/src/podkop/tabs/dashboard/render.ts index c647cc6..1417740 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/renderDashboard.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/render.ts @@ -1,7 +1,6 @@ -import { renderSections } from './renderSections'; -import { renderWidget } from './renderWidget'; +import { renderSections, renderWidget } from './partials'; -export function renderDashboard() { +export function render() { return E( 'div', { diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index f33d11c..67d7c8b 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -1,13 +1,13 @@ -import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { PodkopShellMethods } from '../../../methods'; import { IDiagnosticsChecksItem } from '../../../services'; +import { updateCheckStore } from './updateCheckStore'; export async function runDnsCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, @@ -19,7 +19,7 @@ export async function runDnsCheck() { const dnsChecks = await PodkopShellMethods.checkDNSAvailable(); if (!dnsChecks.success) { - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, @@ -57,7 +57,7 @@ export async function runDnsCheck() { return 'error'; } - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts index 5983a84..dbb7169 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -1,13 +1,13 @@ -import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { PodkopShellMethods, RemoteFakeIPMethods } from '../../../methods'; import { IDiagnosticsChecksItem } from '../../../services'; +import { updateCheckStore } from './updateCheckStore'; export async function runFakeIPCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, @@ -68,7 +68,7 @@ export async function runFakeIPCheck() { const { state, description } = getMeta(); - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index 0fc8d87..1d46031 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -1,11 +1,11 @@ -import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { RemoteFakeIPMethods, PodkopShellMethods } from '../../../methods'; +import { updateCheckStore } from './updateCheckStore'; export async function runNftCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, @@ -20,7 +20,7 @@ export async function runNftCheck() { const nftablesChecks = await PodkopShellMethods.checkNftRules(); if (!nftablesChecks.success) { - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, @@ -68,7 +68,7 @@ export async function runNftCheck() { return 'error'; } - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts index 037e905..deb18fe 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts @@ -1,11 +1,11 @@ -import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; import { PodkopShellMethods } from '../../../methods'; +import { updateCheckStore } from './updateCheckStore'; export async function runSingBoxCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, @@ -17,7 +17,7 @@ export async function runSingBoxCheck() { const singBoxChecks = await PodkopShellMethods.checkSingBox(); if (!singBoxChecks.success) { - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, @@ -61,7 +61,7 @@ export async function runSingBoxCheck() { return 'error'; } - updateDiagnosticsCheck({ + updateCheckStore({ order, code, title, diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/updateCheckStore.ts similarity index 81% rename from fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts rename to fe-app-podkop/src/podkop/tabs/diagnostic/checks/updateCheckStore.ts index 62e75f2..85583b8 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/updateCheckStore.ts @@ -1,6 +1,6 @@ -import { IDiagnosticsChecksStoreItem, store } from '../../services'; +import { IDiagnosticsChecksStoreItem, store } from '../../../services'; -export function updateDiagnosticsCheck( +export function updateCheckStore( check: IDiagnosticsChecksStoreItem, minified?: boolean, ) { diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts index 01db02e..ce38706 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts @@ -1,2 +1,7 @@ -export * from './renderDiagnostic'; -export * from './initDiagnosticController'; +import { render } from './renderDiagnostic'; +import { initController } from './initController'; + +export const DiagnosticTab = { + render, + initController, +}; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts similarity index 90% rename from fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts rename to fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index 94ea952..16e8cd9 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -1,14 +1,16 @@ import { onMount, preserveScrollForPage } from '../../../helpers'; -import { renderCheckSection } from './renderCheckSection'; import { runDnsCheck } from './checks/runDnsCheck'; import { runSingBoxCheck } from './checks/runSingBoxCheck'; import { runNftCheck } from './checks/runNftCheck'; import { runFakeIPCheck } from './checks/runFakeIPCheck'; -import { renderDiagnosticRunAction } from './renderDiagnosticRunAction'; -import { renderAvailableActions } from './renderAvailableActions'; -import { renderSystemInfo } from './renderSystemInfo'; import { loadingDiagnosticsChecksStore } from './diagnostic.store'; import { store, StoreType } from '../../services'; +import { + renderAvailableActions, + renderCheckSection, + renderRunAction, + renderSystemInfo, +} from './partials'; function renderDiagnosticsChecks() { console.log('renderDiagnosticsChecks'); @@ -32,7 +34,7 @@ function renderDiagnosticRunActionWidget() { const { loading } = store.get().diagnosticsRunAction; const container = document.getElementById('pdk_diagnostic-page-run-check'); - const renderedAction = renderDiagnosticRunAction({ + const renderedAction = renderRunAction({ loading, click: () => runChecks(), }); @@ -124,7 +126,7 @@ async function runChecks() { } } -export async function initDiagnosticController(): Promise { +export async function initController(): Promise { onMount('diagnostic-status').then(() => { console.log('diagnostic controller initialized.'); // Remove old listener diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/index.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/index.ts new file mode 100644 index 0000000..517093a --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/index.ts @@ -0,0 +1,4 @@ +export * from './renderAvailableActions'; +export * from './renderCheckSection'; +export * from './renderRunAction'; +export * from './renderSystemInfo'; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts similarity index 100% rename from fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts rename to fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderCheckSection.ts similarity index 98% rename from fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts rename to fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderCheckSection.ts index 5f83387..c003ac5 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderCheckSection.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderCheckSection.ts @@ -7,8 +7,8 @@ import { renderLoaderCircleIcon24, renderTriangleAlertIcon24, renderXIcon24, -} from '../../../icons'; -import { IDiagnosticsChecksStoreItem } from '../../services'; +} from '../../../../icons'; +import { IDiagnosticsChecksStoreItem } from '../../../services'; type IRenderCheckSectionProps = IDiagnosticsChecksStoreItem; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts similarity index 89% rename from fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts rename to fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts index f26af81..cada852 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts @@ -3,7 +3,7 @@ interface IRenderDiagnosticRunActionProps { click: () => void; } -export function renderDiagnosticRunAction({ +export function renderRunAction({ loading, click, }: IRenderDiagnosticRunActionProps) { diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderSystemInfo.ts similarity index 100% rename from fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts rename to fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderSystemInfo.ts diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts index b4a5766..2b98d67 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts @@ -1,4 +1,4 @@ -export function renderDiagnostic() { +export function render() { return E('div', { id: 'diagnostic-status', class: 'pdk_diagnostic-page' }, [ E('div', { class: 'pdk_diagnostic-page__left-bar' }, [ E('div', { id: 'pdk_diagnostic-page-run-check' }), 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 423b47f..be74520 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 @@ -10,8 +10,8 @@ function createDashboardContent(section) { const o = section.option(form.DummyValue, '_mount_node'); o.rawhtml = true; o.cfgvalue = () => { - main.initDashboardController(); - return main.renderDashboard(); + main.DashboardTab.initController(); + return main.DashboardTab.render(); }; } 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 e94cae0..f2cf0df 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 @@ -10,8 +10,8 @@ function createDiagnosticContent(section) { const o = section.option(form.DummyValue, '_mount_node'); o.rawhtml = true; o.cfgvalue = () => { - main.initDiagnosticController(); - return main.renderDiagnostic(); + main.DiagnosticTab.initController(); + return main.DiagnosticTab.render(); }; } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 61b965f..7ef6f9a 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1716,7 +1716,7 @@ var SocketManager = class _SocketManager { }; var socket = SocketManager.getInstance(); -// src/podkop/tabs/dashboard/renderSections.ts +// src/podkop/tabs/dashboard/partials/renderSections.ts function renderFailedState() { return E( "div", @@ -1823,7 +1823,7 @@ function renderSections(props) { return renderDefaultState(props); } -// src/podkop/tabs/dashboard/renderWidget.ts +// src/podkop/tabs/dashboard/partials/renderWidget.ts function renderFailedState2() { return E( "div", @@ -1885,8 +1885,8 @@ function renderWidget(props) { return renderDefaultState2(props); } -// src/podkop/tabs/dashboard/renderDashboard.ts -function renderDashboard() { +// src/podkop/tabs/dashboard/render.ts +function render() { return E( "div", { @@ -1953,7 +1953,7 @@ function prettyBytes(n) { return n + " " + unit; } -// src/podkop/tabs/dashboard/initDashboardController.ts +// src/podkop/tabs/dashboard/initController.ts async function fetchDashboardSections() { const prev = store.get().sectionsWidget; store.set({ @@ -2290,7 +2290,7 @@ async function onStoreUpdate(next, prev, diff) { renderServicesInfoWidget(); } } -async function initDashboardController() { +async function initController() { onMount("dashboard-status").then(() => { store.unsubscribe(onStoreUpdate); store.reset([ @@ -2307,8 +2307,14 @@ async function initDashboardController() { }); } +// src/podkop/tabs/dashboard/index.ts +var DashboardTab = { + render, + initController +}; + // src/podkop/tabs/diagnostic/renderDiagnostic.ts -function renderDiagnostic() { +function render2() { return E("div", { id: "diagnostic-status", class: "pdk_diagnostic-page" }, [ E("div", { class: "pdk_diagnostic-page__left-bar" }, [ E("div", { id: "pdk_diagnostic-page-run-check" }), @@ -2324,6 +2330,349 @@ function renderDiagnostic() { ]); } +// src/podkop/tabs/diagnostic/checks/updateCheckStore.ts +function updateCheckStore(check, minified) { + const diagnosticsChecks = store.get().diagnosticsChecks; + const other = diagnosticsChecks.filter((item) => item.code !== check.code); + const smallCheck = { + ...check, + items: check.items.filter((item) => item.state !== "success") + }; + const targetCheck = minified ? smallCheck : check; + store.set({ + diagnosticsChecks: [...other, targetCheck] + }); +} + +// src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +async function runDnsCheck() { + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; + updateCheckStore({ + order, + code, + title, + description: _("Checking dns, please wait"), + state: "loading", + items: [] + }); + const dnsChecks = await PodkopShellMethods.checkDNSAvailable(); + if (!dnsChecks.success) { + updateCheckStore({ + order, + code, + title, + description: _("Cannot receive DNS checks result"), + state: "error", + items: [] + }); + throw new Error("DNS checks failed"); + } + const data = dnsChecks.data; + const allGood = Boolean(data.local_dns_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); + const atLeastOneGood = Boolean(data.local_dns_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); + console.log("dnsChecks", dnsChecks); + function getStatus() { + if (allGood) { + return "success"; + } + if (atLeastOneGood) { + return "warning"; + } + return "error"; + } + updateCheckStore({ + order, + code, + title, + description: _("DNS checks passed"), + state: getStatus(), + items: [ + ...insertIf( + data.dns_type === "doh" || data.dns_type === "dot", + [ + { + state: data.bootstrap_dns_status ? "success" : "error", + key: _("Bootsrap DNS"), + value: data.bootstrap_dns_server + } + ] + ), + { + state: data.dns_status ? "success" : "error", + key: _("Main DNS"), + value: `${data.dns_server} [${data.dns_type}]` + }, + { + state: data.local_dns_status ? "success" : "error", + key: _("Local DNS"), + value: "" + } + ] + }); + if (!atLeastOneGood) { + throw new Error("DNS checks failed"); + } +} + +// src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +async function runSingBoxCheck() { + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; + updateCheckStore({ + order, + code, + title, + description: _("Checking sing-box, please wait"), + state: "loading", + items: [] + }); + const singBoxChecks = await PodkopShellMethods.checkSingBox(); + if (!singBoxChecks.success) { + updateCheckStore({ + order, + code, + title, + description: _("Cannot receive Sing-box checks result"), + state: "error", + items: [] + }); + throw new Error("Sing-box checks failed"); + } + const data = singBoxChecks.data; + const allGood = Boolean(data.sing_box_installed) && Boolean(data.sing_box_version_ok) && Boolean(data.sing_box_service_exist) && Boolean(data.sing_box_autostart_disabled) && Boolean(data.sing_box_process_running) && Boolean(data.sing_box_ports_listening); + const atLeastOneGood = Boolean(data.sing_box_installed) || Boolean(data.sing_box_version_ok) || Boolean(data.sing_box_service_exist) || Boolean(data.sing_box_autostart_disabled) || Boolean(data.sing_box_process_running) || Boolean(data.sing_box_ports_listening); + console.log("singBoxChecks", singBoxChecks); + function getStatus() { + if (allGood) { + return "success"; + } + if (atLeastOneGood) { + return "warning"; + } + return "error"; + } + updateCheckStore({ + order, + code, + title, + description: _("Sing-box checks passed"), + state: getStatus(), + items: [ + { + state: data.sing_box_installed ? "success" : "error", + key: _("Sing-box installed"), + value: "" + }, + { + state: data.sing_box_version_ok ? "success" : "error", + key: _("Sing-box version >= 1.12.4"), + value: "" + }, + { + state: data.sing_box_service_exist ? "success" : "error", + key: _("Sing-box service exist"), + value: "" + }, + { + state: data.sing_box_autostart_disabled ? "success" : "error", + key: _("Sing-box autostart disabled"), + value: "" + }, + { + state: data.sing_box_process_running ? "success" : "error", + key: _("Sing-box process running"), + value: "" + }, + { + state: data.sing_box_ports_listening ? "success" : "error", + key: _("Sing-box listening ports"), + value: "" + } + ] + }); + if (!atLeastOneGood) { + throw new Error("Sing-box checks failed"); + } +} + +// src/podkop/tabs/diagnostic/checks/runNftCheck.ts +async function runNftCheck() { + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; + updateCheckStore({ + order, + code, + title, + description: _("Checking nftables, please wait"), + state: "loading", + items: [] + }); + await RemoteFakeIPMethods.getFakeIpCheck(); + await RemoteFakeIPMethods.getIpCheck(); + const nftablesChecks = await PodkopShellMethods.checkNftRules(); + if (!nftablesChecks.success) { + updateCheckStore({ + order, + code, + title, + description: _("Cannot receive nftables checks result"), + state: "error", + items: [] + }); + throw new Error("Nftables checks failed"); + } + const data = nftablesChecks.data; + const allGood = Boolean(data.table_exist) && Boolean(data.rules_mangle_exist) && Boolean(data.rules_mangle_counters) && Boolean(data.rules_mangle_output_exist) && Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && Boolean(data.rules_other_mark_exist); + const atLeastOneGood = Boolean(data.table_exist) || Boolean(data.rules_mangle_exist) || Boolean(data.rules_mangle_counters) || Boolean(data.rules_mangle_output_exist) || Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || Boolean(data.rules_other_mark_exist); + console.log("nftablesChecks", nftablesChecks); + function getStatus() { + if (allGood) { + return "success"; + } + if (atLeastOneGood) { + return "warning"; + } + return "error"; + } + updateCheckStore({ + order, + code, + title, + description: allGood ? _("Nftables checks passed") : _("Nftables checks partially passed"), + state: getStatus(), + items: [ + { + state: data.table_exist ? "success" : "error", + key: _("Table exist"), + value: "" + }, + { + state: data.rules_mangle_exist ? "success" : "error", + key: _("Rules mangle exist"), + value: "" + }, + { + state: data.rules_mangle_counters ? "success" : "error", + key: _("Rules mangle counters"), + value: "" + }, + { + state: data.rules_mangle_output_exist ? "success" : "error", + key: _("Rules mangle output exist"), + value: "" + }, + { + state: data.rules_mangle_output_counters ? "success" : "error", + key: _("Rules mangle output counters"), + value: "" + }, + { + state: data.rules_proxy_exist ? "success" : "error", + key: _("Rules proxy exist"), + value: "" + }, + { + state: data.rules_proxy_counters ? "success" : "error", + key: _("Rules proxy counters"), + value: "" + }, + { + state: !data.rules_other_mark_exist ? "success" : "warning", + key: !data.rules_other_mark_exist ? _("No other marking rules found") : _("Additional marking rules found"), + value: "" + } + ] + }); + if (!atLeastOneGood) { + throw new Error("Nftables checks failed"); + } +} + +// src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +async function runFakeIPCheck() { + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; + updateCheckStore({ + order, + code, + title, + description: _("Checking FakeIP, please wait"), + state: "loading", + items: [] + }); + const routerFakeIPResponse = await PodkopShellMethods.checkFakeIP(); + const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); + const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); + console.log("runFakeIPCheck", { + routerFakeIPResponse, + checkFakeIPResponse, + checkIPResponse + }); + const checks = { + router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, + browserFakeIP: checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, + differentIP: checkFakeIPResponse.success && checkIPResponse.success && checkFakeIPResponse.data.IP !== checkIPResponse.data.IP + }; + console.log("checks", checks); + const allGood = checks.router || checks.browserFakeIP || checks.differentIP; + const atLeastOneGood = checks.router && checks.browserFakeIP && checks.differentIP; + function getMeta() { + if (allGood) { + return { + state: "success", + description: _("FakeIP checks passed") + }; + } + if (atLeastOneGood) { + return { + state: "warning", + description: _("FakeIP checks partially passed") + }; + } + return { + state: "error", + description: _("FakeIP checks failed") + }; + } + const { state, description } = getMeta(); + updateCheckStore({ + order, + code, + title, + description, + state, + items: [ + { + state: checks.router ? "success" : "warning", + key: checks.router ? _("Router DNS is routed through sing-box") : _("Router DNS is not routed through sing-box"), + value: "" + }, + { + state: checks.browserFakeIP ? "success" : "error", + key: checks.browserFakeIP ? _("Browser is using FakeIP correctly") : _("Browser is not using FakeIP"), + value: "" + }, + ...insertIf(checks.browserFakeIP, [ + { + state: checks.differentIP ? "success" : "error", + key: checks.differentIP ? _("Proxy traffic is routed via FakeIP") : _("Proxy traffic is not routed via FakeIP"), + value: "" + } + ]) + ] + }); +} + +// src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts +function renderAvailableActions() { + return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [ + E("b", {}, "Available actions"), + E("button", { class: "btn" }, "Restart podkop"), + E("button", { class: "btn" }, "Stop podkop"), + E("button", { class: "btn" }, "Disable podkop"), + E("button", { class: "btn" }, "Get global check"), + E("button", { class: "btn" }, "View logs"), + E("button", { class: "btn" }, "Show sing-box config") + ]); +} + // src/icons/renderLoaderCircleIcon24.ts function renderLoaderCircleIcon24() { const NS = "http://www.w3.org/2000/svg"; @@ -2560,7 +2909,7 @@ function renderTriangleAlertIcon24() { ); } -// src/podkop/tabs/diagnostic/renderCheckSection.ts +// src/podkop/tabs/diagnostic/partials/renderCheckSection.ts function renderCheckSummary(items) { if (!items.length) { return E("div", {}, ""); @@ -2715,338 +3064,8 @@ function renderCheckSection(props) { return E("div", {}, "Not implement yet"); } -// src/podkop/tabs/diagnostic/updateDiagnosticsCheck.ts -function updateDiagnosticsCheck(check, minified) { - const diagnosticsChecks = store.get().diagnosticsChecks; - const other = diagnosticsChecks.filter((item) => item.code !== check.code); - const smallCheck = { - ...check, - items: check.items.filter((item) => item.state !== "success") - }; - const targetCheck = minified ? smallCheck : check; - store.set({ - diagnosticsChecks: [...other, targetCheck] - }); -} - -// src/podkop/tabs/diagnostic/checks/runDnsCheck.ts -async function runDnsCheck() { - const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; - updateDiagnosticsCheck({ - order, - code, - title, - description: _("Checking dns, please wait"), - state: "loading", - items: [] - }); - const dnsChecks = await PodkopShellMethods.checkDNSAvailable(); - if (!dnsChecks.success) { - updateDiagnosticsCheck({ - order, - code, - title, - description: _("Cannot receive DNS checks result"), - state: "error", - items: [] - }); - throw new Error("DNS checks failed"); - } - const data = dnsChecks.data; - const allGood = Boolean(data.local_dns_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); - const atLeastOneGood = Boolean(data.local_dns_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); - console.log("dnsChecks", dnsChecks); - function getStatus() { - if (allGood) { - return "success"; - } - if (atLeastOneGood) { - return "warning"; - } - return "error"; - } - updateDiagnosticsCheck({ - order, - code, - title, - description: _("DNS checks passed"), - state: getStatus(), - items: [ - ...insertIf( - data.dns_type === "doh" || data.dns_type === "dot", - [ - { - state: data.bootstrap_dns_status ? "success" : "error", - key: _("Bootsrap DNS"), - value: data.bootstrap_dns_server - } - ] - ), - { - state: data.dns_status ? "success" : "error", - key: _("Main DNS"), - value: `${data.dns_server} [${data.dns_type}]` - }, - { - state: data.local_dns_status ? "success" : "error", - key: _("Local DNS"), - value: "" - } - ] - }); - if (!atLeastOneGood) { - throw new Error("DNS checks failed"); - } -} - -// src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts -async function runSingBoxCheck() { - const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; - updateDiagnosticsCheck({ - order, - code, - title, - description: _("Checking sing-box, please wait"), - state: "loading", - items: [] - }); - const singBoxChecks = await PodkopShellMethods.checkSingBox(); - if (!singBoxChecks.success) { - updateDiagnosticsCheck({ - order, - code, - title, - description: _("Cannot receive Sing-box checks result"), - state: "error", - items: [] - }); - throw new Error("Sing-box checks failed"); - } - const data = singBoxChecks.data; - const allGood = Boolean(data.sing_box_installed) && Boolean(data.sing_box_version_ok) && Boolean(data.sing_box_service_exist) && Boolean(data.sing_box_autostart_disabled) && Boolean(data.sing_box_process_running) && Boolean(data.sing_box_ports_listening); - const atLeastOneGood = Boolean(data.sing_box_installed) || Boolean(data.sing_box_version_ok) || Boolean(data.sing_box_service_exist) || Boolean(data.sing_box_autostart_disabled) || Boolean(data.sing_box_process_running) || Boolean(data.sing_box_ports_listening); - console.log("singBoxChecks", singBoxChecks); - function getStatus() { - if (allGood) { - return "success"; - } - if (atLeastOneGood) { - return "warning"; - } - return "error"; - } - updateDiagnosticsCheck({ - order, - code, - title, - description: _("Sing-box checks passed"), - state: getStatus(), - items: [ - { - state: data.sing_box_installed ? "success" : "error", - key: _("Sing-box installed"), - value: "" - }, - { - state: data.sing_box_version_ok ? "success" : "error", - key: _("Sing-box version >= 1.12.4"), - value: "" - }, - { - state: data.sing_box_service_exist ? "success" : "error", - key: _("Sing-box service exist"), - value: "" - }, - { - state: data.sing_box_autostart_disabled ? "success" : "error", - key: _("Sing-box autostart disabled"), - value: "" - }, - { - state: data.sing_box_process_running ? "success" : "error", - key: _("Sing-box process running"), - value: "" - }, - { - state: data.sing_box_ports_listening ? "success" : "error", - key: _("Sing-box listening ports"), - value: "" - } - ] - }); - if (!atLeastOneGood) { - throw new Error("Sing-box checks failed"); - } -} - -// src/podkop/tabs/diagnostic/checks/runNftCheck.ts -async function runNftCheck() { - const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; - updateDiagnosticsCheck({ - order, - code, - title, - description: _("Checking nftables, please wait"), - state: "loading", - items: [] - }); - await RemoteFakeIPMethods.getFakeIpCheck(); - await RemoteFakeIPMethods.getIpCheck(); - const nftablesChecks = await PodkopShellMethods.checkNftRules(); - if (!nftablesChecks.success) { - updateDiagnosticsCheck({ - order, - code, - title, - description: _("Cannot receive nftables checks result"), - state: "error", - items: [] - }); - throw new Error("Nftables checks failed"); - } - const data = nftablesChecks.data; - const allGood = Boolean(data.table_exist) && Boolean(data.rules_mangle_exist) && Boolean(data.rules_mangle_counters) && Boolean(data.rules_mangle_output_exist) && Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && Boolean(data.rules_other_mark_exist); - const atLeastOneGood = Boolean(data.table_exist) || Boolean(data.rules_mangle_exist) || Boolean(data.rules_mangle_counters) || Boolean(data.rules_mangle_output_exist) || Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || Boolean(data.rules_other_mark_exist); - console.log("nftablesChecks", nftablesChecks); - function getStatus() { - if (allGood) { - return "success"; - } - if (atLeastOneGood) { - return "warning"; - } - return "error"; - } - updateDiagnosticsCheck({ - order, - code, - title, - description: allGood ? _("Nftables checks passed") : _("Nftables checks partially passed"), - state: getStatus(), - items: [ - { - state: data.table_exist ? "success" : "error", - key: _("Table exist"), - value: "" - }, - { - state: data.rules_mangle_exist ? "success" : "error", - key: _("Rules mangle exist"), - value: "" - }, - { - state: data.rules_mangle_counters ? "success" : "error", - key: _("Rules mangle counters"), - value: "" - }, - { - state: data.rules_mangle_output_exist ? "success" : "error", - key: _("Rules mangle output exist"), - value: "" - }, - { - state: data.rules_mangle_output_counters ? "success" : "error", - key: _("Rules mangle output counters"), - value: "" - }, - { - state: data.rules_proxy_exist ? "success" : "error", - key: _("Rules proxy exist"), - value: "" - }, - { - state: data.rules_proxy_counters ? "success" : "error", - key: _("Rules proxy counters"), - value: "" - }, - { - state: !data.rules_other_mark_exist ? "success" : "warning", - key: !data.rules_other_mark_exist ? _("No other marking rules found") : _("Additional marking rules found"), - value: "" - } - ] - }); - if (!atLeastOneGood) { - throw new Error("Nftables checks failed"); - } -} - -// src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts -async function runFakeIPCheck() { - const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; - updateDiagnosticsCheck({ - order, - code, - title, - description: _("Checking FakeIP, please wait"), - state: "loading", - items: [] - }); - const routerFakeIPResponse = await PodkopShellMethods.checkFakeIP(); - const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); - const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); - console.log("runFakeIPCheck", { - routerFakeIPResponse, - checkFakeIPResponse, - checkIPResponse - }); - const checks = { - router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, - browserFakeIP: checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, - differentIP: checkFakeIPResponse.success && checkIPResponse.success && checkFakeIPResponse.data.IP !== checkIPResponse.data.IP - }; - console.log("checks", checks); - const allGood = checks.router || checks.browserFakeIP || checks.differentIP; - const atLeastOneGood = checks.router && checks.browserFakeIP && checks.differentIP; - function getMeta() { - if (allGood) { - return { - state: "success", - description: _("FakeIP checks passed") - }; - } - if (atLeastOneGood) { - return { - state: "warning", - description: _("FakeIP checks partially passed") - }; - } - return { - state: "error", - description: _("FakeIP checks failed") - }; - } - const { state, description } = getMeta(); - updateDiagnosticsCheck({ - order, - code, - title, - description, - state, - items: [ - { - state: checks.router ? "success" : "warning", - key: checks.router ? _("Router DNS is routed through sing-box") : _("Router DNS is not routed through sing-box"), - value: "" - }, - { - state: checks.browserFakeIP ? "success" : "error", - key: checks.browserFakeIP ? _("Browser is using FakeIP correctly") : _("Browser is not using FakeIP"), - value: "" - }, - ...insertIf(checks.browserFakeIP, [ - { - state: checks.differentIP ? "success" : "error", - key: checks.differentIP ? _("Proxy traffic is routed via FakeIP") : _("Proxy traffic is not routed via FakeIP"), - value: "" - } - ]) - ] - }); -} - -// src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts -function renderDiagnosticRunAction({ +// src/podkop/tabs/diagnostic/partials/renderRunAction.ts +function renderRunAction({ loading, click }) { @@ -3059,20 +3078,7 @@ function renderDiagnosticRunAction({ ]); } -// src/podkop/tabs/diagnostic/renderAvailableActions.ts -function renderAvailableActions() { - return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [ - E("b", {}, "Available actions"), - E("button", { class: "btn" }, "Restart podkop"), - E("button", { class: "btn" }, "Stop podkop"), - E("button", { class: "btn" }, "Disable podkop"), - E("button", { class: "btn" }, "Get global check"), - E("button", { class: "btn" }, "View logs"), - E("button", { class: "btn" }, "Show sing-box config") - ]); -} - -// src/podkop/tabs/diagnostic/renderSystemInfo.ts +// src/podkop/tabs/diagnostic/partials/renderSystemInfo.ts function renderSystemInfo({ items }) { return E("div", { class: "pdk_diagnostic-page__right-bar__system-info" }, [ E( @@ -3089,7 +3095,7 @@ function renderSystemInfo({ items }) { ]); } -// src/podkop/tabs/diagnostic/initDiagnosticController.ts +// src/podkop/tabs/diagnostic/initController.ts function renderDiagnosticsChecks() { console.log("renderDiagnosticsChecks"); const diagnosticsChecks = store.get().diagnosticsChecks.sort((a, b) => a.order - b.order); @@ -3105,7 +3111,7 @@ function renderDiagnosticRunActionWidget() { console.log("renderDiagnosticRunActionWidget"); const { loading } = store.get().diagnosticsRunAction; const container = document.getElementById("pdk_diagnostic-page-run-check"); - const renderedAction = renderDiagnosticRunAction({ + const renderedAction = renderRunAction({ loading, click: () => runChecks() }); @@ -3176,7 +3182,7 @@ async function runChecks() { store.set({ diagnosticsRunAction: { loading: false } }); } } -async function initDiagnosticController() { +async function initController2() { onMount("diagnostic-status").then(() => { console.log("diagnostic controller initialized."); store.unsubscribe(onStoreUpdate2); @@ -3187,6 +3193,12 @@ async function initDiagnosticController() { renderDiagnosticSystemInfoWidget(); }); } + +// src/podkop/tabs/diagnostic/index.ts +var DiagnosticTab = { + render: render2, + initController: initController2 +}; return baseclass.extend({ ALLOWED_WITH_RUSSIA_INSIDE, BOOTSTRAP_DNS_SERVER_OPTIONS, @@ -3200,6 +3212,8 @@ return baseclass.extend({ DIAGNOSTICS_UPDATE_INTERVAL, DNS_SERVER_OPTIONS, DOMAIN_LIST_OPTIONS, + DashboardTab, + DiagnosticTab, ERROR_POLL_INTERVAL, FAKEIP_CHECK_DOMAIN, FETCH_TIMEOUT, @@ -3220,8 +3234,6 @@ return baseclass.extend({ getClashUIUrl, getClashWsUrl, getProxyUrlName, - initDashboardController, - initDiagnosticController, injectGlobalStyles, insertIf, insertIfObj, @@ -3230,8 +3242,6 @@ return baseclass.extend({ parseQueryString, parseValueList, preserveScrollForPage, - renderDashboard, - renderDiagnostic, socket, splitProxyString, store, From 7cd32910d92e8cb0bc3eae3cba6b04c007cd16ac Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 22:11:10 +0300 Subject: [PATCH 056/121] refactor: reorganize styles --- .../src/icons/renderLoaderCircleIcon24.ts | 2 +- .../src/podkop/tabs/dashboard/index.ts | 2 + .../src/podkop/tabs/dashboard/styles.ts | 120 ++ .../src/podkop/tabs/diagnostic/index.ts | 2 + .../src/podkop/tabs/diagnostic/styles.ts | 142 ++ fe-app-podkop/src/styles.ts | 315 +---- .../src/validators/validateVlessUrl.ts | 2 +- .../luci-static/resources/view/podkop/main.js | 1213 ++++++++--------- 8 files changed, 887 insertions(+), 911 deletions(-) create mode 100644 fe-app-podkop/src/podkop/tabs/dashboard/styles.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts diff --git a/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts b/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts index 43e2030..927964d 100644 --- a/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts +++ b/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts @@ -14,7 +14,7 @@ export function renderLoaderCircleIcon24() { 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', - class: 'lucide lucide-loader-circle lucide-rotate', + class: 'lucide lucide-loader-circle rotate', }, [ svgEl('path', { diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/index.ts b/fe-app-podkop/src/podkop/tabs/dashboard/index.ts index 105b142..7540731 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/index.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/index.ts @@ -1,7 +1,9 @@ import { render } from './render'; import { initController } from './initController'; +import { styles } from './styles'; export const DashboardTab = { render, initController, + styles, }; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/styles.ts b/fe-app-podkop/src/podkop/tabs/dashboard/styles.ts new file mode 100644 index 0000000..32066e5 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/dashboard/styles.ts @@ -0,0 +1,120 @@ +// language=CSS +export const styles = ` +#cbi-podkop-dashboard-_mount_node > div { + width: 100%; +} + +#cbi-podkop-dashboard > h3 { + display: none; +} + +.pdk_dashboard-page { + width: 100%; + --dashboard-grid-columns: 4; +} + +@media (max-width: 900px) { + .pdk_dashboard-page { + --dashboard-grid-columns: 2; + } +} + +.pdk_dashboard-page__widgets-section { + margin-top: 10px; + display: grid; + grid-template-columns: repeat(var(--dashboard-grid-columns), 1fr); + grid-gap: 10px; +} + +.pdk_dashboard-page__widgets-section__item { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; +} + +.pdk_dashboard-page__widgets-section__item__title {} + +.pdk_dashboard-page__widgets-section__item__row {} + +.pdk_dashboard-page__widgets-section__item__row--success .pdk_dashboard-page__widgets-section__item__row__value { + color: var(--success-color-medium, green); +} + +.pdk_dashboard-page__widgets-section__item__row--error .pdk_dashboard-page__widgets-section__item__row__value { + color: var(--error-color-medium, red); +} + +.pdk_dashboard-page__widgets-section__item__row__key {} + +.pdk_dashboard-page__widgets-section__item__row__value {} + +.pdk_dashboard-page__outbound-section { + margin-top: 10px; + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; +} + +.pdk_dashboard-page__outbound-section__title-section { + display: flex; + align-items: center; + justify-content: space-between; +} + +.pdk_dashboard-page__outbound-section__title-section__title { + color: var(--text-color-high); + font-weight: 700; +} + +.pdk_dashboard-page__outbound-grid { + margin-top: 5px; + display: grid; + grid-template-columns: repeat(var(--dashboard-grid-columns), 1fr); + grid-gap: 10px; +} + +.pdk_dashboard-page__outbound-grid__item { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + transition: border 0.2s ease; +} + +.pdk_dashboard-page__outbound-grid__item--selectable { + cursor: pointer; +} + +.pdk_dashboard-page__outbound-grid__item--selectable:hover { + border-color: var(--primary-color-high, dodgerblue); +} + +.pdk_dashboard-page__outbound-grid__item--active { + border-color: var(--success-color-medium, green); +} + +.pdk_dashboard-page__outbound-grid__item__footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 10px; +} + +.pdk_dashboard-page__outbound-grid__item__type {} + +.pdk_dashboard-page__outbound-grid__item__latency--empty { + color: var(--primary-color-low, lightgray); +} + +.pdk_dashboard-page__outbound-grid__item__latency--green { + color: var(--success-color-medium, green); +} + +.pdk_dashboard-page__outbound-grid__item__latency--yellow { + color: var(--warn-color-medium, orange); +} + +.pdk_dashboard-page__outbound-grid__item__latency--red { + color: var(--error-color-medium, red); +} + +`; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts index ce38706..2275b00 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/index.ts @@ -1,7 +1,9 @@ import { render } from './renderDiagnostic'; import { initController } from './initController'; +import { styles } from './styles'; export const DiagnosticTab = { render, initController, + styles, }; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts new file mode 100644 index 0000000..7fe2c21 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts @@ -0,0 +1,142 @@ +// language=CSS +export const styles = ` + +#cbi-podkop-diagnostic-_mount_node > div { + width: 100%; +} + +#cbi-podkop-diagnostic > h3 { + display: none; +} + +.pdk_diagnostic-page { + display: grid; + grid-template-columns: 2fr 1fr; + grid-column-gap: 10px; + align-items: start; +} + +.pdk_diagnostic-page__right-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__actions { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; + +} + +.pdk_diagnostic-page__right-bar__system-info { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__system-info__title { + +} + +.pdk_diagnostic-page__right-bar__system-info__row { + display: grid; + grid-template-columns: auto 1fr; + grid-column-gap: 5px; +} + +.pdk_diagnostic-page__left-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__run_check_wrapper {} + +.pdk_diagnostic-page__run_check_wrapper button { + width: 100%; +} + +.pdk_diagnostic-page__checks { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic_alert { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + + display: grid; + grid-template-columns: 24px 1fr; + grid-column-gap: 10px; + align-items: center; + padding: 10px; +} + +.pdk_diagnostic_alert--loading { + border: 2px var(--primary-color-high, dodgerblue) solid; +} + +.pdk_diagnostic_alert--warning { + border: 2px var(--warn-color-medium, orange) solid; + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic_alert--error { + border: 2px var(--error-color-medium, red) solid; + color: var(--error-color-medium, red); +} + +.pdk_diagnostic_alert--success { + border: 2px var(--success-color-medium, green) solid; + color: var(--success-color-medium, green); +} + +.pdk_diagnostic_alert--skipped {} + +.pdk_diagnostic_alert__icon {} + +.pdk_diagnostic_alert__content {} + +.pdk_diagnostic_alert__title { + display: block; +} + +.pdk_diagnostic_alert__description {} + +.pdk_diagnostic_alert__summary { + margin-top: 10px; +} + +.pdk_diagnostic_alert__summary__item { + display: grid; + grid-template-columns: 16px auto 1fr; + grid-column-gap: 10px; +} + +.pdk_diagnostic_alert__summary__item--error { + color: var(--error-color-medium, red); +} + +.pdk_diagnostic_alert__summary__item--warning { + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic_alert__summary__item--success { + color: var(--success-color-medium, green); +} + +.pdk_diagnostic_alert__summary__item__icon { + width: 16px; + height: 16px; +} +`; diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index 4b240bf..2518fd8 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -1,174 +1,43 @@ // language=CSS +import { DashboardTab, DiagnosticTab } from './podkop'; + export const GlobalStyles = ` -.cbi-value { - margin-bottom: 10px !important; -} +${DashboardTab.styles} +${DiagnosticTab.styles} -#diagnostics-status .table > div { - background: var(--background-color-primary); - border: 1px solid var(--border-color-medium); - border-radius: var(--border-radius); -} - -#diagnostics-status .table > div pre, -#diagnostics-status .table > div div[style*="monospace"] { - color: var(--color-text-primary); -} - -#diagnostics-status .alert-message { - background: var(--background-color-primary); - border-color: var(--border-color-medium); -} - -#cbi-podkop:has(.cbi-tab-disabled[data-tab="basic"]) #cbi-podkop-extra { - display: none; -} - -#cbi-podkop-dashboard-_mount_node > div { - width: 100%; -} - -#cbi-podkop-dashboard > h3 { - display: none; -} +/* Hide extra H3 for settings tab */ #cbi-podkop-settings > h3 { display: none; } +/* Hide extra H3 for sections tab */ #cbi-podkop-section > h3:nth-child(1) { display: none; } -#cbi-podkop-diagnostic > h3 { - display: none; -} - -.cbi-section-remove { +/* Vertical align for remove section action button */ +#cbi-podkop-section > .cbi-section-remove { margin-bottom: -32px; } -.cbi-value { - margin-bottom: 20px !important; -} - -/* Dashboard styles */ - -.pdk_dashboard-page { - width: 100%; - --dashboard-grid-columns: 4; -} - -@media (max-width: 900px) { - .pdk_dashboard-page { - --dashboard-grid-columns: 2; - } -} - -.pdk_dashboard-page__widgets-section { - margin-top: 10px; - display: grid; - grid-template-columns: repeat(var(--dashboard-grid-columns), 1fr); - grid-gap: 10px; -} - -.pdk_dashboard-page__widgets-section__item { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; -} - -.pdk_dashboard-page__widgets-section__item__title {} - -.pdk_dashboard-page__widgets-section__item__row {} - -.pdk_dashboard-page__widgets-section__item__row--success .pdk_dashboard-page__widgets-section__item__row__value { - color: var(--success-color-medium, green); -} - -.pdk_dashboard-page__widgets-section__item__row--error .pdk_dashboard-page__widgets-section__item__row__value { - color: var(--error-color-medium, red); -} - -.pdk_dashboard-page__widgets-section__item__row__key {} - -.pdk_dashboard-page__widgets-section__item__row__value {} - -.pdk_dashboard-page__outbound-section { - margin-top: 10px; - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; -} - -.pdk_dashboard-page__outbound-section__title-section { - display: flex; - align-items: center; - justify-content: space-between; -} - -.pdk_dashboard-page__outbound-section__title-section__title { - color: var(--text-color-high); - font-weight: 700; -} - -.pdk_dashboard-page__outbound-grid { - margin-top: 5px; - display: grid; - grid-template-columns: repeat(var(--dashboard-grid-columns), 1fr); - grid-gap: 10px; -} - -.pdk_dashboard-page__outbound-grid__item { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; - transition: border 0.2s ease; -} - -.pdk_dashboard-page__outbound-grid__item--selectable { - cursor: pointer; -} - -.pdk_dashboard-page__outbound-grid__item--selectable:hover { - border-color: var(--primary-color-high, dodgerblue); -} - -.pdk_dashboard-page__outbound-grid__item--active { - border-color: var(--success-color-medium, green); -} - -.pdk_dashboard-page__outbound-grid__item__footer { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 10px; -} - -.pdk_dashboard-page__outbound-grid__item__type {} - -.pdk_dashboard-page__outbound-grid__item__latency--empty { - color: var(--primary-color-low, lightgray); -} - -.pdk_dashboard-page__outbound-grid__item__latency--green { - color: var(--success-color-medium, green); -} - -.pdk_dashboard-page__outbound-grid__item__latency--yellow { - color: var(--warn-color-medium, orange); -} - -.pdk_dashboard-page__outbound-grid__item__latency--red { - color: var(--error-color-medium, red); -} - +/* Centered class helper */ .centered { display: flex; align-items: center; justify-content: center; } +/* Rotate class helper */ +.rotate { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + /* Skeleton styles*/ .skeleton { background-color: var(--background-color-low, #e0e0e0); @@ -198,150 +67,4 @@ export const GlobalStyles = ` left: 150%; } } - -/* Lucide spinner animate */ -.lucide-rotate { - animation: spin 1s linear infinite; -} - -@keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -#cbi-podkop-diagnostic-_mount_node > div { - width: 100%; -} - -.pdk_diagnostic-page { - display: grid; - grid-template-columns: 2fr 1fr; - grid-column-gap: 10px; - align-items: start; -} - -.pdk_diagnostic-page__right-bar { - display: grid; - grid-template-columns: 1fr; - grid-row-gap: 10px; -} - -.pdk_diagnostic-page__right-bar__actions { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; - - display: grid; - grid-template-columns: auto; - grid-row-gap: 10px; - -} - -.pdk_diagnostic-page__right-bar__system-info { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; - - display: grid; - grid-template-columns: auto; - grid-row-gap: 10px; -} - -.pdk_diagnostic-page__right-bar__system-info__title { - -} - -.pdk_diagnostic-page__right-bar__system-info__row { - display: grid; - grid-template-columns: auto 1fr; - grid-column-gap: 5px; -} - -.pdk_diagnostic-page__left-bar { - display: grid; - grid-template-columns: 1fr; - grid-row-gap: 10px; -} - -.pdk_diagnostic-page__run_check_wrapper {} - -.pdk_diagnostic-page__run_check_wrapper button { - width: 100%; -} - -.pdk_diagnostic-page__checks { - display: grid; - grid-template-columns: 1fr; - grid-row-gap: 10px; -} - -.pdk_diagnostic_alert { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - - display: grid; - grid-template-columns: 24px 1fr; - grid-column-gap: 10px; - align-items: center; - padding: 10px; -} - -.pdk_diagnostic_alert--loading { - border: 2px var(--primary-color-high, dodgerblue) solid; -} - -.pdk_diagnostic_alert--warning { - border: 2px var(--warn-color-medium, orange) solid; - color: var(--warn-color-medium, orange); -} - -.pdk_diagnostic_alert--error { - border: 2px var(--error-color-medium, red) solid; - color: var(--error-color-medium, red); -} - -.pdk_diagnostic_alert--success { - border: 2px var(--success-color-medium, green) solid; - color: var(--success-color-medium, green); -} - -.pdk_diagnostic_alert--skipped {} - -.pdk_diagnostic_alert__icon {} - -.pdk_diagnostic_alert__content {} - -.pdk_diagnostic_alert__title { - display: block; -} - -.pdk_diagnostic_alert__description {} - -.pdk_diagnostic_alert__summary { - margin-top: 10px; -} - -.pdk_diagnostic_alert__summary__item { - display: grid; - grid-template-columns: 16px auto 1fr; - grid-column-gap: 10px; -} - -.pdk_diagnostic_alert__summary__item--error { - color: var(--error-color-medium, red); -} - -.pdk_diagnostic_alert__summary__item--warning { - color: var(--warn-color-medium, orange); -} - -.pdk_diagnostic_alert__summary__item--success { - color: var(--success-color-medium, green); -} - -.pdk_diagnostic_alert__summary__item__icon { - width: 16px; - height: 16px; -} - `; diff --git a/fe-app-podkop/src/validators/validateVlessUrl.ts b/fe-app-podkop/src/validators/validateVlessUrl.ts index 06a84ba..23cebc9 100644 --- a/fe-app-podkop/src/validators/validateVlessUrl.ts +++ b/fe-app-podkop/src/validators/validateVlessUrl.ts @@ -1,5 +1,5 @@ import { ValidationResult } from './types'; -import { parseQueryString } from '../helpers'; +import { parseQueryString } from '../helpers/parseQueryString'; export function validateVlessUrl(url: string): ValidationResult { try { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 7ef6f9a..08517ae 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -210,597 +210,6 @@ function validateShadowsocksUrl(url) { return { valid: true, message: _("Valid") }; } -// src/helpers/getBaseUrl.ts -function getBaseUrl() { - const { protocol, hostname } = window.location; - return `${protocol}//${hostname}`; -} - -// src/helpers/parseValueList.ts -function parseValueList(value) { - return value.split(/\n/).map((line) => line.split("//")[0]).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean); -} - -// src/styles.ts -var GlobalStyles = ` -.cbi-value { - margin-bottom: 10px !important; -} - -#diagnostics-status .table > div { - background: var(--background-color-primary); - border: 1px solid var(--border-color-medium); - border-radius: var(--border-radius); -} - -#diagnostics-status .table > div pre, -#diagnostics-status .table > div div[style*="monospace"] { - color: var(--color-text-primary); -} - -#diagnostics-status .alert-message { - background: var(--background-color-primary); - border-color: var(--border-color-medium); -} - -#cbi-podkop:has(.cbi-tab-disabled[data-tab="basic"]) #cbi-podkop-extra { - display: none; -} - -#cbi-podkop-dashboard-_mount_node > div { - width: 100%; -} - -#cbi-podkop-dashboard > h3 { - display: none; -} - -#cbi-podkop-settings > h3 { - display: none; -} - -#cbi-podkop-section > h3:nth-child(1) { - display: none; -} - -#cbi-podkop-diagnostic > h3 { - display: none; -} - -.cbi-section-remove { - margin-bottom: -32px; -} - -.cbi-value { - margin-bottom: 20px !important; -} - -/* Dashboard styles */ - -.pdk_dashboard-page { - width: 100%; - --dashboard-grid-columns: 4; -} - -@media (max-width: 900px) { - .pdk_dashboard-page { - --dashboard-grid-columns: 2; - } -} - -.pdk_dashboard-page__widgets-section { - margin-top: 10px; - display: grid; - grid-template-columns: repeat(var(--dashboard-grid-columns), 1fr); - grid-gap: 10px; -} - -.pdk_dashboard-page__widgets-section__item { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; -} - -.pdk_dashboard-page__widgets-section__item__title {} - -.pdk_dashboard-page__widgets-section__item__row {} - -.pdk_dashboard-page__widgets-section__item__row--success .pdk_dashboard-page__widgets-section__item__row__value { - color: var(--success-color-medium, green); -} - -.pdk_dashboard-page__widgets-section__item__row--error .pdk_dashboard-page__widgets-section__item__row__value { - color: var(--error-color-medium, red); -} - -.pdk_dashboard-page__widgets-section__item__row__key {} - -.pdk_dashboard-page__widgets-section__item__row__value {} - -.pdk_dashboard-page__outbound-section { - margin-top: 10px; - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; -} - -.pdk_dashboard-page__outbound-section__title-section { - display: flex; - align-items: center; - justify-content: space-between; -} - -.pdk_dashboard-page__outbound-section__title-section__title { - color: var(--text-color-high); - font-weight: 700; -} - -.pdk_dashboard-page__outbound-grid { - margin-top: 5px; - display: grid; - grid-template-columns: repeat(var(--dashboard-grid-columns), 1fr); - grid-gap: 10px; -} - -.pdk_dashboard-page__outbound-grid__item { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; - transition: border 0.2s ease; -} - -.pdk_dashboard-page__outbound-grid__item--selectable { - cursor: pointer; -} - -.pdk_dashboard-page__outbound-grid__item--selectable:hover { - border-color: var(--primary-color-high, dodgerblue); -} - -.pdk_dashboard-page__outbound-grid__item--active { - border-color: var(--success-color-medium, green); -} - -.pdk_dashboard-page__outbound-grid__item__footer { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 10px; -} - -.pdk_dashboard-page__outbound-grid__item__type {} - -.pdk_dashboard-page__outbound-grid__item__latency--empty { - color: var(--primary-color-low, lightgray); -} - -.pdk_dashboard-page__outbound-grid__item__latency--green { - color: var(--success-color-medium, green); -} - -.pdk_dashboard-page__outbound-grid__item__latency--yellow { - color: var(--warn-color-medium, orange); -} - -.pdk_dashboard-page__outbound-grid__item__latency--red { - color: var(--error-color-medium, red); -} - -.centered { - display: flex; - align-items: center; - justify-content: center; -} - -/* Skeleton styles*/ -.skeleton { - background-color: var(--background-color-low, #e0e0e0); - border-radius: 4px; - position: relative; - overflow: hidden; -} - -.skeleton::after { - content: ''; - position: absolute; - top: 0; - left: -150%; - width: 150%; - height: 100%; - background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.4), - transparent - ); - animation: skeleton-shimmer 1.6s infinite; -} - -@keyframes skeleton-shimmer { - 100% { - left: 150%; - } -} - -/* Lucide spinner animate */ -.lucide-rotate { - animation: spin 1s linear infinite; -} - -@keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -#cbi-podkop-diagnostic-_mount_node > div { - width: 100%; -} - -.pdk_diagnostic-page { - display: grid; - grid-template-columns: 2fr 1fr; - grid-column-gap: 10px; - align-items: start; -} - -.pdk_diagnostic-page__right-bar { - display: grid; - grid-template-columns: 1fr; - grid-row-gap: 10px; -} - -.pdk_diagnostic-page__right-bar__actions { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; - - display: grid; - grid-template-columns: auto; - grid-row-gap: 10px; - -} - -.pdk_diagnostic-page__right-bar__system-info { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - padding: 10px; - - display: grid; - grid-template-columns: auto; - grid-row-gap: 10px; -} - -.pdk_diagnostic-page__right-bar__system-info__title { - -} - -.pdk_diagnostic-page__right-bar__system-info__row { - display: grid; - grid-template-columns: auto 1fr; - grid-column-gap: 5px; -} - -.pdk_diagnostic-page__left-bar { - display: grid; - grid-template-columns: 1fr; - grid-row-gap: 10px; -} - -.pdk_diagnostic-page__run_check_wrapper {} - -.pdk_diagnostic-page__run_check_wrapper button { - width: 100%; -} - -.pdk_diagnostic-page__checks { - display: grid; - grid-template-columns: 1fr; - grid-row-gap: 10px; -} - -.pdk_diagnostic_alert { - border: 2px var(--background-color-low, lightgray) solid; - border-radius: 4px; - - display: grid; - grid-template-columns: 24px 1fr; - grid-column-gap: 10px; - align-items: center; - padding: 10px; -} - -.pdk_diagnostic_alert--loading { - border: 2px var(--primary-color-high, dodgerblue) solid; -} - -.pdk_diagnostic_alert--warning { - border: 2px var(--warn-color-medium, orange) solid; - color: var(--warn-color-medium, orange); -} - -.pdk_diagnostic_alert--error { - border: 2px var(--error-color-medium, red) solid; - color: var(--error-color-medium, red); -} - -.pdk_diagnostic_alert--success { - border: 2px var(--success-color-medium, green) solid; - color: var(--success-color-medium, green); -} - -.pdk_diagnostic_alert--skipped {} - -.pdk_diagnostic_alert__icon {} - -.pdk_diagnostic_alert__content {} - -.pdk_diagnostic_alert__title { - display: block; -} - -.pdk_diagnostic_alert__description {} - -.pdk_diagnostic_alert__summary { - margin-top: 10px; -} - -.pdk_diagnostic_alert__summary__item { - display: grid; - grid-template-columns: 16px auto 1fr; - grid-column-gap: 10px; -} - -.pdk_diagnostic_alert__summary__item--error { - color: var(--error-color-medium, red); -} - -.pdk_diagnostic_alert__summary__item--warning { - color: var(--warn-color-medium, orange); -} - -.pdk_diagnostic_alert__summary__item--success { - color: var(--success-color-medium, green); -} - -.pdk_diagnostic_alert__summary__item__icon { - width: 16px; - height: 16px; -} - -`; - -// src/helpers/injectGlobalStyles.ts -function injectGlobalStyles() { - document.head.insertAdjacentHTML( - "beforeend", - ` - - ` - ); -} - -// src/helpers/withTimeout.ts -async function withTimeout(promise, timeoutMs, operationName, timeoutMessage = _("Operation timed out")) { - let timeoutId; - const start = performance.now(); - const timeoutPromise = new Promise((_2, reject) => { - timeoutId = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); - }); - try { - return await Promise.race([promise, timeoutPromise]); - } finally { - clearTimeout(timeoutId); - const elapsed = performance.now() - start; - console.log(`[${operationName}] Execution time: ${elapsed.toFixed(2)} ms`); - } -} - -// src/constants.ts -var STATUS_COLORS = { - SUCCESS: "#4caf50", - ERROR: "#f44336", - WARNING: "#ff9800" -}; -var PODKOP_LUCI_APP_VERSION = "__COMPILED_VERSION_VARIABLE__"; -var FAKEIP_CHECK_DOMAIN = "fakeip.podkop.fyi"; -var IP_CHECK_DOMAIN = "ip.podkop.fyi"; -var REGIONAL_OPTIONS = [ - "russia_inside", - "russia_outside", - "ukraine_inside" -]; -var ALLOWED_WITH_RUSSIA_INSIDE = [ - "russia_inside", - "meta", - "twitter", - "discord", - "telegram", - "cloudflare", - "google_ai", - "google_play", - "hetzner", - "ovh", - "hodca", - "digitalocean", - "cloudfront" -]; -var DOMAIN_LIST_OPTIONS = { - russia_inside: "Russia inside", - russia_outside: "Russia outside", - ukraine_inside: "Ukraine", - geoblock: "Geo Block", - block: "Block", - porn: "Porn", - news: "News", - anime: "Anime", - youtube: "Youtube", - discord: "Discord", - meta: "Meta", - twitter: "Twitter (X)", - hdrezka: "HDRezka", - tiktok: "Tik-Tok", - telegram: "Telegram", - cloudflare: "Cloudflare", - google_ai: "Google AI", - google_play: "Google Play", - hodca: "H.O.D.C.A", - hetzner: "Hetzner ASN", - ovh: "OVH ASN", - digitalocean: "Digital Ocean ASN", - cloudfront: "CloudFront ASN" -}; -var UPDATE_INTERVAL_OPTIONS = { - "1h": "Every hour", - "3h": "Every 3 hours", - "12h": "Every 12 hours", - "1d": "Every day", - "3d": "Every 3 days" -}; -var DNS_SERVER_OPTIONS = { - "1.1.1.1": "1.1.1.1 (Cloudflare)", - "8.8.8.8": "8.8.8.8 (Google)", - "9.9.9.9": "9.9.9.9 (Quad9)", - "dns.adguard-dns.com": "dns.adguard-dns.com (AdGuard Default)", - "unfiltered.adguard-dns.com": "unfiltered.adguard-dns.com (AdGuard Unfiltered)", - "family.adguard-dns.com": "family.adguard-dns.com (AdGuard Family)" -}; -var BOOTSTRAP_DNS_SERVER_OPTIONS = { - "77.88.8.8": "77.88.8.8 (Yandex DNS)", - "77.88.8.1": "77.88.8.1 (Yandex DNS)", - "1.1.1.1": "1.1.1.1 (Cloudflare DNS)", - "1.0.0.1": "1.0.0.1 (Cloudflare DNS)", - "8.8.8.8": "8.8.8.8 (Google DNS)", - "8.8.4.4": "8.8.4.4 (Google DNS)", - "9.9.9.9": "9.9.9.9 (Quad9 DNS)", - "9.9.9.11": "9.9.9.11 (Quad9 DNS)" -}; -var DIAGNOSTICS_UPDATE_INTERVAL = 1e4; -var CACHE_TIMEOUT = DIAGNOSTICS_UPDATE_INTERVAL - 1e3; -var ERROR_POLL_INTERVAL = 1e4; -var COMMAND_TIMEOUT = 1e4; -var FETCH_TIMEOUT = 1e4; -var BUTTON_FEEDBACK_TIMEOUT = 1e3; -var DIAGNOSTICS_INITIAL_DELAY = 100; -var COMMAND_SCHEDULING = { - P0_PRIORITY: 0, - // Highest priority (no delay) - P1_PRIORITY: 100, - // Very high priority - P2_PRIORITY: 300, - // High priority - P3_PRIORITY: 500, - // Above average - P4_PRIORITY: 700, - // Standard priority - P5_PRIORITY: 900, - // Below average - P6_PRIORITY: 1100, - // Low priority - P7_PRIORITY: 1300, - // Very low priority - P8_PRIORITY: 1500, - // Background execution - P9_PRIORITY: 1700, - // Idle mode execution - P10_PRIORITY: 1900 - // Lowest priority -}; - -// src/helpers/executeShellCommand.ts -async function executeShellCommand({ - command, - args, - timeout = COMMAND_TIMEOUT -}) { - try { - return withTimeout( - fs.exec(command, args), - timeout, - [command, ...args].join(" ") - ); - } catch (err) { - const error = err; - return { stdout: "", stderr: error?.message, code: 0 }; - } -} - -// src/helpers/maskIP.ts -function maskIP(ip = "") { - const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; - return ip.replace(ipv4Regex, (_match, _p1, _p2, _p3, p4) => `XX.XX.XX.${p4}`); -} - -// src/helpers/getProxyUrlName.ts -function getProxyUrlName(url) { - try { - const [_link, hash] = url.split("#"); - if (!hash) { - return ""; - } - return decodeURIComponent(hash); - } catch { - return ""; - } -} - -// src/helpers/onMount.ts -async function onMount(id) { - return new Promise((resolve) => { - const el = document.getElementById(id); - if (el && el.offsetParent !== null) { - return resolve(el); - } - const observer = new MutationObserver(() => { - const target = document.getElementById(id); - if (target) { - const io = new IntersectionObserver((entries) => { - const visible = entries.some((e) => e.isIntersecting); - if (visible) { - observer.disconnect(); - io.disconnect(); - resolve(target); - } - }); - io.observe(target); - } - }); - observer.observe(document.body, { - childList: true, - subtree: true - }); - }); -} - -// src/helpers/getClashApiUrl.ts -function getClashApiUrl() { - const { hostname } = window.location; - return `http://${hostname}:9090`; -} -function getClashWsUrl() { - const { hostname } = window.location; - return `ws://${hostname}:9090`; -} -function getClashUIUrl() { - const { hostname } = window.location; - return `http://${hostname}:9090/ui`; -} - -// src/helpers/splitProxyString.ts -function splitProxyString(str) { - return str.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("//")).filter(Boolean); -} - -// src/helpers/preserveScrollForPage.ts -function preserveScrollForPage(renderFn) { - const scrollY = window.scrollY; - renderFn(); - requestAnimationFrame(() => { - window.scrollTo({ top: scrollY }); - }); -} - // src/helpers/parseQueryString.ts function parseQueryString(query) { const clean = query.startsWith("?") ? query.slice(1) : query; @@ -818,25 +227,6 @@ function parseQueryString(query) { ); } -// src/helpers/svgEl.ts -function svgEl(tag, attrs = {}, children = []) { - const NS = "http://www.w3.org/2000/svg"; - const el = document.createElementNS(NS, tag); - for (const [k, v] of Object.entries(attrs)) { - if (v != null) el.setAttribute(k, String(v)); - } - (Array.isArray(children) ? children : [children]).filter(Boolean).forEach((ch) => el.appendChild(ch)); - return el; -} - -// src/helpers/insertIf.ts -function insertIf(condition, elements) { - return condition ? elements : []; -} -function insertIfObj(condition, object) { - return condition ? object : {}; -} - // src/validators/validateVlessUrl.ts function validateVlessUrl(url) { try { @@ -1003,6 +393,17 @@ function validateProxyUrl(url) { }; } +// src/helpers/getBaseUrl.ts +function getBaseUrl() { + const { protocol, hostname } = window.location; + return `${protocol}//${hostname}`; +} + +// src/helpers/parseValueList.ts +function parseValueList(value) { + return value.split(/\n/).map((line) => line.split("//")[0]).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean); +} + // src/podkop/api.ts async function createBaseApiRequest(fetchFn, options) { const wrappedFn = () => options?.timeoutMs && options?.operationName ? withTimeout( @@ -1224,6 +625,117 @@ var CustomPodkopMethods = { getDashboardSections }; +// src/constants.ts +var STATUS_COLORS = { + SUCCESS: "#4caf50", + ERROR: "#f44336", + WARNING: "#ff9800" +}; +var PODKOP_LUCI_APP_VERSION = "__COMPILED_VERSION_VARIABLE__"; +var FAKEIP_CHECK_DOMAIN = "fakeip.podkop.fyi"; +var IP_CHECK_DOMAIN = "ip.podkop.fyi"; +var REGIONAL_OPTIONS = [ + "russia_inside", + "russia_outside", + "ukraine_inside" +]; +var ALLOWED_WITH_RUSSIA_INSIDE = [ + "russia_inside", + "meta", + "twitter", + "discord", + "telegram", + "cloudflare", + "google_ai", + "google_play", + "hetzner", + "ovh", + "hodca", + "digitalocean", + "cloudfront" +]; +var DOMAIN_LIST_OPTIONS = { + russia_inside: "Russia inside", + russia_outside: "Russia outside", + ukraine_inside: "Ukraine", + geoblock: "Geo Block", + block: "Block", + porn: "Porn", + news: "News", + anime: "Anime", + youtube: "Youtube", + discord: "Discord", + meta: "Meta", + twitter: "Twitter (X)", + hdrezka: "HDRezka", + tiktok: "Tik-Tok", + telegram: "Telegram", + cloudflare: "Cloudflare", + google_ai: "Google AI", + google_play: "Google Play", + hodca: "H.O.D.C.A", + hetzner: "Hetzner ASN", + ovh: "OVH ASN", + digitalocean: "Digital Ocean ASN", + cloudfront: "CloudFront ASN" +}; +var UPDATE_INTERVAL_OPTIONS = { + "1h": "Every hour", + "3h": "Every 3 hours", + "12h": "Every 12 hours", + "1d": "Every day", + "3d": "Every 3 days" +}; +var DNS_SERVER_OPTIONS = { + "1.1.1.1": "1.1.1.1 (Cloudflare)", + "8.8.8.8": "8.8.8.8 (Google)", + "9.9.9.9": "9.9.9.9 (Quad9)", + "dns.adguard-dns.com": "dns.adguard-dns.com (AdGuard Default)", + "unfiltered.adguard-dns.com": "unfiltered.adguard-dns.com (AdGuard Unfiltered)", + "family.adguard-dns.com": "family.adguard-dns.com (AdGuard Family)" +}; +var BOOTSTRAP_DNS_SERVER_OPTIONS = { + "77.88.8.8": "77.88.8.8 (Yandex DNS)", + "77.88.8.1": "77.88.8.1 (Yandex DNS)", + "1.1.1.1": "1.1.1.1 (Cloudflare DNS)", + "1.0.0.1": "1.0.0.1 (Cloudflare DNS)", + "8.8.8.8": "8.8.8.8 (Google DNS)", + "8.8.4.4": "8.8.4.4 (Google DNS)", + "9.9.9.9": "9.9.9.9 (Quad9 DNS)", + "9.9.9.11": "9.9.9.11 (Quad9 DNS)" +}; +var DIAGNOSTICS_UPDATE_INTERVAL = 1e4; +var CACHE_TIMEOUT = DIAGNOSTICS_UPDATE_INTERVAL - 1e3; +var ERROR_POLL_INTERVAL = 1e4; +var COMMAND_TIMEOUT = 1e4; +var FETCH_TIMEOUT = 1e4; +var BUTTON_FEEDBACK_TIMEOUT = 1e3; +var DIAGNOSTICS_INITIAL_DELAY = 100; +var COMMAND_SCHEDULING = { + P0_PRIORITY: 0, + // Highest priority (no delay) + P1_PRIORITY: 100, + // Very high priority + P2_PRIORITY: 300, + // High priority + P3_PRIORITY: 500, + // Above average + P4_PRIORITY: 700, + // Standard priority + P5_PRIORITY: 900, + // Below average + P6_PRIORITY: 1100, + // Low priority + P7_PRIORITY: 1300, + // Very low priority + P8_PRIORITY: 1500, + // Background execution + P9_PRIORITY: 1700, + // Idle mode execution + P10_PRIORITY: 1900 + // Lowest priority +}; + // src/podkop/methods/fakeip/getFakeIpCheck.ts async function getFakeIpCheck() { return createBaseApiRequest( @@ -2307,10 +1819,132 @@ async function initController() { }); } +// src/podkop/tabs/dashboard/styles.ts +var styles = ` +#cbi-podkop-dashboard-_mount_node > div { + width: 100%; +} + +#cbi-podkop-dashboard > h3 { + display: none; +} + +.pdk_dashboard-page { + width: 100%; + --dashboard-grid-columns: 4; +} + +@media (max-width: 900px) { + .pdk_dashboard-page { + --dashboard-grid-columns: 2; + } +} + +.pdk_dashboard-page__widgets-section { + margin-top: 10px; + display: grid; + grid-template-columns: repeat(var(--dashboard-grid-columns), 1fr); + grid-gap: 10px; +} + +.pdk_dashboard-page__widgets-section__item { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; +} + +.pdk_dashboard-page__widgets-section__item__title {} + +.pdk_dashboard-page__widgets-section__item__row {} + +.pdk_dashboard-page__widgets-section__item__row--success .pdk_dashboard-page__widgets-section__item__row__value { + color: var(--success-color-medium, green); +} + +.pdk_dashboard-page__widgets-section__item__row--error .pdk_dashboard-page__widgets-section__item__row__value { + color: var(--error-color-medium, red); +} + +.pdk_dashboard-page__widgets-section__item__row__key {} + +.pdk_dashboard-page__widgets-section__item__row__value {} + +.pdk_dashboard-page__outbound-section { + margin-top: 10px; + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; +} + +.pdk_dashboard-page__outbound-section__title-section { + display: flex; + align-items: center; + justify-content: space-between; +} + +.pdk_dashboard-page__outbound-section__title-section__title { + color: var(--text-color-high); + font-weight: 700; +} + +.pdk_dashboard-page__outbound-grid { + margin-top: 5px; + display: grid; + grid-template-columns: repeat(var(--dashboard-grid-columns), 1fr); + grid-gap: 10px; +} + +.pdk_dashboard-page__outbound-grid__item { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + transition: border 0.2s ease; +} + +.pdk_dashboard-page__outbound-grid__item--selectable { + cursor: pointer; +} + +.pdk_dashboard-page__outbound-grid__item--selectable:hover { + border-color: var(--primary-color-high, dodgerblue); +} + +.pdk_dashboard-page__outbound-grid__item--active { + border-color: var(--success-color-medium, green); +} + +.pdk_dashboard-page__outbound-grid__item__footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 10px; +} + +.pdk_dashboard-page__outbound-grid__item__type {} + +.pdk_dashboard-page__outbound-grid__item__latency--empty { + color: var(--primary-color-low, lightgray); +} + +.pdk_dashboard-page__outbound-grid__item__latency--green { + color: var(--success-color-medium, green); +} + +.pdk_dashboard-page__outbound-grid__item__latency--yellow { + color: var(--warn-color-medium, orange); +} + +.pdk_dashboard-page__outbound-grid__item__latency--red { + color: var(--error-color-medium, red); +} + +`; + // src/podkop/tabs/dashboard/index.ts var DashboardTab = { render, - initController + initController, + styles }; // src/podkop/tabs/diagnostic/renderDiagnostic.ts @@ -2688,7 +2322,7 @@ function renderLoaderCircleIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-loader-circle lucide-rotate" + class: "lucide lucide-loader-circle rotate" }, [ svgEl("path", { @@ -3194,11 +2828,364 @@ async function initController2() { }); } +// src/podkop/tabs/diagnostic/styles.ts +var styles2 = ` + +#cbi-podkop-diagnostic-_mount_node > div { + width: 100%; +} + +#cbi-podkop-diagnostic > h3 { + display: none; +} + +.pdk_diagnostic-page { + display: grid; + grid-template-columns: 2fr 1fr; + grid-column-gap: 10px; + align-items: start; +} + +.pdk_diagnostic-page__right-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__actions { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; + +} + +.pdk_diagnostic-page__right-bar__system-info { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__system-info__title { + +} + +.pdk_diagnostic-page__right-bar__system-info__row { + display: grid; + grid-template-columns: auto 1fr; + grid-column-gap: 5px; +} + +.pdk_diagnostic-page__left-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__run_check_wrapper {} + +.pdk_diagnostic-page__run_check_wrapper button { + width: 100%; +} + +.pdk_diagnostic-page__checks { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic_alert { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + + display: grid; + grid-template-columns: 24px 1fr; + grid-column-gap: 10px; + align-items: center; + padding: 10px; +} + +.pdk_diagnostic_alert--loading { + border: 2px var(--primary-color-high, dodgerblue) solid; +} + +.pdk_diagnostic_alert--warning { + border: 2px var(--warn-color-medium, orange) solid; + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic_alert--error { + border: 2px var(--error-color-medium, red) solid; + color: var(--error-color-medium, red); +} + +.pdk_diagnostic_alert--success { + border: 2px var(--success-color-medium, green) solid; + color: var(--success-color-medium, green); +} + +.pdk_diagnostic_alert--skipped {} + +.pdk_diagnostic_alert__icon {} + +.pdk_diagnostic_alert__content {} + +.pdk_diagnostic_alert__title { + display: block; +} + +.pdk_diagnostic_alert__description {} + +.pdk_diagnostic_alert__summary { + margin-top: 10px; +} + +.pdk_diagnostic_alert__summary__item { + display: grid; + grid-template-columns: 16px auto 1fr; + grid-column-gap: 10px; +} + +.pdk_diagnostic_alert__summary__item--error { + color: var(--error-color-medium, red); +} + +.pdk_diagnostic_alert__summary__item--warning { + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic_alert__summary__item--success { + color: var(--success-color-medium, green); +} + +.pdk_diagnostic_alert__summary__item__icon { + width: 16px; + height: 16px; +} +`; + // src/podkop/tabs/diagnostic/index.ts var DiagnosticTab = { render: render2, - initController: initController2 + initController: initController2, + styles: styles2 }; + +// src/styles.ts +var GlobalStyles = ` +${DashboardTab.styles} +${DiagnosticTab.styles} + + +/* Hide extra H3 for settings tab */ +#cbi-podkop-settings > h3 { + display: none; +} + +/* Hide extra H3 for sections tab */ +#cbi-podkop-section > h3:nth-child(1) { + display: none; +} + +/* Vertical align for remove section action button */ +#cbi-podkop-section > .cbi-section-remove { + margin-bottom: -32px; +} + +/* Centered class helper */ +.centered { + display: flex; + align-items: center; + justify-content: center; +} + +/* Rotate class helper */ +.rotate { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* Skeleton styles*/ +.skeleton { + background-color: var(--background-color-low, #e0e0e0); + border-radius: 4px; + position: relative; + overflow: hidden; +} + +.skeleton::after { + content: ''; + position: absolute; + top: 0; + left: -150%; + width: 150%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.4), + transparent + ); + animation: skeleton-shimmer 1.6s infinite; +} + +@keyframes skeleton-shimmer { + 100% { + left: 150%; + } +} +`; + +// src/helpers/injectGlobalStyles.ts +function injectGlobalStyles() { + document.head.insertAdjacentHTML( + "beforeend", + ` + + ` + ); +} + +// src/helpers/withTimeout.ts +async function withTimeout(promise, timeoutMs, operationName, timeoutMessage = _("Operation timed out")) { + let timeoutId; + const start = performance.now(); + const timeoutPromise = new Promise((_2, reject) => { + timeoutId = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); + }); + try { + return await Promise.race([promise, timeoutPromise]); + } finally { + clearTimeout(timeoutId); + const elapsed = performance.now() - start; + console.log(`[${operationName}] Execution time: ${elapsed.toFixed(2)} ms`); + } +} + +// src/helpers/executeShellCommand.ts +async function executeShellCommand({ + command, + args, + timeout = COMMAND_TIMEOUT +}) { + try { + return withTimeout( + fs.exec(command, args), + timeout, + [command, ...args].join(" ") + ); + } catch (err) { + const error = err; + return { stdout: "", stderr: error?.message, code: 0 }; + } +} + +// src/helpers/maskIP.ts +function maskIP(ip = "") { + const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; + return ip.replace(ipv4Regex, (_match, _p1, _p2, _p3, p4) => `XX.XX.XX.${p4}`); +} + +// src/helpers/getProxyUrlName.ts +function getProxyUrlName(url) { + try { + const [_link, hash] = url.split("#"); + if (!hash) { + return ""; + } + return decodeURIComponent(hash); + } catch { + return ""; + } +} + +// src/helpers/onMount.ts +async function onMount(id) { + return new Promise((resolve) => { + const el = document.getElementById(id); + if (el && el.offsetParent !== null) { + return resolve(el); + } + const observer = new MutationObserver(() => { + const target = document.getElementById(id); + if (target) { + const io = new IntersectionObserver((entries) => { + const visible = entries.some((e) => e.isIntersecting); + if (visible) { + observer.disconnect(); + io.disconnect(); + resolve(target); + } + }); + io.observe(target); + } + }); + observer.observe(document.body, { + childList: true, + subtree: true + }); + }); +} + +// src/helpers/getClashApiUrl.ts +function getClashApiUrl() { + const { hostname } = window.location; + return `http://${hostname}:9090`; +} +function getClashWsUrl() { + const { hostname } = window.location; + return `ws://${hostname}:9090`; +} +function getClashUIUrl() { + const { hostname } = window.location; + return `http://${hostname}:9090/ui`; +} + +// src/helpers/splitProxyString.ts +function splitProxyString(str) { + return str.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("//")).filter(Boolean); +} + +// src/helpers/preserveScrollForPage.ts +function preserveScrollForPage(renderFn) { + const scrollY = window.scrollY; + renderFn(); + requestAnimationFrame(() => { + window.scrollTo({ top: scrollY }); + }); +} + +// src/helpers/svgEl.ts +function svgEl(tag, attrs = {}, children = []) { + const NS = "http://www.w3.org/2000/svg"; + const el = document.createElementNS(NS, tag); + for (const [k, v] of Object.entries(attrs)) { + if (v != null) el.setAttribute(k, String(v)); + } + (Array.isArray(children) ? children : [children]).filter(Boolean).forEach((ch) => el.appendChild(ch)); + return el; +} + +// src/helpers/insertIf.ts +function insertIf(condition, elements) { + return condition ? elements : []; +} +function insertIfObj(condition, object) { + return condition ? object : {}; +} return baseclass.extend({ ALLOWED_WITH_RUSSIA_INSIDE, BOOTSTRAP_DNS_SERVER_OPTIONS, From ffa00734414af228616f1f2f148f514188df867c Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 22:36:14 +0300 Subject: [PATCH 057/121] feat: migrate to proxied clash api methods --- fe-app-podkop/src/helpers/getBaseUrl.ts | 4 - fe-app-podkop/src/helpers/getClashApiUrl.ts | 6 - fe-app-podkop/src/helpers/index.ts | 1 - .../podkop/methods/clash/getGroupLatency.ts | 18 -- .../src/podkop/methods/clash/getProxies.ts | 14 - .../podkop/methods/clash/getProxyLatency.ts | 18 -- .../src/podkop/methods/clash/index.ts | 11 - .../src/podkop/methods/clash/setProxy.ts | 15 - .../methods/custom/getDashboardSections.ts | 4 +- fe-app-podkop/src/podkop/methods/index.ts | 1 - .../podkop/methods/shell/callBaseMethod.ts | 3 +- .../src/podkop/methods/shell/index.ts | 22 +- .../src/podkop/services/socket.service.ts | 25 +- .../podkop/tabs/dashboard/initController.ts | 15 +- .../tabs/dashboard/partials/renderSections.ts | 6 +- fe-app-podkop/src/podkop/types.ts | 8 + .../luci-static/resources/view/podkop/main.js | 292 ++++++++---------- .../resources/view/podkop/settings.js | 2 +- 18 files changed, 189 insertions(+), 276 deletions(-) delete mode 100644 fe-app-podkop/src/helpers/getBaseUrl.ts delete mode 100644 fe-app-podkop/src/podkop/methods/clash/getGroupLatency.ts delete mode 100644 fe-app-podkop/src/podkop/methods/clash/getProxies.ts delete mode 100644 fe-app-podkop/src/podkop/methods/clash/getProxyLatency.ts delete mode 100644 fe-app-podkop/src/podkop/methods/clash/index.ts delete mode 100644 fe-app-podkop/src/podkop/methods/clash/setProxy.ts diff --git a/fe-app-podkop/src/helpers/getBaseUrl.ts b/fe-app-podkop/src/helpers/getBaseUrl.ts deleted file mode 100644 index 88b82ff..0000000 --- a/fe-app-podkop/src/helpers/getBaseUrl.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function getBaseUrl(): string { - const { protocol, hostname } = window.location; - return `${protocol}//${hostname}`; -} diff --git a/fe-app-podkop/src/helpers/getClashApiUrl.ts b/fe-app-podkop/src/helpers/getClashApiUrl.ts index 27032a0..0428f3d 100644 --- a/fe-app-podkop/src/helpers/getClashApiUrl.ts +++ b/fe-app-podkop/src/helpers/getClashApiUrl.ts @@ -1,9 +1,3 @@ -export function getClashApiUrl(): string { - const { hostname } = window.location; - - return `http://${hostname}:9090`; -} - export function getClashWsUrl(): string { const { hostname } = window.location; diff --git a/fe-app-podkop/src/helpers/index.ts b/fe-app-podkop/src/helpers/index.ts index 3ec39ab..06baeb7 100644 --- a/fe-app-podkop/src/helpers/index.ts +++ b/fe-app-podkop/src/helpers/index.ts @@ -1,4 +1,3 @@ -export * from './getBaseUrl'; export * from './parseValueList'; export * from './injectGlobalStyles'; export * from './withTimeout'; diff --git a/fe-app-podkop/src/podkop/methods/clash/getGroupLatency.ts b/fe-app-podkop/src/podkop/methods/clash/getGroupLatency.ts deleted file mode 100644 index 7b26ac7..0000000 --- a/fe-app-podkop/src/podkop/methods/clash/getGroupLatency.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { getClashApiUrl } from '../../../helpers'; -import { createBaseApiRequest, IBaseApiResponse } from '../../api'; - -export async function getGroupLatency( - tag: string, - timeout: number = 5000, - url: string = 'https://www.gstatic.com/generate_204', -): Promise> { - return createBaseApiRequest(() => - fetch( - `${getClashApiUrl()}/group/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, - { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }, - ), - ); -} diff --git a/fe-app-podkop/src/podkop/methods/clash/getProxies.ts b/fe-app-podkop/src/podkop/methods/clash/getProxies.ts deleted file mode 100644 index 2c8d90c..0000000 --- a/fe-app-podkop/src/podkop/methods/clash/getProxies.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ClashAPI } from '../../types'; -import { getClashApiUrl } from '../../../helpers'; -import { createBaseApiRequest, IBaseApiResponse } from '../../api'; - -export async function getProxies(): Promise< - IBaseApiResponse -> { - return createBaseApiRequest(() => - fetch(`${getClashApiUrl()}/proxies`, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }), - ); -} diff --git a/fe-app-podkop/src/podkop/methods/clash/getProxyLatency.ts b/fe-app-podkop/src/podkop/methods/clash/getProxyLatency.ts deleted file mode 100644 index 6714efa..0000000 --- a/fe-app-podkop/src/podkop/methods/clash/getProxyLatency.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { getClashApiUrl } from '../../../helpers'; -import { createBaseApiRequest, IBaseApiResponse } from '../../api'; - -export async function getProxyLatency( - tag: string, - timeout: number = 2000, - url: string = 'https://www.gstatic.com/generate_204', -): Promise> { - return createBaseApiRequest(() => - fetch( - `${getClashApiUrl()}/proxies/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, - { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }, - ), - ); -} diff --git a/fe-app-podkop/src/podkop/methods/clash/index.ts b/fe-app-podkop/src/podkop/methods/clash/index.ts deleted file mode 100644 index d40db47..0000000 --- a/fe-app-podkop/src/podkop/methods/clash/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getGroupLatency } from './getGroupLatency'; -import { getProxies } from './getProxies'; -import { getProxyLatency } from './getProxyLatency'; -import { setProxy } from './setProxy'; - -export const ClashMethods = { - getGroupLatency, - getProxies, - getProxyLatency, - setProxy, -}; diff --git a/fe-app-podkop/src/podkop/methods/clash/setProxy.ts b/fe-app-podkop/src/podkop/methods/clash/setProxy.ts deleted file mode 100644 index 4642230..0000000 --- a/fe-app-podkop/src/podkop/methods/clash/setProxy.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { getClashApiUrl } from '../../../helpers'; -import { createBaseApiRequest, IBaseApiResponse } from '../../api'; - -export async function setProxy( - selector: string, - outbound: string, -): Promise> { - return createBaseApiRequest(() => - fetch(`${getClashApiUrl()}/proxies/${selector}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: outbound }), - }), - ); -} diff --git a/fe-app-podkop/src/podkop/methods/custom/getDashboardSections.ts b/fe-app-podkop/src/podkop/methods/custom/getDashboardSections.ts index 96ff4b1..709d6a1 100644 --- a/fe-app-podkop/src/podkop/methods/custom/getDashboardSections.ts +++ b/fe-app-podkop/src/podkop/methods/custom/getDashboardSections.ts @@ -1,7 +1,7 @@ import { getConfigSections } from './getConfigSections'; import { Podkop } from '../../types'; -import { ClashMethods } from '../clash'; import { getProxyUrlName, splitProxyString } from '../../../helpers'; +import { PodkopShellMethods } from '../shell'; interface IGetDashboardSectionsResponse { success: boolean; @@ -10,7 +10,7 @@ interface IGetDashboardSectionsResponse { export async function getDashboardSections(): Promise { const configSections = await getConfigSections(); - const clashProxies = await ClashMethods.getProxies(); + const clashProxies = await PodkopShellMethods.getClashApiProxies(); if (!clashProxies.success) { return { diff --git a/fe-app-podkop/src/podkop/methods/index.ts b/fe-app-podkop/src/podkop/methods/index.ts index 28101c6..fca93a4 100644 --- a/fe-app-podkop/src/podkop/methods/index.ts +++ b/fe-app-podkop/src/podkop/methods/index.ts @@ -1,4 +1,3 @@ -export * from './clash'; export * from './custom'; export * from './fakeip'; export * from './shell'; diff --git a/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts b/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts index 55da36e..37c1b37 100644 --- a/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts +++ b/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts @@ -3,10 +3,11 @@ import { Podkop } from '../../types'; export async function callBaseMethod( method: Podkop.AvailableMethods, + args: string[] = [], ): Promise> { const response = await executeShellCommand({ command: '/usr/bin/podkop', - args: [method], + args: [method as string, ...args], timeout: 10000, }); diff --git a/fe-app-podkop/src/podkop/methods/shell/index.ts b/fe-app-podkop/src/podkop/methods/shell/index.ts index 9b1cd93..8f8394f 100644 --- a/fe-app-podkop/src/podkop/methods/shell/index.ts +++ b/fe-app-podkop/src/podkop/methods/shell/index.ts @@ -1,5 +1,5 @@ import { callBaseMethod } from './callBaseMethod'; -import { Podkop } from '../../types'; +import { ClashAPI, Podkop } from '../../types'; export const PodkopShellMethods = { checkDNSAvailable: async () => @@ -24,4 +24,24 @@ export const PodkopShellMethods = { callBaseMethod( Podkop.AvailableMethods.GET_SING_BOX_STATUS, ), + getClashApiProxies: async () => + callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_PROXIES, + ]), + getClashApiProxyLatency: async (tag: string) => + callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, + tag, + ]), + getClashApiGroupLatency: async (tag: string) => + callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, + tag, + ]), + setClashApiGroupProxy: async (group: string, proxy: string) => + callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY, + group, + proxy, + ]), }; diff --git a/fe-app-podkop/src/podkop/services/socket.service.ts b/fe-app-podkop/src/podkop/services/socket.service.ts index 5a401b8..5210155 100644 --- a/fe-app-podkop/src/podkop/services/socket.service.ts +++ b/fe-app-podkop/src/podkop/services/socket.service.ts @@ -21,7 +21,16 @@ class SocketManager { connect(url: string): void { if (this.sockets.has(url)) return; - const ws = new WebSocket(url); + let ws: WebSocket; + + try { + ws = new WebSocket(url); + } catch (err) { + console.error(`Failed to construct WebSocket for ${url}:`, err); + this.triggerError(url, err instanceof Event ? err : String(err)); + return; + } + this.sockets.set(url, ws); this.connected.set(url, false); this.listeners.set(url, new Set()); @@ -58,15 +67,21 @@ class SocketManager { } subscribe(url: string, listener: Listener, onError?: ErrorListener): void { + if (!this.errorListeners.has(url)) { + this.errorListeners.set(url, new Set()); + } + if (onError) { + this.errorListeners.get(url)?.add(onError); + } + if (!this.sockets.has(url)) { this.connect(url); } - this.listeners.get(url)?.add(listener); - - if (onError) { - this.errorListeners.get(url)?.add(onError); + if (!this.listeners.has(url)) { + this.listeners.set(url, new Set()); } + this.listeners.get(url)?.add(listener); } unsubscribe(url: string, listener: Listener, onError?: ErrorListener): void { diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts index d10eb11..a5e1ae2 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts @@ -1,15 +1,10 @@ import { - getClashApiUrl, getClashWsUrl, onMount, preserveScrollForPage, } from '../../../helpers'; import { prettyBytes } from '../../../helpers/prettyBytes'; -import { - ClashMethods, - CustomPodkopMethods, - PodkopShellMethods, -} from '../../methods'; +import { CustomPodkopMethods, PodkopShellMethods } from '../../methods'; import { socket, store, StoreType } from '../../services'; import { renderSections, renderWidget } from './partials'; @@ -28,7 +23,7 @@ async function fetchDashboardSections() { const { data, success } = await CustomPodkopMethods.getDashboardSections(); if (!success) { - console.log('[fetchDashboardSections]: failed to fetch', getClashApiUrl()); + console.log('[fetchDashboardSections]: failed to fetch'); } store.set({ @@ -148,7 +143,7 @@ async function connectToClashSockets() { // Handlers async function handleChooseOutbound(selector: string, tag: string) { - await ClashMethods.setProxy(selector, tag); + await PodkopShellMethods.setClashApiGroupProxy(selector, tag); await fetchDashboardSections(); } @@ -160,7 +155,7 @@ async function handleTestGroupLatency(tag: string) { }, }); - await ClashMethods.getGroupLatency(tag); + await PodkopShellMethods.getClashApiGroupLatency(tag); await fetchDashboardSections(); store.set({ @@ -179,7 +174,7 @@ async function handleTestProxyLatency(tag: string) { }, }); - await ClashMethods.getProxyLatency(tag); + await PodkopShellMethods.getClashApiProxyLatency(tag); await fetchDashboardSections(); store.set({ diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/partials/renderSections.ts b/fe-app-podkop/src/podkop/tabs/dashboard/partials/renderSections.ts index 2389379..bf78e7e 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/partials/renderSections.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/partials/renderSections.ts @@ -1,5 +1,4 @@ import { Podkop } from '../../../types'; -import { getClashApiUrl } from '../../../../helpers'; interface IRenderSectionsProps { loading: boolean; @@ -17,10 +16,7 @@ function renderFailedState() { class: 'pdk_dashboard-page__outbound-section centered', style: 'height: 127px', }, - E('span', {}, [ - E('span', {}, _('Dashboard currently unavailable')), - E('div', { style: 'text-align: center;' }, `API: ${getClashApiUrl()}`), - ]), + E('span', {}, [E('span', {}, _('Dashboard currently unavailable'))]), ); } diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index 32c0447..358e9a4 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -28,6 +28,14 @@ export namespace Podkop { GET_STATUS = 'get_status', CHECK_SING_BOX = 'check_sing_box', GET_SING_BOX_STATUS = 'get_sing_box_status', + CLASH_API = 'clash_api', + } + + export enum AvailableClashAPIMethods { + GET_PROXIES = 'get_proxies', + GET_PROXY_LATENCY = 'get_proxy_latency', + GET_GROUP_LATENCY = 'get_group_latency', + SET_GROUP_PROXY = 'set_group_proxy', } export interface Outbound { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 08517ae..c7a8948 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -393,110 +393,97 @@ function validateProxyUrl(url) { }; } -// src/helpers/getBaseUrl.ts -function getBaseUrl() { - const { protocol, hostname } = window.location; - return `${protocol}//${hostname}`; -} - // src/helpers/parseValueList.ts function parseValueList(value) { return value.split(/\n/).map((line) => line.split("//")[0]).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean); } -// src/podkop/api.ts -async function createBaseApiRequest(fetchFn, options) { - const wrappedFn = () => options?.timeoutMs && options?.operationName ? withTimeout( - fetchFn(), - options.timeoutMs, - options.operationName, - options.timeoutMessage - ) : fetchFn(); - try { - const response = await wrappedFn(); - if (!response.ok) { - return { - success: false, - message: `${_("HTTP error")} ${response.status}: ${response.statusText}` - }; - } - const data = await response.json(); - return { - success: true, - data - }; - } catch (e) { - return { - success: false, - message: e instanceof Error ? e.message : _("Unknown error") - }; - } -} - -// src/podkop/methods/clash/getGroupLatency.ts -async function getGroupLatency(tag, timeout = 5e3, url = "https://www.gstatic.com/generate_204") { - return createBaseApiRequest( - () => fetch( - `${getClashApiUrl()}/group/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, - { - method: "GET", - headers: { "Content-Type": "application/json" } - } - ) - ); -} - -// src/podkop/methods/clash/getProxies.ts -async function getProxies() { - return createBaseApiRequest( - () => fetch(`${getClashApiUrl()}/proxies`, { - method: "GET", - headers: { "Content-Type": "application/json" } - }) - ); -} - -// src/podkop/methods/clash/getProxyLatency.ts -async function getProxyLatency(tag, timeout = 2e3, url = "https://www.gstatic.com/generate_204") { - return createBaseApiRequest( - () => fetch( - `${getClashApiUrl()}/proxies/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, - { - method: "GET", - headers: { "Content-Type": "application/json" } - } - ) - ); -} - -// src/podkop/methods/clash/setProxy.ts -async function setProxy(selector, outbound) { - return createBaseApiRequest( - () => fetch(`${getClashApiUrl()}/proxies/${selector}`, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name: outbound }) - }) - ); -} - -// src/podkop/methods/clash/index.ts -var ClashMethods = { - getGroupLatency, - getProxies, - getProxyLatency, - setProxy -}; - // src/podkop/methods/custom/getConfigSections.ts async function getConfigSections() { return uci.load("podkop").then(() => uci.sections("podkop")); } +// src/podkop/methods/shell/callBaseMethod.ts +async function callBaseMethod(method, args = []) { + const response = await executeShellCommand({ + command: "/usr/bin/podkop", + args: [method, ...args], + timeout: 1e4 + }); + if (response.stdout) { + return { + success: true, + data: JSON.parse(response.stdout) + }; + } + return { + success: false, + error: "" + }; +} + +// src/podkop/types.ts +var Podkop; +((Podkop2) => { + let AvailableMethods; + ((AvailableMethods2) => { + AvailableMethods2["CHECK_DNS_AVAILABLE"] = "check_dns_available"; + AvailableMethods2["CHECK_FAKEIP"] = "check_fakeip"; + AvailableMethods2["CHECK_NFT_RULES"] = "check_nft_rules"; + AvailableMethods2["GET_STATUS"] = "get_status"; + AvailableMethods2["CHECK_SING_BOX"] = "check_sing_box"; + AvailableMethods2["GET_SING_BOX_STATUS"] = "get_sing_box_status"; + AvailableMethods2["CLASH_API"] = "clash_api"; + })(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})); + let AvailableClashAPIMethods; + ((AvailableClashAPIMethods2) => { + AvailableClashAPIMethods2["GET_PROXIES"] = "get_proxies"; + AvailableClashAPIMethods2["GET_PROXY_LATENCY"] = "get_proxy_latency"; + AvailableClashAPIMethods2["GET_GROUP_LATENCY"] = "get_group_latency"; + AvailableClashAPIMethods2["SET_GROUP_PROXY"] = "set_group_proxy"; + })(AvailableClashAPIMethods = Podkop2.AvailableClashAPIMethods || (Podkop2.AvailableClashAPIMethods = {})); +})(Podkop || (Podkop = {})); + +// src/podkop/methods/shell/index.ts +var PodkopShellMethods = { + checkDNSAvailable: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_DNS_AVAILABLE + ), + checkFakeIP: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_FAKEIP + ), + checkNftRules: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_NFT_RULES + ), + getStatus: async () => callBaseMethod(Podkop.AvailableMethods.GET_STATUS), + checkSingBox: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_SING_BOX + ), + getSingBoxStatus: async () => callBaseMethod( + Podkop.AvailableMethods.GET_SING_BOX_STATUS + ), + getClashApiProxies: async () => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_PROXIES + ]), + getClashApiProxyLatency: async (tag) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, + tag + ]), + getClashApiGroupLatency: async (tag) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, + tag + ]), + setClashApiGroupProxy: async (group, proxy) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY, + group, + proxy + ]) +}; + // src/podkop/methods/custom/getDashboardSections.ts async function getDashboardSections() { const configSections = await getConfigSections(); - const clashProxies = await ClashMethods.getProxies(); + const clashProxies = await PodkopShellMethods.getClashApiProxies(); if (!clashProxies.success) { return { success: false, @@ -736,6 +723,35 @@ var COMMAND_SCHEDULING = { // Lowest priority }; +// src/podkop/api.ts +async function createBaseApiRequest(fetchFn, options) { + const wrappedFn = () => options?.timeoutMs && options?.operationName ? withTimeout( + fetchFn(), + options.timeoutMs, + options.operationName, + options.timeoutMessage + ) : fetchFn(); + try { + const response = await wrappedFn(); + if (!response.ok) { + return { + success: false, + message: `${_("HTTP error")} ${response.status}: ${response.statusText}` + }; + } + const data = await response.json(); + return { + success: true, + data + }; + } catch (e) { + return { + success: false, + message: e instanceof Error ? e.message : _("Unknown error") + }; + } +} + // src/podkop/methods/fakeip/getFakeIpCheck.ts async function getFakeIpCheck() { return createBaseApiRequest( @@ -770,59 +786,6 @@ var RemoteFakeIPMethods = { getIpCheck }; -// src/podkop/methods/shell/callBaseMethod.ts -async function callBaseMethod(method) { - const response = await executeShellCommand({ - command: "/usr/bin/podkop", - args: [method], - timeout: 1e4 - }); - if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) - }; - } - return { - success: false, - error: "" - }; -} - -// src/podkop/types.ts -var Podkop; -((Podkop2) => { - let AvailableMethods; - ((AvailableMethods2) => { - AvailableMethods2["CHECK_DNS_AVAILABLE"] = "check_dns_available"; - AvailableMethods2["CHECK_FAKEIP"] = "check_fakeip"; - AvailableMethods2["CHECK_NFT_RULES"] = "check_nft_rules"; - AvailableMethods2["GET_STATUS"] = "get_status"; - AvailableMethods2["CHECK_SING_BOX"] = "check_sing_box"; - AvailableMethods2["GET_SING_BOX_STATUS"] = "get_sing_box_status"; - })(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})); -})(Podkop || (Podkop = {})); - -// src/podkop/methods/shell/index.ts -var PodkopShellMethods = { - checkDNSAvailable: async () => callBaseMethod( - Podkop.AvailableMethods.CHECK_DNS_AVAILABLE - ), - checkFakeIP: async () => callBaseMethod( - Podkop.AvailableMethods.CHECK_FAKEIP - ), - checkNftRules: async () => callBaseMethod( - Podkop.AvailableMethods.CHECK_NFT_RULES - ), - getStatus: async () => callBaseMethod(Podkop.AvailableMethods.GET_STATUS), - checkSingBox: async () => callBaseMethod( - Podkop.AvailableMethods.CHECK_SING_BOX - ), - getSingBoxStatus: async () => callBaseMethod( - Podkop.AvailableMethods.GET_SING_BOX_STATUS - ) -}; - // src/podkop/services/tab.service.ts var TabService = class _TabService { constructor() { @@ -1142,7 +1105,14 @@ var SocketManager = class _SocketManager { } connect(url) { if (this.sockets.has(url)) return; - const ws = new WebSocket(url); + let ws; + try { + ws = new WebSocket(url); + } catch (err) { + console.error(`Failed to construct WebSocket for ${url}:`, err); + this.triggerError(url, err instanceof Event ? err : String(err)); + return; + } this.sockets.set(url, ws); this.connected.set(url, false); this.listeners.set(url, /* @__PURE__ */ new Set()); @@ -1174,13 +1144,19 @@ var SocketManager = class _SocketManager { }); } subscribe(url, listener, onError) { - if (!this.sockets.has(url)) { - this.connect(url); + if (!this.errorListeners.has(url)) { + this.errorListeners.set(url, /* @__PURE__ */ new Set()); } - this.listeners.get(url)?.add(listener); if (onError) { this.errorListeners.get(url)?.add(onError); } + if (!this.sockets.has(url)) { + this.connect(url); + } + if (!this.listeners.has(url)) { + this.listeners.set(url, /* @__PURE__ */ new Set()); + } + this.listeners.get(url)?.add(listener); } unsubscribe(url, listener, onError) { this.listeners.get(url)?.delete(listener); @@ -1236,10 +1212,7 @@ function renderFailedState() { class: "pdk_dashboard-page__outbound-section centered", style: "height: 127px" }, - E("span", {}, [ - E("span", {}, _("Dashboard currently unavailable")), - E("div", { style: "text-align: center;" }, `API: ${getClashApiUrl()}`) - ]) + E("span", {}, [E("span", {}, _("Dashboard currently unavailable"))]) ); } function renderLoadingState() { @@ -1476,7 +1449,7 @@ async function fetchDashboardSections() { }); const { data, success } = await CustomPodkopMethods.getDashboardSections(); if (!success) { - console.log("[fetchDashboardSections]: failed to fetch", getClashApiUrl()); + console.log("[fetchDashboardSections]: failed to fetch"); } store.set({ sectionsWidget: { @@ -1585,7 +1558,7 @@ async function connectToClashSockets() { ); } async function handleChooseOutbound(selector, tag) { - await ClashMethods.setProxy(selector, tag); + await PodkopShellMethods.setClashApiGroupProxy(selector, tag); await fetchDashboardSections(); } async function handleTestGroupLatency(tag) { @@ -1595,7 +1568,7 @@ async function handleTestGroupLatency(tag) { latencyFetching: true } }); - await ClashMethods.getGroupLatency(tag); + await PodkopShellMethods.getClashApiGroupLatency(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { @@ -1611,7 +1584,7 @@ async function handleTestProxyLatency(tag) { latencyFetching: true } }); - await ClashMethods.getProxyLatency(tag); + await PodkopShellMethods.getClashApiProxyLatency(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { @@ -3141,10 +3114,6 @@ async function onMount(id) { } // src/helpers/getClashApiUrl.ts -function getClashApiUrl() { - const { hostname } = window.location; - return `http://${hostname}:9090`; -} function getClashWsUrl() { const { hostname } = window.location; return `ws://${hostname}:9090`; @@ -3193,7 +3162,6 @@ return baseclass.extend({ CACHE_TIMEOUT, COMMAND_SCHEDULING, COMMAND_TIMEOUT, - ClashMethods, CustomPodkopMethods, DIAGNOSTICS_INITIAL_DELAY, DIAGNOSTICS_UPDATE_INTERVAL, @@ -3216,8 +3184,6 @@ return baseclass.extend({ bulkValidate, coreService, executeShellCommand, - getBaseUrl, - getClashApiUrl, getClashUIUrl, getClashWsUrl, getProxyUrlName, 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 6c26ad9..0715a65 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 @@ -170,7 +170,7 @@ function createSettingsContent(section) { form.Flag, 'enable_yacd', _('Enable YACD'), - `${main.getClashApiUrl()}/ui`, + `${main.getClashUIUrl()}`, ); o.default = '0'; o.rmempty = false; From 739e0d2ba7aa2157296e70ec1cde24c2c7aebc93 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 23:15:17 +0300 Subject: [PATCH 058/121] feat: add some shell methods --- .../src/podkop/methods/shell/index.ts | 16 ++++++++ fe-app-podkop/src/podkop/types.ts | 38 +++++++++++++++++++ .../luci-static/resources/view/podkop/main.js | 23 ++++++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/fe-app-podkop/src/podkop/methods/shell/index.ts b/fe-app-podkop/src/podkop/methods/shell/index.ts index 8f8394f..7719605 100644 --- a/fe-app-podkop/src/podkop/methods/shell/index.ts +++ b/fe-app-podkop/src/podkop/methods/shell/index.ts @@ -44,4 +44,20 @@ export const PodkopShellMethods = { group, proxy, ]), + restart: async () => + callBaseMethod(Podkop.AvailableMethods.RESTART), + start: async () => + callBaseMethod(Podkop.AvailableMethods.START), + stop: async () => + callBaseMethod(Podkop.AvailableMethods.STOP), + enable: async () => + callBaseMethod(Podkop.AvailableMethods.ENABLE), + disable: async () => + callBaseMethod(Podkop.AvailableMethods.DISABLE), + globalCheck: async () => + callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), + showSingBoxConfig: async () => + callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), + checkLogs: async () => + callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), }; diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index 358e9a4..fc5f39f 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -21,6 +21,36 @@ export namespace ClashAPI { // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Podkop { + // Available commands: + // start Start podkop service + // stop Stop podkop service + // reload Reload podkop configuration + // restart Restart podkop service + // enable Enable podkop autostart + // disable Disable podkop autostart + // main Run main podkop process + // list_update Update domain lists + // check_proxy Check proxy connectivity + // check_nft Check NFT rules + // check_nft_rules Check NFT rules status + // check_sing_box Check sing-box installation and status + // check_github Check GitHub connectivity + // check_logs Show podkop logs from system journal + // check_sing_box_connections Show active sing-box connections + // check_sing_box_logs Show sing-box logs + // check_dnsmasq Check DNSMasq configuration + // check_fakeip Test FakeIP on router + // clash_api Clash API interface for managing proxies and groups + // show_config Display current podkop configuration + // show_version Show podkop version + // show_sing_box_config Show sing-box configuration + // show_sing_box_version Show sing-box version + // show_system_info Show system information + // get_status Get podkop service status + // get_sing_box_status Get sing-box service status + // check_dns_available Check DNS server availability + // global_check Run global system check + export enum AvailableMethods { CHECK_DNS_AVAILABLE = 'check_dns_available', CHECK_FAKEIP = 'check_fakeip', @@ -29,6 +59,14 @@ export namespace Podkop { CHECK_SING_BOX = 'check_sing_box', GET_SING_BOX_STATUS = 'get_sing_box_status', CLASH_API = 'clash_api', + RESTART = 'restart', + START = 'start', + STOP = 'stop', + ENABLE = 'enable', + DISABLE = 'disable', + GLOBAL_CHECK = 'global_check', + SHOW_SING_BOX_CONFIG = 'show_sing_box_config', + CHECK_LOGS = 'check_logs', } export enum AvailableClashAPIMethods { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index c7a8948..3d833eb 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -434,6 +434,14 @@ var Podkop; AvailableMethods2["CHECK_SING_BOX"] = "check_sing_box"; AvailableMethods2["GET_SING_BOX_STATUS"] = "get_sing_box_status"; AvailableMethods2["CLASH_API"] = "clash_api"; + AvailableMethods2["RESTART"] = "restart"; + AvailableMethods2["START"] = "start"; + AvailableMethods2["STOP"] = "stop"; + AvailableMethods2["ENABLE"] = "enable"; + AvailableMethods2["DISABLE"] = "disable"; + AvailableMethods2["GLOBAL_CHECK"] = "global_check"; + AvailableMethods2["SHOW_SING_BOX_CONFIG"] = "show_sing_box_config"; + AvailableMethods2["CHECK_LOGS"] = "check_logs"; })(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})); let AvailableClashAPIMethods; ((AvailableClashAPIMethods2) => { @@ -477,7 +485,15 @@ var PodkopShellMethods = { Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY, group, proxy - ]) + ]), + restart: async () => callBaseMethod(Podkop.AvailableMethods.RESTART), + start: async () => callBaseMethod(Podkop.AvailableMethods.START), + stop: async () => callBaseMethod(Podkop.AvailableMethods.STOP), + enable: async () => callBaseMethod(Podkop.AvailableMethods.ENABLE), + disable: async () => callBaseMethod(Podkop.AvailableMethods.DISABLE), + globalCheck: async () => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), + showSingBoxConfig: async () => callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), + checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS) }; // src/podkop/methods/custom/getDashboardSections.ts @@ -2745,7 +2761,7 @@ function renderDiagnosticSystemInfoWidget() { }, { key: "Luci App", - value: "1" + value: PODKOP_LUCI_APP_VERSION }, { key: "Sing-box", @@ -2789,6 +2805,8 @@ async function runChecks() { store.set({ diagnosticsRunAction: { loading: false } }); } } +async function test() { +} async function initController2() { onMount("diagnostic-status").then(() => { console.log("diagnostic controller initialized."); @@ -2798,6 +2816,7 @@ async function initController2() { renderDiagnosticRunActionWidget(); renderDiagnosticAvailableActionsWidget(); renderDiagnosticSystemInfoWidget(); + test(); }); } From b8ccb4abfafa80165878fb3a3e008bc988bccd3d Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Tue, 14 Oct 2025 23:47:31 +0300 Subject: [PATCH 059/121] feat: get latest podkop release --- podkop/files/usr/bin/podkop | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 01e0db9..25a392e 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1794,6 +1794,20 @@ get_status() { echo "{\"enabled\":$enabled,\"status\":\"$status\"}" } +get_latest_podkop_version() { + local latest_version + local current_version="$PODKOP_VERSION" + + latest_version=$(curl -m 3 -s https://api.github.com/repos/itdoginfo/podkop/releases/latest | grep '"tag_name":' | cut -d'"' -f4) + + if [ -z "$latest_version" ]; then + echo "{\"error\": \"Unable to fetch latest version\"}" + return 1 + fi + + echo "{\"current\": \"$current_version\", \"latest\": \"$latest_version\"}" | jq . +} + check_dns_available() { local dns_type dns_server bootstrap_dns_server config_get dns_type "settings" "dns_type" @@ -1801,7 +1815,7 @@ check_dns_available() { config_get bootstrap_dns_server "settings" "bootstrap_dns_server" local dns_status=0 - local local_dns_status=0 + local dns_on_router=0 local bootstrap_dns_status=0 local dhcp_has_dns_server=0 local domain="google.com" @@ -1842,7 +1856,7 @@ check_dns_available() { # Check if local DNS resolver is working if dig @127.0.0.1 "$domain" +timeout=2 +tries=1 > /dev/null 2>&1; then - local_dns_status=1 + dns_on_router=1 fi # Check bootstrap DNS server @@ -1857,7 +1871,7 @@ check_dns_available() { config_foreach check_dhcp_has_podkop_dns dnsmasq config_load "$PODKOP_CONFIG" - echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"dns_status\":$dns_status,\"local_dns_status\":$local_dns_status,\"bootstrap_dns_server\":\"$bootstrap_dns_server\",\"bootstrap_dns_status\":$bootstrap_dns_status,\"dhcp_has_dns_server\":$dhcp_has_dns_server}" | jq . + echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"dns_status\":$dns_status,\"dns_on_router\":$dns_on_router,\"bootstrap_dns_server\":\"$bootstrap_dns_server\",\"bootstrap_dns_status\":$bootstrap_dns_status,\"dhcp_has_dns_server\":$dhcp_has_dns_server}" | jq . } check_dhcp_has_podkop_dns() { @@ -2311,6 +2325,7 @@ Available commands: show_system_info Show system information get_status Get podkop service status get_sing_box_status Get sing-box service status + get_latest_podkop_version Get latest podkop version from GitHubs check_dns_available Check DNS server availability global_check Run global system check EOF @@ -2389,6 +2404,9 @@ get_status) get_sing_box_status) get_sing_box_status ;; +get_latest_podkop_version) + get_latest_podkop_version + ;; check_dns_available) check_dns_available ;; From 6772b838614c5fb32ac06b534eb68d0cf559ce0a Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 01:11:30 +0300 Subject: [PATCH 060/121] feat: implement most diagnostics actions --- fe-app-podkop/src/icons/index.ts | 9 + .../src/icons/renderCircleCheckBigIcon24.ts | 26 + .../src/icons/renderCirclePlayIcon24.ts | 28 + .../src/icons/renderCircleStopIcon24.ts | 32 + fe-app-podkop/src/icons/renderCogIcon24.ts | 34 + .../src/icons/renderLoaderCircleIcon24.ts | 2 - fe-app-podkop/src/icons/renderPauseIcon24.ts | 34 + fe-app-podkop/src/icons/renderPlayIcon24.ts | 23 + .../src/icons/renderRotateCcwIcon24.ts | 26 + fe-app-podkop/src/icons/renderSearchIcon24.ts | 22 + .../src/icons/renderSquareChartGanttIcon24.ts | 30 + .../src/partials/button/renderButton.ts | 69 ++ fe-app-podkop/src/partials/button/styles.ts | 33 + fe-app-podkop/src/partials/index.ts | 7 + .../podkop/methods/shell/callBaseMethod.ts | 15 +- .../src/podkop/methods/shell/index.ts | 21 +- .../src/podkop/services/store.service.ts | 10 + .../tabs/diagnostic/diagnostic.store.ts | 28 +- .../podkop/tabs/diagnostic/initController.ts | 169 ++++- .../partials/renderAvailableActions.ts | 116 +++- .../diagnostic/partials/renderRunAction.ts | 15 +- fe-app-podkop/src/styles.ts | 2 + .../luci-static/resources/view/podkop/main.js | 657 +++++++++++++++++- 23 files changed, 1346 insertions(+), 62 deletions(-) create mode 100644 fe-app-podkop/src/icons/renderCircleCheckBigIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderCirclePlayIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderCircleStopIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderCogIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderPauseIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderPlayIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderRotateCcwIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderSearchIcon24.ts create mode 100644 fe-app-podkop/src/icons/renderSquareChartGanttIcon24.ts create mode 100644 fe-app-podkop/src/partials/button/renderButton.ts create mode 100644 fe-app-podkop/src/partials/button/styles.ts create mode 100644 fe-app-podkop/src/partials/index.ts diff --git a/fe-app-podkop/src/icons/index.ts b/fe-app-podkop/src/icons/index.ts index 73c766a..71d9625 100644 --- a/fe-app-podkop/src/icons/index.ts +++ b/fe-app-podkop/src/icons/index.ts @@ -6,3 +6,12 @@ export * from './renderCircleXIcon24'; export * from './renderCheckIcon24'; export * from './renderXIcon24'; export * from './renderTriangleAlertIcon24'; +export * from './renderPauseIcon24'; +export * from './renderPlayIcon24'; +export * from './renderRotateCcwIcon24'; +export * from './renderCircleStopIcon24'; +export * from './renderCirclePlayIcon24'; +export * from './renderCircleCheckBigIcon24'; +export * from './renderSquareChartGanttIcon24'; +export * from './renderCogIcon24'; +export * from './renderSearchIcon24'; diff --git a/fe-app-podkop/src/icons/renderCircleCheckBigIcon24.ts b/fe-app-podkop/src/icons/renderCircleCheckBigIcon24.ts new file mode 100644 index 0000000..e353091 --- /dev/null +++ b/fe-app-podkop/src/icons/renderCircleCheckBigIcon24.ts @@ -0,0 +1,26 @@ +import { svgEl } from '../helpers'; + +export function renderCircleCheckBigIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-circle-check-big-icon lucide-circle-check-big', + }, + [ + svgEl('path', { + d: 'M21.801 10A10 10 0 1 1 17 3.335', + }), + svgEl('path', { + d: 'm9 11 3 3L22 4', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderCirclePlayIcon24.ts b/fe-app-podkop/src/icons/renderCirclePlayIcon24.ts new file mode 100644 index 0000000..a4102f1 --- /dev/null +++ b/fe-app-podkop/src/icons/renderCirclePlayIcon24.ts @@ -0,0 +1,28 @@ +import { svgEl } from '../helpers'; + +export function renderCirclePlayIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-circle-play-icon lucide-circle-play', + }, + [ + svgEl('path', { + d: 'M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z', + }), + svgEl('circle', { + cx: '12', + cy: '12', + r: '10', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderCircleStopIcon24.ts b/fe-app-podkop/src/icons/renderCircleStopIcon24.ts new file mode 100644 index 0000000..bb9e614 --- /dev/null +++ b/fe-app-podkop/src/icons/renderCircleStopIcon24.ts @@ -0,0 +1,32 @@ +import { svgEl } from '../helpers'; + +export function renderCircleStopIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-circle-stop-icon lucide-circle-stop', + }, + [ + svgEl('circle', { + cx: '12', + cy: '12', + r: '10', + }), + svgEl('rect', { + x: '9', + y: '9', + width: '6', + height: '6', + rx: '1', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderCogIcon24.ts b/fe-app-podkop/src/icons/renderCogIcon24.ts new file mode 100644 index 0000000..0093bbb --- /dev/null +++ b/fe-app-podkop/src/icons/renderCogIcon24.ts @@ -0,0 +1,34 @@ +import { svgEl } from '../helpers'; + +export function renderCogIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-cog-icon lucide-cog', + }, + [ + svgEl('path', { d: 'M11 10.27 7 3.34' }), + svgEl('path', { d: 'm11 13.73-4 6.93' }), + svgEl('path', { d: 'M12 22v-2' }), + svgEl('path', { d: 'M12 2v2' }), + svgEl('path', { d: 'M14 12h8' }), + svgEl('path', { d: 'm17 20.66-1-1.73' }), + svgEl('path', { d: 'm17 3.34-1 1.73' }), + svgEl('path', { d: 'M2 12h2' }), + svgEl('path', { d: 'm20.66 17-1.73-1' }), + svgEl('path', { d: 'm20.66 7-1.73 1' }), + svgEl('path', { d: 'm3.34 17 1.73-1' }), + svgEl('path', { d: 'm3.34 7 1.73 1' }), + svgEl('circle', { cx: '12', cy: '12', r: '2' }), + svgEl('circle', { cx: '12', cy: '12', r: '8' }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts b/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts index 927964d..e6ecd3d 100644 --- a/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts +++ b/fe-app-podkop/src/icons/renderLoaderCircleIcon24.ts @@ -6,8 +6,6 @@ export function renderLoaderCircleIcon24() { 'svg', { xmlns: NS, - width: '24', - height: '24', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', diff --git a/fe-app-podkop/src/icons/renderPauseIcon24.ts b/fe-app-podkop/src/icons/renderPauseIcon24.ts new file mode 100644 index 0000000..8b150c7 --- /dev/null +++ b/fe-app-podkop/src/icons/renderPauseIcon24.ts @@ -0,0 +1,34 @@ +import { svgEl } from '../helpers'; + +export function renderPauseIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-pause-icon lucide-pause', + }, + [ + svgEl('rect', { + x: '14', + y: '3', + width: '5', + height: '18', + rx: '1', + }), + svgEl('rect', { + x: '5', + y: '3', + width: '5', + height: '18', + rx: '1', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderPlayIcon24.ts b/fe-app-podkop/src/icons/renderPlayIcon24.ts new file mode 100644 index 0000000..46c161d --- /dev/null +++ b/fe-app-podkop/src/icons/renderPlayIcon24.ts @@ -0,0 +1,23 @@ +import { svgEl } from '../helpers'; + +export function renderPlayIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-play-icon lucide-play', + }, + [ + svgEl('path', { + d: 'M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderRotateCcwIcon24.ts b/fe-app-podkop/src/icons/renderRotateCcwIcon24.ts new file mode 100644 index 0000000..82a5d16 --- /dev/null +++ b/fe-app-podkop/src/icons/renderRotateCcwIcon24.ts @@ -0,0 +1,26 @@ +import { svgEl } from '../helpers'; + +export function renderRotateCcwIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-rotate-ccw-icon lucide-rotate-ccw', + }, + [ + svgEl('path', { + d: 'M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8', + }), + svgEl('path', { + d: 'M3 3v5h5', + }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderSearchIcon24.ts b/fe-app-podkop/src/icons/renderSearchIcon24.ts new file mode 100644 index 0000000..3025384 --- /dev/null +++ b/fe-app-podkop/src/icons/renderSearchIcon24.ts @@ -0,0 +1,22 @@ +import { svgEl } from '../helpers'; + +export function renderSearchIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-search-icon lucide-search', + }, + [ + svgEl('path', { d: 'm21 21-4.34-4.34' }), + svgEl('circle', { cx: '11', cy: '11', r: '8' }), + ], + ); +} diff --git a/fe-app-podkop/src/icons/renderSquareChartGanttIcon24.ts b/fe-app-podkop/src/icons/renderSquareChartGanttIcon24.ts new file mode 100644 index 0000000..16c500e --- /dev/null +++ b/fe-app-podkop/src/icons/renderSquareChartGanttIcon24.ts @@ -0,0 +1,30 @@ +import { svgEl } from '../helpers'; + +export function renderSquareChartGanttIcon24() { + const NS = 'http://www.w3.org/2000/svg'; + return svgEl( + 'svg', + { + xmlns: NS, + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': '2', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + class: 'lucide lucide-square-chart-gantt-icon lucide-square-chart-gantt', + }, + [ + svgEl('rect', { + width: '18', + height: '18', + x: '3', + y: '3', + rx: '2', + }), + svgEl('path', { d: 'M9 8h7' }), + svgEl('path', { d: 'M8 12h6' }), + svgEl('path', { d: 'M11 16h5' }), + ], + ); +} diff --git a/fe-app-podkop/src/partials/button/renderButton.ts b/fe-app-podkop/src/partials/button/renderButton.ts new file mode 100644 index 0000000..5912cc4 --- /dev/null +++ b/fe-app-podkop/src/partials/button/renderButton.ts @@ -0,0 +1,69 @@ +import { insertIf } from '../../helpers'; +import { renderLoaderCircleIcon24 } from '../../icons'; + +interface IRenderButtonProps { + classNames?: string[]; + disabled?: boolean; + loading?: boolean; + icon?: () => SVGSVGElement; + onClick: () => void; + text: string; +} + +export function renderButton({ + classNames = [], + disabled, + loading, + onClick, + text, + icon, +}: IRenderButtonProps) { + const hasIcon = !!loading || !!icon; + + function getWrappedIcon() { + const iconWrap = E('span', { + class: 'pdk-partial-button__icon', + }); + + if (loading) { + iconWrap.appendChild(renderLoaderCircleIcon24()); + + return iconWrap; + } + + if (icon) { + iconWrap.appendChild(icon()); + + return iconWrap; + } + + return iconWrap; + } + + function getClass() { + return [ + 'btn', + 'pdk-partial-button', + ...insertIf(Boolean(disabled), ['pdk-partial-button--disabled']), + ...insertIf(Boolean(loading), ['pdk-partial-button--loading']), + ...insertIf(Boolean(hasIcon), ['pdk-partial-button--with-icon']), + ...classNames, + ] + .filter(Boolean) + .join(' '); + } + + function getDisabled() { + if (loading || disabled) { + return true; + } + + return undefined; + } + + return E( + 'button', + { class: getClass(), disabled: getDisabled(), click: onClick }, + [...insertIf(hasIcon, [getWrappedIcon()]), E('span', {}, text)], + ); +} diff --git a/fe-app-podkop/src/partials/button/styles.ts b/fe-app-podkop/src/partials/button/styles.ts new file mode 100644 index 0000000..777ef0b --- /dev/null +++ b/fe-app-podkop/src/partials/button/styles.ts @@ -0,0 +1,33 @@ +// language=CSS +export const styles = ` +.pdk-partial-button { + text-align: center; +} + +.pdk-partial-button--with-icon { + display: flex; + align-items: center; + justify-content: center; +} + +.pdk-partial-button--loading { +} + +.pdk-partial-button--disabled { +} + +.pdk-partial-button__icon { + margin-right: 5px; +} + +.pdk-partial-button__icon { + display: flex; + align-items: center; + justify-content: center; +} + +.pdk-partial-button__icon svg { + width: 16px; + height: 16px; +} +`; diff --git a/fe-app-podkop/src/partials/index.ts b/fe-app-podkop/src/partials/index.ts new file mode 100644 index 0000000..3f1e135 --- /dev/null +++ b/fe-app-podkop/src/partials/index.ts @@ -0,0 +1,7 @@ +import { styles as ButtonStyles } from './button/styles'; + +export * from './button/renderButton'; + +export const PartialStyles = ` +${ButtonStyles} +`; diff --git a/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts b/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts index 37c1b37..e317584 100644 --- a/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts +++ b/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts @@ -12,10 +12,17 @@ export async function callBaseMethod( }); if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) as T, - }; + try { + return { + success: true, + data: JSON.parse(response.stdout) as T, + }; + } catch (_e) { + return { + success: true, + data: response.stdout as T, + }; + } } return { diff --git a/fe-app-podkop/src/podkop/methods/shell/index.ts b/fe-app-podkop/src/podkop/methods/shell/index.ts index 7719605..983712c 100644 --- a/fe-app-podkop/src/podkop/methods/shell/index.ts +++ b/fe-app-podkop/src/podkop/methods/shell/index.ts @@ -44,20 +44,15 @@ export const PodkopShellMethods = { group, proxy, ]), - restart: async () => - callBaseMethod(Podkop.AvailableMethods.RESTART), - start: async () => - callBaseMethod(Podkop.AvailableMethods.START), - stop: async () => - callBaseMethod(Podkop.AvailableMethods.STOP), - enable: async () => - callBaseMethod(Podkop.AvailableMethods.ENABLE), - disable: async () => - callBaseMethod(Podkop.AvailableMethods.DISABLE), + restart: async () => callBaseMethod(Podkop.AvailableMethods.RESTART), + start: async () => callBaseMethod(Podkop.AvailableMethods.START), + stop: async () => callBaseMethod(Podkop.AvailableMethods.STOP), + enable: async () => callBaseMethod(Podkop.AvailableMethods.ENABLE), + disable: async () => callBaseMethod(Podkop.AvailableMethods.DISABLE), globalCheck: async () => - callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), + callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), showSingBoxConfig: async () => - callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), + callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), checkLogs: async () => - callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), + callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), }; diff --git a/fe-app-podkop/src/podkop/services/store.service.ts b/fe-app-podkop/src/podkop/services/store.service.ts index 5093ba7..996630f 100644 --- a/fe-app-podkop/src/podkop/services/store.service.ts +++ b/fe-app-podkop/src/podkop/services/store.service.ts @@ -171,6 +171,16 @@ export interface StoreType { loading: boolean; }; diagnosticsChecks: Array; + diagnosticsActions: { + restart: { loading: boolean }; + start: { loading: boolean }; + stop: { loading: boolean }; + enable: { loading: boolean }; + disable: { loading: boolean }; + globalCheck: { loading: boolean }; + viewLogs: { loading: boolean }; + showSingBoxConfig: { loading: boolean }; + }; } const initialStore: StoreType = { diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts index b34bbc0..3ed5c1e 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts @@ -6,8 +6,34 @@ import { StoreType } from '../../services'; export const initialDiagnosticStore: Pick< StoreType, - 'diagnosticsChecks' | 'diagnosticsRunAction' + 'diagnosticsChecks' | 'diagnosticsRunAction' | 'diagnosticsActions' > = { + diagnosticsActions: { + restart: { + loading: false, + }, + start: { + loading: false, + }, + stop: { + loading: false, + }, + enable: { + loading: false, + }, + disable: { + loading: false, + }, + globalCheck: { + loading: false, + }, + viewLogs: { + loading: false, + }, + showSingBoxConfig: { + loading: false, + }, + }, diagnosticsRunAction: { loading: false }, diagnosticsChecks: [ { diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index 16e8cd9..d69c636 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -11,6 +11,7 @@ import { renderRunAction, renderSystemInfo, } from './partials'; +import { PodkopShellMethods } from '../../methods'; function renderDiagnosticsChecks() { console.log('renderDiagnosticsChecks'); @@ -44,12 +45,174 @@ function renderDiagnosticRunActionWidget() { }); } +async function handleRestart() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + restart: { loading: true }, + }, + }); + + try { + await PodkopShellMethods.restart(); + } catch (e) { + console.log('handleRestart - e', e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + restart: { loading: false }, + }, + }); + location.reload(); + } +} + +async function handleStop() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + stop: { loading: true }, + }, + }); + + try { + await PodkopShellMethods.stop(); + } catch (e) { + console.log('handleStop - e', e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + stop: { loading: false }, + }, + }); + // TODO actualize dashboard + } +} + +async function handleStart() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + start: { loading: true }, + }, + }); + + try { + await PodkopShellMethods.start(); + } catch (e) { + console.log('handleStart - e', e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + start: { loading: false }, + }, + }); + location.reload(); + } +} + +async function handleEnable() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + enable: { loading: true }, + }, + }); + + try { + await PodkopShellMethods.enable(); + } catch (e) { + console.log('handleEnable - e', e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + enable: { loading: false }, + }, + }); + //TODO actualize dashboard + } +} + +async function handleDisable() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + disable: { loading: true }, + }, + }); + + try { + await PodkopShellMethods.disable(); + } catch (e) { + console.log('handleDisable - e', e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + disable: { loading: false }, + }, + }); + //TODO actualize dashboard + } +} + function renderDiagnosticAvailableActionsWidget() { + const diagnosticsActions = store.get().diagnosticsActions; console.log('renderDiagnosticActionsWidget'); const container = document.getElementById('pdk_diagnostic-page-actions'); - const renderedActions = renderAvailableActions(); + const renderedActions = renderAvailableActions({ + restart: { + loading: diagnosticsActions.restart.loading, + visible: true, + onClick: handleRestart, + }, + start: { + loading: diagnosticsActions.start.loading, + visible: true, + onClick: handleStart, + }, + stop: { + loading: diagnosticsActions.stop.loading, + visible: true, + onClick: handleStop, + }, + enable: { + loading: diagnosticsActions.enable.loading, + visible: true, + onClick: handleEnable, + }, + disable: { + loading: diagnosticsActions.disable.loading, + visible: true, + onClick: handleDisable, + }, + globalCheck: { + loading: diagnosticsActions.globalCheck.loading, + visible: true, + onClick: () => {}, + }, + viewLogs: { + loading: diagnosticsActions.viewLogs.loading, + visible: true, + onClick: () => {}, + }, + showSingBoxConfig: { + loading: diagnosticsActions.showSingBoxConfig.loading, + visible: true, + onClick: () => {}, + }, + }); return preserveScrollForPage(() => { container!.replaceChildren(renderedActions); @@ -103,6 +266,10 @@ async function onStoreUpdate( if (diff.diagnosticsRunAction) { renderDiagnosticRunActionWidget(); } + + if (diff.diagnosticsActions) { + renderDiagnosticAvailableActionsWidget(); + } } async function runChecks() { diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts index a8e2ed0..e24f436 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts @@ -1,11 +1,113 @@ -export function renderAvailableActions() { +import { renderButton } from '../../../../partials'; +import { + renderCircleCheckBigIcon24, + renderCirclePlayIcon24, + renderCircleStopIcon24, + renderCogIcon24, + renderPauseIcon24, + renderPlayIcon24, + renderRotateCcwIcon24, + renderSquareChartGanttIcon24, +} from '../../../../icons'; +import { insertIf } from '../../../../helpers'; + +interface ActionProps { + loading: boolean; + visible: boolean; + onClick: () => void; +} + +interface IRenderAvailableActionsProps { + restart: ActionProps; + start: ActionProps; + stop: ActionProps; + enable: ActionProps; + disable: ActionProps; + globalCheck: ActionProps; + viewLogs: ActionProps; + showSingBoxConfig: ActionProps; +} + +export function renderAvailableActions({ + restart, + start, + stop, + enable, + disable, + globalCheck, + viewLogs, + showSingBoxConfig, +}: IRenderAvailableActionsProps) { return E('div', { class: 'pdk_diagnostic-page__right-bar__actions' }, [ E('b', {}, 'Available actions'), - E('button', { class: 'btn' }, 'Restart podkop'), - E('button', { class: 'btn' }, 'Stop podkop'), - E('button', { class: 'btn' }, 'Disable podkop'), - E('button', { class: 'btn' }, 'Get global check'), - E('button', { class: 'btn' }, 'View logs'), - E('button', { class: 'btn' }, 'Show sing-box config'), + ...insertIf(restart.visible, [ + renderButton({ + classNames: ['cbi-button-apply'], + onClick: restart.onClick, + icon: renderRotateCcwIcon24, + text: 'Restart podkop', + loading: restart.loading, + }), + ]), + ...insertIf(stop.visible, [ + renderButton({ + classNames: ['cbi-button-remove'], + onClick: stop.onClick, + icon: renderCircleStopIcon24, + text: 'Stop podkop', + loading: stop.loading, + }), + ]), + ...insertIf(start.visible, [ + renderButton({ + classNames: ['cbi-button-save'], + onClick: start.onClick, + icon: renderCirclePlayIcon24, + text: 'Start podkop', + loading: start.loading, + }), + ]), + ...insertIf(disable.visible, [ + renderButton({ + classNames: ['cbi-button-remove'], + onClick: disable.onClick, + icon: renderPauseIcon24, + text: 'Disable podkop', + loading: disable.loading, + }), + ]), + ...insertIf(enable.visible, [ + renderButton({ + classNames: ['cbi-button-save'], + onClick: enable.onClick, + icon: renderPlayIcon24, + text: 'Enable podkop', + loading: enable.loading, + }), + ]), + ...insertIf(globalCheck.visible, [ + renderButton({ + onClick: globalCheck.onClick, + icon: renderCircleCheckBigIcon24, + text: 'Get global check', + loading: globalCheck.loading, + }), + ]), + ...insertIf(viewLogs.visible, [ + renderButton({ + onClick: viewLogs.onClick, + icon: renderSquareChartGanttIcon24, + text: 'View logs', + loading: viewLogs.loading, + }), + ]), + ...insertIf(showSingBoxConfig.visible, [ + renderButton({ + onClick: showSingBoxConfig.onClick, + icon: renderCogIcon24, + text: 'Show sing-box config', + loading: showSingBoxConfig.loading, + }), + ]), ]); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts index cada852..4c9ee69 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts @@ -1,3 +1,6 @@ +import { renderButton } from '../../../../partials'; +import { renderSearchIcon24 } from '../../../../icons'; + interface IRenderDiagnosticRunActionProps { loading: boolean; click: () => void; @@ -8,10 +11,12 @@ export function renderRunAction({ click, }: IRenderDiagnosticRunActionProps) { return E('div', { class: 'pdk_diagnostic-page__run_check_wrapper' }, [ - E( - 'button', - { class: 'btn', disabled: loading ? true : undefined, click }, - loading ? _('Running... please wait') : _('Run Diagnostic'), - ), + renderButton({ + text: 'Run Diagnostic', + onClick: click, + icon: renderSearchIcon24, + loading, + classNames: ['cbi-button-apply'], + }), ]); } diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index 2518fd8..5d562e9 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -1,9 +1,11 @@ // language=CSS import { DashboardTab, DiagnosticTab } from './podkop'; +import { PartialStyles } from './partials'; export const GlobalStyles = ` ${DashboardTab.styles} ${DiagnosticTab.styles} +${PartialStyles} /* Hide extra H3 for settings tab */ diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 3d833eb..6e85e56 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -411,10 +411,17 @@ async function callBaseMethod(method, args = []) { timeout: 1e4 }); if (response.stdout) { - return { - success: true, - data: JSON.parse(response.stdout) - }; + try { + return { + success: true, + data: JSON.parse(response.stdout) + }; + } catch (_e) { + return { + success: true, + data: response.stdout + }; + } } return { success: false, @@ -895,6 +902,32 @@ var DIAGNOSTICS_CHECKS_MAP = { // src/podkop/tabs/diagnostic/diagnostic.store.ts var initialDiagnosticStore = { + diagnosticsActions: { + restart: { + loading: false + }, + start: { + loading: false + }, + stop: { + loading: false + }, + enable: { + loading: false + }, + disable: { + loading: false + }, + globalCheck: { + loading: false + }, + viewLogs: { + loading: false + }, + showSingBoxConfig: { + loading: false + } + }, diagnosticsRunAction: { loading: false }, diagnosticsChecks: [ { @@ -2283,19 +2316,40 @@ async function runFakeIPCheck() { }); } -// src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts -function renderAvailableActions() { - return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [ - E("b", {}, "Available actions"), - E("button", { class: "btn" }, "Restart podkop"), - E("button", { class: "btn" }, "Stop podkop"), - E("button", { class: "btn" }, "Disable podkop"), - E("button", { class: "btn" }, "Get global check"), - E("button", { class: "btn" }, "View logs"), - E("button", { class: "btn" }, "Show sing-box config") - ]); +// src/partials/button/styles.ts +var styles2 = ` +.pdk-partial-button { + text-align: center; } +.pdk-partial-button--with-icon { + display: flex; + align-items: center; + justify-content: center; +} + +.pdk-partial-button--loading { +} + +.pdk-partial-button--disabled { +} + +.pdk-partial-button__icon { + margin-right: 5px; +} + +.pdk-partial-button__icon { + display: flex; + align-items: center; + justify-content: center; +} + +.pdk-partial-button__icon svg { + width: 16px; + height: 16px; +} +`; + // src/icons/renderLoaderCircleIcon24.ts function renderLoaderCircleIcon24() { const NS = "http://www.w3.org/2000/svg"; @@ -2303,8 +2357,6 @@ function renderLoaderCircleIcon24() { "svg", { xmlns: NS, - width: "24", - height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", @@ -2532,6 +2584,398 @@ function renderTriangleAlertIcon24() { ); } +// src/icons/renderPauseIcon24.ts +function renderPauseIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-pause-icon lucide-pause" + }, + [ + svgEl("rect", { + x: "14", + y: "3", + width: "5", + height: "18", + rx: "1" + }), + svgEl("rect", { + x: "5", + y: "3", + width: "5", + height: "18", + rx: "1" + }) + ] + ); +} + +// src/icons/renderPlayIcon24.ts +function renderPlayIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-play-icon lucide-play" + }, + [ + svgEl("path", { + d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" + }) + ] + ); +} + +// src/icons/renderRotateCcwIcon24.ts +function renderRotateCcwIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-rotate-ccw-icon lucide-rotate-ccw" + }, + [ + svgEl("path", { + d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" + }), + svgEl("path", { + d: "M3 3v5h5" + }) + ] + ); +} + +// src/icons/renderCircleStopIcon24.ts +function renderCircleStopIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-circle-stop-icon lucide-circle-stop" + }, + [ + svgEl("circle", { + cx: "12", + cy: "12", + r: "10" + }), + svgEl("rect", { + x: "9", + y: "9", + width: "6", + height: "6", + rx: "1" + }) + ] + ); +} + +// src/icons/renderCirclePlayIcon24.ts +function renderCirclePlayIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-circle-play-icon lucide-circle-play" + }, + [ + svgEl("path", { + d: "M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z" + }), + svgEl("circle", { + cx: "12", + cy: "12", + r: "10" + }) + ] + ); +} + +// src/icons/renderCircleCheckBigIcon24.ts +function renderCircleCheckBigIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-circle-check-big-icon lucide-circle-check-big" + }, + [ + svgEl("path", { + d: "M21.801 10A10 10 0 1 1 17 3.335" + }), + svgEl("path", { + d: "m9 11 3 3L22 4" + }) + ] + ); +} + +// src/icons/renderSquareChartGanttIcon24.ts +function renderSquareChartGanttIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-square-chart-gantt-icon lucide-square-chart-gantt" + }, + [ + svgEl("rect", { + width: "18", + height: "18", + x: "3", + y: "3", + rx: "2" + }), + svgEl("path", { d: "M9 8h7" }), + svgEl("path", { d: "M8 12h6" }), + svgEl("path", { d: "M11 16h5" }) + ] + ); +} + +// src/icons/renderCogIcon24.ts +function renderCogIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-cog-icon lucide-cog" + }, + [ + svgEl("path", { d: "M11 10.27 7 3.34" }), + svgEl("path", { d: "m11 13.73-4 6.93" }), + svgEl("path", { d: "M12 22v-2" }), + svgEl("path", { d: "M12 2v2" }), + svgEl("path", { d: "M14 12h8" }), + svgEl("path", { d: "m17 20.66-1-1.73" }), + svgEl("path", { d: "m17 3.34-1 1.73" }), + svgEl("path", { d: "M2 12h2" }), + svgEl("path", { d: "m20.66 17-1.73-1" }), + svgEl("path", { d: "m20.66 7-1.73 1" }), + svgEl("path", { d: "m3.34 17 1.73-1" }), + svgEl("path", { d: "m3.34 7 1.73 1" }), + svgEl("circle", { cx: "12", cy: "12", r: "2" }), + svgEl("circle", { cx: "12", cy: "12", r: "8" }) + ] + ); +} + +// src/icons/renderSearchIcon24.ts +function renderSearchIcon24() { + const NS = "http://www.w3.org/2000/svg"; + return svgEl( + "svg", + { + xmlns: NS, + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + class: "lucide lucide-search-icon lucide-search" + }, + [ + svgEl("path", { d: "m21 21-4.34-4.34" }), + svgEl("circle", { cx: "11", cy: "11", r: "8" }) + ] + ); +} + +// src/partials/button/renderButton.ts +function renderButton({ + classNames = [], + disabled, + loading, + onClick, + text, + icon +}) { + const hasIcon = !!loading || !!icon; + function getWrappedIcon() { + const iconWrap = E("span", { + class: "pdk-partial-button__icon" + }); + if (loading) { + iconWrap.appendChild(renderLoaderCircleIcon24()); + return iconWrap; + } + if (icon) { + iconWrap.appendChild(icon()); + return iconWrap; + } + return iconWrap; + } + function getClass() { + return [ + "btn", + "pdk-partial-button", + ...insertIf(Boolean(disabled), ["pdk-partial-button--disabled"]), + ...insertIf(Boolean(loading), ["pdk-partial-button--loading"]), + ...insertIf(Boolean(hasIcon), ["pdk-partial-button--with-icon"]), + ...classNames + ].filter(Boolean).join(" "); + } + function getDisabled() { + if (loading || disabled) { + return true; + } + return void 0; + } + return E( + "button", + { class: getClass(), disabled: getDisabled(), click: onClick }, + [...insertIf(hasIcon, [getWrappedIcon()]), E("span", {}, text)] + ); +} + +// src/partials/index.ts +var PartialStyles = ` +${styles2} +`; + +// src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts +function renderAvailableActions({ + restart, + start, + stop, + enable, + disable, + globalCheck, + viewLogs, + showSingBoxConfig +}) { + return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [ + E("b", {}, "Available actions"), + ...insertIf(restart.visible, [ + renderButton({ + classNames: ["cbi-button-apply"], + onClick: restart.onClick, + icon: renderRotateCcwIcon24, + text: "Restart podkop", + loading: restart.loading + }) + ]), + ...insertIf(stop.visible, [ + renderButton({ + classNames: ["cbi-button-remove"], + onClick: stop.onClick, + icon: renderCircleStopIcon24, + text: "Stop podkop", + loading: stop.loading + }) + ]), + ...insertIf(start.visible, [ + renderButton({ + classNames: ["cbi-button-save"], + onClick: start.onClick, + icon: renderCirclePlayIcon24, + text: "Start podkop", + loading: start.loading + }) + ]), + ...insertIf(disable.visible, [ + renderButton({ + classNames: ["cbi-button-remove"], + onClick: disable.onClick, + icon: renderPauseIcon24, + text: "Disable podkop", + loading: disable.loading + }) + ]), + ...insertIf(enable.visible, [ + renderButton({ + classNames: ["cbi-button-save"], + onClick: enable.onClick, + icon: renderPlayIcon24, + text: "Enable podkop", + loading: enable.loading + }) + ]), + ...insertIf(globalCheck.visible, [ + renderButton({ + onClick: globalCheck.onClick, + icon: renderCircleCheckBigIcon24, + text: "Get global check", + loading: globalCheck.loading + }) + ]), + ...insertIf(viewLogs.visible, [ + renderButton({ + onClick: viewLogs.onClick, + icon: renderSquareChartGanttIcon24, + text: "View logs", + loading: viewLogs.loading + }) + ]), + ...insertIf(showSingBoxConfig.visible, [ + renderButton({ + onClick: showSingBoxConfig.onClick, + icon: renderCogIcon24, + text: "Show sing-box config", + loading: showSingBoxConfig.loading + }) + ]) + ]); +} + // src/podkop/tabs/diagnostic/partials/renderCheckSection.ts function renderCheckSummary(items) { if (!items.length) { @@ -2693,11 +3137,13 @@ function renderRunAction({ click }) { return E("div", { class: "pdk_diagnostic-page__run_check_wrapper" }, [ - E( - "button", - { class: "btn", disabled: loading ? true : void 0, click }, - loading ? _("Running... please wait") : _("Run Diagnostic") - ) + renderButton({ + text: "Run Diagnostic", + onClick: click, + icon: renderSearchIcon24, + loading, + classNames: ["cbi-button-apply"] + }) ]); } @@ -2742,10 +3188,162 @@ function renderDiagnosticRunActionWidget() { container.replaceChildren(renderedAction); }); } +async function handleRestart() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + restart: { loading: true } + } + }); + try { + await PodkopShellMethods.restart(); + } catch (e) { + console.log("handleRestart - e", e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + restart: { loading: false } + } + }); + location.reload(); + } +} +async function handleStop() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + stop: { loading: true } + } + }); + try { + await PodkopShellMethods.stop(); + } catch (e) { + console.log("handleStop - e", e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + stop: { loading: false } + } + }); + } +} +async function handleStart() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + start: { loading: true } + } + }); + try { + await PodkopShellMethods.start(); + } catch (e) { + console.log("handleStart - e", e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + start: { loading: false } + } + }); + location.reload(); + } +} +async function handleEnable() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + enable: { loading: true } + } + }); + try { + await PodkopShellMethods.enable(); + } catch (e) { + console.log("handleEnable - e", e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + enable: { loading: false } + } + }); + } +} +async function handleDisable() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + disable: { loading: true } + } + }); + try { + await PodkopShellMethods.disable(); + } catch (e) { + console.log("handleDisable - e", e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + disable: { loading: false } + } + }); + } +} function renderDiagnosticAvailableActionsWidget() { + const diagnosticsActions = store.get().diagnosticsActions; console.log("renderDiagnosticActionsWidget"); const container = document.getElementById("pdk_diagnostic-page-actions"); - const renderedActions = renderAvailableActions(); + const renderedActions = renderAvailableActions({ + restart: { + loading: diagnosticsActions.restart.loading, + visible: true, + onClick: handleRestart + }, + start: { + loading: diagnosticsActions.start.loading, + visible: true, + onClick: handleStart + }, + stop: { + loading: diagnosticsActions.stop.loading, + visible: true, + onClick: handleStop + }, + enable: { + loading: diagnosticsActions.enable.loading, + visible: true, + onClick: handleEnable + }, + disable: { + loading: diagnosticsActions.disable.loading, + visible: true, + onClick: handleDisable + }, + globalCheck: { + loading: diagnosticsActions.globalCheck.loading, + visible: true, + onClick: () => { + } + }, + viewLogs: { + loading: diagnosticsActions.viewLogs.loading, + visible: true, + onClick: () => { + } + }, + showSingBoxConfig: { + loading: diagnosticsActions.showSingBoxConfig.loading, + visible: true, + onClick: () => { + } + } + }); return preserveScrollForPage(() => { container.replaceChildren(renderedActions); }); @@ -2761,7 +3359,7 @@ function renderDiagnosticSystemInfoWidget() { }, { key: "Luci App", - value: PODKOP_LUCI_APP_VERSION + value: "1" }, { key: "Sing-box", @@ -2788,6 +3386,9 @@ async function onStoreUpdate2(next, prev, diff) { if (diff.diagnosticsRunAction) { renderDiagnosticRunActionWidget(); } + if (diff.diagnosticsActions) { + renderDiagnosticAvailableActionsWidget(); + } } async function runChecks() { try { @@ -2805,8 +3406,6 @@ async function runChecks() { store.set({ diagnosticsRunAction: { loading: false } }); } } -async function test() { -} async function initController2() { onMount("diagnostic-status").then(() => { console.log("diagnostic controller initialized."); @@ -2816,12 +3415,11 @@ async function initController2() { renderDiagnosticRunActionWidget(); renderDiagnosticAvailableActionsWidget(); renderDiagnosticSystemInfoWidget(); - test(); }); } // src/podkop/tabs/diagnostic/styles.ts -var styles2 = ` +var styles3 = ` #cbi-podkop-diagnostic-_mount_node > div { width: 100%; @@ -2967,13 +3565,14 @@ var styles2 = ` var DiagnosticTab = { render: render2, initController: initController2, - styles: styles2 + styles: styles3 }; // src/styles.ts var GlobalStyles = ` ${DashboardTab.styles} ${DiagnosticTab.styles} +${PartialStyles} /* Hide extra H3 for settings tab */ From 5087be83d32471085f2c45fbf9aea98de183fda7 Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 01:18:32 +0300 Subject: [PATCH 061/121] feat: adapt diagnostics page to mobile --- fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts | 6 ++++++ .../htdocs/luci-static/resources/view/podkop/main.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts index 7fe2c21..d2d3dab 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts @@ -16,6 +16,12 @@ export const styles = ` align-items: start; } +@media (max-width: 800px) { + .pdk_diagnostic-page { + grid-template-columns: 1fr; + } +} + .pdk_diagnostic-page__right-bar { display: grid; grid-template-columns: 1fr; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 6e85e56..1bc7c4c 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -3436,6 +3436,12 @@ var styles3 = ` align-items: start; } +@media (max-width: 800px) { + .pdk_diagnostic-page { + grid-template-columns: 1fr; + } +} + .pdk_diagnostic-page__right-bar { display: grid; grid-template-columns: 1fr; From 53b71ec4b0b9eace32c8ebebf8904a437c342d3a Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 01:21:49 +0300 Subject: [PATCH 062/121] fix: change dns_on_router params --- .../src/podkop/tabs/diagnostic/checks/runDnsCheck.ts | 6 +++--- fe-app-podkop/src/podkop/types.ts | 2 +- .../htdocs/luci-static/resources/view/podkop/main.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index 67d7c8b..f879330 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -34,12 +34,12 @@ export async function runDnsCheck() { const data = dnsChecks.data; const allGood = - Boolean(data.local_dns_status) && + Boolean(data.dns_on_router) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); const atLeastOneGood = - Boolean(data.local_dns_status) || + Boolean(data.dns_on_router) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); @@ -80,7 +80,7 @@ export async function runDnsCheck() { value: `${data.dns_server} [${data.dns_type}]`, }, { - state: data.local_dns_status ? 'success' : 'error', + state: data.dns_on_router ? 'success' : 'error', key: _('Local DNS'), value: '', }, diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index fc5f39f..6300551 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -148,7 +148,7 @@ export namespace Podkop { dns_type: 'udp' | 'doh' | 'dot'; dns_server: string; dns_status: 0 | 1; - local_dns_status: 0 | 1; + dns_on_router: 0 | 1; bootstrap_dns_server: string; bootstrap_dns_status: 0 | 1; dhcp_has_dns_server: 0 | 1; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 1bc7c4c..6945a52 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2024,8 +2024,8 @@ async function runDnsCheck() { throw new Error("DNS checks failed"); } const data = dnsChecks.data; - const allGood = Boolean(data.local_dns_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); - const atLeastOneGood = Boolean(data.local_dns_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); + const allGood = Boolean(data.dns_on_router) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); + const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); console.log("dnsChecks", dnsChecks); function getStatus() { if (allGood) { @@ -2059,7 +2059,7 @@ async function runDnsCheck() { value: `${data.dns_server} [${data.dns_type}]` }, { - state: data.local_dns_status ? "success" : "error", + state: data.dns_on_router ? "success" : "error", key: _("Local DNS"), value: "" } From d51ac63c94f19719965a6163a36774009a20ba7a Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Wed, 15 Oct 2025 11:51:27 +0300 Subject: [PATCH 063/121] feat: one method for system info --- podkop/files/usr/bin/podkop | 58 ++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 25a392e..9a3aab7 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1736,6 +1736,44 @@ show_system_info() { cat /tmp/sysinfo/model } +get_system_info() { + local podkop_version podkop_latest_version luci_app_version sing_box_version openwrt_version device_model + + podkop_version="$PODKOP_VERSION" + + podkop_latest_version=$(curl -m 3 -s https://api.github.com/repos/itdoginfo/podkop/releases/latest | grep '"tag_name":' | cut -d'"' -f4) + [ -z "$podkop_latest_version" ] && podkop_latest_version="unknown" + + if [ -f /www/luci-static/resources/view/podkop/main.js ]; then + luci_app_version=$(grep 'var PODKOP_LUCI_APP_VERSION' /www/luci-static/resources/view/podkop/main.js | cut -d'"' -f2) + else + luci_app_version="not installed" + fi + + if command -v sing-box >/dev/null 2>&1; then + sing_box_version=$(sing-box version 2>/dev/null | head -n 1 | awk '{print $3}') + [ -z "$sing_box_version" ] && sing_box_version="unknown" + else + sing_box_version="not installed" + fi + + if [ -f /etc/os-release ]; then + openwrt_version=$(grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2) + [ -z "$openwrt_version" ] && openwrt_version="unknown" + else + openwrt_version="unknown" + fi + + if [ -f /tmp/sysinfo/model ]; then + device_model=$(cat /tmp/sysinfo/model) + [ -z "$device_model" ] && device_model="unknown" + else + device_model="unknown" + fi + + echo "{\"podkop_version\": \"$podkop_version\", \"podkop_latest_version\": \"$podkop_latest_version\", \"luci_app_version\": \"$luci_app_version\", \"sing_box_version\": \"$sing_box_version\", \"openwrt_version\": \"$openwrt_version\", \"device_model\": \"$device_model\"}" | jq . +} + get_sing_box_status() { local running=0 local enabled=0 @@ -1794,20 +1832,6 @@ get_status() { echo "{\"enabled\":$enabled,\"status\":\"$status\"}" } -get_latest_podkop_version() { - local latest_version - local current_version="$PODKOP_VERSION" - - latest_version=$(curl -m 3 -s https://api.github.com/repos/itdoginfo/podkop/releases/latest | grep '"tag_name":' | cut -d'"' -f4) - - if [ -z "$latest_version" ]; then - echo "{\"error\": \"Unable to fetch latest version\"}" - return 1 - fi - - echo "{\"current\": \"$current_version\", \"latest\": \"$latest_version\"}" | jq . -} - check_dns_available() { local dns_type dns_server bootstrap_dns_server config_get dns_type "settings" "dns_type" @@ -2325,7 +2349,7 @@ Available commands: show_system_info Show system information get_status Get podkop service status get_sing_box_status Get sing-box service status - get_latest_podkop_version Get latest podkop version from GitHubs + get_system_info Get system information in JSON format check_dns_available Check DNS server availability global_check Run global system check EOF @@ -2404,8 +2428,8 @@ get_status) get_sing_box_status) get_sing_box_status ;; -get_latest_podkop_version) - get_latest_podkop_version +get_system_info) + get_system_info ;; check_dns_available) check_dns_available From 46ec79e003c60154f73d9d7d0793e24532e5c50e Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 13:11:29 +0300 Subject: [PATCH 064/121] fix: change command for enable/disable actions --- .../src/podkop/methods/shell/callBaseMethod.ts | 3 ++- fe-app-podkop/src/podkop/methods/shell/index.ts | 14 ++++++++++++-- .../luci-static/resources/view/podkop/main.js | 16 ++++++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts b/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts index e317584..04f1ef9 100644 --- a/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts +++ b/fe-app-podkop/src/podkop/methods/shell/callBaseMethod.ts @@ -4,9 +4,10 @@ import { Podkop } from '../../types'; export async function callBaseMethod( method: Podkop.AvailableMethods, args: string[] = [], + command: string = '/usr/bin/podkop', ): Promise> { const response = await executeShellCommand({ - command: '/usr/bin/podkop', + command, args: [method as string, ...args], timeout: 10000, }); diff --git a/fe-app-podkop/src/podkop/methods/shell/index.ts b/fe-app-podkop/src/podkop/methods/shell/index.ts index 983712c..d4f3eb4 100644 --- a/fe-app-podkop/src/podkop/methods/shell/index.ts +++ b/fe-app-podkop/src/podkop/methods/shell/index.ts @@ -47,8 +47,18 @@ export const PodkopShellMethods = { restart: async () => callBaseMethod(Podkop.AvailableMethods.RESTART), start: async () => callBaseMethod(Podkop.AvailableMethods.START), stop: async () => callBaseMethod(Podkop.AvailableMethods.STOP), - enable: async () => callBaseMethod(Podkop.AvailableMethods.ENABLE), - disable: async () => callBaseMethod(Podkop.AvailableMethods.DISABLE), + enable: async () => + callBaseMethod( + Podkop.AvailableMethods.ENABLE, + [], + '/etc/init.d/podkop', + ), + disable: async () => + callBaseMethod( + Podkop.AvailableMethods.DISABLE, + [], + '/etc/init.d/podkop', + ), globalCheck: async () => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), showSingBoxConfig: async () => diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 6945a52..1720eee 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -404,9 +404,9 @@ async function getConfigSections() { } // src/podkop/methods/shell/callBaseMethod.ts -async function callBaseMethod(method, args = []) { +async function callBaseMethod(method, args = [], command = "/usr/bin/podkop") { const response = await executeShellCommand({ - command: "/usr/bin/podkop", + command, args: [method, ...args], timeout: 1e4 }); @@ -496,8 +496,16 @@ var PodkopShellMethods = { restart: async () => callBaseMethod(Podkop.AvailableMethods.RESTART), start: async () => callBaseMethod(Podkop.AvailableMethods.START), stop: async () => callBaseMethod(Podkop.AvailableMethods.STOP), - enable: async () => callBaseMethod(Podkop.AvailableMethods.ENABLE), - disable: async () => callBaseMethod(Podkop.AvailableMethods.DISABLE), + enable: async () => callBaseMethod( + Podkop.AvailableMethods.ENABLE, + [], + "/etc/init.d/podkop" + ), + disable: async () => callBaseMethod( + Podkop.AvailableMethods.DISABLE, + [], + "/etc/init.d/podkop" + ), globalCheck: async () => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), showSingBoxConfig: async () => callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS) From b2a6971700411745bcee0364aedba41b42995fe0 Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 14:33:46 +0300 Subject: [PATCH 065/121] fix: change command fir start/stop/restart actions --- .../src/podkop/methods/shell/index.ts | 21 ++++++++++++++++--- .../luci-static/resources/view/podkop/main.js | 18 +++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/fe-app-podkop/src/podkop/methods/shell/index.ts b/fe-app-podkop/src/podkop/methods/shell/index.ts index d4f3eb4..eca780c 100644 --- a/fe-app-podkop/src/podkop/methods/shell/index.ts +++ b/fe-app-podkop/src/podkop/methods/shell/index.ts @@ -44,9 +44,24 @@ export const PodkopShellMethods = { group, proxy, ]), - restart: async () => callBaseMethod(Podkop.AvailableMethods.RESTART), - start: async () => callBaseMethod(Podkop.AvailableMethods.START), - stop: async () => callBaseMethod(Podkop.AvailableMethods.STOP), + restart: async () => + callBaseMethod( + Podkop.AvailableMethods.RESTART, + [], + '/etc/init.d/podkop', + ), + start: async () => + callBaseMethod( + Podkop.AvailableMethods.START, + [], + '/etc/init.d/podkop', + ), + stop: async () => + callBaseMethod( + Podkop.AvailableMethods.STOP, + [], + '/etc/init.d/podkop', + ), enable: async () => callBaseMethod( Podkop.AvailableMethods.ENABLE, diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 1720eee..67add3f 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -493,9 +493,21 @@ var PodkopShellMethods = { group, proxy ]), - restart: async () => callBaseMethod(Podkop.AvailableMethods.RESTART), - start: async () => callBaseMethod(Podkop.AvailableMethods.START), - stop: async () => callBaseMethod(Podkop.AvailableMethods.STOP), + restart: async () => callBaseMethod( + Podkop.AvailableMethods.RESTART, + [], + "/etc/init.d/podkop" + ), + start: async () => callBaseMethod( + Podkop.AvailableMethods.START, + [], + "/etc/init.d/podkop" + ), + stop: async () => callBaseMethod( + Podkop.AvailableMethods.STOP, + [], + "/etc/init.d/podkop" + ), enable: async () => callBaseMethod( Podkop.AvailableMethods.ENABLE, [], From c35a174708180a1298c31086140d92da1bed81a0 Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 15:40:03 +0300 Subject: [PATCH 066/121] feat: add dhcp_has_dns_server displaying --- .../podkop/tabs/diagnostic/checks/runDnsCheck.ts | 15 +++++++++++---- .../luci-static/resources/view/podkop/main.js | 11 ++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index f879330..58d9f56 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -35,13 +35,15 @@ export async function runDnsCheck() { const allGood = Boolean(data.dns_on_router) && + Boolean(data.dhcp_has_dns_server) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); const atLeastOneGood = - Boolean(data.dns_on_router) || - Boolean(data.bootstrap_dns_status) || - Boolean(data.dns_status); + Boolean(data.dns_on_router) || + Boolean(data.dhcp_has_dns_server) || + Boolean(data.bootstrap_dns_status) || + Boolean(data.dns_status); console.log('dnsChecks', dnsChecks); @@ -81,7 +83,12 @@ export async function runDnsCheck() { }, { state: data.dns_on_router ? 'success' : 'error', - key: _('Local DNS'), + key: _('DNS on router'), + value: '', + }, + { + state: data.dhcp_has_dns_server ? 'success' : 'error', + key: _('Dhcp has dns server'), value: '', }, ], diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 67add3f..395ff9f 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2044,8 +2044,8 @@ async function runDnsCheck() { throw new Error("DNS checks failed"); } const data = dnsChecks.data; - const allGood = Boolean(data.dns_on_router) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); - const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); + const allGood = Boolean(data.dns_on_router) && Boolean(data.dhcp_has_dns_server) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); + const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.dhcp_has_dns_server) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); console.log("dnsChecks", dnsChecks); function getStatus() { if (allGood) { @@ -2080,7 +2080,12 @@ async function runDnsCheck() { }, { state: data.dns_on_router ? "success" : "error", - key: _("Local DNS"), + key: _("DNS on router"), + value: "" + }, + { + state: data.dhcp_has_dns_server ? "success" : "error", + key: _("Dhcp has dns server"), value: "" } ] From c0b35c865d26fe74a3aae14bb06914e1ead1a5c6 Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 21:13:52 +0300 Subject: [PATCH 067/121] feat: actualize system actions behavior --- .../src/podkop/fetchers/fetchServicesInfo.ts | 29 ++++ fe-app-podkop/src/podkop/fetchers/index.ts | 1 + .../podkop/tabs/dashboard/initController.ts | 28 +--- .../tabs/diagnostic/checks/runDnsCheck.ts | 10 +- .../podkop/tabs/diagnostic/initController.ts | 69 +++++--- .../partials/renderAvailableActions.ts | 13 +- .../luci-static/resources/view/podkop/main.js | 150 +++++++++++------- 7 files changed, 184 insertions(+), 116 deletions(-) create mode 100644 fe-app-podkop/src/podkop/fetchers/fetchServicesInfo.ts create mode 100644 fe-app-podkop/src/podkop/fetchers/index.ts diff --git a/fe-app-podkop/src/podkop/fetchers/fetchServicesInfo.ts b/fe-app-podkop/src/podkop/fetchers/fetchServicesInfo.ts new file mode 100644 index 0000000..777ab94 --- /dev/null +++ b/fe-app-podkop/src/podkop/fetchers/fetchServicesInfo.ts @@ -0,0 +1,29 @@ +import { PodkopShellMethods } from '../methods'; +import { store } from '../services'; + +export async function fetchServicesInfo() { + const [podkop, singbox] = await Promise.all([ + PodkopShellMethods.getStatus(), + PodkopShellMethods.getSingBoxStatus(), + ]); + + if (!podkop.success || !singbox.success) { + store.set({ + servicesInfoWidget: { + loading: false, + failed: true, + data: { singbox: 0, podkop: 0 }, + }, + }); + } + + if (podkop.success && singbox.success) { + store.set({ + servicesInfoWidget: { + loading: false, + failed: false, + data: { singbox: singbox.data.running, podkop: podkop.data.enabled }, + }, + }); + } +} diff --git a/fe-app-podkop/src/podkop/fetchers/index.ts b/fe-app-podkop/src/podkop/fetchers/index.ts new file mode 100644 index 0000000..9b58655 --- /dev/null +++ b/fe-app-podkop/src/podkop/fetchers/index.ts @@ -0,0 +1 @@ +export * from './fetchServicesInfo'; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts index a5e1ae2..4734098 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts @@ -7,6 +7,7 @@ import { prettyBytes } from '../../../helpers/prettyBytes'; import { CustomPodkopMethods, PodkopShellMethods } from '../../methods'; import { socket, store, StoreType } from '../../services'; import { renderSections, renderWidget } from './partials'; +import { fetchServicesInfo } from '../../fetchers'; // Fetchers @@ -36,33 +37,6 @@ async function fetchDashboardSections() { }); } -async function fetchServicesInfo() { - const [podkop, singbox] = await Promise.all([ - PodkopShellMethods.getStatus(), - PodkopShellMethods.getSingBoxStatus(), - ]); - - if (!podkop.success || !singbox.success) { - store.set({ - servicesInfoWidget: { - loading: false, - failed: true, - data: { singbox: 0, podkop: 0 }, - }, - }); - } - - if (podkop.success && singbox.success) { - store.set({ - servicesInfoWidget: { - loading: false, - failed: false, - data: { singbox: singbox.data.running, podkop: podkop.data.enabled }, - }, - }); - } -} - async function connectToClashSockets() { socket.subscribe( `${getClashWsUrl()}/traffic?token=`, diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index 58d9f56..c8ee05a 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -40,10 +40,10 @@ export async function runDnsCheck() { Boolean(data.dns_status); const atLeastOneGood = - Boolean(data.dns_on_router) || - Boolean(data.dhcp_has_dns_server) || - Boolean(data.bootstrap_dns_status) || - Boolean(data.dns_status); + Boolean(data.dns_on_router) || + Boolean(data.dhcp_has_dns_server) || + Boolean(data.bootstrap_dns_status) || + Boolean(data.dns_status); console.log('dnsChecks', dnsChecks); @@ -88,7 +88,7 @@ export async function runDnsCheck() { }, { state: data.dhcp_has_dns_server ? 'success' : 'error', - key: _('Dhcp has dns server'), + key: _('DHCP has DNS server'), value: '', }, ], diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index d69c636..469ff7a 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -12,6 +12,7 @@ import { renderSystemInfo, } from './partials'; import { PodkopShellMethods } from '../../methods'; +import { fetchServicesInfo } from '../../fetchers'; function renderDiagnosticsChecks() { console.log('renderDiagnosticsChecks'); @@ -59,13 +60,15 @@ async function handleRestart() { } catch (e) { console.log('handleRestart - e', e); } finally { - store.set({ - diagnosticsActions: { - ...diagnosticsActions, - restart: { loading: false }, - }, - }); - location.reload(); + setTimeout(async () => { + await fetchServicesInfo(); + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + restart: { loading: false }, + }, + }); + }, 5000); } } @@ -83,13 +86,13 @@ async function handleStop() { } catch (e) { console.log('handleStop - e', e); } finally { + await fetchServicesInfo(); store.set({ diagnosticsActions: { ...diagnosticsActions, stop: { loading: false }, }, }); - // TODO actualize dashboard } } @@ -107,13 +110,15 @@ async function handleStart() { } catch (e) { console.log('handleStart - e', e); } finally { - store.set({ - diagnosticsActions: { - ...diagnosticsActions, - start: { loading: false }, - }, - }); - location.reload(); + setTimeout(async () => { + await fetchServicesInfo(); + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + start: { loading: false }, + }, + }); + }, 5000); } } @@ -131,13 +136,13 @@ async function handleEnable() { } catch (e) { console.log('handleEnable - e', e); } finally { + await fetchServicesInfo(); store.set({ diagnosticsActions: { ...diagnosticsActions, enable: { loading: false }, }, }); - //TODO actualize dashboard } } @@ -155,20 +160,29 @@ async function handleDisable() { } catch (e) { console.log('handleDisable - e', e); } finally { + await fetchServicesInfo(); store.set({ diagnosticsActions: { ...diagnosticsActions, disable: { loading: false }, }, }); - //TODO actualize dashboard } } function renderDiagnosticAvailableActionsWidget() { const diagnosticsActions = store.get().diagnosticsActions; + const servicesInfoWidget = store.get().servicesInfoWidget; console.log('renderDiagnosticActionsWidget'); + const podkopEnabled = Boolean(servicesInfoWidget.data.podkop); + const singBoxRunning = Boolean(servicesInfoWidget.data.singbox); + const atLeastOneServiceCommandLoading = + servicesInfoWidget.loading || + diagnosticsActions.restart.loading || + diagnosticsActions.start.loading || + diagnosticsActions.stop.loading; + const container = document.getElementById('pdk_diagnostic-page-actions'); const renderedActions = renderAvailableActions({ @@ -176,41 +190,49 @@ function renderDiagnosticAvailableActionsWidget() { loading: diagnosticsActions.restart.loading, visible: true, onClick: handleRestart, + disabled: atLeastOneServiceCommandLoading, }, start: { loading: diagnosticsActions.start.loading, - visible: true, + visible: !singBoxRunning, onClick: handleStart, + disabled: atLeastOneServiceCommandLoading, }, stop: { loading: diagnosticsActions.stop.loading, - visible: true, + visible: singBoxRunning, onClick: handleStop, + disabled: atLeastOneServiceCommandLoading, }, enable: { loading: diagnosticsActions.enable.loading, - visible: true, + visible: !podkopEnabled, onClick: handleEnable, + disabled: atLeastOneServiceCommandLoading, }, disable: { loading: diagnosticsActions.disable.loading, - visible: true, + visible: podkopEnabled, onClick: handleDisable, + disabled: atLeastOneServiceCommandLoading, }, globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, onClick: () => {}, + disabled: atLeastOneServiceCommandLoading, }, viewLogs: { loading: diagnosticsActions.viewLogs.loading, visible: true, onClick: () => {}, + disabled: atLeastOneServiceCommandLoading, }, showSingBoxConfig: { loading: diagnosticsActions.showSingBoxConfig.loading, visible: true, onClick: () => {}, + disabled: atLeastOneServiceCommandLoading, }, }); @@ -267,7 +289,7 @@ async function onStoreUpdate( renderDiagnosticRunActionWidget(); } - if (diff.diagnosticsActions) { + if (diff.diagnosticsActions || diff.servicesInfoWidget) { renderDiagnosticAvailableActionsWidget(); } } @@ -313,5 +335,8 @@ export async function initController(): Promise { // Initial system info render renderDiagnosticSystemInfoWidget(); + + // Initial services info fetch + fetchServicesInfo(); }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts index e24f436..a943ba2 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts @@ -14,6 +14,7 @@ import { insertIf } from '../../../../helpers'; interface ActionProps { loading: boolean; visible: boolean; + disabled: boolean; onClick: () => void; } @@ -47,6 +48,7 @@ export function renderAvailableActions({ icon: renderRotateCcwIcon24, text: 'Restart podkop', loading: restart.loading, + disabled: restart.disabled, }), ]), ...insertIf(stop.visible, [ @@ -56,6 +58,7 @@ export function renderAvailableActions({ icon: renderCircleStopIcon24, text: 'Stop podkop', loading: stop.loading, + disabled: stop.disabled, }), ]), ...insertIf(start.visible, [ @@ -65,6 +68,7 @@ export function renderAvailableActions({ icon: renderCirclePlayIcon24, text: 'Start podkop', loading: start.loading, + disabled: start.disabled, }), ]), ...insertIf(disable.visible, [ @@ -72,8 +76,9 @@ export function renderAvailableActions({ classNames: ['cbi-button-remove'], onClick: disable.onClick, icon: renderPauseIcon24, - text: 'Disable podkop', + text: 'Disable autostart', loading: disable.loading, + disabled: disable.disabled, }), ]), ...insertIf(enable.visible, [ @@ -81,8 +86,9 @@ export function renderAvailableActions({ classNames: ['cbi-button-save'], onClick: enable.onClick, icon: renderPlayIcon24, - text: 'Enable podkop', + text: 'Enable autostart', loading: enable.loading, + disabled: enable.disabled, }), ]), ...insertIf(globalCheck.visible, [ @@ -91,6 +97,7 @@ export function renderAvailableActions({ icon: renderCircleCheckBigIcon24, text: 'Get global check', loading: globalCheck.loading, + disabled: globalCheck.disabled, }), ]), ...insertIf(viewLogs.visible, [ @@ -99,6 +106,7 @@ export function renderAvailableActions({ icon: renderSquareChartGanttIcon24, text: 'View logs', loading: viewLogs.loading, + disabled: viewLogs.disabled, }), ]), ...insertIf(showSingBoxConfig.visible, [ @@ -107,6 +115,7 @@ export function renderAvailableActions({ icon: renderCogIcon24, text: 'Show sing-box config', loading: showSingBoxConfig.loading, + disabled: showSingBoxConfig.disabled, }), ]), ]); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 395ff9f..ab81562 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1507,28 +1507,7 @@ function prettyBytes(n) { return n + " " + unit; } -// src/podkop/tabs/dashboard/initController.ts -async function fetchDashboardSections() { - const prev = store.get().sectionsWidget; - store.set({ - sectionsWidget: { - ...prev, - failed: false - } - }); - const { data, success } = await CustomPodkopMethods.getDashboardSections(); - if (!success) { - console.log("[fetchDashboardSections]: failed to fetch"); - } - store.set({ - sectionsWidget: { - latencyFetching: false, - loading: false, - failed: !success, - data - } - }); -} +// src/podkop/fetchers/fetchServicesInfo.ts async function fetchServicesInfo() { const [podkop, singbox] = await Promise.all([ PodkopShellMethods.getStatus(), @@ -1553,6 +1532,29 @@ async function fetchServicesInfo() { }); } } + +// src/podkop/tabs/dashboard/initController.ts +async function fetchDashboardSections() { + const prev = store.get().sectionsWidget; + store.set({ + sectionsWidget: { + ...prev, + failed: false + } + }); + const { data, success } = await CustomPodkopMethods.getDashboardSections(); + if (!success) { + console.log("[fetchDashboardSections]: failed to fetch"); + } + store.set({ + sectionsWidget: { + latencyFetching: false, + loading: false, + failed: !success, + data + } + }); +} async function connectToClashSockets() { socket.subscribe( `${getClashWsUrl()}/traffic?token=`, @@ -2085,7 +2087,7 @@ async function runDnsCheck() { }, { state: data.dhcp_has_dns_server ? "success" : "error", - key: _("Dhcp has dns server"), + key: _("DHCP has DNS server"), value: "" } ] @@ -2935,7 +2937,8 @@ function renderAvailableActions({ onClick: restart.onClick, icon: renderRotateCcwIcon24, text: "Restart podkop", - loading: restart.loading + loading: restart.loading, + disabled: restart.disabled }) ]), ...insertIf(stop.visible, [ @@ -2944,7 +2947,8 @@ function renderAvailableActions({ onClick: stop.onClick, icon: renderCircleStopIcon24, text: "Stop podkop", - loading: stop.loading + loading: stop.loading, + disabled: stop.disabled }) ]), ...insertIf(start.visible, [ @@ -2953,7 +2957,8 @@ function renderAvailableActions({ onClick: start.onClick, icon: renderCirclePlayIcon24, text: "Start podkop", - loading: start.loading + loading: start.loading, + disabled: start.disabled }) ]), ...insertIf(disable.visible, [ @@ -2961,8 +2966,9 @@ function renderAvailableActions({ classNames: ["cbi-button-remove"], onClick: disable.onClick, icon: renderPauseIcon24, - text: "Disable podkop", - loading: disable.loading + text: "Disable autostart", + loading: disable.loading, + disabled: disable.disabled }) ]), ...insertIf(enable.visible, [ @@ -2970,8 +2976,9 @@ function renderAvailableActions({ classNames: ["cbi-button-save"], onClick: enable.onClick, icon: renderPlayIcon24, - text: "Enable podkop", - loading: enable.loading + text: "Enable autostart", + loading: enable.loading, + disabled: enable.disabled }) ]), ...insertIf(globalCheck.visible, [ @@ -2979,7 +2986,8 @@ function renderAvailableActions({ onClick: globalCheck.onClick, icon: renderCircleCheckBigIcon24, text: "Get global check", - loading: globalCheck.loading + loading: globalCheck.loading, + disabled: globalCheck.disabled }) ]), ...insertIf(viewLogs.visible, [ @@ -2987,7 +2995,8 @@ function renderAvailableActions({ onClick: viewLogs.onClick, icon: renderSquareChartGanttIcon24, text: "View logs", - loading: viewLogs.loading + loading: viewLogs.loading, + disabled: viewLogs.disabled }) ]), ...insertIf(showSingBoxConfig.visible, [ @@ -2995,7 +3004,8 @@ function renderAvailableActions({ onClick: showSingBoxConfig.onClick, icon: renderCogIcon24, text: "Show sing-box config", - loading: showSingBoxConfig.loading + loading: showSingBoxConfig.loading, + disabled: showSingBoxConfig.disabled }) ]) ]); @@ -3226,13 +3236,15 @@ async function handleRestart() { } catch (e) { console.log("handleRestart - e", e); } finally { - store.set({ - diagnosticsActions: { - ...diagnosticsActions, - restart: { loading: false } - } - }); - location.reload(); + setTimeout(async () => { + await fetchServicesInfo(); + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + restart: { loading: false } + } + }); + }, 5e3); } } async function handleStop() { @@ -3248,6 +3260,7 @@ async function handleStop() { } catch (e) { console.log("handleStop - e", e); } finally { + await fetchServicesInfo(); store.set({ diagnosticsActions: { ...diagnosticsActions, @@ -3269,13 +3282,15 @@ async function handleStart() { } catch (e) { console.log("handleStart - e", e); } finally { - store.set({ - diagnosticsActions: { - ...diagnosticsActions, - start: { loading: false } - } - }); - location.reload(); + setTimeout(async () => { + await fetchServicesInfo(); + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + start: { loading: false } + } + }); + }, 5e3); } } async function handleEnable() { @@ -3291,6 +3306,7 @@ async function handleEnable() { } catch (e) { console.log("handleEnable - e", e); } finally { + await fetchServicesInfo(); store.set({ diagnosticsActions: { ...diagnosticsActions, @@ -3312,6 +3328,7 @@ async function handleDisable() { } catch (e) { console.log("handleDisable - e", e); } finally { + await fetchServicesInfo(); store.set({ diagnosticsActions: { ...diagnosticsActions, @@ -3322,51 +3339,63 @@ async function handleDisable() { } function renderDiagnosticAvailableActionsWidget() { const diagnosticsActions = store.get().diagnosticsActions; + const servicesInfoWidget = store.get().servicesInfoWidget; console.log("renderDiagnosticActionsWidget"); + const podkopEnabled = Boolean(servicesInfoWidget.data.podkop); + const singBoxRunning = Boolean(servicesInfoWidget.data.singbox); + const atLeastOneServiceCommandLoading = servicesInfoWidget.loading || diagnosticsActions.restart.loading || diagnosticsActions.start.loading || diagnosticsActions.stop.loading; const container = document.getElementById("pdk_diagnostic-page-actions"); const renderedActions = renderAvailableActions({ restart: { loading: diagnosticsActions.restart.loading, visible: true, - onClick: handleRestart + onClick: handleRestart, + disabled: atLeastOneServiceCommandLoading }, start: { loading: diagnosticsActions.start.loading, - visible: true, - onClick: handleStart + visible: !singBoxRunning, + onClick: handleStart, + disabled: atLeastOneServiceCommandLoading }, stop: { loading: diagnosticsActions.stop.loading, - visible: true, - onClick: handleStop + visible: singBoxRunning, + onClick: handleStop, + disabled: atLeastOneServiceCommandLoading }, enable: { loading: diagnosticsActions.enable.loading, - visible: true, - onClick: handleEnable + visible: !podkopEnabled, + onClick: handleEnable, + disabled: atLeastOneServiceCommandLoading }, disable: { loading: diagnosticsActions.disable.loading, - visible: true, - onClick: handleDisable + visible: podkopEnabled, + onClick: handleDisable, + disabled: atLeastOneServiceCommandLoading }, globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, onClick: () => { - } + }, + disabled: atLeastOneServiceCommandLoading }, viewLogs: { loading: diagnosticsActions.viewLogs.loading, visible: true, onClick: () => { - } + }, + disabled: atLeastOneServiceCommandLoading }, showSingBoxConfig: { loading: diagnosticsActions.showSingBoxConfig.loading, visible: true, onClick: () => { - } + }, + disabled: atLeastOneServiceCommandLoading } }); return preserveScrollForPage(() => { @@ -3411,7 +3440,7 @@ async function onStoreUpdate2(next, prev, diff) { if (diff.diagnosticsRunAction) { renderDiagnosticRunActionWidget(); } - if (diff.diagnosticsActions) { + if (diff.diagnosticsActions || diff.servicesInfoWidget) { renderDiagnosticAvailableActionsWidget(); } } @@ -3440,6 +3469,7 @@ async function initController2() { renderDiagnosticRunActionWidget(); renderDiagnosticAvailableActionsWidget(); renderDiagnosticSystemInfoWidget(); + fetchServicesInfo(); }); } From d8b7e12c4dc28460aa8c956137e5fc9ab91dbf3b Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 21:24:56 +0300 Subject: [PATCH 068/121] feat: add skip next checks if sb is not running --- fe-app-podkop/src/podkop/methods/shell/index.ts | 2 ++ .../src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts | 2 +- .../src/podkop/tabs/diagnostic/initController.ts | 3 +++ fe-app-podkop/src/podkop/types.ts | 1 + .../htdocs/luci-static/resources/view/podkop/main.js | 9 +++++++-- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/fe-app-podkop/src/podkop/methods/shell/index.ts b/fe-app-podkop/src/podkop/methods/shell/index.ts index eca780c..15c079a 100644 --- a/fe-app-podkop/src/podkop/methods/shell/index.ts +++ b/fe-app-podkop/src/podkop/methods/shell/index.ts @@ -80,4 +80,6 @@ export const PodkopShellMethods = { callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), + getSystemInfo: async () => + callBaseMethod(Podkop.AvailableMethods.GET_SYSTEM_INFO), }; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts index deb18fe..4d73acc 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts @@ -101,7 +101,7 @@ export async function runSingBoxCheck() { ], }); - if (!atLeastOneGood) { + if (!atLeastOneGood || !data.sing_box_process_running) { throw new Error('Sing-box checks failed'); } } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index 469ff7a..e237757 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -68,6 +68,7 @@ async function handleRestart() { restart: { loading: false }, }, }); + store.reset(['diagnosticsChecks']); }, 5000); } } @@ -93,6 +94,7 @@ async function handleStop() { stop: { loading: false }, }, }); + store.reset(['diagnosticsChecks']); } } @@ -118,6 +120,7 @@ async function handleStart() { start: { loading: false }, }, }); + store.reset(['diagnosticsChecks']); }, 5000); } } diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index 6300551..718a5d1 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -67,6 +67,7 @@ export namespace Podkop { GLOBAL_CHECK = 'global_check', SHOW_SING_BOX_CONFIG = 'show_sing_box_config', CHECK_LOGS = 'check_logs', + GET_SYSTEM_INFO = 'get_system_info', } export enum AvailableClashAPIMethods { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index ab81562..5ed5fb1 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -449,6 +449,7 @@ var Podkop; AvailableMethods2["GLOBAL_CHECK"] = "global_check"; AvailableMethods2["SHOW_SING_BOX_CONFIG"] = "show_sing_box_config"; AvailableMethods2["CHECK_LOGS"] = "check_logs"; + AvailableMethods2["GET_SYSTEM_INFO"] = "get_system_info"; })(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})); let AvailableClashAPIMethods; ((AvailableClashAPIMethods2) => { @@ -520,7 +521,8 @@ var PodkopShellMethods = { ), globalCheck: async () => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), showSingBoxConfig: async () => callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), - checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS) + checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), + getSystemInfo: async () => callBaseMethod(Podkop.AvailableMethods.GET_SYSTEM_INFO) }; // src/podkop/methods/custom/getDashboardSections.ts @@ -2172,7 +2174,7 @@ async function runSingBoxCheck() { } ] }); - if (!atLeastOneGood) { + if (!atLeastOneGood || !data.sing_box_process_running) { throw new Error("Sing-box checks failed"); } } @@ -3244,6 +3246,7 @@ async function handleRestart() { restart: { loading: false } } }); + store.reset(["diagnosticsChecks"]); }, 5e3); } } @@ -3267,6 +3270,7 @@ async function handleStop() { stop: { loading: false } } }); + store.reset(["diagnosticsChecks"]); } } async function handleStart() { @@ -3290,6 +3294,7 @@ async function handleStart() { start: { loading: false } } }); + store.reset(["diagnosticsChecks"]); }, 5e3); } } From 40dac07b29a516c226794f0dc83647f622ade8e4 Mon Sep 17 00:00:00 2001 From: divocat Date: Wed, 15 Oct 2025 22:17:26 +0300 Subject: [PATCH 069/121] feat: integrate system info on diagnostic --- .../src/helpers/normalizeCompiledVersion.ts | 7 + .../src/podkop/methods/shell/index.ts | 4 +- .../src/podkop/services/store.service.ts | 9 ++ .../tabs/diagnostic/diagnostic.store.ts | 14 +- .../podkop/tabs/diagnostic/initController.ts | 87 ++++++++++- .../diagnostic/partials/renderSystemInfo.ts | 44 +++++- .../src/podkop/tabs/diagnostic/styles.ts | 17 +++ fe-app-podkop/src/podkop/types.ts | 9 ++ .../luci-static/resources/view/podkop/main.js | 142 ++++++++++++++++-- 9 files changed, 301 insertions(+), 32 deletions(-) create mode 100644 fe-app-podkop/src/helpers/normalizeCompiledVersion.ts diff --git a/fe-app-podkop/src/helpers/normalizeCompiledVersion.ts b/fe-app-podkop/src/helpers/normalizeCompiledVersion.ts new file mode 100644 index 0000000..60274b0 --- /dev/null +++ b/fe-app-podkop/src/helpers/normalizeCompiledVersion.ts @@ -0,0 +1,7 @@ +export function normalizeCompiledVersion(version: string) { + if (version.includes('COMPILED')) { + return 'dev'; + } + + return version; +} diff --git a/fe-app-podkop/src/podkop/methods/shell/index.ts b/fe-app-podkop/src/podkop/methods/shell/index.ts index 15c079a..1a6fe7c 100644 --- a/fe-app-podkop/src/podkop/methods/shell/index.ts +++ b/fe-app-podkop/src/podkop/methods/shell/index.ts @@ -81,5 +81,7 @@ export const PodkopShellMethods = { checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), getSystemInfo: async () => - callBaseMethod(Podkop.AvailableMethods.GET_SYSTEM_INFO), + callBaseMethod( + Podkop.AvailableMethods.GET_SYSTEM_INFO, + ), }; diff --git a/fe-app-podkop/src/podkop/services/store.service.ts b/fe-app-podkop/src/podkop/services/store.service.ts index 996630f..4069240 100644 --- a/fe-app-podkop/src/podkop/services/store.service.ts +++ b/fe-app-podkop/src/podkop/services/store.service.ts @@ -181,6 +181,15 @@ export interface StoreType { viewLogs: { loading: boolean }; showSingBoxConfig: { loading: boolean }; }; + diagnosticsSystemInfo: { + loading: boolean; + podkop_version: string; + podkop_latest_version: string; + luci_app_version: string; + sing_box_version: string; + openwrt_version: string; + device_model: string; + }; } const initialStore: StoreType = { diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts index 3ed5c1e..4e91415 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts @@ -6,8 +6,20 @@ import { StoreType } from '../../services'; export const initialDiagnosticStore: Pick< StoreType, - 'diagnosticsChecks' | 'diagnosticsRunAction' | 'diagnosticsActions' + | 'diagnosticsChecks' + | 'diagnosticsRunAction' + | 'diagnosticsActions' + | 'diagnosticsSystemInfo' > = { + diagnosticsSystemInfo: { + loading: true, + podkop_version: 'loading', + podkop_latest_version: 'loading', + luci_app_version: 'loading', + sing_box_version: 'loading', + openwrt_version: 'loading', + device_model: 'loading', + }, diagnosticsActions: { restart: { loading: false, diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index e237757..bf7d0a9 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -6,6 +6,7 @@ import { runFakeIPCheck } from './checks/runFakeIPCheck'; import { loadingDiagnosticsChecksStore } from './diagnostic.store'; import { store, StoreType } from '../../services'; import { + IRenderSystemInfoRow, renderAvailableActions, renderCheckSection, renderRunAction, @@ -13,6 +14,32 @@ import { } from './partials'; import { PodkopShellMethods } from '../../methods'; import { fetchServicesInfo } from '../../fetchers'; +import { normalizeCompiledVersion } from '../../../helpers/normalizeCompiledVersion'; + +async function fetchSystemInfo() { + const systemInfo = await PodkopShellMethods.getSystemInfo(); + + if (systemInfo.success) { + store.set({ + diagnosticsSystemInfo: { + loading: false, + ...systemInfo.data, + }, + }); + } else { + store.set({ + diagnosticsSystemInfo: { + loading: false, + podkop_version: 'unknown', + podkop_latest_version: 'unknown', + luci_app_version: 'unknown', + sing_box_version: 'unknown', + openwrt_version: 'unknown', + device_model: 'unknown', + }, + }); + } +} function renderDiagnosticsChecks() { console.log('renderDiagnosticsChecks'); @@ -246,30 +273,67 @@ function renderDiagnosticAvailableActionsWidget() { function renderDiagnosticSystemInfoWidget() { console.log('renderDiagnosticSystemInfoWidget'); + const diagnosticsSystemInfo = store.get().diagnosticsSystemInfo; const container = document.getElementById('pdk_diagnostic-page-system-info'); + function getPodkopVersionRow(): IRenderSystemInfoRow { + const loading = diagnosticsSystemInfo.loading; + const unknown = diagnosticsSystemInfo.podkop_version === 'unknown'; + const hasActualVersion = Boolean( + diagnosticsSystemInfo.podkop_latest_version, + ); + const version = normalizeCompiledVersion( + diagnosticsSystemInfo.podkop_version, + ); + const isDevVersion = version === 'dev'; + + if (loading || unknown || !hasActualVersion || isDevVersion) { + return { + key: 'Podkop', + value: version, + }; + } + + if (version !== diagnosticsSystemInfo.podkop_latest_version) { + return { + key: 'Podkop', + value: version, + tag: { + label: 'Outdated', + kind: 'warning', + }, + }; + } + + return { + key: 'Podkop', + value: version, + tag: { + label: 'Latest', + kind: 'success', + }, + }; + } + const renderedSystemInfo = renderSystemInfo({ items: [ - { - key: 'Podkop', - value: '1', - }, + getPodkopVersionRow(), { key: 'Luci App', - value: '1', + value: normalizeCompiledVersion(diagnosticsSystemInfo.luci_app_version), }, { key: 'Sing-box', - value: '1', + value: diagnosticsSystemInfo.sing_box_version, }, { key: 'OS', - value: '1', + value: diagnosticsSystemInfo.openwrt_version, }, { key: 'Device', - value: '1', + value: diagnosticsSystemInfo.device_model, }, ], }); @@ -295,6 +359,10 @@ async function onStoreUpdate( if (diff.diagnosticsActions || diff.servicesInfoWidget) { renderDiagnosticAvailableActionsWidget(); } + + if (diff.diagnosticsSystemInfo) { + renderDiagnosticSystemInfoWidget(); + } } async function runChecks() { @@ -341,5 +409,8 @@ export async function initController(): Promise { // Initial services info fetch fetchServicesInfo(); + + // Initial system info fetch + fetchSystemInfo(); }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderSystemInfo.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderSystemInfo.ts index b1b3bc5..4b05a45 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderSystemInfo.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderSystemInfo.ts @@ -1,5 +1,16 @@ +import { insertIf } from '../../../../helpers'; + +export interface IRenderSystemInfoRow { + key: string; + value: string; + tag?: { + label: string; + kind: 'warning' | 'success'; + }; +} + interface IRenderSystemInfoProps { - items: Array<{ key: string; value: string }>; + items: Array; } export function renderSystemInfo({ items }: IRenderSystemInfoProps) { @@ -9,11 +20,30 @@ export function renderSystemInfo({ items }: IRenderSystemInfoProps) { { class: 'pdk_diagnostic-page__right-bar__system-info__title' }, 'System information', ), - ...items.map((item) => - E('div', { class: 'pdk_diagnostic-page__right-bar__system-info__row' }, [ - E('b', {}, item.key), - E('div', {}, item.value), - ]), - ), + ...items.map((item) => { + const tagClass = [ + 'pdk_diagnostic-page__right-bar__system-info__row__tag', + ...insertIf(item.tag?.kind === 'warning', [ + 'pdk_diagnostic-page__right-bar__system-info__row__tag--warning', + ]), + ...insertIf(item.tag?.kind === 'success', [ + 'pdk_diagnostic-page__right-bar__system-info__row__tag--success', + ]), + ] + .filter(Boolean) + .join(' '); + + return E( + 'div', + { class: 'pdk_diagnostic-page__right-bar__system-info__row' }, + [ + E('b', {}, item.key), + E('div', {}, [ + E('span', {}, item.value), + E('span', { class: tagClass }, item?.tag?.label), + ]), + ], + ); + }), ]); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts index d2d3dab..75a9042 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/styles.ts @@ -59,6 +59,23 @@ export const styles = ` grid-column-gap: 5px; } +.pdk_diagnostic-page__right-bar__system-info__row__tag { + padding: 2px 4px; + border: 1px transparent solid; + border-radius: 4px; + margin-left: 5px; +} + +.pdk_diagnostic-page__right-bar__system-info__row__tag--warning { + border: 1px var(--warn-color-medium, orange) solid; + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic-page__right-bar__system-info__row__tag--success { + border: 1px var(--success-color-medium, green) solid; + color: var(--success-color-medium, green); +} + .pdk_diagnostic-page__left-bar { display: grid; grid-template-columns: 1fr; diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index 718a5d1..e89774e 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -190,4 +190,13 @@ export namespace Podkop { enabled: number; status: string; } + + export interface GetSystemInfo { + podkop_version: string; + podkop_latest_version: string; + luci_app_version: string; + sing_box_version: string; + openwrt_version: string; + device_model: string; + } } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 5ed5fb1..6703731 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -522,7 +522,9 @@ var PodkopShellMethods = { globalCheck: async () => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), showSingBoxConfig: async () => callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), - getSystemInfo: async () => callBaseMethod(Podkop.AvailableMethods.GET_SYSTEM_INFO) + getSystemInfo: async () => callBaseMethod( + Podkop.AvailableMethods.GET_SYSTEM_INFO + ) }; // src/podkop/methods/custom/getDashboardSections.ts @@ -924,6 +926,15 @@ var DIAGNOSTICS_CHECKS_MAP = { // src/podkop/tabs/diagnostic/diagnostic.store.ts var initialDiagnosticStore = { + diagnosticsSystemInfo: { + loading: true, + podkop_version: "loading", + podkop_latest_version: "loading", + luci_app_version: "loading", + sing_box_version: "loading", + openwrt_version: "loading", + device_model: "loading" + }, diagnosticsActions: { restart: { loading: false @@ -3192,16 +3203,63 @@ function renderSystemInfo({ items }) { { class: "pdk_diagnostic-page__right-bar__system-info__title" }, "System information" ), - ...items.map( - (item) => E("div", { class: "pdk_diagnostic-page__right-bar__system-info__row" }, [ - E("b", {}, item.key), - E("div", {}, item.value) - ]) - ) + ...items.map((item) => { + const tagClass = [ + "pdk_diagnostic-page__right-bar__system-info__row__tag", + ...insertIf(item.tag?.kind === "warning", [ + "pdk_diagnostic-page__right-bar__system-info__row__tag--warning" + ]), + ...insertIf(item.tag?.kind === "success", [ + "pdk_diagnostic-page__right-bar__system-info__row__tag--success" + ]) + ].filter(Boolean).join(" "); + return E( + "div", + { class: "pdk_diagnostic-page__right-bar__system-info__row" }, + [ + E("b", {}, item.key), + E("div", {}, [ + E("span", {}, item.value), + E("span", { class: tagClass }, item?.tag?.label) + ]) + ] + ); + }) ]); } +// src/helpers/normalizeCompiledVersion.ts +function normalizeCompiledVersion(version) { + if (version.includes("COMPILED")) { + return "dev"; + } + return version; +} + // src/podkop/tabs/diagnostic/initController.ts +async function fetchSystemInfo() { + const systemInfo = await PodkopShellMethods.getSystemInfo(); + if (systemInfo.success) { + store.set({ + diagnosticsSystemInfo: { + loading: false, + ...systemInfo.data + } + }); + } else { + store.set({ + diagnosticsSystemInfo: { + loading: false, + podkop_version: "unknown", + podkop_latest_version: "unknown", + luci_app_version: "unknown", + sing_box_version: "unknown", + openwrt_version: "unknown", + device_model: "unknown" + } + }); + } +} function renderDiagnosticsChecks() { console.log("renderDiagnosticsChecks"); const diagnosticsChecks = store.get().diagnosticsChecks.sort((a, b) => a.order - b.order); @@ -3409,28 +3467,61 @@ function renderDiagnosticAvailableActionsWidget() { } function renderDiagnosticSystemInfoWidget() { console.log("renderDiagnosticSystemInfoWidget"); + const diagnosticsSystemInfo = store.get().diagnosticsSystemInfo; const container = document.getElementById("pdk_diagnostic-page-system-info"); + function getPodkopVersionRow() { + const loading = diagnosticsSystemInfo.loading; + const unknown = diagnosticsSystemInfo.podkop_version === "unknown"; + const hasActualVersion = Boolean( + diagnosticsSystemInfo.podkop_latest_version + ); + const version = normalizeCompiledVersion( + diagnosticsSystemInfo.podkop_version + ); + const isDevVersion = version === "dev"; + if (loading || unknown || !hasActualVersion || isDevVersion) { + return { + key: "Podkop", + value: version + }; + } + if (version !== diagnosticsSystemInfo.podkop_latest_version) { + return { + key: "Podkop", + value: version, + tag: { + label: "Outdated", + kind: "warning" + } + }; + } + return { + key: "Podkop", + value: version, + tag: { + label: "Latest", + kind: "success" + } + }; + } const renderedSystemInfo = renderSystemInfo({ items: [ - { - key: "Podkop", - value: "1" - }, + getPodkopVersionRow(), { key: "Luci App", - value: "1" + value: normalizeCompiledVersion(diagnosticsSystemInfo.luci_app_version) }, { key: "Sing-box", - value: "1" + value: diagnosticsSystemInfo.sing_box_version }, { key: "OS", - value: "1" + value: diagnosticsSystemInfo.openwrt_version }, { key: "Device", - value: "1" + value: diagnosticsSystemInfo.device_model } ] }); @@ -3448,6 +3539,9 @@ async function onStoreUpdate2(next, prev, diff) { if (diff.diagnosticsActions || diff.servicesInfoWidget) { renderDiagnosticAvailableActionsWidget(); } + if (diff.diagnosticsSystemInfo) { + renderDiagnosticSystemInfoWidget(); + } } async function runChecks() { try { @@ -3475,6 +3569,7 @@ async function initController2() { renderDiagnosticAvailableActionsWidget(); renderDiagnosticSystemInfoWidget(); fetchServicesInfo(); + fetchSystemInfo(); }); } @@ -3539,6 +3634,23 @@ var styles3 = ` grid-column-gap: 5px; } +.pdk_diagnostic-page__right-bar__system-info__row__tag { + padding: 2px 4px; + border: 1px transparent solid; + border-radius: 4px; + margin-left: 5px; +} + +.pdk_diagnostic-page__right-bar__system-info__row__tag--warning { + border: 1px var(--warn-color-medium, orange) solid; + color: var(--warn-color-medium, orange); +} + +.pdk_diagnostic-page__right-bar__system-info__row__tag--success { + border: 1px var(--success-color-medium, green) solid; + color: var(--success-color-medium, green); +} + .pdk_diagnostic-page__left-bar { display: grid; grid-template-columns: 1fr; From cfb821974ffc0bef10babe22586d0603f9a0db04 Mon Sep 17 00:00:00 2001 From: itdoginfo Date: Thu, 16 Oct 2025 16:49:47 +0300 Subject: [PATCH 070/121] refactor: global check #214 --- podkop/files/usr/bin/podkop | 330 ++++++++++++++++++++++++++++-------- 1 file changed, 258 insertions(+), 72 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 9a3aab7..c0b83dd 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1841,7 +1841,7 @@ check_dns_available() { local dns_status=0 local dns_on_router=0 local bootstrap_dns_status=0 - local dhcp_has_dns_server=0 + local dhcp_config_status=1 local domain="google.com" # Mask NextDNS ID if present @@ -1895,21 +1895,29 @@ check_dns_available() { config_foreach check_dhcp_has_podkop_dns dnsmasq config_load "$PODKOP_CONFIG" - echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"dns_status\":$dns_status,\"dns_on_router\":$dns_on_router,\"bootstrap_dns_server\":\"$bootstrap_dns_server\",\"bootstrap_dns_status\":$bootstrap_dns_status,\"dhcp_has_dns_server\":$dhcp_has_dns_server}" | jq . + echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$display_dns_server\",\"dns_status\":$dns_status,\"dns_on_router\":$dns_on_router,\"bootstrap_dns_server\":\"$bootstrap_dns_server\",\"bootstrap_dns_status\":$bootstrap_dns_status,\"dhcp_config_status\":$dhcp_config_status}" | jq . } check_dhcp_has_podkop_dns() { - local server_list + local server_list cachesize noresolv server_found config_get server_list "$1" "server" + config_get cachesize "$1" "cachesize" + config_get noresolv "$1" "noresolv" + + server_found=0 if [ -n "$server_list" ]; then for server in $server_list; do if [ "$server" = "127.0.0.42" ]; then - dhcp_has_dns_server=1 - return 0 + server_found=1 + break fi done fi + + if [ "$cachesize" != "0" ] || [ "$noresolv" != "1" ] || [ "$server_found" != "1" ]; then + dhcp_config_status=0 + fi } check_nft_rules() { @@ -2187,50 +2195,229 @@ global_check() { print_global "📡 Global check run!" print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "🛠️ System info" - print_global "🕳️ Podkop: ${PODKOP_VERSION}" - print_global "🕳️ LuCI App: ${PODKOP_LUCI_VERSION}" - print_global "📦 Sing-box: $(sing-box version | head -n 1 | awk '{print $3}')" - print_global "🛜 OpenWrt: $(grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2)" - print_global "🛜 Device: $(cat /tmp/sysinfo/model)" + + local system_info_json + system_info_json=$(get_system_info) + + if [ -n "$system_info_json" ]; then + local podkop_version podkop_latest_version luci_app_version sing_box_version openwrt_version device_model + + podkop_version=$(echo "$system_info_json" | jq -r '.podkop_version // "unknown"') + podkop_latest_version=$(echo "$system_info_json" | jq -r '.podkop_latest_version // "unknown"') + luci_app_version=$(echo "$system_info_json" | jq -r '.luci_app_version // "unknown"') + sing_box_version=$(echo "$system_info_json" | jq -r '.sing_box_version // "unknown"') + openwrt_version=$(echo "$system_info_json" | jq -r '.openwrt_version // "unknown"') + device_model=$(echo "$system_info_json" | jq -r '.device_model // "unknown"') + + print_global "🕳️ Podkop: $podkop_version (latest: $podkop_latest_version)" + print_global "🕳️ LuCI App: $luci_app_version" + print_global "📦 Sing-box: $sing_box_version" + print_global "🛜 OpenWrt: $openwrt_version" + print_global "🛜 Device: $device_model" + else + print_global "❌ Failed to get system info" + fi + + print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" + print_global "➡️ DNS status" + + local dns_check_json + dns_check_json=$(check_dns_available) + + if [ -n "$dns_check_json" ]; then + local dns_type dns_server dns_status dns_on_router bootstrap_dns_server bootstrap_dns_status dhcp_config_status + + dns_type=$(echo "$dns_check_json" | jq -r '.dns_type // "unknown"') + dns_server=$(echo "$dns_check_json" | jq -r '.dns_server // "unknown"') + dns_status=$(echo "$dns_check_json" | jq -r '.dns_status // 0') + dns_on_router=$(echo "$dns_check_json" | jq -r '.dns_on_router // 0') + bootstrap_dns_server=$(echo "$dns_check_json" | jq -r '.bootstrap_dns_server // ""') + bootstrap_dns_status=$(echo "$dns_check_json" | jq -r '.bootstrap_dns_status // 0') + dhcp_config_status=$(echo "$dns_check_json" | jq -r '.dhcp_config_status // 0') + + # Bootstrap DNS + if [ -n "$bootstrap_dns_server" ]; then + if [ "$bootstrap_dns_status" -eq 1 ]; then + print_global "✅ Bootstrap DNS: $bootstrap_dns_server" + else + print_global "❌ Bootstrap DNS: $bootstrap_dns_server" + fi + fi + + # DNS server status + if [ "$dns_status" -eq 1 ]; then + print_global "✅ Main DNS: $dns_server [$dns_type]" + else + print_global "❌ Main DNS: $dns_server [$dns_type]" + fi + + # DNS on router + if [ "$dns_on_router" -eq 1 ]; then + print_global "✅ DNS on router" + else + print_global "❌ DNS on router" + fi + + # DHCP configuration check + local dont_touch_dhcp + config_get dont_touch_dhcp "main" "dont_touch_dhcp" + + if [ "$dont_touch_dhcp" = "1" ]; then + print_global "⚠️ dont_touch_dhcp is enabled. 📄 DHCP config:" + awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp + elif [ "$dhcp_config_status" -eq 0 ]; then + print_global "❌ DHCP configuration differs from template. 📄 DHCP config:" + awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp + else + print_global "✅ /etc/config/dhcp" + fi + else + print_global "❌ Failed to get DNS info" + fi + + print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" + print_global "📦 Sing-box status" + + local singbox_check_json + singbox_check_json=$(check_sing_box) + + if [ -n "$singbox_check_json" ]; then + local sing_box_installed sing_box_version_ok sing_box_service_exist sing_box_autostart_disabled sing_box_process_running sing_box_ports_listening + + sing_box_installed=$(echo "$singbox_check_json" | jq -r '.sing_box_installed // 0') + sing_box_version_ok=$(echo "$singbox_check_json" | jq -r '.sing_box_version_ok // 0') + sing_box_service_exist=$(echo "$singbox_check_json" | jq -r '.sing_box_service_exist // 0') + sing_box_autostart_disabled=$(echo "$singbox_check_json" | jq -r '.sing_box_autostart_disabled // 0') + sing_box_process_running=$(echo "$singbox_check_json" | jq -r '.sing_box_process_running // 0') + sing_box_ports_listening=$(echo "$singbox_check_json" | jq -r '.sing_box_ports_listening // 0') + + if [ "$sing_box_installed" -eq 1 ]; then + print_global "✅ Sing-box installed" + else + print_global "❌ Sing-box installed" + fi + + if [ "$sing_box_version_ok" -eq 1 ]; then + print_global "✅ Sing-box version >= 1.12.4" + else + print_global "❌ Sing-box version >= 1.12.4" + fi + + if [ "$sing_box_service_exist" -eq 1 ]; then + print_global "✅ Sing-box service exist" + else + print_global "❌ Sing-box service exist" + fi + + if [ "$sing_box_autostart_disabled" -eq 1 ]; then + print_global "✅ Sing-box autostart disabled" + else + print_global "❌ Sing-box autostart disabled" + fi + + if [ "$sing_box_process_running" -eq 1 ]; then + print_global "✅ Sing-box process running" + else + print_global "❌ Sing-box process running" + fi + + if [ "$sing_box_ports_listening" -eq 1 ]; then + print_global "✅ Sing-box listening ports" + else + print_global "❌ Sing-box listening ports" + fi + else + print_global "❌ Failed to get sing-box info" + fi + + print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" + print_global "🧱 NFT rules status" + + local nft_check_json + nft_check_json=$(check_nft_rules) + + if [ -n "$nft_check_json" ]; then + local table_exist rules_mangle_exist rules_mangle_counters rules_mangle_output_exist rules_mangle_output_counters rules_proxy_exist rules_proxy_counters rules_other_mark_exist + + table_exist=$(echo "$nft_check_json" | jq -r '.table_exist // 0') + rules_mangle_exist=$(echo "$nft_check_json" | jq -r '.rules_mangle_exist // 0') + rules_mangle_counters=$(echo "$nft_check_json" | jq -r '.rules_mangle_counters // 0') + rules_mangle_output_exist=$(echo "$nft_check_json" | jq -r '.rules_mangle_output_exist // 0') + rules_mangle_output_counters=$(echo "$nft_check_json" | jq -r '.rules_mangle_output_counters // 0') + rules_proxy_exist=$(echo "$nft_check_json" | jq -r '.rules_proxy_exist // 0') + rules_proxy_counters=$(echo "$nft_check_json" | jq -r '.rules_proxy_counters // 0') + rules_other_mark_exist=$(echo "$nft_check_json" | jq -r '.rules_other_mark_exist // 0') + + if [ "$table_exist" -eq 1 ]; then + print_global "✅ Table exist" + else + print_global "❌ Table exist" + fi + + if [ "$rules_mangle_exist" -eq 1 ]; then + print_global "✅ Rules mangle exist" + else + print_global "❌ Rules mangle exist" + fi + + if [ "$rules_mangle_counters" -eq 1 ]; then + print_global "✅ Rules mangle counters" + else + print_global "⚠️ Rules mangle counters" + fi + + if [ "$rules_mangle_output_exist" -eq 1 ]; then + print_global "✅ Rules mangle output exist" + else + print_global "❌ Rules mangle output exist" + fi + + if [ "$rules_mangle_output_counters" -eq 1 ]; then + print_global "✅ Rules mangle output counters" + else + print_global "⚠️ Rules mangle output counters" + fi + + if [ "$rules_proxy_exist" -eq 1 ]; then + print_global "✅ Rules proxy exist" + else + print_global "❌ Rules proxy exist" + fi + + if [ "$rules_proxy_counters" -eq 1 ]; then + print_global "✅ Rules proxy counters" + else + print_global "⚠️ Rules proxy counters" + fi + + if [ "$rules_other_mark_exist" -eq 1 ]; then + print_global "⚠️ Additional marking rules found:" + nft list ruleset | awk '/table inet '"$NFT_TABLE_NAME"'/{flag=1; next} /^table/{flag=0} !flag' | grep -E "mark set|meta mark" + else + print_global "✅ Additional marking rules found" + fi + else + print_global "❌ Failed to get NFT rules info" + fi print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "📄 Podkop config" show_config - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "🔧 System check" + # print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" + # print_global "🔧 System check" - if grep -E "^nameserver\s+([0-9]{1,3}\.){3}[0-9]{1,3}" "$RESOLV_CONF" | grep -vqE "127\.0\.0\.1|0\.0\.0\.0"; then - print_global "❌ /etc/resolv.conf contains external nameserver:" - cat /etc/resolv.conf - echo "" - else - print_global "✅ /etc/resolv.conf" - fi + # if grep -E "^nameserver\s+([0-9]{1,3}\.){3}[0-9]{1,3}" "$RESOLV_CONF" | grep -vqE "127\.0\.0\.1|0\.0\.0\.0"; then + # print_global "❌ /etc/resolv.conf contains external nameserver:" + # cat /etc/resolv.conf + # echo "" + # else + # print_global "✅ /etc/resolv.conf" + # fi - cachesize="$(uci get dhcp.@dnsmasq[0].cachesize 2> /dev/null)" - noresolv="$(uci get dhcp.@dnsmasq[0].noresolv 2> /dev/null)" - server="$(uci get dhcp.@dnsmasq[0].server 2> /dev/null)" - - if [ "$cachesize" != "0" ] || [ "$noresolv" != "1" ] || [ "$server" != "127.0.0.42" ]; then - print_global "❌ DHCP configuration differs from template. 📄 DHCP config:" - awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp - elif [ "$(uci get podkop.main.dont_touch_dhcp 2> /dev/null)" = "1" ]; then - print_global "⚠️ dont_touch_dhcp is enabled. 📄 DHCP config:" - awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp - else - print_global "✅ /etc/config/dhcp" - fi - - if ! pgrep -f "sing-box" > /dev/null; then - print_global "❌ sing-box is not running" - else - print_global "✅ sing-box is running" - fi - - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "🧱 NFT table" - check_nft + # print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" + # print_global "🧱 NFT table" + # check_nft print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "📄 WAN config" @@ -2273,11 +2460,13 @@ global_check() { fi if uci show network | grep -q route_allowed_ips; then - uci show network | grep route_allowed_ips | cut -d"'" -f2 | while read -r value; do - if [ "$value" = "1" ]; then + uci show network | grep "wireguard_.*\.route_allowed_ips='1'" | cut -d'.' -f1-2 | while read -r peer_section; do + local allowed_ips + allowed_ips=$(uci get "${peer_section}.allowed_ips" 2>/dev/null) + + if [ "$allowed_ips" = "0.0.0.0/0" ]; then print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "⚠️ WG Route allowed IP enabled" - continue + print_global "⚠️ WG Route allowed IP enabled with 0.0.0.0/0" fi done fi @@ -2288,33 +2477,32 @@ global_check() { fi print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "➡️ DNS status" - dns_info=$(check_dns_available) - dns_type=$(echo "$dns_info" | jq -r '.dns_type') - dns_server=$(echo "$dns_info" | jq -r '.dns_server') - status=$(echo "$dns_info" | jq -r '.status') - print_global "$dns_type ($dns_server) is $status" + print_global "🥸 FakeIP status" + + local fakeip_check_json + fakeip_check_json=$(check_fakeip) - print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" - print_global "🔁 FakeIP" - - print_global "➡️ DNS resolution: system DNS server" - nslookup -timeout=2 $FAKEIP_TEST_DOMAIN - - print_global "➡️ DNS resolution: sing-box DNS server (127.0.0.42)" - local result - result=$(nslookup -timeout=2 $FAKEIP_TEST_DOMAIN 127.0.0.42 2>&1) - echo "$result" - - if echo "$result" | grep -q "198.18"; then - print_global "✅ FakeIP is working correctly on router (198.18.x.x)" - else - print_global "❌ FakeIP test failed: Domain did not resolve to FakeIP range" - if ! pgrep -f "sing-box" > /dev/null; then - print_global " ❌ sing-box is not running" + if [ -n "$fakeip_check_json" ]; then + local fakeip_status + + fakeip_status=$(echo "$fakeip_check_json" | jq -r '.fakeip // false') + + if [ "$fakeip_status" = "true" ]; then + print_global "✅ Router DNS is routed through sing-box" else - print_global " 🤔 sing-box is running" + print_global "⚠️ Router DNS is NOT routed through sing-box" fi + else + print_global "❌ Failed to get FakeIP info" + fi + + local fakeip_address + fakeip_address=$(dig +short @127.0.0.42 $FAKEIP_TEST_DOMAIN) + + if echo "$fakeip_address" | grep -q "^198\.18\."; then + print_global "✅ Sing-box works with FakeIP: $fakeip_address" + else + print_global "❌ Sing-box does NOT work with FakeIP: $fakeip_address" fi } @@ -2327,8 +2515,6 @@ Available commands: stop Stop podkop service reload Reload podkop configuration restart Restart podkop service - enable Enable podkop autostart - disable Disable podkop autostart main Run main podkop process list_update Update domain lists check_proxy Check proxy connectivity From af36cf3026f85e37f9917116396578c3f3320b72 Mon Sep 17 00:00:00 2001 From: divocat Date: Fri, 17 Oct 2025 23:06:36 +0300 Subject: [PATCH 071/121] feat: init new partial modal --- fe-app-podkop/src/luci.d.ts | 5 +++++ fe-app-podkop/src/main.ts | 1 + fe-app-podkop/src/partials/index.ts | 3 +++ fe-app-podkop/src/partials/modal/renderModal.ts | 1 + fe-app-podkop/src/partials/modal/styles.ts | 2 ++ .../src/podkop/tabs/diagnostic/initController.ts | 2 +- .../htdocs/luci-static/resources/view/podkop/main.js | 12 ++++++++---- 7 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 fe-app-podkop/src/partials/modal/renderModal.ts create mode 100644 fe-app-podkop/src/partials/modal/styles.ts diff --git a/fe-app-podkop/src/luci.d.ts b/fe-app-podkop/src/luci.d.ts index 01ff833..9f70c21 100644 --- a/fe-app-podkop/src/luci.d.ts +++ b/fe-app-podkop/src/luci.d.ts @@ -35,6 +35,11 @@ declare global { }; const _ = (_key: string) => string; + + const ui = { + showModal: (_title: stirng, _content: HtmlElement) => undefined, + hideModal: () => undefined, + }; } export {}; diff --git a/fe-app-podkop/src/main.ts b/fe-app-podkop/src/main.ts index 79b8260..bffa791 100644 --- a/fe-app-podkop/src/main.ts +++ b/fe-app-podkop/src/main.ts @@ -2,6 +2,7 @@ 'require baseclass'; 'require fs'; 'require uci'; +'require ui'; export * from './validators'; export * from './helpers'; diff --git a/fe-app-podkop/src/partials/index.ts b/fe-app-podkop/src/partials/index.ts index 3f1e135..fc5f5d8 100644 --- a/fe-app-podkop/src/partials/index.ts +++ b/fe-app-podkop/src/partials/index.ts @@ -1,7 +1,10 @@ import { styles as ButtonStyles } from './button/styles'; +import { styles as ModalStyles } from './modal/styles'; export * from './button/renderButton'; +export * from './modal/renderModal'; export const PartialStyles = ` ${ButtonStyles} +${ModalStyles} `; diff --git a/fe-app-podkop/src/partials/modal/renderModal.ts b/fe-app-podkop/src/partials/modal/renderModal.ts new file mode 100644 index 0000000..b52c018 --- /dev/null +++ b/fe-app-podkop/src/partials/modal/renderModal.ts @@ -0,0 +1 @@ +export function renderModal() {} diff --git a/fe-app-podkop/src/partials/modal/styles.ts b/fe-app-podkop/src/partials/modal/styles.ts new file mode 100644 index 0000000..f017188 --- /dev/null +++ b/fe-app-podkop/src/partials/modal/styles.ts @@ -0,0 +1,2 @@ +// language=CSS +export const styles = ``; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index bf7d0a9..1c684b8 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -249,7 +249,7 @@ function renderDiagnosticAvailableActionsWidget() { globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, - onClick: () => {}, + onClick: () => ui.showModal('globalCheck', E('div', {}, 'Example')), disabled: atLeastOneServiceCommandLoading, }, viewLogs: { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 6703731..dcb1232 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -3,6 +3,7 @@ "require baseclass"; "require fs"; "require uci"; +"require ui"; // src/validators/validateIp.ts function validateIPV4(ip) { @@ -2390,6 +2391,9 @@ var styles2 = ` } `; +// src/partials/modal/styles.ts +var styles3 = ``; + // src/icons/renderLoaderCircleIcon24.ts function renderLoaderCircleIcon24() { const NS = "http://www.w3.org/2000/svg"; @@ -2929,6 +2933,7 @@ function renderButton({ // src/partials/index.ts var PartialStyles = ` ${styles2} +${styles3} `; // src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts @@ -3442,8 +3447,7 @@ function renderDiagnosticAvailableActionsWidget() { globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, - onClick: () => { - }, + onClick: () => ui.showModal("globalCheck", E("div", {}, "Example")), disabled: atLeastOneServiceCommandLoading }, viewLogs: { @@ -3574,7 +3578,7 @@ async function initController2() { } // src/podkop/tabs/diagnostic/styles.ts -var styles3 = ` +var styles4 = ` #cbi-podkop-diagnostic-_mount_node > div { width: 100%; @@ -3743,7 +3747,7 @@ var styles3 = ` var DiagnosticTab = { render: render2, initController: initController2, - styles: styles3 + styles: styles4 }; // src/styles.ts From 7dd3f332849d1c9fd1b565579d08d7e5ee307d75 Mon Sep 17 00:00:00 2001 From: divocat Date: Fri, 17 Oct 2025 23:12:37 +0300 Subject: [PATCH 072/121] feat: add vless flow validation for xtls-rprx-vision-udp443 --- fe-app-podkop/src/validators/validateVlessUrl.ts | 8 ++++++++ .../htdocs/luci-static/resources/view/podkop/main.js | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/fe-app-podkop/src/validators/validateVlessUrl.ts b/fe-app-podkop/src/validators/validateVlessUrl.ts index 23cebc9..6ac5b5a 100644 --- a/fe-app-podkop/src/validators/validateVlessUrl.ts +++ b/fe-app-podkop/src/validators/validateVlessUrl.ts @@ -96,6 +96,14 @@ export function validateVlessUrl(url: string): ValidationResult { }; } + if (params.flow === 'xtls-rprx-vision-udp443') { + return { + valid: false, + message: + 'Invalid VLESS URL: flow xtls-rprx-vision-udp443 does not supported', + }; + } + return { valid: true, message: _('Valid') }; } catch (_e) { return { valid: false, message: _('Invalid VLESS URL: parsing failed') }; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index dcb1232..9628e2c 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -305,6 +305,12 @@ function validateVlessUrl(url) { message: "Invalid VLESS URL: missing fp for reality" }; } + if (params.flow === "xtls-rprx-vision-udp443") { + return { + valid: false, + message: "Invalid VLESS URL: flow xtls-rprx-vision-udp443 does not supported" + }; + } return { valid: true, message: _("Valid") }; } catch (_e) { return { valid: false, message: _("Invalid VLESS URL: parsing failed") }; From 97ab638b31f35caed6d9c003381146d0ec23a34d Mon Sep 17 00:00:00 2001 From: divocat Date: Fri, 17 Oct 2025 23:33:29 +0300 Subject: [PATCH 073/121] feat: actualize dns checks --- .../src/podkop/tabs/diagnostic/checks/runDnsCheck.ts | 6 +++--- fe-app-podkop/src/podkop/types.ts | 2 +- .../htdocs/luci-static/resources/view/podkop/main.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index c8ee05a..9fe7454 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -35,13 +35,13 @@ export async function runDnsCheck() { const allGood = Boolean(data.dns_on_router) && - Boolean(data.dhcp_has_dns_server) && + Boolean(data.dhcp_config_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); const atLeastOneGood = Boolean(data.dns_on_router) || - Boolean(data.dhcp_has_dns_server) || + Boolean(data.dhcp_config_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); @@ -87,7 +87,7 @@ export async function runDnsCheck() { value: '', }, { - state: data.dhcp_has_dns_server ? 'success' : 'error', + state: data.dhcp_config_status ? 'success' : 'error', key: _('DHCP has DNS server'), value: '', }, diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index e89774e..fb30465 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -152,7 +152,7 @@ export namespace Podkop { dns_on_router: 0 | 1; bootstrap_dns_server: string; bootstrap_dns_status: 0 | 1; - dhcp_has_dns_server: 0 | 1; + dhcp_config_status: 0 | 1; } export interface NftRulesCheckResult { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 9628e2c..24fe606 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2066,8 +2066,8 @@ async function runDnsCheck() { throw new Error("DNS checks failed"); } const data = dnsChecks.data; - const allGood = Boolean(data.dns_on_router) && Boolean(data.dhcp_has_dns_server) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); - const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.dhcp_has_dns_server) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); + const allGood = Boolean(data.dns_on_router) && Boolean(data.dhcp_config_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); + const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.dhcp_config_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); console.log("dnsChecks", dnsChecks); function getStatus() { if (allGood) { @@ -2106,7 +2106,7 @@ async function runDnsCheck() { value: "" }, { - state: data.dhcp_has_dns_server ? "success" : "error", + state: data.dhcp_config_status ? "success" : "error", key: _("DHCP has DNS server"), value: "" } From 30b30dcca6032bc6331e5005bd1e0ec1af58b146 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 00:27:06 +0300 Subject: [PATCH 074/121] feat: integrate additional actions on diagnostics tab --- .../src/partials/modal/renderModal.ts | 25 +++- fe-app-podkop/src/partials/modal/styles.ts | 20 ++- .../podkop/tabs/diagnostic/initController.ts | 97 ++++++++++++- .../luci-static/resources/view/podkop/main.js | 130 +++++++++++++++++- 4 files changed, 261 insertions(+), 11 deletions(-) diff --git a/fe-app-podkop/src/partials/modal/renderModal.ts b/fe-app-podkop/src/partials/modal/renderModal.ts index b52c018..b7abcab 100644 --- a/fe-app-podkop/src/partials/modal/renderModal.ts +++ b/fe-app-podkop/src/partials/modal/renderModal.ts @@ -1 +1,24 @@ -export function renderModal() {} +import { renderButton } from '../button/renderButton'; + +export function renderModal(text: string) { + return E( + 'div', + { class: 'pdk-partial-modal__body' }, + E('div', {}, [ + E('pre', { class: 'pdk-partial-modal__content' }, E('code', {}, text)), + + E('div', { class: 'pdk-partial-modal__footer' }, [ + renderButton({ + classNames: ['cbi-button-apply'], + text: 'Copy content', + onClick: () => {}, + }), + renderButton({ + classNames: ['cbi-button-remove'], + text: 'Close modal', + onClick: ui.hideModal, + }), + ]), + ]), + ); +} diff --git a/fe-app-podkop/src/partials/modal/styles.ts b/fe-app-podkop/src/partials/modal/styles.ts index f017188..929e5ec 100644 --- a/fe-app-podkop/src/partials/modal/styles.ts +++ b/fe-app-podkop/src/partials/modal/styles.ts @@ -1,2 +1,20 @@ // language=CSS -export const styles = ``; +export const styles = ` + +.pdk-partial-modal__body {} + +.pdk-partial-modal__content { + max-height: 70vh; + overflow: scroll; + border-radius: 4px; +} + +.pdk-partial-modal__footer { + display: flex; + justify-content: flex-end; +} + +.pdk-partial-modal__footer button { + margin-left: 10px; +} +`; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index 1c684b8..da45fe9 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -15,6 +15,7 @@ import { import { PodkopShellMethods } from '../../methods'; import { fetchServicesInfo } from '../../fetchers'; import { normalizeCompiledVersion } from '../../../helpers/normalizeCompiledVersion'; +import { renderModal } from '../../../partials'; async function fetchSystemInfo() { const systemInfo = await PodkopShellMethods.getSystemInfo(); @@ -200,6 +201,96 @@ async function handleDisable() { } } +async function handleShowGlobalCheck() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + globalCheck: { loading: true }, + }, + }); + + try { + const globalCheck = await PodkopShellMethods.globalCheck(); + + if (globalCheck.success) { + console.log('globalCheck', globalCheck.data); + + ui.showModal('Global check', renderModal(globalCheck.data as string)); + } + } catch (e) { + console.log('handleShowGlobalCheck - e', e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + globalCheck: { loading: false }, + }, + }); + } +} + +async function handleViewLogs() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + viewLogs: { loading: true }, + }, + }); + + try { + const viewLogs = await PodkopShellMethods.checkLogs(); + + if (viewLogs.success) { + console.log('viewLogs', viewLogs.data); + + ui.showModal('View logs', renderModal(viewLogs.data as string)); + } + } catch (e) { + console.log('handleViewLogs - e', e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + viewLogs: { loading: false }, + }, + }); + } +} + +async function handleShowSingBoxConfig() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + showSingBoxConfig: { loading: true }, + }, + }); + + try { + const showSingBoxConfig = await PodkopShellMethods.showSingBoxConfig(); + + if (showSingBoxConfig.success) { + console.log('showSingBoxConfig', showSingBoxConfig.data); + + ui.showModal( + 'Show sing-box config', + renderModal(showSingBoxConfig.data as string), + ); + } + } catch (e) { + console.log('handleViewLogs - e', e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + showSingBoxConfig: { loading: false }, + }, + }); + } +} + function renderDiagnosticAvailableActionsWidget() { const diagnosticsActions = store.get().diagnosticsActions; const servicesInfoWidget = store.get().servicesInfoWidget; @@ -249,19 +340,19 @@ function renderDiagnosticAvailableActionsWidget() { globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, - onClick: () => ui.showModal('globalCheck', E('div', {}, 'Example')), + onClick: handleShowGlobalCheck, disabled: atLeastOneServiceCommandLoading, }, viewLogs: { loading: diagnosticsActions.viewLogs.loading, visible: true, - onClick: () => {}, + onClick: handleViewLogs, disabled: atLeastOneServiceCommandLoading, }, showSingBoxConfig: { loading: diagnosticsActions.showSingBoxConfig.loading, visible: true, - onClick: () => {}, + onClick: handleShowSingBoxConfig, disabled: atLeastOneServiceCommandLoading, }, }); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 24fe606..8b7abde 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2398,7 +2398,25 @@ var styles2 = ` `; // src/partials/modal/styles.ts -var styles3 = ``; +var styles3 = ` + +.pdk-partial-modal__body {} + +.pdk-partial-modal__content { + max-height: 70vh; + overflow: scroll; + border-radius: 4px; +} + +.pdk-partial-modal__footer { + display: flex; + justify-content: flex-end; +} + +.pdk-partial-modal__footer button { + margin-left: 10px; +} +`; // src/icons/renderLoaderCircleIcon24.ts function renderLoaderCircleIcon24() { @@ -2936,6 +2954,30 @@ function renderButton({ ); } +// src/partials/modal/renderModal.ts +function renderModal(text) { + return E( + "div", + { class: "pdk-partial-modal__body" }, + E("div", {}, [ + E("pre", { class: "pdk-partial-modal__content" }, E("code", {}, text)), + E("div", { class: "pdk-partial-modal__footer" }, [ + renderButton({ + classNames: ["cbi-button-apply"], + text: "Copy content", + onClick: () => { + } + }), + renderButton({ + classNames: ["cbi-button-remove"], + text: "Close modal", + onClick: ui.hideModal + }) + ]) + ]) + ); +} + // src/partials/index.ts var PartialStyles = ` ${styles2} @@ -3411,6 +3453,84 @@ async function handleDisable() { }); } } +async function handleShowGlobalCheck() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + globalCheck: { loading: true } + } + }); + try { + const globalCheck = await PodkopShellMethods.globalCheck(); + if (globalCheck.success) { + console.log("globalCheck", globalCheck.data); + ui.showModal("Global check", renderModal(globalCheck.data)); + } + } catch (e) { + console.log("handleShowGlobalCheck - e", e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + globalCheck: { loading: false } + } + }); + } +} +async function handleViewLogs() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + viewLogs: { loading: true } + } + }); + try { + const viewLogs = await PodkopShellMethods.checkLogs(); + if (viewLogs.success) { + console.log("viewLogs", viewLogs.data); + ui.showModal("View logs", renderModal(viewLogs.data)); + } + } catch (e) { + console.log("handleViewLogs - e", e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + viewLogs: { loading: false } + } + }); + } +} +async function handleShowSingBoxConfig() { + const diagnosticsActions = store.get().diagnosticsActions; + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + showSingBoxConfig: { loading: true } + } + }); + try { + const showSingBoxConfig = await PodkopShellMethods.showSingBoxConfig(); + if (showSingBoxConfig.success) { + console.log("showSingBoxConfig", showSingBoxConfig.data); + ui.showModal( + "Show sing-box config", + renderModal(showSingBoxConfig.data) + ); + } + } catch (e) { + console.log("handleViewLogs - e", e); + } finally { + store.set({ + diagnosticsActions: { + ...diagnosticsActions, + showSingBoxConfig: { loading: false } + } + }); + } +} function renderDiagnosticAvailableActionsWidget() { const diagnosticsActions = store.get().diagnosticsActions; const servicesInfoWidget = store.get().servicesInfoWidget; @@ -3453,21 +3573,19 @@ function renderDiagnosticAvailableActionsWidget() { globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, - onClick: () => ui.showModal("globalCheck", E("div", {}, "Example")), + onClick: handleShowGlobalCheck, disabled: atLeastOneServiceCommandLoading }, viewLogs: { loading: diagnosticsActions.viewLogs.loading, visible: true, - onClick: () => { - }, + onClick: handleViewLogs, disabled: atLeastOneServiceCommandLoading }, showSingBoxConfig: { loading: diagnosticsActions.showSingBoxConfig.loading, visible: true, - onClick: () => { - }, + onClick: handleShowSingBoxConfig, disabled: atLeastOneServiceCommandLoading } }); From d7235e8c06874a56ddd8fdbd780b08a9eddf00ef Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 00:44:29 +0300 Subject: [PATCH 075/121] feat: migrate static texts to locales --- .../src/partials/modal/renderModal.ts | 4 +- .../podkop/tabs/diagnostic/initController.ts | 30 +++++------ .../partials/renderAvailableActions.ts | 16 +++--- .../diagnostic/partials/renderCheckSection.ts | 2 +- .../diagnostic/partials/renderRunAction.ts | 2 +- .../luci-static/resources/view/podkop/main.js | 51 +++++++++---------- 6 files changed, 48 insertions(+), 57 deletions(-) diff --git a/fe-app-podkop/src/partials/modal/renderModal.ts b/fe-app-podkop/src/partials/modal/renderModal.ts index b7abcab..381e8e0 100644 --- a/fe-app-podkop/src/partials/modal/renderModal.ts +++ b/fe-app-podkop/src/partials/modal/renderModal.ts @@ -10,12 +10,12 @@ export function renderModal(text: string) { E('div', { class: 'pdk-partial-modal__footer' }, [ renderButton({ classNames: ['cbi-button-apply'], - text: 'Copy content', + text: _('Copy'), onClick: () => {}, }), renderButton({ classNames: ['cbi-button-remove'], - text: 'Close modal', + text: _('Close'), onClick: ui.hideModal, }), ]), diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index da45fe9..397a81f 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -31,12 +31,12 @@ async function fetchSystemInfo() { store.set({ diagnosticsSystemInfo: { loading: false, - podkop_version: 'unknown', - podkop_latest_version: 'unknown', - luci_app_version: 'unknown', - sing_box_version: 'unknown', - openwrt_version: 'unknown', - device_model: 'unknown', + podkop_version: _('unknown'), + podkop_latest_version: _('unknown'), + luci_app_version: _('unknown'), + sing_box_version: _('unknown'), + openwrt_version: _('unknown'), + device_model: _('unknown'), }, }); } @@ -214,9 +214,7 @@ async function handleShowGlobalCheck() { const globalCheck = await PodkopShellMethods.globalCheck(); if (globalCheck.success) { - console.log('globalCheck', globalCheck.data); - - ui.showModal('Global check', renderModal(globalCheck.data as string)); + ui.showModal(_('Global check'), renderModal(globalCheck.data as string)); } } catch (e) { console.log('handleShowGlobalCheck - e', e); @@ -243,9 +241,7 @@ async function handleViewLogs() { const viewLogs = await PodkopShellMethods.checkLogs(); if (viewLogs.success) { - console.log('viewLogs', viewLogs.data); - - ui.showModal('View logs', renderModal(viewLogs.data as string)); + ui.showModal(_('View logs'), renderModal(viewLogs.data as string)); } } catch (e) { console.log('handleViewLogs - e', e); @@ -272,10 +268,8 @@ async function handleShowSingBoxConfig() { const showSingBoxConfig = await PodkopShellMethods.showSingBoxConfig(); if (showSingBoxConfig.success) { - console.log('showSingBoxConfig', showSingBoxConfig.data); - ui.showModal( - 'Show sing-box config', + _('Show sing-box config'), renderModal(showSingBoxConfig.data as string), ); } @@ -370,7 +364,7 @@ function renderDiagnosticSystemInfoWidget() { function getPodkopVersionRow(): IRenderSystemInfoRow { const loading = diagnosticsSystemInfo.loading; - const unknown = diagnosticsSystemInfo.podkop_version === 'unknown'; + const unknown = diagnosticsSystemInfo.podkop_version === _('unknown'); const hasActualVersion = Boolean( diagnosticsSystemInfo.podkop_latest_version, ); @@ -391,7 +385,7 @@ function renderDiagnosticSystemInfoWidget() { key: 'Podkop', value: version, tag: { - label: 'Outdated', + label: _('Outdated'), kind: 'warning', }, }; @@ -401,7 +395,7 @@ function renderDiagnosticSystemInfoWidget() { key: 'Podkop', value: version, tag: { - label: 'Latest', + label: _('Latest'), kind: 'success', }, }; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts index a943ba2..4b81bef 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts @@ -46,7 +46,7 @@ export function renderAvailableActions({ classNames: ['cbi-button-apply'], onClick: restart.onClick, icon: renderRotateCcwIcon24, - text: 'Restart podkop', + text: _('Restart podkop'), loading: restart.loading, disabled: restart.disabled, }), @@ -56,7 +56,7 @@ export function renderAvailableActions({ classNames: ['cbi-button-remove'], onClick: stop.onClick, icon: renderCircleStopIcon24, - text: 'Stop podkop', + text: _('Stop podkop'), loading: stop.loading, disabled: stop.disabled, }), @@ -66,7 +66,7 @@ export function renderAvailableActions({ classNames: ['cbi-button-save'], onClick: start.onClick, icon: renderCirclePlayIcon24, - text: 'Start podkop', + text: _('Start podkop'), loading: start.loading, disabled: start.disabled, }), @@ -76,7 +76,7 @@ export function renderAvailableActions({ classNames: ['cbi-button-remove'], onClick: disable.onClick, icon: renderPauseIcon24, - text: 'Disable autostart', + text: _('Disable autostart'), loading: disable.loading, disabled: disable.disabled, }), @@ -86,7 +86,7 @@ export function renderAvailableActions({ classNames: ['cbi-button-save'], onClick: enable.onClick, icon: renderPlayIcon24, - text: 'Enable autostart', + text: _('Enable autostart'), loading: enable.loading, disabled: enable.disabled, }), @@ -95,7 +95,7 @@ export function renderAvailableActions({ renderButton({ onClick: globalCheck.onClick, icon: renderCircleCheckBigIcon24, - text: 'Get global check', + text: _('Get global check'), loading: globalCheck.loading, disabled: globalCheck.disabled, }), @@ -104,7 +104,7 @@ export function renderAvailableActions({ renderButton({ onClick: viewLogs.onClick, icon: renderSquareChartGanttIcon24, - text: 'View logs', + text: _('View logs'), loading: viewLogs.loading, disabled: viewLogs.disabled, }), @@ -113,7 +113,7 @@ export function renderAvailableActions({ renderButton({ onClick: showSingBoxConfig.onClick, icon: renderCogIcon24, - text: 'Show sing-box config', + text: _('Show sing-box config'), loading: showSingBoxConfig.loading, disabled: showSingBoxConfig.disabled, }), diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderCheckSection.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderCheckSection.ts index c003ac5..a54da42 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderCheckSection.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderCheckSection.ts @@ -186,5 +186,5 @@ export function renderCheckSection(props: IRenderCheckSectionProps) { return renderSkippedState(props); } - return E('div', {}, 'Not implement yet'); + return E('div', {}, _('Not implement yet')); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts index 4c9ee69..24c578c 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/partials/renderRunAction.ts @@ -12,7 +12,7 @@ export function renderRunAction({ }: IRenderDiagnosticRunActionProps) { return E('div', { class: 'pdk_diagnostic-page__run_check_wrapper' }, [ renderButton({ - text: 'Run Diagnostic', + text: _('Run Diagnostic'), onClick: click, icon: renderSearchIcon24, loading, diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 8b7abde..b9476a9 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2964,13 +2964,13 @@ function renderModal(text) { E("div", { class: "pdk-partial-modal__footer" }, [ renderButton({ classNames: ["cbi-button-apply"], - text: "Copy content", + text: _("Copy"), onClick: () => { } }), renderButton({ classNames: ["cbi-button-remove"], - text: "Close modal", + text: _("Close"), onClick: ui.hideModal }) ]) @@ -3002,7 +3002,7 @@ function renderAvailableActions({ classNames: ["cbi-button-apply"], onClick: restart.onClick, icon: renderRotateCcwIcon24, - text: "Restart podkop", + text: _("Restart podkop"), loading: restart.loading, disabled: restart.disabled }) @@ -3012,7 +3012,7 @@ function renderAvailableActions({ classNames: ["cbi-button-remove"], onClick: stop.onClick, icon: renderCircleStopIcon24, - text: "Stop podkop", + text: _("Stop podkop"), loading: stop.loading, disabled: stop.disabled }) @@ -3022,7 +3022,7 @@ function renderAvailableActions({ classNames: ["cbi-button-save"], onClick: start.onClick, icon: renderCirclePlayIcon24, - text: "Start podkop", + text: _("Start podkop"), loading: start.loading, disabled: start.disabled }) @@ -3032,7 +3032,7 @@ function renderAvailableActions({ classNames: ["cbi-button-remove"], onClick: disable.onClick, icon: renderPauseIcon24, - text: "Disable autostart", + text: _("Disable autostart"), loading: disable.loading, disabled: disable.disabled }) @@ -3042,7 +3042,7 @@ function renderAvailableActions({ classNames: ["cbi-button-save"], onClick: enable.onClick, icon: renderPlayIcon24, - text: "Enable autostart", + text: _("Enable autostart"), loading: enable.loading, disabled: enable.disabled }) @@ -3051,7 +3051,7 @@ function renderAvailableActions({ renderButton({ onClick: globalCheck.onClick, icon: renderCircleCheckBigIcon24, - text: "Get global check", + text: _("Get global check"), loading: globalCheck.loading, disabled: globalCheck.disabled }) @@ -3060,7 +3060,7 @@ function renderAvailableActions({ renderButton({ onClick: viewLogs.onClick, icon: renderSquareChartGanttIcon24, - text: "View logs", + text: _("View logs"), loading: viewLogs.loading, disabled: viewLogs.disabled }) @@ -3069,7 +3069,7 @@ function renderAvailableActions({ renderButton({ onClick: showSingBoxConfig.onClick, icon: renderCogIcon24, - text: "Show sing-box config", + text: _("Show sing-box config"), loading: showSingBoxConfig.loading, disabled: showSingBoxConfig.disabled }) @@ -3229,7 +3229,7 @@ function renderCheckSection(props) { if (props.state === "skipped") { return renderSkippedState(props); } - return E("div", {}, "Not implement yet"); + return E("div", {}, _("Not implement yet")); } // src/podkop/tabs/diagnostic/partials/renderRunAction.ts @@ -3239,7 +3239,7 @@ function renderRunAction({ }) { return E("div", { class: "pdk_diagnostic-page__run_check_wrapper" }, [ renderButton({ - text: "Run Diagnostic", + text: _("Run Diagnostic"), onClick: click, icon: renderSearchIcon24, loading, @@ -3303,12 +3303,12 @@ async function fetchSystemInfo() { store.set({ diagnosticsSystemInfo: { loading: false, - podkop_version: "unknown", - podkop_latest_version: "unknown", - luci_app_version: "unknown", - sing_box_version: "unknown", - openwrt_version: "unknown", - device_model: "unknown" + podkop_version: _("unknown"), + podkop_latest_version: _("unknown"), + luci_app_version: _("unknown"), + sing_box_version: _("unknown"), + openwrt_version: _("unknown"), + device_model: _("unknown") } }); } @@ -3464,8 +3464,7 @@ async function handleShowGlobalCheck() { try { const globalCheck = await PodkopShellMethods.globalCheck(); if (globalCheck.success) { - console.log("globalCheck", globalCheck.data); - ui.showModal("Global check", renderModal(globalCheck.data)); + ui.showModal(_("Global check"), renderModal(globalCheck.data)); } } catch (e) { console.log("handleShowGlobalCheck - e", e); @@ -3489,8 +3488,7 @@ async function handleViewLogs() { try { const viewLogs = await PodkopShellMethods.checkLogs(); if (viewLogs.success) { - console.log("viewLogs", viewLogs.data); - ui.showModal("View logs", renderModal(viewLogs.data)); + ui.showModal(_("View logs"), renderModal(viewLogs.data)); } } catch (e) { console.log("handleViewLogs - e", e); @@ -3514,9 +3512,8 @@ async function handleShowSingBoxConfig() { try { const showSingBoxConfig = await PodkopShellMethods.showSingBoxConfig(); if (showSingBoxConfig.success) { - console.log("showSingBoxConfig", showSingBoxConfig.data); ui.showModal( - "Show sing-box config", + _("Show sing-box config"), renderModal(showSingBoxConfig.data) ); } @@ -3599,7 +3596,7 @@ function renderDiagnosticSystemInfoWidget() { const container = document.getElementById("pdk_diagnostic-page-system-info"); function getPodkopVersionRow() { const loading = diagnosticsSystemInfo.loading; - const unknown = diagnosticsSystemInfo.podkop_version === "unknown"; + const unknown = diagnosticsSystemInfo.podkop_version === _("unknown"); const hasActualVersion = Boolean( diagnosticsSystemInfo.podkop_latest_version ); @@ -3618,7 +3615,7 @@ function renderDiagnosticSystemInfoWidget() { key: "Podkop", value: version, tag: { - label: "Outdated", + label: _("Outdated"), kind: "warning" } }; @@ -3627,7 +3624,7 @@ function renderDiagnosticSystemInfoWidget() { key: "Podkop", value: version, tag: { - label: "Latest", + label: _("Latest"), kind: "success" } }; From fd64eb5bcb98d951bab257ac8dc9ada0cb4713ff Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 00:56:52 +0300 Subject: [PATCH 076/121] feat: add copy & download actions for modal --- fe-app-podkop/src/helpers/copyToClipboard.ts | 12 +++++ fe-app-podkop/src/helpers/downloadAsTxt.ts | 15 ++++++ .../src/partials/modal/renderModal.ts | 12 ++++- .../podkop/tabs/diagnostic/initController.ts | 12 +++-- .../luci-static/resources/view/podkop/main.js | 51 ++++++++++++++++--- 5 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 fe-app-podkop/src/helpers/copyToClipboard.ts create mode 100644 fe-app-podkop/src/helpers/downloadAsTxt.ts diff --git a/fe-app-podkop/src/helpers/copyToClipboard.ts b/fe-app-podkop/src/helpers/copyToClipboard.ts new file mode 100644 index 0000000..b66cf50 --- /dev/null +++ b/fe-app-podkop/src/helpers/copyToClipboard.ts @@ -0,0 +1,12 @@ +export function copyToClipboard(text: string) { + const textarea = document.createElement('textarea'); + textarea.value = text; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + } catch (_err) { + console.error('copyToClipboard - e', _err); + } + document.body.removeChild(textarea); +} diff --git a/fe-app-podkop/src/helpers/downloadAsTxt.ts b/fe-app-podkop/src/helpers/downloadAsTxt.ts new file mode 100644 index 0000000..a0d9222 --- /dev/null +++ b/fe-app-podkop/src/helpers/downloadAsTxt.ts @@ -0,0 +1,15 @@ +export function downloadAsTxt(text: string, filename: string) { + const blob = new Blob([text], { type: 'text/plain;charset=utf-8' }); + + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + + const safeName = filename.endsWith('.txt') ? filename : `${filename}.txt`; + link.download = safeName; + + document.body.appendChild(link); + link.click(); + + document.body.removeChild(link); + URL.revokeObjectURL(link.href); +} diff --git a/fe-app-podkop/src/partials/modal/renderModal.ts b/fe-app-podkop/src/partials/modal/renderModal.ts index 381e8e0..8c91742 100644 --- a/fe-app-podkop/src/partials/modal/renderModal.ts +++ b/fe-app-podkop/src/partials/modal/renderModal.ts @@ -1,6 +1,8 @@ import { renderButton } from '../button/renderButton'; +import { copyToClipboard } from '../../helpers/copyToClipboard'; +import { downloadAsTxt } from '../../helpers/downloadAsTxt'; -export function renderModal(text: string) { +export function renderModal(text: string, name: string) { return E( 'div', { class: 'pdk-partial-modal__body' }, @@ -8,10 +10,16 @@ export function renderModal(text: string) { E('pre', { class: 'pdk-partial-modal__content' }, E('code', {}, text)), E('div', { class: 'pdk-partial-modal__footer' }, [ + renderButton({ + classNames: ['cbi-button-apply'], + text: _('Download'), + onClick: () => downloadAsTxt(text, name), + }), renderButton({ classNames: ['cbi-button-apply'], text: _('Copy'), - onClick: () => {}, + onClick: () => + copyToClipboard(` \`\`\`${name} \n ${text} \n \`\`\``), }), renderButton({ classNames: ['cbi-button-remove'], diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index 397a81f..70fbc6d 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -214,7 +214,10 @@ async function handleShowGlobalCheck() { const globalCheck = await PodkopShellMethods.globalCheck(); if (globalCheck.success) { - ui.showModal(_('Global check'), renderModal(globalCheck.data as string)); + ui.showModal( + _('Global check'), + renderModal(globalCheck.data as string, 'global_check'), + ); } } catch (e) { console.log('handleShowGlobalCheck - e', e); @@ -241,7 +244,10 @@ async function handleViewLogs() { const viewLogs = await PodkopShellMethods.checkLogs(); if (viewLogs.success) { - ui.showModal(_('View logs'), renderModal(viewLogs.data as string)); + ui.showModal( + _('View logs'), + renderModal(viewLogs.data as string, 'view_logs'), + ); } } catch (e) { console.log('handleViewLogs - e', e); @@ -270,7 +276,7 @@ async function handleShowSingBoxConfig() { if (showSingBoxConfig.success) { ui.showModal( _('Show sing-box config'), - renderModal(showSingBoxConfig.data as string), + renderModal(showSingBoxConfig.data as string, 'show_sing_box_config'), ); } } catch (e) { diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index b9476a9..9370e99 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2954,19 +2954,52 @@ function renderButton({ ); } +// src/helpers/copyToClipboard.ts +function copyToClipboard(text) { + const textarea = document.createElement("textarea"); + textarea.value = text; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand("copy"); + } catch (_err) { + console.error("copyToClipboard - e", _err); + } + document.body.removeChild(textarea); +} + +// src/helpers/downloadAsTxt.ts +function downloadAsTxt(text, filename) { + const blob = new Blob([text], { type: "text/plain;charset=utf-8" }); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + const safeName = filename.endsWith(".txt") ? filename : `${filename}.txt`; + link.download = safeName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(link.href); +} + // src/partials/modal/renderModal.ts -function renderModal(text) { +function renderModal(text, name) { return E( "div", { class: "pdk-partial-modal__body" }, E("div", {}, [ E("pre", { class: "pdk-partial-modal__content" }, E("code", {}, text)), E("div", { class: "pdk-partial-modal__footer" }, [ + renderButton({ + classNames: ["cbi-button-apply"], + text: _("Download"), + onClick: () => downloadAsTxt(text, name) + }), renderButton({ classNames: ["cbi-button-apply"], text: _("Copy"), - onClick: () => { - } + onClick: () => copyToClipboard(` \`\`\`${name} + ${text} + \`\`\``) }), renderButton({ classNames: ["cbi-button-remove"], @@ -3464,7 +3497,10 @@ async function handleShowGlobalCheck() { try { const globalCheck = await PodkopShellMethods.globalCheck(); if (globalCheck.success) { - ui.showModal(_("Global check"), renderModal(globalCheck.data)); + ui.showModal( + _("Global check"), + renderModal(globalCheck.data, "global_check") + ); } } catch (e) { console.log("handleShowGlobalCheck - e", e); @@ -3488,7 +3524,10 @@ async function handleViewLogs() { try { const viewLogs = await PodkopShellMethods.checkLogs(); if (viewLogs.success) { - ui.showModal(_("View logs"), renderModal(viewLogs.data)); + ui.showModal( + _("View logs"), + renderModal(viewLogs.data, "view_logs") + ); } } catch (e) { console.log("handleViewLogs - e", e); @@ -3514,7 +3553,7 @@ async function handleShowSingBoxConfig() { if (showSingBoxConfig.success) { ui.showModal( _("Show sing-box config"), - renderModal(showSingBoxConfig.data) + renderModal(showSingBoxConfig.data, "show_sing_box_config") ); } } catch (e) { From 96039f92a93350551ec62ac3ab725bbaa53311fa Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 01:07:10 +0300 Subject: [PATCH 077/121] feat: implement show toast --- fe-app-podkop/src/helpers/copyToClipboard.ts | 4 ++ fe-app-podkop/src/helpers/showToast.ts | 24 ++++++++ fe-app-podkop/src/styles.ts | 40 ++++++++++++ .../luci-static/resources/view/podkop/main.js | 61 +++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 fe-app-podkop/src/helpers/showToast.ts diff --git a/fe-app-podkop/src/helpers/copyToClipboard.ts b/fe-app-podkop/src/helpers/copyToClipboard.ts index b66cf50..c9e97a8 100644 --- a/fe-app-podkop/src/helpers/copyToClipboard.ts +++ b/fe-app-podkop/src/helpers/copyToClipboard.ts @@ -1,3 +1,5 @@ +import { showToast } from './showToast'; + export function copyToClipboard(text: string) { const textarea = document.createElement('textarea'); textarea.value = text; @@ -5,7 +7,9 @@ export function copyToClipboard(text: string) { textarea.select(); try { document.execCommand('copy'); + showToast(_('Successfully copied!'), 'success'); } catch (_err) { + showToast(_('Failed to copy!'), 'error'); console.error('copyToClipboard - e', _err); } document.body.removeChild(textarea); diff --git a/fe-app-podkop/src/helpers/showToast.ts b/fe-app-podkop/src/helpers/showToast.ts new file mode 100644 index 0000000..92dcdd9 --- /dev/null +++ b/fe-app-podkop/src/helpers/showToast.ts @@ -0,0 +1,24 @@ +export function showToast( + message: string, + type: 'success' | 'error', + duration: number = 3000, +) { + let container = document.querySelector('.toast-container'); + if (!container) { + container = document.createElement('div'); + container.className = 'toast-container'; + document.body.appendChild(container); + } + + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + toast.textContent = message; + + container.appendChild(toast); + setTimeout(() => toast.classList.add('visible'), 100); + + setTimeout(() => { + toast.classList.remove('visible'); + setTimeout(() => toast.remove(), 300); + }, duration); +} diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index 5d562e9..e40000d 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -69,4 +69,44 @@ ${PartialStyles} left: 150%; } } +/* Toast */ +.toast-container { + position: fixed; + bottom: 30px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + z-index: 9999; + font-family: system-ui, sans-serif; +} + +.toast { + opacity: 0; + transform: translateY(10px); + transition: opacity 0.3s ease, transform 0.3s ease; + padding: 10px 16px; + border-radius: 6px; + color: #fff; + font-size: 14px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + min-width: 220px; + max-width: 340px; + text-align: center; +} + +.toast-success { + background-color: #28a745; +} + +.toast-error { + background-color: #dc3545; +} + +.toast.visible { + opacity: 1; + transform: translateY(0); +} `; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 9370e99..633e57e 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2954,6 +2954,25 @@ function renderButton({ ); } +// src/helpers/showToast.ts +function showToast(message, type, duration = 3e3) { + let container = document.querySelector(".toast-container"); + if (!container) { + container = document.createElement("div"); + container.className = "toast-container"; + document.body.appendChild(container); + } + const toast = document.createElement("div"); + toast.className = `toast toast-${type}`; + toast.textContent = message; + container.appendChild(toast); + setTimeout(() => toast.classList.add("visible"), 100); + setTimeout(() => { + toast.classList.remove("visible"); + setTimeout(() => toast.remove(), 300); + }, duration); +} + // src/helpers/copyToClipboard.ts function copyToClipboard(text) { const textarea = document.createElement("textarea"); @@ -2962,7 +2981,9 @@ function copyToClipboard(text) { textarea.select(); try { document.execCommand("copy"); + showToast(_("Successfully copied!"), "success"); } catch (_err) { + showToast(_("Failed to copy!"), "error"); console.error("copyToClipboard - e", _err); } document.body.removeChild(textarea); @@ -3978,6 +3999,46 @@ ${PartialStyles} left: 150%; } } +/* Toast */ +.toast-container { + position: fixed; + bottom: 30px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + z-index: 9999; + font-family: system-ui, sans-serif; +} + +.toast { + opacity: 0; + transform: translateY(10px); + transition: opacity 0.3s ease, transform 0.3s ease; + padding: 10px 16px; + border-radius: 6px; + color: #fff; + font-size: 14px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + min-width: 220px; + max-width: 340px; + text-align: center; +} + +.toast-success { + background-color: #28a745; +} + +.toast-error { + background-color: #dc3545; +} + +.toast.visible { + opacity: 1; + transform: translateY(0); +} `; // src/helpers/injectGlobalStyles.ts From f155d6a1187ca7d4336fc820313898fcd5056340 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 01:11:17 +0300 Subject: [PATCH 078/121] fix: restore selected value for specific proxy section --- .../htdocs/luci-static/resources/view/podkop/settings.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 0715a65..f837a8b 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 @@ -214,7 +214,9 @@ function createSettingsContent(section) { 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 ?? {}; From 4d4164ae6f385108e0bb3fd77b9e059bde44ae18 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 01:16:03 +0300 Subject: [PATCH 079/121] feat: return community list change handler --- .../resources/view/podkop/section.js | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) 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 d6e4a96..af69b10 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,6 +1,7 @@ 'use strict'; 'require form'; 'require baseclass'; +'require ui'; 'require tools.widgets as widgets'; 'require view.podkop.main as main'; @@ -231,6 +232,81 @@ function createSectionContent(section) { 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, From 7ab0384e0b2cfc9a04620b7b175aa17f415b5584 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 01:45:02 +0300 Subject: [PATCH 080/121] fix: correct dynamic page behavior on lifecycle events --- .../src/podkop/services/socket.service.ts | 21 ++++ .../podkop/tabs/dashboard/initController.ts | 72 ++++++++--- .../podkop/tabs/diagnostic/initController.ts | 80 +++++++++--- .../luci-static/resources/view/podkop/main.js | 116 ++++++++++++++---- 4 files changed, 231 insertions(+), 58 deletions(-) diff --git a/fe-app-podkop/src/podkop/services/socket.service.ts b/fe-app-podkop/src/podkop/services/socket.service.ts index 5210155..adb6e13 100644 --- a/fe-app-podkop/src/podkop/services/socket.service.ts +++ b/fe-app-podkop/src/podkop/services/socket.service.ts @@ -18,6 +18,27 @@ class SocketManager { return SocketManager.instance; } + resetAll(): void { + for (const [url, ws] of this.sockets.entries()) { + try { + if ( + ws.readyState === WebSocket.OPEN || + ws.readyState === WebSocket.CONNECTING + ) { + ws.close(); + } + } catch (err) { + console.warn(`resetAll: failed to close socket ${url}`, err); + } + } + + this.sockets.clear(); + this.listeners.clear(); + this.errorListeners.clear(); + this.connected.clear(); + console.info('[SocketManager] All connections and state have been reset.'); + } + connect(url: string): void { if (this.sockets.has(url)) return; diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts index 4734098..04b6af0 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts @@ -38,6 +38,7 @@ async function fetchDashboardSections() { } async function connectToClashSockets() { + console.log('[SOCKET] connectToClashSockets'); socket.subscribe( `${getClashWsUrl()}/traffic?token=`, (msg) => { @@ -388,25 +389,60 @@ async function onStoreUpdate( } } -export async function initController(): Promise { - onMount('dashboard-status').then(() => { - // Remove old listener - store.unsubscribe(onStoreUpdate); - // Clear store - store.reset([ - 'bandwidthWidget', - 'trafficTotalWidget', - 'systemInfoWidget', - 'servicesInfoWidget', - 'sectionsWidget', - ]); +async function onPageMount() { + // Cleanup before mount + onPageUnmount(); - // Add new listener - store.subscribe(onStoreUpdate); + // Add new listener + store.subscribe(onStoreUpdate); - // Initial sections fetch - fetchDashboardSections(); - fetchServicesInfo(); - connectToClashSockets(); + // Initial sections fetch + await fetchDashboardSections(); + await fetchServicesInfo(); + await connectToClashSockets(); +} + +function onPageUnmount() { + // Remove old listener + store.unsubscribe(onStoreUpdate); + // Clear store + store.reset([ + 'bandwidthWidget', + 'trafficTotalWidget', + 'systemInfoWidget', + 'servicesInfoWidget', + 'sectionsWidget', + ]); + socket.resetAll(); +} + +function registerLifecycleListeners() { + store.subscribe((next, prev, diff) => { + if ( + diff.tabService && + next.tabService.current !== prev.tabService.current + ) { + console.log( + new Date().toISOString(), + '[Active Tab on dashboard]', + diff.tabService.current, + ); + const isDashboardVisible = next.tabService.current === 'dashboard'; + + if (isDashboardVisible) { + return onPageMount(); + } + + if (!isDashboardVisible) { + onPageUnmount(); + } + } + }); +} + +export async function initController(): Promise { + onMount('dashboard-status').then(() => { + onPageMount(); + registerLifecycleListeners(); }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index 70fbc6d..5daa4ea 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -477,31 +477,73 @@ async function runChecks() { } } -export async function initController(): Promise { - onMount('diagnostic-status').then(() => { - console.log('diagnostic controller initialized.'); - // Remove old listener - store.unsubscribe(onStoreUpdate); +function onPageMount() { + console.log('diagnostic controller initialized.'); + // Cleanup before mount + onPageUnmount(); - // Add new listener - store.subscribe(onStoreUpdate); + // Add new listener + store.subscribe(onStoreUpdate); - // Initial checks render - renderDiagnosticsChecks(); + // Initial checks render + renderDiagnosticsChecks(); - // Initial run checks action render - renderDiagnosticRunActionWidget(); + // Initial run checks action render + renderDiagnosticRunActionWidget(); - // Initial available actions render - renderDiagnosticAvailableActionsWidget(); + // Initial available actions render + renderDiagnosticAvailableActionsWidget(); - // Initial system info render - renderDiagnosticSystemInfoWidget(); + // Initial system info render + renderDiagnosticSystemInfoWidget(); - // Initial services info fetch - fetchServicesInfo(); + // Initial services info fetch + fetchServicesInfo(); - // Initial system info fetch - fetchSystemInfo(); + // Initial system info fetch + fetchSystemInfo(); +} + +function onPageUnmount() { + // Remove old listener + store.unsubscribe(onStoreUpdate); + + // Clear store + store.reset([ + 'diagnosticsActions', + 'diagnosticsSystemInfo', + 'diagnosticsChecks', + 'diagnosticsRunAction', + ]); +} + +function registerLifecycleListeners() { + store.subscribe((next, prev, diff) => { + if ( + diff.tabService && + next.tabService.current !== prev.tabService.current + ) { + console.log( + new Date().toISOString(), + '[Active Tab on diagnostics]', + diff.tabService.current, + ); + const isDashboardVisible = next.tabService.current === 'diagnostic'; + + if (isDashboardVisible) { + return onPageMount(); + } + + if (!isDashboardVisible) { + onPageUnmount(); + } + } + }); +} + +export async function initController(): Promise { + onMount('diagnostic-status').then(() => { + onPageMount(); + registerLifecycleListeners(); }); } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 633e57e..4140a1f 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1192,6 +1192,22 @@ var SocketManager = class _SocketManager { } return _SocketManager.instance; } + resetAll() { + for (const [url, ws] of this.sockets.entries()) { + try { + if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) { + ws.close(); + } + } catch (err) { + console.warn(`resetAll: failed to close socket ${url}`, err); + } + } + this.sockets.clear(); + this.listeners.clear(); + this.errorListeners.clear(); + this.connected.clear(); + console.info("[SocketManager] All connections and state have been reset."); + } connect(url) { if (this.sockets.has(url)) return; let ws; @@ -1576,6 +1592,7 @@ async function fetchDashboardSections() { }); } async function connectToClashSockets() { + console.log("[SOCKET] connectToClashSockets"); socket.subscribe( `${getClashWsUrl()}/traffic?token=`, (msg) => { @@ -1866,20 +1883,46 @@ async function onStoreUpdate(next, prev, diff) { renderServicesInfoWidget(); } } +async function onPageMount() { + onPageUnmount(); + store.subscribe(onStoreUpdate); + await fetchDashboardSections(); + await fetchServicesInfo(); + await connectToClashSockets(); +} +function onPageUnmount() { + store.unsubscribe(onStoreUpdate); + store.reset([ + "bandwidthWidget", + "trafficTotalWidget", + "systemInfoWidget", + "servicesInfoWidget", + "sectionsWidget" + ]); + socket.resetAll(); +} +function registerLifecycleListeners() { + store.subscribe((next, prev, diff) => { + if (diff.tabService && next.tabService.current !== prev.tabService.current) { + console.log( + (/* @__PURE__ */ new Date()).toISOString(), + "[Active Tab on dashboard]", + diff.tabService.current + ); + const isDashboardVisible = next.tabService.current === "dashboard"; + if (isDashboardVisible) { + return onPageMount(); + } + if (!isDashboardVisible) { + onPageUnmount(); + } + } + }); +} async function initController() { onMount("dashboard-status").then(() => { - store.unsubscribe(onStoreUpdate); - store.reset([ - "bandwidthWidget", - "trafficTotalWidget", - "systemInfoWidget", - "servicesInfoWidget", - "sectionsWidget" - ]); - store.subscribe(onStoreUpdate); - fetchDashboardSections(); - fetchServicesInfo(); - connectToClashSockets(); + onPageMount(); + registerLifecycleListeners(); }); } @@ -3744,17 +3787,48 @@ async function runChecks() { store.set({ diagnosticsRunAction: { loading: false } }); } } +function onPageMount2() { + console.log("diagnostic controller initialized."); + onPageUnmount2(); + store.subscribe(onStoreUpdate2); + renderDiagnosticsChecks(); + renderDiagnosticRunActionWidget(); + renderDiagnosticAvailableActionsWidget(); + renderDiagnosticSystemInfoWidget(); + fetchServicesInfo(); + fetchSystemInfo(); +} +function onPageUnmount2() { + store.unsubscribe(onStoreUpdate2); + store.reset([ + "diagnosticsActions", + "diagnosticsSystemInfo", + "diagnosticsChecks", + "diagnosticsRunAction" + ]); +} +function registerLifecycleListeners2() { + store.subscribe((next, prev, diff) => { + if (diff.tabService && next.tabService.current !== prev.tabService.current) { + console.log( + (/* @__PURE__ */ new Date()).toISOString(), + "[Active Tab on diagnostics]", + diff.tabService.current + ); + const isDashboardVisible = next.tabService.current === "diagnostic"; + if (isDashboardVisible) { + return onPageMount2(); + } + if (!isDashboardVisible) { + onPageUnmount2(); + } + } + }); +} async function initController2() { onMount("diagnostic-status").then(() => { - console.log("diagnostic controller initialized."); - store.unsubscribe(onStoreUpdate2); - store.subscribe(onStoreUpdate2); - renderDiagnosticsChecks(); - renderDiagnosticRunActionWidget(); - renderDiagnosticAvailableActionsWidget(); - renderDiagnosticSystemInfoWidget(); - fetchServicesInfo(); - fetchSystemInfo(); + onPageMount2(); + registerLifecycleListeners2(); }); } From f6e347af78805a60162dcdaf958e9c6a9b073d9b Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 22:15:43 +0300 Subject: [PATCH 081/121] feat: add logger --- fe-app-podkop/src/helpers/withTimeout.ts | 4 +- .../src/podkop/services/core.service.ts | 2 + fe-app-podkop/src/podkop/services/index.ts | 1 + .../src/podkop/services/logger.service.ts | 66 ++++++ .../src/podkop/services/socket.service.ts | 28 ++- .../podkop/tabs/dashboard/initController.ts | 44 ++-- .../tabs/diagnostic/checks/runDnsCheck.ts | 2 - .../tabs/diagnostic/checks/runFakeIPCheck.ts | 8 - .../tabs/diagnostic/checks/runNftCheck.ts | 2 - .../tabs/diagnostic/checks/runSingBoxCheck.ts | 2 - .../podkop/tabs/diagnostic/initController.ts | 54 +++-- .../luci-static/resources/view/podkop/main.js | 215 ++++++++++++------ 12 files changed, 298 insertions(+), 130 deletions(-) create mode 100644 fe-app-podkop/src/podkop/services/logger.service.ts diff --git a/fe-app-podkop/src/helpers/withTimeout.ts b/fe-app-podkop/src/helpers/withTimeout.ts index f06108a..e220740 100644 --- a/fe-app-podkop/src/helpers/withTimeout.ts +++ b/fe-app-podkop/src/helpers/withTimeout.ts @@ -1,3 +1,5 @@ +import { logger } from '../podkop'; + export async function withTimeout( promise: Promise, timeoutMs: number, @@ -16,6 +18,6 @@ export async function withTimeout( } finally { clearTimeout(timeoutId); const elapsed = performance.now() - start; - console.log(`[${operationName}] Execution time: ${elapsed.toFixed(2)} ms`); + logger.info('[SHELL]', `[${operationName}] took ${elapsed.toFixed(2)} ms`); } } diff --git a/fe-app-podkop/src/podkop/services/core.service.ts b/fe-app-podkop/src/podkop/services/core.service.ts index ac9df68..276475d 100644 --- a/fe-app-podkop/src/podkop/services/core.service.ts +++ b/fe-app-podkop/src/podkop/services/core.service.ts @@ -1,8 +1,10 @@ import { TabServiceInstance } from './tab.service'; import { store } from './store.service'; +import { logger } from './logger.service'; export function coreService() { TabServiceInstance.onChange((activeId, tabs) => { + logger.info('[TAB]', activeId); store.set({ tabService: { current: activeId || '', diff --git a/fe-app-podkop/src/podkop/services/index.ts b/fe-app-podkop/src/podkop/services/index.ts index d6477f7..e3160a8 100644 --- a/fe-app-podkop/src/podkop/services/index.ts +++ b/fe-app-podkop/src/podkop/services/index.ts @@ -2,3 +2,4 @@ export * from './tab.service'; export * from './core.service'; export * from './socket.service'; export * from './store.service'; +export * from './logger.service'; diff --git a/fe-app-podkop/src/podkop/services/logger.service.ts b/fe-app-podkop/src/podkop/services/logger.service.ts new file mode 100644 index 0000000..ecc0ff0 --- /dev/null +++ b/fe-app-podkop/src/podkop/services/logger.service.ts @@ -0,0 +1,66 @@ +import { downloadAsTxt } from '../../helpers/downloadAsTxt'; + +export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; + +export class Logger { + private logs: string[] = []; + private readonly levels: LogLevel[] = ['debug', 'info', 'warn', 'error']; + + private format(level: LogLevel, ...args: unknown[]): string { + return `[${level.toUpperCase()}] ${args.join(' ')}`; + } + + private push(level: LogLevel, ...args: unknown[]): void { + if (!this.levels.includes(level)) level = 'info'; + const message = this.format(level, ...args); + this.logs.push(message); + + switch (level) { + case 'error': + console.error(message); + break; + case 'warn': + console.warn(message); + break; + case 'info': + console.info(message); + break; + default: + console.log(message); + } + } + + debug(...args: unknown[]): void { + this.push('debug', ...args); + } + + info(...args: unknown[]): void { + this.push('info', ...args); + } + + warn(...args: unknown[]): void { + this.push('warn', ...args); + } + + error(...args: unknown[]): void { + this.push('error', ...args); + } + + clear(): void { + this.logs = []; + } + + getLogs(): string { + return this.logs.join('\n'); + } + + download(filename = 'logs.txt'): void { + if (typeof document === 'undefined') { + console.warn('Logger.download() доступен только в браузере'); + return; + } + downloadAsTxt(this.getLogs(), filename); + } +} + +export const logger = new Logger(); diff --git a/fe-app-podkop/src/podkop/services/socket.service.ts b/fe-app-podkop/src/podkop/services/socket.service.ts index adb6e13..2a21091 100644 --- a/fe-app-podkop/src/podkop/services/socket.service.ts +++ b/fe-app-podkop/src/podkop/services/socket.service.ts @@ -1,3 +1,5 @@ +import { logger } from './logger.service'; + // eslint-disable-next-line type Listener = (data: any) => void; type ErrorListener = (error: Event | string) => void; @@ -28,7 +30,11 @@ class SocketManager { ws.close(); } } catch (err) { - console.warn(`resetAll: failed to close socket ${url}`, err); + logger.error( + '[SOCKET]', + `resetAll: failed to close socket ${url}`, + err, + ); } } @@ -36,7 +42,7 @@ class SocketManager { this.listeners.clear(); this.errorListeners.clear(); this.connected.clear(); - console.info('[SocketManager] All connections and state have been reset.'); + logger.info('[SOCKET]', 'All connections and state have been reset.'); } connect(url: string): void { @@ -47,7 +53,11 @@ class SocketManager { try { ws = new WebSocket(url); } catch (err) { - console.error(`Failed to construct WebSocket for ${url}:`, err); + logger.error( + '[SOCKET]', + `failed to construct WebSocket for ${url}:`, + err, + ); this.triggerError(url, err instanceof Event ? err : String(err)); return; } @@ -59,7 +69,7 @@ class SocketManager { ws.addEventListener('open', () => { this.connected.set(url, true); - console.info(`Connected: ${url}`); + logger.info('[SOCKET]', 'Connected to', url); }); ws.addEventListener('message', (event) => { @@ -69,7 +79,7 @@ class SocketManager { try { handler(event.data); } catch (err) { - console.error(`Handler error for ${url}:`, err); + logger.error('[SOCKET]', `Handler error for ${url}:`, err); } } } @@ -77,12 +87,12 @@ class SocketManager { ws.addEventListener('close', () => { this.connected.set(url, false); - console.warn(`Disconnected: ${url}`); + logger.warn('[SOCKET]', `Disconnected: ${url}`); this.triggerError(url, 'Connection closed'); }); ws.addEventListener('error', (err) => { - console.error(`Socket error for ${url}:`, err); + logger.error('[SOCKET]', `Socket error for ${url}:`, err); this.triggerError(url, err); }); } @@ -118,7 +128,7 @@ class SocketManager { if (ws && this.connected.get(url)) { ws.send(typeof data === 'string' ? data : JSON.stringify(data)); } else { - console.warn(`Cannot send: not connected to ${url}`); + logger.warn('[SOCKET]', `Cannot send: not connected to ${url}`); this.triggerError(url, 'Not connected'); } } @@ -147,7 +157,7 @@ class SocketManager { try { cb(err); } catch (e) { - console.error(`Error handler threw for ${url}:`, e); + logger.error('[SOCKET]', `Error handler threw for ${url}:`, e); } } } diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts index 04b6af0..e1abeab 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initController.ts @@ -5,7 +5,7 @@ import { } from '../../../helpers'; import { prettyBytes } from '../../../helpers/prettyBytes'; import { CustomPodkopMethods, PodkopShellMethods } from '../../methods'; -import { socket, store, StoreType } from '../../services'; +import { logger, socket, store, StoreType } from '../../services'; import { renderSections, renderWidget } from './partials'; import { fetchServicesInfo } from '../../fetchers'; @@ -24,7 +24,7 @@ async function fetchDashboardSections() { const { data, success } = await CustomPodkopMethods.getDashboardSections(); if (!success) { - console.log('[fetchDashboardSections]: failed to fetch'); + logger.error('[DASHBOARD]', 'fetchDashboardSections: failed to fetch'); } store.set({ @@ -38,7 +38,6 @@ async function fetchDashboardSections() { } async function connectToClashSockets() { - console.log('[SOCKET] connectToClashSockets'); socket.subscribe( `${getClashWsUrl()}/traffic?token=`, (msg) => { @@ -53,8 +52,9 @@ async function connectToClashSockets() { }); }, (_err) => { - console.log( - '[fetchDashboardSections]: failed to connect', + logger.error( + '[DASHBOARD]', + 'connectToClashSockets - traffic: failed to connect to', getClashWsUrl(), ); store.set({ @@ -92,8 +92,9 @@ async function connectToClashSockets() { }); }, (_err) => { - console.log( - '[fetchDashboardSections]: failed to connect', + logger.error( + '[DASHBOARD]', + 'connectToClashSockets - connections: failed to connect to', getClashWsUrl(), ); store.set({ @@ -163,7 +164,7 @@ async function handleTestProxyLatency(tag: string) { // Renderer async function renderSectionsWidget() { - console.log('renderSectionsWidget'); + logger.debug('[DASHBOARD]', 'renderSectionsWidget'); const sectionsWidget = store.get().sectionsWidget; const container = document.getElementById('dashboard-sections-grid'); @@ -212,7 +213,7 @@ async function renderSectionsWidget() { } async function renderBandwidthWidget() { - console.log('renderBandwidthWidget'); + logger.debug('[DASHBOARD]', 'renderBandwidthWidget'); const traffic = store.get().bandwidthWidget; const container = document.getElementById('dashboard-widget-traffic'); @@ -242,7 +243,7 @@ async function renderBandwidthWidget() { } async function renderTrafficTotalWidget() { - console.log('renderTrafficTotalWidget'); + logger.debug('[DASHBOARD]', 'renderTrafficTotalWidget'); const trafficTotalWidget = store.get().trafficTotalWidget; const container = document.getElementById('dashboard-widget-traffic-total'); @@ -278,7 +279,7 @@ async function renderTrafficTotalWidget() { } async function renderSystemInfoWidget() { - console.log('renderSystemInfoWidget'); + logger.debug('[DASHBOARD]', 'renderSystemInfoWidget'); const systemInfoWidget = store.get().systemInfoWidget; const container = document.getElementById('dashboard-widget-system-info'); @@ -314,7 +315,7 @@ async function renderSystemInfoWidget() { } async function renderServicesInfoWidget() { - console.log('renderServicesInfoWidget'); + logger.debug('[DASHBOARD]', 'renderServicesInfoWidget'); const servicesInfoWidget = store.get().servicesInfoWidget; const container = document.getElementById('dashboard-widget-service-info'); @@ -422,19 +423,29 @@ function registerLifecycleListeners() { diff.tabService && next.tabService.current !== prev.tabService.current ) { - console.log( - new Date().toISOString(), - '[Active Tab on dashboard]', + logger.debug( + '[DASHBOARD]', + 'active tab diff event, active tab:', diff.tabService.current, ); const isDashboardVisible = next.tabService.current === 'dashboard'; if (isDashboardVisible) { + logger.debug( + '[DASHBOARD]', + 'registerLifecycleListeners', + 'onPageMount', + ); return onPageMount(); } if (!isDashboardVisible) { - onPageUnmount(); + logger.debug( + '[DASHBOARD]', + 'registerLifecycleListeners', + 'onPageUnmount', + ); + return onPageUnmount(); } } }); @@ -442,6 +453,7 @@ function registerLifecycleListeners() { export async function initController(): Promise { onMount('dashboard-status').then(() => { + logger.debug('[DASHBOARD]', 'initController', 'onMount'); onPageMount(); registerLifecycleListeners(); }); diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index 9fe7454..c64cf7b 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -45,8 +45,6 @@ export async function runDnsCheck() { Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); - console.log('dnsChecks', dnsChecks); - function getStatus() { if (allGood) { return 'success'; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts index dbb7169..780a547 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -20,12 +20,6 @@ export async function runFakeIPCheck() { const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); - console.log('runFakeIPCheck', { - routerFakeIPResponse, - checkFakeIPResponse, - checkIPResponse, - }); - const checks = { router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, browserFakeIP: @@ -36,8 +30,6 @@ export async function runFakeIPCheck() { checkFakeIPResponse.data.IP !== checkIPResponse.data.IP, }; - console.log('checks', checks); - const allGood = checks.router || checks.browserFakeIP || checks.differentIP; const atLeastOneGood = checks.router && checks.browserFakeIP && checks.differentIP; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index 1d46031..46c46a1 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -54,8 +54,6 @@ export async function runNftCheck() { Boolean(data.rules_proxy_counters) || Boolean(data.rules_other_mark_exist); - console.log('nftablesChecks', nftablesChecks); - function getStatus() { if (allGood) { return 'success'; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts index 4d73acc..15e0bc9 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts @@ -47,8 +47,6 @@ export async function runSingBoxCheck() { Boolean(data.sing_box_process_running) || Boolean(data.sing_box_ports_listening); - console.log('singBoxChecks', singBoxChecks); - function getStatus() { if (allGood) { return 'success'; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts index 5daa4ea..400dff9 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initController.ts @@ -4,7 +4,7 @@ import { runSingBoxCheck } from './checks/runSingBoxCheck'; import { runNftCheck } from './checks/runNftCheck'; import { runFakeIPCheck } from './checks/runFakeIPCheck'; import { loadingDiagnosticsChecksStore } from './diagnostic.store'; -import { store, StoreType } from '../../services'; +import { logger, store, StoreType } from '../../services'; import { IRenderSystemInfoRow, renderAvailableActions, @@ -43,7 +43,7 @@ async function fetchSystemInfo() { } function renderDiagnosticsChecks() { - console.log('renderDiagnosticsChecks'); + logger.debug('[DIAGNOSTIC]', 'renderDiagnosticsChecks'); const diagnosticsChecks = store .get() .diagnosticsChecks.sort((a, b) => a.order - b.order); @@ -59,7 +59,7 @@ function renderDiagnosticsChecks() { } function renderDiagnosticRunActionWidget() { - console.log('renderDiagnosticRunActionWidget'); + logger.debug('[DIAGNOSTIC]', 'renderDiagnosticRunActionWidget'); const { loading } = store.get().diagnosticsRunAction; const container = document.getElementById('pdk_diagnostic-page-run-check'); @@ -86,7 +86,7 @@ async function handleRestart() { try { await PodkopShellMethods.restart(); } catch (e) { - console.log('handleRestart - e', e); + logger.error('[DIAGNOSTIC]', 'handleRestart - e', e); } finally { setTimeout(async () => { await fetchServicesInfo(); @@ -113,7 +113,7 @@ async function handleStop() { try { await PodkopShellMethods.stop(); } catch (e) { - console.log('handleStop - e', e); + logger.error('[DIAGNOSTIC]', 'handleStop - e', e); } finally { await fetchServicesInfo(); store.set({ @@ -138,7 +138,7 @@ async function handleStart() { try { await PodkopShellMethods.start(); } catch (e) { - console.log('handleStart - e', e); + logger.error('[DIAGNOSTIC]', 'handleStart - e', e); } finally { setTimeout(async () => { await fetchServicesInfo(); @@ -165,7 +165,7 @@ async function handleEnable() { try { await PodkopShellMethods.enable(); } catch (e) { - console.log('handleEnable - e', e); + logger.error('[DIAGNOSTIC]', 'handleEnable - e', e); } finally { await fetchServicesInfo(); store.set({ @@ -189,7 +189,7 @@ async function handleDisable() { try { await PodkopShellMethods.disable(); } catch (e) { - console.log('handleDisable - e', e); + logger.error('[DIAGNOSTIC]', 'handleDisable - e', e); } finally { await fetchServicesInfo(); store.set({ @@ -220,7 +220,7 @@ async function handleShowGlobalCheck() { ); } } catch (e) { - console.log('handleShowGlobalCheck - e', e); + logger.error('[DIAGNOSTIC]', 'handleShowGlobalCheck - e', e); } finally { store.set({ diagnosticsActions: { @@ -250,7 +250,7 @@ async function handleViewLogs() { ); } } catch (e) { - console.log('handleViewLogs - e', e); + logger.error('[DIAGNOSTIC]', 'handleViewLogs - e', e); } finally { store.set({ diagnosticsActions: { @@ -280,7 +280,7 @@ async function handleShowSingBoxConfig() { ); } } catch (e) { - console.log('handleViewLogs - e', e); + logger.error('[DIAGNOSTIC]', 'handleShowSingBoxConfig - e', e); } finally { store.set({ diagnosticsActions: { @@ -294,7 +294,7 @@ async function handleShowSingBoxConfig() { function renderDiagnosticAvailableActionsWidget() { const diagnosticsActions = store.get().diagnosticsActions; const servicesInfoWidget = store.get().servicesInfoWidget; - console.log('renderDiagnosticActionsWidget'); + logger.debug('[DIAGNOSTIC]', 'renderDiagnosticAvailableActionsWidget'); const podkopEnabled = Boolean(servicesInfoWidget.data.podkop); const singBoxRunning = Boolean(servicesInfoWidget.data.singbox); @@ -363,7 +363,7 @@ function renderDiagnosticAvailableActionsWidget() { } function renderDiagnosticSystemInfoWidget() { - console.log('renderDiagnosticSystemInfoWidget'); + logger.debug('[DIAGNOSTIC]', 'renderDiagnosticSystemInfoWidget'); const diagnosticsSystemInfo = store.get().diagnosticsSystemInfo; const container = document.getElementById('pdk_diagnostic-page-system-info'); @@ -471,14 +471,13 @@ async function runChecks() { await runFakeIPCheck(); } catch (e) { - console.log('runChecks - e', e); + logger.error('[DIAGNOSTIC]', 'runChecks - e', e); } finally { store.set({ diagnosticsRunAction: { loading: false } }); } } function onPageMount() { - console.log('diagnostic controller initialized.'); // Cleanup before mount onPageUnmount(); @@ -523,19 +522,29 @@ function registerLifecycleListeners() { diff.tabService && next.tabService.current !== prev.tabService.current ) { - console.log( - new Date().toISOString(), - '[Active Tab on diagnostics]', + logger.debug( + '[DIAGNOSTIC]', + 'active tab diff event, active tab:', diff.tabService.current, ); - const isDashboardVisible = next.tabService.current === 'diagnostic'; + const isDIAGNOSTICVisible = next.tabService.current === 'diagnostic'; - if (isDashboardVisible) { + if (isDIAGNOSTICVisible) { + logger.debug( + '[DIAGNOSTIC]', + 'registerLifecycleListeners', + 'onPageMount', + ); return onPageMount(); } - if (!isDashboardVisible) { - onPageUnmount(); + if (!isDIAGNOSTICVisible) { + logger.debug( + '[DIAGNOSTIC]', + 'registerLifecycleListeners', + 'onPageUnmount', + ); + return onPageUnmount(); } } }); @@ -543,6 +552,7 @@ function registerLifecycleListeners() { export async function initController(): Promise { onMount('diagnostic-status').then(() => { + logger.debug('[DIAGNOSTIC]', 'initController', 'onMount'); onPageMount(); registerLifecycleListeners(); }); diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 4140a1f..3a9c4a1 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1166,9 +1166,78 @@ var initialStore = { }; var store = new StoreService(initialStore); +// src/helpers/downloadAsTxt.ts +function downloadAsTxt(text, filename) { + const blob = new Blob([text], { type: "text/plain;charset=utf-8" }); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + const safeName = filename.endsWith(".txt") ? filename : `${filename}.txt`; + link.download = safeName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(link.href); +} + +// src/podkop/services/logger.service.ts +var Logger = class { + constructor() { + this.logs = []; + this.levels = ["debug", "info", "warn", "error"]; + } + format(level, ...args) { + return `[${level.toUpperCase()}] ${args.join(" ")}`; + } + push(level, ...args) { + if (!this.levels.includes(level)) level = "info"; + const message = this.format(level, ...args); + this.logs.push(message); + switch (level) { + case "error": + console.error(message); + break; + case "warn": + console.warn(message); + break; + case "info": + console.info(message); + break; + default: + console.log(message); + } + } + debug(...args) { + this.push("debug", ...args); + } + info(...args) { + this.push("info", ...args); + } + warn(...args) { + this.push("warn", ...args); + } + error(...args) { + this.push("error", ...args); + } + clear() { + this.logs = []; + } + getLogs() { + return this.logs.join("\n"); + } + download(filename = "logs.txt") { + if (typeof document === "undefined") { + console.warn("Logger.download() \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435"); + return; + } + downloadAsTxt(this.getLogs(), filename); + } +}; +var logger = new Logger(); + // src/podkop/services/core.service.ts function coreService() { TabServiceInstance.onChange((activeId, tabs) => { + logger.info("[TAB]", activeId); store.set({ tabService: { current: activeId || "", @@ -1199,14 +1268,18 @@ var SocketManager = class _SocketManager { ws.close(); } } catch (err) { - console.warn(`resetAll: failed to close socket ${url}`, err); + logger.error( + "[SOCKET]", + `resetAll: failed to close socket ${url}`, + err + ); } } this.sockets.clear(); this.listeners.clear(); this.errorListeners.clear(); this.connected.clear(); - console.info("[SocketManager] All connections and state have been reset."); + logger.info("[SOCKET]", "All connections and state have been reset."); } connect(url) { if (this.sockets.has(url)) return; @@ -1214,7 +1287,11 @@ var SocketManager = class _SocketManager { try { ws = new WebSocket(url); } catch (err) { - console.error(`Failed to construct WebSocket for ${url}:`, err); + logger.error( + "[SOCKET]", + `failed to construct WebSocket for ${url}:`, + err + ); this.triggerError(url, err instanceof Event ? err : String(err)); return; } @@ -1224,7 +1301,7 @@ var SocketManager = class _SocketManager { this.errorListeners.set(url, /* @__PURE__ */ new Set()); ws.addEventListener("open", () => { this.connected.set(url, true); - console.info(`Connected: ${url}`); + logger.info("[SOCKET]", "Connected to", url); }); ws.addEventListener("message", (event) => { const handlers = this.listeners.get(url); @@ -1233,18 +1310,18 @@ var SocketManager = class _SocketManager { try { handler(event.data); } catch (err) { - console.error(`Handler error for ${url}:`, err); + logger.error("[SOCKET]", `Handler error for ${url}:`, err); } } } }); ws.addEventListener("close", () => { this.connected.set(url, false); - console.warn(`Disconnected: ${url}`); + logger.warn("[SOCKET]", `Disconnected: ${url}`); this.triggerError(url, "Connection closed"); }); ws.addEventListener("error", (err) => { - console.error(`Socket error for ${url}:`, err); + logger.error("[SOCKET]", `Socket error for ${url}:`, err); this.triggerError(url, err); }); } @@ -1275,7 +1352,7 @@ var SocketManager = class _SocketManager { if (ws && this.connected.get(url)) { ws.send(typeof data === "string" ? data : JSON.stringify(data)); } else { - console.warn(`Cannot send: not connected to ${url}`); + logger.warn("[SOCKET]", `Cannot send: not connected to ${url}`); this.triggerError(url, "Not connected"); } } @@ -1301,7 +1378,7 @@ var SocketManager = class _SocketManager { try { cb(err); } catch (e) { - console.error(`Error handler threw for ${url}:`, e); + logger.error("[SOCKET]", `Error handler threw for ${url}:`, e); } } } @@ -1580,7 +1657,7 @@ async function fetchDashboardSections() { }); const { data, success } = await CustomPodkopMethods.getDashboardSections(); if (!success) { - console.log("[fetchDashboardSections]: failed to fetch"); + logger.error("[DASHBOARD]", "fetchDashboardSections: failed to fetch"); } store.set({ sectionsWidget: { @@ -1592,7 +1669,6 @@ async function fetchDashboardSections() { }); } async function connectToClashSockets() { - console.log("[SOCKET] connectToClashSockets"); socket.subscribe( `${getClashWsUrl()}/traffic?token=`, (msg) => { @@ -1606,8 +1682,9 @@ async function connectToClashSockets() { }); }, (_err) => { - console.log( - "[fetchDashboardSections]: failed to connect", + logger.error( + "[DASHBOARD]", + "connectToClashSockets - traffic: failed to connect to", getClashWsUrl() ); store.set({ @@ -1643,8 +1720,9 @@ async function connectToClashSockets() { }); }, (_err) => { - console.log( - "[fetchDashboardSections]: failed to connect", + logger.error( + "[DASHBOARD]", + "connectToClashSockets - connections: failed to connect to", getClashWsUrl() ); store.set({ @@ -1702,7 +1780,7 @@ async function handleTestProxyLatency(tag) { }); } async function renderSectionsWidget() { - console.log("renderSectionsWidget"); + logger.debug("[DASHBOARD]", "renderSectionsWidget"); const sectionsWidget = store.get().sectionsWidget; const container = document.getElementById("dashboard-sections-grid"); if (sectionsWidget.loading || sectionsWidget.failed) { @@ -1747,7 +1825,7 @@ async function renderSectionsWidget() { }); } async function renderBandwidthWidget() { - console.log("renderBandwidthWidget"); + logger.debug("[DASHBOARD]", "renderBandwidthWidget"); const traffic = store.get().bandwidthWidget; const container = document.getElementById("dashboard-widget-traffic"); if (traffic.loading || traffic.failed) { @@ -1771,7 +1849,7 @@ async function renderBandwidthWidget() { container.replaceChildren(renderedWidget); } async function renderTrafficTotalWidget() { - console.log("renderTrafficTotalWidget"); + logger.debug("[DASHBOARD]", "renderTrafficTotalWidget"); const trafficTotalWidget = store.get().trafficTotalWidget; const container = document.getElementById("dashboard-widget-traffic-total"); if (trafficTotalWidget.loading || trafficTotalWidget.failed) { @@ -1801,7 +1879,7 @@ async function renderTrafficTotalWidget() { container.replaceChildren(renderedWidget); } async function renderSystemInfoWidget() { - console.log("renderSystemInfoWidget"); + logger.debug("[DASHBOARD]", "renderSystemInfoWidget"); const systemInfoWidget = store.get().systemInfoWidget; const container = document.getElementById("dashboard-widget-system-info"); if (systemInfoWidget.loading || systemInfoWidget.failed) { @@ -1831,7 +1909,7 @@ async function renderSystemInfoWidget() { container.replaceChildren(renderedWidget); } async function renderServicesInfoWidget() { - console.log("renderServicesInfoWidget"); + logger.debug("[DASHBOARD]", "renderServicesInfoWidget"); const servicesInfoWidget = store.get().servicesInfoWidget; const container = document.getElementById("dashboard-widget-service-info"); if (servicesInfoWidget.loading || servicesInfoWidget.failed) { @@ -1904,23 +1982,34 @@ function onPageUnmount() { function registerLifecycleListeners() { store.subscribe((next, prev, diff) => { if (diff.tabService && next.tabService.current !== prev.tabService.current) { - console.log( - (/* @__PURE__ */ new Date()).toISOString(), - "[Active Tab on dashboard]", + logger.debug( + "[DASHBOARD]", + "active tab diff event, active tab:", diff.tabService.current ); const isDashboardVisible = next.tabService.current === "dashboard"; if (isDashboardVisible) { + logger.debug( + "[DASHBOARD]", + "registerLifecycleListeners", + "onPageMount" + ); return onPageMount(); } if (!isDashboardVisible) { - onPageUnmount(); + logger.debug( + "[DASHBOARD]", + "registerLifecycleListeners", + "onPageUnmount" + ); + return onPageUnmount(); } } }); } async function initController() { onMount("dashboard-status").then(() => { + logger.debug("[DASHBOARD]", "initController", "onMount"); onPageMount(); registerLifecycleListeners(); }); @@ -2111,7 +2200,6 @@ async function runDnsCheck() { const data = dnsChecks.data; const allGood = Boolean(data.dns_on_router) && Boolean(data.dhcp_config_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.dhcp_config_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); - console.log("dnsChecks", dnsChecks); function getStatus() { if (allGood) { return "success"; @@ -2186,7 +2274,6 @@ async function runSingBoxCheck() { const data = singBoxChecks.data; const allGood = Boolean(data.sing_box_installed) && Boolean(data.sing_box_version_ok) && Boolean(data.sing_box_service_exist) && Boolean(data.sing_box_autostart_disabled) && Boolean(data.sing_box_process_running) && Boolean(data.sing_box_ports_listening); const atLeastOneGood = Boolean(data.sing_box_installed) || Boolean(data.sing_box_version_ok) || Boolean(data.sing_box_service_exist) || Boolean(data.sing_box_autostart_disabled) || Boolean(data.sing_box_process_running) || Boolean(data.sing_box_ports_listening); - console.log("singBoxChecks", singBoxChecks); function getStatus() { if (allGood) { return "success"; @@ -2268,7 +2355,6 @@ async function runNftCheck() { const data = nftablesChecks.data; const allGood = Boolean(data.table_exist) && Boolean(data.rules_mangle_exist) && Boolean(data.rules_mangle_counters) && Boolean(data.rules_mangle_output_exist) && Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && Boolean(data.rules_other_mark_exist); const atLeastOneGood = Boolean(data.table_exist) || Boolean(data.rules_mangle_exist) || Boolean(data.rules_mangle_counters) || Boolean(data.rules_mangle_output_exist) || Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || Boolean(data.rules_other_mark_exist); - console.log("nftablesChecks", nftablesChecks); function getStatus() { if (allGood) { return "success"; @@ -2346,17 +2432,11 @@ async function runFakeIPCheck() { const routerFakeIPResponse = await PodkopShellMethods.checkFakeIP(); const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); - console.log("runFakeIPCheck", { - routerFakeIPResponse, - checkFakeIPResponse, - checkIPResponse - }); const checks = { router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, browserFakeIP: checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, differentIP: checkFakeIPResponse.success && checkIPResponse.success && checkFakeIPResponse.data.IP !== checkIPResponse.data.IP }; - console.log("checks", checks); const allGood = checks.router || checks.browserFakeIP || checks.differentIP; const atLeastOneGood = checks.router && checks.browserFakeIP && checks.differentIP; function getMeta() { @@ -3032,19 +3112,6 @@ function copyToClipboard(text) { document.body.removeChild(textarea); } -// src/helpers/downloadAsTxt.ts -function downloadAsTxt(text, filename) { - const blob = new Blob([text], { type: "text/plain;charset=utf-8" }); - const link = document.createElement("a"); - link.href = URL.createObjectURL(blob); - const safeName = filename.endsWith(".txt") ? filename : `${filename}.txt`; - link.download = safeName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(link.href); -} - // src/partials/modal/renderModal.ts function renderModal(text, name) { return E( @@ -3411,7 +3478,7 @@ async function fetchSystemInfo() { } } function renderDiagnosticsChecks() { - console.log("renderDiagnosticsChecks"); + logger.debug("[DIAGNOSTIC]", "renderDiagnosticsChecks"); const diagnosticsChecks = store.get().diagnosticsChecks.sort((a, b) => a.order - b.order); const container = document.getElementById("pdk_diagnostic-page-checks"); const renderedDiagnosticsChecks = diagnosticsChecks.map( @@ -3422,7 +3489,7 @@ function renderDiagnosticsChecks() { }); } function renderDiagnosticRunActionWidget() { - console.log("renderDiagnosticRunActionWidget"); + logger.debug("[DIAGNOSTIC]", "renderDiagnosticRunActionWidget"); const { loading } = store.get().diagnosticsRunAction; const container = document.getElementById("pdk_diagnostic-page-run-check"); const renderedAction = renderRunAction({ @@ -3444,7 +3511,7 @@ async function handleRestart() { try { await PodkopShellMethods.restart(); } catch (e) { - console.log("handleRestart - e", e); + logger.error("[DIAGNOSTIC]", "handleRestart - e", e); } finally { setTimeout(async () => { await fetchServicesInfo(); @@ -3469,7 +3536,7 @@ async function handleStop() { try { await PodkopShellMethods.stop(); } catch (e) { - console.log("handleStop - e", e); + logger.error("[DIAGNOSTIC]", "handleStop - e", e); } finally { await fetchServicesInfo(); store.set({ @@ -3492,7 +3559,7 @@ async function handleStart() { try { await PodkopShellMethods.start(); } catch (e) { - console.log("handleStart - e", e); + logger.error("[DIAGNOSTIC]", "handleStart - e", e); } finally { setTimeout(async () => { await fetchServicesInfo(); @@ -3517,7 +3584,7 @@ async function handleEnable() { try { await PodkopShellMethods.enable(); } catch (e) { - console.log("handleEnable - e", e); + logger.error("[DIAGNOSTIC]", "handleEnable - e", e); } finally { await fetchServicesInfo(); store.set({ @@ -3539,7 +3606,7 @@ async function handleDisable() { try { await PodkopShellMethods.disable(); } catch (e) { - console.log("handleDisable - e", e); + logger.error("[DIAGNOSTIC]", "handleDisable - e", e); } finally { await fetchServicesInfo(); store.set({ @@ -3567,7 +3634,7 @@ async function handleShowGlobalCheck() { ); } } catch (e) { - console.log("handleShowGlobalCheck - e", e); + logger.error("[DIAGNOSTIC]", "handleShowGlobalCheck - e", e); } finally { store.set({ diagnosticsActions: { @@ -3594,7 +3661,7 @@ async function handleViewLogs() { ); } } catch (e) { - console.log("handleViewLogs - e", e); + logger.error("[DIAGNOSTIC]", "handleViewLogs - e", e); } finally { store.set({ diagnosticsActions: { @@ -3621,7 +3688,7 @@ async function handleShowSingBoxConfig() { ); } } catch (e) { - console.log("handleViewLogs - e", e); + logger.error("[DIAGNOSTIC]", "handleShowSingBoxConfig - e", e); } finally { store.set({ diagnosticsActions: { @@ -3634,7 +3701,7 @@ async function handleShowSingBoxConfig() { function renderDiagnosticAvailableActionsWidget() { const diagnosticsActions = store.get().diagnosticsActions; const servicesInfoWidget = store.get().servicesInfoWidget; - console.log("renderDiagnosticActionsWidget"); + logger.debug("[DIAGNOSTIC]", "renderDiagnosticAvailableActionsWidget"); const podkopEnabled = Boolean(servicesInfoWidget.data.podkop); const singBoxRunning = Boolean(servicesInfoWidget.data.singbox); const atLeastOneServiceCommandLoading = servicesInfoWidget.loading || diagnosticsActions.restart.loading || diagnosticsActions.start.loading || diagnosticsActions.stop.loading; @@ -3694,7 +3761,7 @@ function renderDiagnosticAvailableActionsWidget() { }); } function renderDiagnosticSystemInfoWidget() { - console.log("renderDiagnosticSystemInfoWidget"); + logger.debug("[DIAGNOSTIC]", "renderDiagnosticSystemInfoWidget"); const diagnosticsSystemInfo = store.get().diagnosticsSystemInfo; const container = document.getElementById("pdk_diagnostic-page-system-info"); function getPodkopVersionRow() { @@ -3782,13 +3849,12 @@ async function runChecks() { await runNftCheck(); await runFakeIPCheck(); } catch (e) { - console.log("runChecks - e", e); + logger.error("[DIAGNOSTIC]", "runChecks - e", e); } finally { store.set({ diagnosticsRunAction: { loading: false } }); } } function onPageMount2() { - console.log("diagnostic controller initialized."); onPageUnmount2(); store.subscribe(onStoreUpdate2); renderDiagnosticsChecks(); @@ -3810,23 +3876,34 @@ function onPageUnmount2() { function registerLifecycleListeners2() { store.subscribe((next, prev, diff) => { if (diff.tabService && next.tabService.current !== prev.tabService.current) { - console.log( - (/* @__PURE__ */ new Date()).toISOString(), - "[Active Tab on diagnostics]", + logger.debug( + "[DIAGNOSTIC]", + "active tab diff event, active tab:", diff.tabService.current ); - const isDashboardVisible = next.tabService.current === "diagnostic"; - if (isDashboardVisible) { + const isDIAGNOSTICVisible = next.tabService.current === "diagnostic"; + if (isDIAGNOSTICVisible) { + logger.debug( + "[DIAGNOSTIC]", + "registerLifecycleListeners", + "onPageMount" + ); return onPageMount2(); } - if (!isDashboardVisible) { - onPageUnmount2(); + if (!isDIAGNOSTICVisible) { + logger.debug( + "[DIAGNOSTIC]", + "registerLifecycleListeners", + "onPageUnmount" + ); + return onPageUnmount2(); } } }); } async function initController2() { onMount("diagnostic-status").then(() => { + logger.debug("[DIAGNOSTIC]", "initController", "onMount"); onPageMount2(); registerLifecycleListeners2(); }); @@ -4139,7 +4216,7 @@ async function withTimeout(promise, timeoutMs, operationName, timeoutMessage = _ } finally { clearTimeout(timeoutId); const elapsed = performance.now() - start; - console.log(`[${operationName}] Execution time: ${elapsed.toFixed(2)} ms`); + logger.info("[SHELL]", `[${operationName}] took ${elapsed.toFixed(2)} ms`); } } @@ -4268,6 +4345,7 @@ return baseclass.extend({ FAKEIP_CHECK_DOMAIN, FETCH_TIMEOUT, IP_CHECK_DOMAIN, + Logger, PODKOP_LUCI_APP_VERSION, PodkopShellMethods, REGIONAL_OPTIONS, @@ -4285,6 +4363,7 @@ return baseclass.extend({ injectGlobalStyles, insertIf, insertIfObj, + logger, maskIP, onMount, parseQueryString, From 213b4603b78935405f9f6c6eb898908bec9a1ff5 Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 23:02:35 +0300 Subject: [PATCH 082/121] feat: add podkop log watcher with alerts --- fe-app-podkop/src/luci.d.ts | 5 ++ .../src/podkop/services/core.service.ts | 26 ++++++ .../services/podkopLogWatcher.service.ts | 88 +++++++++++++++++++ .../luci-static/resources/view/podkop/main.js | 85 ++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts diff --git a/fe-app-podkop/src/luci.d.ts b/fe-app-podkop/src/luci.d.ts index 9f70c21..3600e76 100644 --- a/fe-app-podkop/src/luci.d.ts +++ b/fe-app-podkop/src/luci.d.ts @@ -39,6 +39,11 @@ declare global { const ui = { showModal: (_title: stirng, _content: HtmlElement) => undefined, hideModal: () => undefined, + addNotification: ( + _title: string, + _children: HtmlElement | HtmlElement[], + _className?: string, + ) => undefined, }; } diff --git a/fe-app-podkop/src/podkop/services/core.service.ts b/fe-app-podkop/src/podkop/services/core.service.ts index 276475d..b9a52b0 100644 --- a/fe-app-podkop/src/podkop/services/core.service.ts +++ b/fe-app-podkop/src/podkop/services/core.service.ts @@ -1,6 +1,8 @@ import { TabServiceInstance } from './tab.service'; import { store } from './store.service'; import { logger } from './logger.service'; +import { PodkopLogWatcher } from './podkopLogWatcher.service'; +import { PodkopShellMethods } from '../methods'; export function coreService() { TabServiceInstance.onChange((activeId, tabs) => { @@ -12,4 +14,28 @@ export function coreService() { }, }); }); + + const watcher = PodkopLogWatcher.getInstance(); + + watcher.init( + async () => { + const logs = await PodkopShellMethods.checkLogs(); + + if (logs.success) { + return logs.data as string; + } + + return ''; + }, + { + intervalMs: 3000, + onNewLog: (line) => { + if (line.includes('[critical]')) { + ui.addNotification('Podkop Critical', E('div', {}, line), 'error'); + } + }, + }, + ); + + watcher.start(); } diff --git a/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts b/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts new file mode 100644 index 0000000..9548408 --- /dev/null +++ b/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts @@ -0,0 +1,88 @@ +import { logger } from './logger.service'; + +export type LogFetcher = () => Promise | string; + +export interface PodkopLogWatcherOptions { + intervalMs?: number; + onNewLog?: (line: string) => void; +} + +export class PodkopLogWatcher { + private static instance: PodkopLogWatcher; + private fetcher?: LogFetcher; + private onNewLog?: (line: string) => void; + private intervalMs = 5000; + private lastLines = new Set(); + private timer?: ReturnType; + private running = false; + + private constructor() {} + + static getInstance(): PodkopLogWatcher { + if (!PodkopLogWatcher.instance) { + PodkopLogWatcher.instance = new PodkopLogWatcher(); + } + return PodkopLogWatcher.instance; + } + + init(fetcher: LogFetcher, options?: PodkopLogWatcherOptions): void { + this.fetcher = fetcher; + this.onNewLog = options?.onNewLog; + this.intervalMs = options?.intervalMs ?? 5000; + } + + async checkOnce(): Promise { + if (!this.fetcher) { + logger.warn('[PodkopLogWatcher]', 'fetcher not found'); + return; + } + + try { + const raw = await this.fetcher(); + const lines = raw.split('\n').filter(Boolean); + + for (const line of lines) { + if (!this.lastLines.has(line)) { + this.lastLines.add(line); + + if (this.onNewLog) { + this.onNewLog(line); + } + } + } + + if (this.lastLines.size > 500) { + const arr = Array.from(this.lastLines); + this.lastLines = new Set(arr.slice(-500)); + } + } catch (err) { + logger.error('[PodkopLogWatcher]', 'failed to read logs:', err); + } + } + + start(): void { + if (this.running) return; + if (!this.fetcher) { + logger.warn('[PodkopLogWatcher]', 'Try to start without fetcher.'); + return; + } + + this.running = true; + this.timer = setInterval(() => this.checkOnce(), this.intervalMs); + logger.info( + `[PodkopLogWatcher]', 'Started with interval ${this.intervalMs} ms`, + ); + } + + stop(): void { + if (!this.running) return; + this.running = false; + if (this.timer) clearInterval(this.timer); + logger.info('[PodkopLogWatcher]', 'Stopped'); + } + + reset(): void { + this.lastLines.clear(); + logger.info('[PodkopLogWatcher]', 'logs history was reset'); + } +} diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 3a9c4a1..822440a 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1234,6 +1234,72 @@ var Logger = class { }; var logger = new Logger(); +// src/podkop/services/podkopLogWatcher.service.ts +var PodkopLogWatcher = class _PodkopLogWatcher { + constructor() { + this.intervalMs = 5e3; + this.lastLines = /* @__PURE__ */ new Set(); + this.running = false; + } + static getInstance() { + if (!_PodkopLogWatcher.instance) { + _PodkopLogWatcher.instance = new _PodkopLogWatcher(); + } + return _PodkopLogWatcher.instance; + } + init(fetcher, options) { + this.fetcher = fetcher; + this.onNewLog = options?.onNewLog; + this.intervalMs = options?.intervalMs ?? 5e3; + } + async checkOnce() { + if (!this.fetcher) { + logger.warn("[PodkopLogWatcher]", "fetcher not found"); + return; + } + try { + const raw = await this.fetcher(); + const lines = raw.split("\n").filter(Boolean); + for (const line of lines) { + if (!this.lastLines.has(line)) { + this.lastLines.add(line); + if (this.onNewLog) { + this.onNewLog(line); + } + } + } + if (this.lastLines.size > 500) { + const arr = Array.from(this.lastLines); + this.lastLines = new Set(arr.slice(-500)); + } + } catch (err) { + logger.error("[PodkopLogWatcher]", "failed to read logs:", err); + } + } + start() { + if (this.running) return; + if (!this.fetcher) { + logger.warn("[PodkopLogWatcher]", "Try to start without fetcher."); + return; + } + this.running = true; + this.timer = setInterval(() => this.checkOnce(), this.intervalMs); + logger.info( + `[PodkopLogWatcher]', 'Started with interval ${this.intervalMs} ms` + ); + } + stop() { + if (!this.running) return; + this.running = false; + if (this.timer) clearInterval(this.timer); + logger.info("[PodkopLogWatcher]", "Stopped"); + } + reset() { + this.lastLines.clear(); + logger.info("[PodkopLogWatcher]", "logs history was reset"); + } +}; + // src/podkop/services/core.service.ts function coreService() { TabServiceInstance.onChange((activeId, tabs) => { @@ -1245,6 +1311,25 @@ function coreService() { } }); }); + const watcher = PodkopLogWatcher.getInstance(); + watcher.init( + async () => { + const logs = await PodkopShellMethods.checkLogs(); + if (logs.success) { + return logs.data; + } + return ""; + }, + { + intervalMs: 3e3, + onNewLog: (line) => { + if (line.includes("[critical]")) { + ui.addNotification("Podkop Critical", E("div", {}, line), "error"); + } + } + } + ); + watcher.start(); } // src/podkop/services/socket.service.ts From 13f15dcf1171da9295e142d486bd0a22fb83ec7c Mon Sep 17 00:00:00 2001 From: divocat Date: Sat, 18 Oct 2025 23:06:07 +0300 Subject: [PATCH 083/121] feat: add pause for log watcher when tab not visible --- .../services/podkopLogWatcher.service.ts | 46 +++++++++++++++---- .../luci-static/resources/view/podkop/main.js | 39 +++++++++++++--- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts b/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts index 9548408..e09b288 100644 --- a/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts +++ b/fe-app-podkop/src/podkop/services/podkopLogWatcher.service.ts @@ -15,8 +15,16 @@ export class PodkopLogWatcher { private lastLines = new Set(); private timer?: ReturnType; private running = false; + private paused = false; - private constructor() {} + private constructor() { + if (typeof document !== 'undefined') { + document.addEventListener('visibilitychange', () => { + if (document.hidden) this.pause(); + else this.resume(); + }); + } + } static getInstance(): PodkopLogWatcher { if (!PodkopLogWatcher.instance) { @@ -29,6 +37,10 @@ export class PodkopLogWatcher { this.fetcher = fetcher; this.onNewLog = options?.onNewLog; this.intervalMs = options?.intervalMs ?? 5000; + logger.info( + '[PodkopLogWatcher]', + `initialized (interval: ${this.intervalMs}ms)`, + ); } async checkOnce(): Promise { @@ -37,6 +49,11 @@ export class PodkopLogWatcher { return; } + if (this.paused) { + logger.debug('[PodkopLogWatcher]', 'skipped check — tab not visible'); + return; + } + try { const raw = await this.fetcher(); const lines = raw.split('\n').filter(Boolean); @@ -44,10 +61,7 @@ export class PodkopLogWatcher { for (const line of lines) { if (!this.lastLines.has(line)) { this.lastLines.add(line); - - if (this.onNewLog) { - this.onNewLog(line); - } + this.onNewLog?.(line); } } @@ -63,14 +77,15 @@ export class PodkopLogWatcher { start(): void { if (this.running) return; if (!this.fetcher) { - logger.warn('[PodkopLogWatcher]', 'Try to start without fetcher.'); + logger.warn('[PodkopLogWatcher]', 'attempted to start without fetcher'); return; } this.running = true; this.timer = setInterval(() => this.checkOnce(), this.intervalMs); logger.info( - `[PodkopLogWatcher]', 'Started with interval ${this.intervalMs} ms`, + '[PodkopLogWatcher]', + `started (interval: ${this.intervalMs}ms)`, ); } @@ -78,11 +93,24 @@ export class PodkopLogWatcher { if (!this.running) return; this.running = false; if (this.timer) clearInterval(this.timer); - logger.info('[PodkopLogWatcher]', 'Stopped'); + logger.info('[PodkopLogWatcher]', 'stopped'); + } + + pause(): void { + if (!this.running || this.paused) return; + this.paused = true; + logger.info('[PodkopLogWatcher]', 'paused (tab not visible)'); + } + + resume(): void { + if (!this.running || !this.paused) return; + this.paused = false; + logger.info('[PodkopLogWatcher]', 'resumed (tab active)'); + this.checkOnce(); // сразу проверить, не появились ли новые логи } reset(): void { this.lastLines.clear(); - logger.info('[PodkopLogWatcher]', 'logs history was reset'); + logger.info('[PodkopLogWatcher]', 'log history reset'); } } diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 822440a..27c52c5 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1240,6 +1240,13 @@ var PodkopLogWatcher = class _PodkopLogWatcher { this.intervalMs = 5e3; this.lastLines = /* @__PURE__ */ new Set(); this.running = false; + this.paused = false; + if (typeof document !== "undefined") { + document.addEventListener("visibilitychange", () => { + if (document.hidden) this.pause(); + else this.resume(); + }); + } } static getInstance() { if (!_PodkopLogWatcher.instance) { @@ -1251,21 +1258,27 @@ var PodkopLogWatcher = class _PodkopLogWatcher { this.fetcher = fetcher; this.onNewLog = options?.onNewLog; this.intervalMs = options?.intervalMs ?? 5e3; + logger.info( + "[PodkopLogWatcher]", + `initialized (interval: ${this.intervalMs}ms)` + ); } async checkOnce() { if (!this.fetcher) { logger.warn("[PodkopLogWatcher]", "fetcher not found"); return; } + if (this.paused) { + logger.debug("[PodkopLogWatcher]", "skipped check \u2014 tab not visible"); + return; + } try { const raw = await this.fetcher(); const lines = raw.split("\n").filter(Boolean); for (const line of lines) { if (!this.lastLines.has(line)) { this.lastLines.add(line); - if (this.onNewLog) { - this.onNewLog(line); - } + this.onNewLog?.(line); } } if (this.lastLines.size > 500) { @@ -1279,24 +1292,36 @@ var PodkopLogWatcher = class _PodkopLogWatcher { start() { if (this.running) return; if (!this.fetcher) { - logger.warn("[PodkopLogWatcher]", "Try to start without fetcher."); + logger.warn("[PodkopLogWatcher]", "attempted to start without fetcher"); return; } this.running = true; this.timer = setInterval(() => this.checkOnce(), this.intervalMs); logger.info( - `[PodkopLogWatcher]', 'Started with interval ${this.intervalMs} ms` + "[PodkopLogWatcher]", + `started (interval: ${this.intervalMs}ms)` ); } stop() { if (!this.running) return; this.running = false; if (this.timer) clearInterval(this.timer); - logger.info("[PodkopLogWatcher]", "Stopped"); + logger.info("[PodkopLogWatcher]", "stopped"); + } + pause() { + if (!this.running || this.paused) return; + this.paused = true; + logger.info("[PodkopLogWatcher]", "paused (tab not visible)"); + } + resume() { + if (!this.running || !this.paused) return; + this.paused = false; + logger.info("[PodkopLogWatcher]", "resumed (tab active)"); + this.checkOnce(); } reset() { this.lastLines.clear(); - logger.info("[PodkopLogWatcher]", "logs history was reset"); + logger.info("[PodkopLogWatcher]", "log history reset"); } }; From 9d4c37b9a2e96767143e65d1fa7687249406677a Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Sun, 19 Oct 2025 18:53:21 +0500 Subject: [PATCH 084/121] refactor: create outbound validation into has_outbound_section and check all sections --- podkop/files/usr/bin/podkop | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index c0b83dd..b6eb9f5 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -71,18 +71,37 @@ check_requirements() { log "Detected https-dns-proxy in dhcp config. Edit /etc/config/dhcp" "warn" fi - local proxy_string interface outbound_json urltest_proxy_links - config_get proxy_string "main" "proxy_string" - config_get interface "main" "interface" - config_get outbound_json "main" "outbound_json" - config_get urltest_proxy_links "main" "urltest_proxy_links" - - if [ -z "$proxy_string" ] && [ -z "$interface" ] && [ -z "$outbound_json" ] && [ -z "$urltest_proxy_links" ]; then - log "Required options (proxy_string, interface, outbound_json, urltest_proxy_links) are missing in 'main' section. Aborted." "error" + if has_outbound_section; then + log "Outbound section found" "debug" + else + log "Outbound section not found. Please check your configuration file (missing proxy_string, interface, outbound_json, or urltest_proxy_links). Aborted." "fatal" exit 1 fi } +has_outbound_section() { + local section_exists=1 + + check_section() { + local section="$1" + local proxy_string interface outbound_json urltest_proxy_links + + config_get proxy_string "$section" "proxy_string" + config_get interface "$section" "interface" + config_get outbound_json "$section" "outbound_json" + config_get urltest_proxy_links "$section" "urltest_proxy_links" + + if [ -n "$proxy_string" ] || [ -n "$interface" ] || \ + [ -n "$outbound_json" ] || [ -n "$urltest_proxy_links" ]; then + section_exists=0 + fi + } + + config_foreach check_section "section" + + return $section_exists +} + start_main() { log "Starting podkop" From 08615b6f04a3a02cf086e36555235005c56d39c7 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Sun, 19 Oct 2025 19:17:50 +0500 Subject: [PATCH 085/121] refactor: use get_first_outbound_section to determine outbound tag, remove SB_MAIN_OUTBOUND_TAG constant --- podkop/files/usr/bin/podkop | 369 ++++++++++++++++-------------- podkop/files/usr/lib/constants.sh | 1 - 2 files changed, 194 insertions(+), 176 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index b6eb9f5..2fdf0d9 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -91,8 +91,8 @@ has_outbound_section() { config_get outbound_json "$section" "outbound_json" config_get urltest_proxy_links "$section" "urltest_proxy_links" - if [ -n "$proxy_string" ] || [ -n "$interface" ] || \ - [ -n "$outbound_json" ] || [ -n "$urltest_proxy_links" ]; then + if [ -n "$proxy_string" ] || [ -n "$interface" ] || + [ -n "$outbound_json" ] || [ -n "$urltest_proxy_links" ]; then section_exists=0 fi } @@ -751,9 +751,10 @@ sing_box_configure_route() { config=$(sing_box_cf_add_single_key_reject_rule "$config" "$SB_TPROXY_INBOUND_TAG" "protocol" "quic") fi - config=$( - sing_box_cf_proxy_domain "$config" "$SB_TPROXY_INBOUND_TAG" "$CHECK_PROXY_IP_DOMAIN" "$SB_MAIN_OUTBOUND_TAG" - ) + local first_outbound_section + first_outbound_section="$(get_first_outbound_section)" + first_outbound_tag="$(get_outbound_tag_by_section "$first_outbound_section")" + config=$(sing_box_cf_proxy_domain "$config" "$SB_TPROXY_INBOUND_TAG" "$CHECK_PROXY_IP_DOMAIN" "$first_outbound_tag") config=$(sing_box_cf_override_domain_port "$config" "$FAKEIP_TEST_DOMAIN" 8443) config_foreach include_source_ips_in_routing_handler "section" @@ -1392,6 +1393,25 @@ get_download_detour_tag() { fi } +_determine_first_outbound_section() { + local section="$1" + + local connection_type + config_get connection_type "$section" "connection_type" + + if [ "$connection_type" = "proxy" ] || [ "$connection_type" = "vpn" ]; then + [ -z "$first_section" ] && first_section="$1" + fi +} + +get_first_outbound_section() { + local first_section="" + + config_foreach _determine_first_outbound_section "section" + + echo "$first_section" +} + get_block_sections() { uci show podkop | grep "\.connection_type='block'" | cut -d'.' -f2 } @@ -1655,10 +1675,10 @@ check_logs() { nolog "Error: logread command not found" return 1 fi - + local logs logs=$(logread | grep -E "podkop|sing-box") - + if [ -z "$logs" ]; then nolog "Logs not found" return 1 @@ -1757,39 +1777,39 @@ show_system_info() { get_system_info() { local podkop_version podkop_latest_version luci_app_version sing_box_version openwrt_version device_model - + podkop_version="$PODKOP_VERSION" - + podkop_latest_version=$(curl -m 3 -s https://api.github.com/repos/itdoginfo/podkop/releases/latest | grep '"tag_name":' | cut -d'"' -f4) [ -z "$podkop_latest_version" ] && podkop_latest_version="unknown" - + if [ -f /www/luci-static/resources/view/podkop/main.js ]; then luci_app_version=$(grep 'var PODKOP_LUCI_APP_VERSION' /www/luci-static/resources/view/podkop/main.js | cut -d'"' -f2) else luci_app_version="not installed" fi - - if command -v sing-box >/dev/null 2>&1; then - sing_box_version=$(sing-box version 2>/dev/null | head -n 1 | awk '{print $3}') + + if command -v sing-box > /dev/null 2>&1; then + sing_box_version=$(sing-box version 2> /dev/null | head -n 1 | awk '{print $3}') [ -z "$sing_box_version" ] && sing_box_version="unknown" else sing_box_version="not installed" fi - + if [ -f /etc/os-release ]; then openwrt_version=$(grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2) [ -z "$openwrt_version" ] && openwrt_version="unknown" else openwrt_version="unknown" fi - + if [ -f /tmp/sysinfo/model ]; then device_model=$(cat /tmp/sysinfo/model) [ -z "$device_model" ] && device_model="unknown" else device_model="unknown" fi - + echo "{\"podkop_version\": \"$podkop_version\", \"podkop_latest_version\": \"$podkop_latest_version\", \"luci_app_version\": \"$luci_app_version\", \"sing_box_version\": \"$sing_box_version\", \"openwrt_version\": \"$openwrt_version\", \"device_model\": \"$device_model\"}" | jq . } @@ -1922,9 +1942,9 @@ check_dhcp_has_podkop_dns() { config_get server_list "$1" "server" config_get cachesize "$1" "cachesize" config_get noresolv "$1" "noresolv" - + server_found=0 - + if [ -n "$server_list" ]; then for server in $server_list; do if [ "$server" = "127.0.0.42" ]; then @@ -1933,7 +1953,7 @@ check_dhcp_has_podkop_dns() { fi done fi - + if [ "$cachesize" != "0" ] || [ "$noresolv" != "1" ] || [ "$server_found" != "1" ]; then dhcp_config_status=0 fi @@ -1948,68 +1968,68 @@ check_nft_rules() { local rules_proxy_exist=0 local rules_proxy_counters=0 local rules_other_mark_exist=0 - + # Generate traffic through PodkopTable curl -m 3 -s "https://$CHECK_PROXY_IP_DOMAIN/check" > /dev/null 2>&1 & local pid1=$! curl -m 3 -s "https://$FAKEIP_TEST_DOMAIN/check" > /dev/null 2>&1 & local pid2=$! - - wait $pid1 2>/dev/null - wait $pid2 2>/dev/null + + wait $pid1 2> /dev/null + wait $pid2 2> /dev/null sleep 1 - + # Check if PodkopTable exists if nft list table inet "$NFT_TABLE_NAME" > /dev/null 2>&1; then table_exist=1 - + # Check mangle chain rules if nft list chain inet "$NFT_TABLE_NAME" mangle > /dev/null 2>&1; then local mangle_output mangle_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle) if echo "$mangle_output" | grep -q "counter"; then rules_mangle_exist=1 - + if echo "$mangle_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then rules_mangle_counters=1 fi fi fi - + # Check mangle_output chain rules if nft list chain inet "$NFT_TABLE_NAME" mangle_output > /dev/null 2>&1; then local mangle_output_output mangle_output_output=$(nft list chain inet "$NFT_TABLE_NAME" mangle_output) if echo "$mangle_output_output" | grep -q "counter"; then rules_mangle_output_exist=1 - + if echo "$mangle_output_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then rules_mangle_output_counters=1 fi fi fi - + # Check proxy chain rules if nft list chain inet "$NFT_TABLE_NAME" proxy > /dev/null 2>&1; then local proxy_output proxy_output=$(nft list chain inet "$NFT_TABLE_NAME" proxy) if echo "$proxy_output" | grep -q "counter"; then rules_proxy_exist=1 - + if echo "$proxy_output" | grep "counter" | grep -qv "packets 0 bytes 0"; then rules_proxy_counters=1 fi fi fi fi - + # Check for other mark rules outside PodkopTable - nft list tables 2>/dev/null | while read -r _ family table_name; do + nft list tables 2> /dev/null | while read -r _ family table_name; do [ -z "$table_name" ] && continue - + [ "$table_name" = "$NFT_TABLE_NAME" ] && continue - - if nft list table "$family" "$table_name" 2>/dev/null | grep -q "meta mark set"; then + + if nft list table "$family" "$table_name" 2> /dev/null | grep -q "meta mark set"; then touch /tmp/podkop_mark_check.$$ break fi @@ -2019,7 +2039,7 @@ check_nft_rules() { rules_other_mark_exist=1 rm -f /tmp/podkop_mark_check.$$ fi - + echo "{\"table_exist\":$table_exist,\"rules_mangle_exist\":$rules_mangle_exist,\"rules_mangle_counters\":$rules_mangle_counters,\"rules_mangle_output_exist\":$rules_mangle_output_exist,\"rules_mangle_output_counters\":$rules_mangle_output_counters,\"rules_proxy_exist\":$rules_proxy_exist,\"rules_proxy_counters\":$rules_proxy_counters,\"rules_other_mark_exist\":$rules_other_mark_exist}" | jq . } @@ -2030,14 +2050,14 @@ check_sing_box() { local sing_box_autostart_disabled=0 local sing_box_process_running=0 local sing_box_ports_listening=0 - + # Check if sing-box is installed if command -v sing-box > /dev/null 2>&1; then sing_box_installed=1 - + # Check version (must be >= 1.12.4) local version - version=$(sing-box version 2>/dev/null | head -n 1 | awk '{print $3}') + version=$(sing-box version 2> /dev/null | head -n 1 | awk '{print $3}') if [ -n "$version" ]; then version=$(echo "$version" | sed 's/^v//') local major @@ -2046,11 +2066,11 @@ check_sing_box() { major=$(echo "$version" | cut -d. -f1) minor=$(echo "$version" | cut -d. -f2) patch=$(echo "$version" | cut -d. -f3) - + # Compare version: must be >= 1.12.4 - if [ "$major" -gt 1 ] || \ - [ "$major" -eq 1 ] && [ "$minor" -gt 12 ] || \ - [ "$major" -eq 1 ] && [ "$minor" -eq 12 ] && [ "$patch" -ge 4 ]; then + if [ "$major" -gt 1 ] || + [ "$major" -eq 1 ] && [ "$minor" -gt 12 ] || + [ "$major" -eq 1 ] && [ "$minor" -eq 12 ] && [ "$patch" -ge 4 ]; then sing_box_version_ok=1 fi fi @@ -2059,34 +2079,34 @@ check_sing_box() { # Check if service exists if [ -f /etc/init.d/sing-box ]; then sing_box_service_exist=1 - - if ! /etc/init.d/sing-box enabled 2>/dev/null; then + + if ! /etc/init.d/sing-box enabled 2> /dev/null; then sing_box_autostart_disabled=1 fi fi - + # Check if process is running if pgrep "sing-box" > /dev/null 2>&1; then sing_box_process_running=1 fi - + # Check if sing-box is listening on required ports local port_53_ok=0 local port_1602_ok=0 - - if netstat -ln 2>/dev/null | grep -q "127.0.0.42:53"; then + + if netstat -ln 2> /dev/null | grep -q "127.0.0.42:53"; then port_53_ok=1 fi - - if netstat -ln 2>/dev/null | grep -q "127.0.0.1:1602"; then + + if netstat -ln 2> /dev/null | grep -q "127.0.0.1:1602"; then port_1602_ok=1 fi - + # Both ports must be listening if [ "$port_53_ok" = "1" ] && [ "$port_1602_ok" = "1" ]; then sing_box_ports_listening=1 fi - + echo "{\"sing_box_installed\":$sing_box_installed,\"sing_box_version_ok\":$sing_box_version_ok,\"sing_box_service_exist\":$sing_box_service_exist,\"sing_box_autostart_disabled\":$sing_box_autostart_disabled,\"sing_box_process_running\":$sing_box_process_running,\"sing_box_ports_listening\":$sing_box_ports_listening}" | jq . } @@ -2113,95 +2133,94 @@ clash_api() { local CLASH_URL="127.0.0.1:9090" local TEST_URL="https://www.gstatic.com/generate_204" local action="$1" - - case "$action" in - get_proxies) - curl -s "$CLASH_URL/proxies" | jq . - ;; - - get_proxy_latency) - local proxy_tag="$2" - local timeout="${3:-2000}" - - if [ -z "$proxy_tag" ]; then - echo '{"error":"proxy_tag required"}' | jq . - return 1 - fi - - curl -G -s "$CLASH_URL/proxies/$proxy_tag/delay" \ - --data-urlencode "url=$TEST_URL" \ - --data-urlencode "timeout=$timeout" | jq . - ;; - - get_group_latency) - local group_tag="$2" - local timeout="${3:-5000}" - - if [ -z "$group_tag" ]; then - echo '{"error":"group_tag required"}' | jq . - return 1 - fi - - curl -G -s "$CLASH_URL/group/$group_tag/delay" \ - --data-urlencode "url=$TEST_URL" \ - --data-urlencode "timeout=$timeout" | jq . - ;; - - set_group_proxy) - local group_tag="$2" - local proxy_tag="$3" - - if [ -z "$group_tag" ] || [ -z "$proxy_tag" ]; then - echo '{"error":"group_tag and proxy_tag required"}' | jq . - return 1 - fi - - local response - response=$(curl -X PUT -s -w "\n%{http_code}" "$CLASH_URL/proxies/$group_tag" \ - --data-raw "{\"name\":\"$proxy_tag\"}") - - local http_code - local body - http_code=$(echo "$response" | tail -n 1) - body=$(echo "$response" | sed '$d') - - case "$http_code" in - 204) - echo "{\"success\":true,\"group\":\"$group_tag\",\"proxy\":\"$proxy_tag\"}" | jq . - ;; - 404) - echo "{\"success\":false,\"error\":\"group_not_found\",\"message\":\"$group_tag does not exist\"}" | jq . - return 1 - ;; - 400) - if echo "$body" | grep -q "not found"; then - echo "{\"success\":false,\"error\":\"proxy_not_found\",\"message\":\"$proxy_tag not found in group $group_tag\"}" | jq . - else - echo '{"success":false,"error":"bad_request","message":"Invalid request"}' | jq . - fi - return 1 - ;; - *) - if [ -n "$body" ]; then - local body_json - body_json=$(echo "$body" | jq -c .) - echo "{\"success\":false,\"http_code\":$http_code,\"body\":$body_json}" | jq . - else - echo "{\"success\":false,\"http_code\":$http_code}" | jq . - fi - return 1 - ;; - esac - ;; - *) - echo '{"error":"unknown action","available":["get_proxies","get_proxy_latency","get_group_latency","set_group_proxy"]}' | jq . + case "$action" in + get_proxies) + curl -s "$CLASH_URL/proxies" | jq . + ;; + + get_proxy_latency) + local proxy_tag="$2" + local timeout="${3:-2000}" + + if [ -z "$proxy_tag" ]; then + echo '{"error":"proxy_tag required"}' | jq . + return 1 + fi + + curl -G -s "$CLASH_URL/proxies/$proxy_tag/delay" \ + --data-urlencode "url=$TEST_URL" \ + --data-urlencode "timeout=$timeout" | jq . + ;; + + get_group_latency) + local group_tag="$2" + local timeout="${3:-5000}" + + if [ -z "$group_tag" ]; then + echo '{"error":"group_tag required"}' | jq . + return 1 + fi + + curl -G -s "$CLASH_URL/group/$group_tag/delay" \ + --data-urlencode "url=$TEST_URL" \ + --data-urlencode "timeout=$timeout" | jq . + ;; + + set_group_proxy) + local group_tag="$2" + local proxy_tag="$3" + + if [ -z "$group_tag" ] || [ -z "$proxy_tag" ]; then + echo '{"error":"group_tag and proxy_tag required"}' | jq . + return 1 + fi + + local response + response=$(curl -X PUT -s -w "\n%{http_code}" "$CLASH_URL/proxies/$group_tag" \ + --data-raw "{\"name\":\"$proxy_tag\"}") + + local http_code + local body + http_code=$(echo "$response" | tail -n 1) + body=$(echo "$response" | sed '$d') + + case "$http_code" in + 204) + echo "{\"success\":true,\"group\":\"$group_tag\",\"proxy\":\"$proxy_tag\"}" | jq . + ;; + 404) + echo "{\"success\":false,\"error\":\"group_not_found\",\"message\":\"$group_tag does not exist\"}" | jq . return 1 ;; + 400) + if echo "$body" | grep -q "not found"; then + echo "{\"success\":false,\"error\":\"proxy_not_found\",\"message\":\"$proxy_tag not found in group $group_tag\"}" | jq . + else + echo '{"success":false,"error":"bad_request","message":"Invalid request"}' | jq . + fi + return 1 + ;; + *) + if [ -n "$body" ]; then + local body_json + body_json=$(echo "$body" | jq -c .) + echo "{\"success\":false,\"http_code\":$http_code,\"body\":$body_json}" | jq . + else + echo "{\"success\":false,\"http_code\":$http_code}" | jq . + fi + return 1 + ;; + esac + ;; + + *) + echo '{"error":"unknown action","available":["get_proxies","get_proxy_latency","get_group_latency","set_group_proxy"]}' | jq . + return 1 + ;; esac } - print_global() { local message="$1" echo "$message" @@ -2214,20 +2233,20 @@ global_check() { print_global "📡 Global check run!" print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "🛠️ System info" - + local system_info_json system_info_json=$(get_system_info) - + if [ -n "$system_info_json" ]; then local podkop_version podkop_latest_version luci_app_version sing_box_version openwrt_version device_model - + podkop_version=$(echo "$system_info_json" | jq -r '.podkop_version // "unknown"') podkop_latest_version=$(echo "$system_info_json" | jq -r '.podkop_latest_version // "unknown"') luci_app_version=$(echo "$system_info_json" | jq -r '.luci_app_version // "unknown"') sing_box_version=$(echo "$system_info_json" | jq -r '.sing_box_version // "unknown"') openwrt_version=$(echo "$system_info_json" | jq -r '.openwrt_version // "unknown"') device_model=$(echo "$system_info_json" | jq -r '.device_model // "unknown"') - + print_global "🕳️ Podkop: $podkop_version (latest: $podkop_latest_version)" print_global "🕳️ LuCI App: $luci_app_version" print_global "📦 Sing-box: $sing_box_version" @@ -2239,13 +2258,13 @@ global_check() { print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "➡️ DNS status" - + local dns_check_json dns_check_json=$(check_dns_available) - + if [ -n "$dns_check_json" ]; then local dns_type dns_server dns_status dns_on_router bootstrap_dns_server bootstrap_dns_status dhcp_config_status - + dns_type=$(echo "$dns_check_json" | jq -r '.dns_type // "unknown"') dns_server=$(echo "$dns_check_json" | jq -r '.dns_server // "unknown"') dns_status=$(echo "$dns_check_json" | jq -r '.dns_status // 0') @@ -2253,7 +2272,7 @@ global_check() { bootstrap_dns_server=$(echo "$dns_check_json" | jq -r '.bootstrap_dns_server // ""') bootstrap_dns_status=$(echo "$dns_check_json" | jq -r '.bootstrap_dns_status // 0') dhcp_config_status=$(echo "$dns_check_json" | jq -r '.dhcp_config_status // 0') - + # Bootstrap DNS if [ -n "$bootstrap_dns_server" ]; then if [ "$bootstrap_dns_status" -eq 1 ]; then @@ -2262,25 +2281,25 @@ global_check() { print_global "❌ Bootstrap DNS: $bootstrap_dns_server" fi fi - + # DNS server status if [ "$dns_status" -eq 1 ]; then print_global "✅ Main DNS: $dns_server [$dns_type]" else print_global "❌ Main DNS: $dns_server [$dns_type]" fi - + # DNS on router if [ "$dns_on_router" -eq 1 ]; then print_global "✅ DNS on router" else print_global "❌ DNS on router" fi - + # DHCP configuration check local dont_touch_dhcp config_get dont_touch_dhcp "main" "dont_touch_dhcp" - + if [ "$dont_touch_dhcp" = "1" ]; then print_global "⚠️ dont_touch_dhcp is enabled. 📄 DHCP config:" awk '/^config /{p=($2=="dnsmasq")} p' /etc/config/dhcp @@ -2296,50 +2315,50 @@ global_check() { print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "📦 Sing-box status" - + local singbox_check_json singbox_check_json=$(check_sing_box) - + if [ -n "$singbox_check_json" ]; then local sing_box_installed sing_box_version_ok sing_box_service_exist sing_box_autostart_disabled sing_box_process_running sing_box_ports_listening - + sing_box_installed=$(echo "$singbox_check_json" | jq -r '.sing_box_installed // 0') sing_box_version_ok=$(echo "$singbox_check_json" | jq -r '.sing_box_version_ok // 0') sing_box_service_exist=$(echo "$singbox_check_json" | jq -r '.sing_box_service_exist // 0') sing_box_autostart_disabled=$(echo "$singbox_check_json" | jq -r '.sing_box_autostart_disabled // 0') sing_box_process_running=$(echo "$singbox_check_json" | jq -r '.sing_box_process_running // 0') sing_box_ports_listening=$(echo "$singbox_check_json" | jq -r '.sing_box_ports_listening // 0') - + if [ "$sing_box_installed" -eq 1 ]; then print_global "✅ Sing-box installed" else print_global "❌ Sing-box installed" fi - + if [ "$sing_box_version_ok" -eq 1 ]; then print_global "✅ Sing-box version >= 1.12.4" else print_global "❌ Sing-box version >= 1.12.4" fi - + if [ "$sing_box_service_exist" -eq 1 ]; then print_global "✅ Sing-box service exist" else print_global "❌ Sing-box service exist" fi - + if [ "$sing_box_autostart_disabled" -eq 1 ]; then print_global "✅ Sing-box autostart disabled" else print_global "❌ Sing-box autostart disabled" fi - + if [ "$sing_box_process_running" -eq 1 ]; then print_global "✅ Sing-box process running" else print_global "❌ Sing-box process running" fi - + if [ "$sing_box_ports_listening" -eq 1 ]; then print_global "✅ Sing-box listening ports" else @@ -2351,13 +2370,13 @@ global_check() { print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "🧱 NFT rules status" - + local nft_check_json nft_check_json=$(check_nft_rules) - + if [ -n "$nft_check_json" ]; then local table_exist rules_mangle_exist rules_mangle_counters rules_mangle_output_exist rules_mangle_output_counters rules_proxy_exist rules_proxy_counters rules_other_mark_exist - + table_exist=$(echo "$nft_check_json" | jq -r '.table_exist // 0') rules_mangle_exist=$(echo "$nft_check_json" | jq -r '.rules_mangle_exist // 0') rules_mangle_counters=$(echo "$nft_check_json" | jq -r '.rules_mangle_counters // 0') @@ -2366,49 +2385,49 @@ global_check() { rules_proxy_exist=$(echo "$nft_check_json" | jq -r '.rules_proxy_exist // 0') rules_proxy_counters=$(echo "$nft_check_json" | jq -r '.rules_proxy_counters // 0') rules_other_mark_exist=$(echo "$nft_check_json" | jq -r '.rules_other_mark_exist // 0') - + if [ "$table_exist" -eq 1 ]; then print_global "✅ Table exist" else print_global "❌ Table exist" fi - + if [ "$rules_mangle_exist" -eq 1 ]; then print_global "✅ Rules mangle exist" else print_global "❌ Rules mangle exist" fi - + if [ "$rules_mangle_counters" -eq 1 ]; then print_global "✅ Rules mangle counters" else print_global "⚠️ Rules mangle counters" fi - + if [ "$rules_mangle_output_exist" -eq 1 ]; then print_global "✅ Rules mangle output exist" else print_global "❌ Rules mangle output exist" fi - + if [ "$rules_mangle_output_counters" -eq 1 ]; then print_global "✅ Rules mangle output counters" else print_global "⚠️ Rules mangle output counters" fi - + if [ "$rules_proxy_exist" -eq 1 ]; then print_global "✅ Rules proxy exist" else print_global "❌ Rules proxy exist" fi - + if [ "$rules_proxy_counters" -eq 1 ]; then print_global "✅ Rules proxy counters" else print_global "⚠️ Rules proxy counters" fi - + if [ "$rules_other_mark_exist" -eq 1 ]; then print_global "⚠️ Additional marking rules found:" nft list ruleset | awk '/table inet '"$NFT_TABLE_NAME"'/{flag=1; next} /^table/{flag=0} !flag' | grep -E "mark set|meta mark" @@ -2481,8 +2500,8 @@ global_check() { if uci show network | grep -q route_allowed_ips; then uci show network | grep "wireguard_.*\.route_allowed_ips='1'" | cut -d'.' -f1-2 | while read -r peer_section; do local allowed_ips - allowed_ips=$(uci get "${peer_section}.allowed_ips" 2>/dev/null) - + allowed_ips=$(uci get "${peer_section}.allowed_ips" 2> /dev/null) + if [ "$allowed_ips" = "0.0.0.0/0" ]; then print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "⚠️ WG Route allowed IP enabled with 0.0.0.0/0" @@ -2497,15 +2516,15 @@ global_check() { print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_global "🥸 FakeIP status" - + local fakeip_check_json fakeip_check_json=$(check_fakeip) if [ -n "$fakeip_check_json" ]; then local fakeip_status - + fakeip_status=$(echo "$fakeip_check_json" | jq -r '.fakeip // false') - + if [ "$fakeip_status" = "true" ]; then print_global "✅ Router DNS is routed through sing-box" else @@ -2517,7 +2536,7 @@ global_check() { local fakeip_address fakeip_address=$(dig +short @127.0.0.42 $FAKEIP_TEST_DOMAIN) - + if echo "$fakeip_address" | grep -q "^198\.18\."; then print_global "✅ Sing-box works with FakeIP: $fakeip_address" else @@ -2646,4 +2665,4 @@ global_check) show_help exit 1 ;; -esac \ No newline at end of file +esac diff --git a/podkop/files/usr/lib/constants.sh b/podkop/files/usr/lib/constants.sh index 621ffe9..6d98d79 100644 --- a/podkop/files/usr/lib/constants.sh +++ b/podkop/files/usr/lib/constants.sh @@ -44,7 +44,6 @@ SB_SERVICE_MIXED_INBOUND_ADDRESS="127.0.0.1" SB_SERVICE_MIXED_INBOUND_PORT=4534 # Outbounds SB_DIRECT_OUTBOUND_TAG="direct-out" -SB_MAIN_OUTBOUND_TAG="main-out" # Route SB_REJECT_RULE_TAG="reject-rule-tag" # Experimental From c0e3e256e33ceba92a0d0386f65db23d9afc82eb Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Sun, 19 Oct 2025 19:19:59 +0500 Subject: [PATCH 086/121] chore: extract outbound section check into _check_outbound_section and call it via config_foreach --- podkop/files/usr/bin/podkop | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 2fdf0d9..742759b 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -79,25 +79,25 @@ check_requirements() { fi } +_check_outbound_section() { + local section="$1" + local proxy_string interface outbound_json urltest_proxy_links + + config_get proxy_string "$section" "proxy_string" + config_get interface "$section" "interface" + config_get outbound_json "$section" "outbound_json" + config_get urltest_proxy_links "$section" "urltest_proxy_links" + + if [ -n "$proxy_string" ] || [ -n "$interface" ] || + [ -n "$outbound_json" ] || [ -n "$urltest_proxy_links" ]; then + section_exists=0 + fi +} + has_outbound_section() { local section_exists=1 - check_section() { - local section="$1" - local proxy_string interface outbound_json urltest_proxy_links - - config_get proxy_string "$section" "proxy_string" - config_get interface "$section" "interface" - config_get outbound_json "$section" "outbound_json" - config_get urltest_proxy_links "$section" "urltest_proxy_links" - - if [ -n "$proxy_string" ] || [ -n "$interface" ] || - [ -n "$outbound_json" ] || [ -n "$urltest_proxy_links" ]; then - section_exists=0 - fi - } - - config_foreach check_section "section" + config_foreach _check_outbound_section "section" return $section_exists } From e7f3d15bce73681d462bdadf3428b94f53f97114 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Sun, 19 Oct 2025 19:23:49 +0500 Subject: [PATCH 087/121] fix: read dont_touch_dhcp from "settings" section instead of "main" --- podkop/files/usr/bin/podkop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 742759b..a718faa 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -2298,7 +2298,7 @@ global_check() { # DHCP configuration check local dont_touch_dhcp - config_get dont_touch_dhcp "main" "dont_touch_dhcp" + config_get dont_touch_dhcp "settings" "dont_touch_dhcp" if [ "$dont_touch_dhcp" = "1" ]; then print_global "⚠️ dont_touch_dhcp is enabled. 📄 DHCP config:" From 72ad10d7379685e46cc4821ec5a4d8931ea5ff61 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Mon, 20 Oct 2025 16:58:45 +0500 Subject: [PATCH 088/121] chore: standardize log messages and severities --- podkop/files/usr/bin/podkop | 38 +++++++++---------- podkop/files/usr/lib/helpers.sh | 2 +- .../files/usr/lib/sing_box_config_facade.sh | 4 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index a718faa..722a218 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -38,43 +38,43 @@ check_requirements() { coreutils_base64_version="$(base64 --version | head -n1 | awk '{print $4}')" if [ -z "$sing_box_version" ]; then - log "Package 'sing-box' is not installed." "error" + log "Package 'sing-box' is not installed. Aborted." "error" exit 1 else if ! is_min_package_version "$sing_box_version" "$SB_REQUIRED_VERSION"; then - log "Package 'sing-box' version ($sing_box_version) is lower than the required minimum ($SB_REQUIRED_VERSION). Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box" "error" + log "Package 'sing-box' version ($sing_box_version) is lower than the required minimum ($SB_REQUIRED_VERSION). Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box. Aborted." "error" exit 1 fi if ! service_exists "sing-box"; then - log "Service 'sing-box' is missing. Please install the official package to ensure the service is available." "error" + log "Service 'sing-box' is missing. Please install the official package to ensure the service is available. Aborted." "error" exit 1 fi fi if [ -z "$jq_version" ]; then - log "Package 'jq' is not installed." "error" + log "Package 'jq' is not installed. Aborted." "error" exit 1 elif ! is_min_package_version "$jq_version" "$JQ_REQUIRED_VERSION"; then - log "Package 'jq' version ($jq_version) is lower than the required minimum ($JQ_REQUIRED_VERSION)." "error" + log "Package 'jq' version ($jq_version) is lower than the required minimum ($JQ_REQUIRED_VERSION). Aborted." "error" exit 1 fi if [ -z "$coreutils_base64_version" ]; then - log "Package 'coreutils-base64' is not installed." "error" + log "Package 'coreutils-base64' is not installed. Aborted." "error" exit 1 elif ! is_min_package_version "$coreutils_base64_version" "$COREUTILS_BASE64_REQUIRED_VERSION"; then log "Package 'coreutils-base64' version ($coreutils_base64_version) is lower than the required minimum ($COREUTILS_BASE64_REQUIRED_VERSION). This may cause issues when decoding base64 streams with missing padding, as automatic padding support is not available in older versions." "warn" fi if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then - log "Detected https-dns-proxy in dhcp config. Edit /etc/config/dhcp" "warn" + log "Detected https-dns-proxy in DHCP config. Edit /etc/config/dhcp" "error" fi if has_outbound_section; then log "Outbound section found" "debug" else - log "Outbound section not found. Please check your configuration file (missing proxy_string, interface, outbound_json, or urltest_proxy_links). Aborted." "fatal" + log "Outbound section not found. Please check your configuration file (missing proxy_string, interface, outbound_json, or urltest_proxy_links). Aborted." "error" exit 1 fi } @@ -254,17 +254,17 @@ route_table_rule_mark() { grep -q "105 $table" /etc/iproute2/rt_tables || echo "105 $table" >> /etc/iproute2/rt_tables if ! ip route list table $table | grep -q "local default dev lo scope host"; then - log "Added route for tproxy" + log "Added route for tproxy" "debug" ip route add local 0.0.0.0/0 dev lo table $table else - log "Route for tproxy exists" + log "Route for tproxy exists" "debug" fi if ! ip rule list | grep -q "from all fwmark 0x105 lookup $table"; then - log "Create marking rule" + log "Create marking rule" "debug" ip -4 rule add fwmark 0x105 table $table priority 105 else - log "Marking rule exist" + log "Marking rule exist" "debug" fi } @@ -911,7 +911,7 @@ prepare_common_ruleset() { config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$ruleset_tag") ;; subnets) ;; - *) log "Unsupported remote rule set type: $type" "warn" ;; + *) log "Unsupported remote rule set type: $type" "error" ;; esac fi } @@ -995,7 +995,7 @@ configure_local_domain_or_subnet_lists() { config_list_foreach "$section" "local_subnet_lists" import_local_domain_or_subnet_list "$type" \ "$section" "$ruleset_filepath" ;; - *) log "Unsupported local rule set type: $type" "warn" ;; + *) log "Unsupported local rule set type: $type" "error" ;; esac } @@ -1006,7 +1006,7 @@ import_local_domain_or_subnet_list() { local ruleset_filepath="$4" if ! file_exists "$filepath"; then - log "File $filepath not found" "warn" + log "File $filepath not found" "error" return 1 fi @@ -1014,7 +1014,7 @@ import_local_domain_or_subnet_list() { items="$(parse_domain_or_subnet_file_to_comma_string "$filepath" "$type")" if [ -z "$items" ]; then - log "No valid $type found in $filepath" + log "No valid $type found in $filepath" "warn" return 0 fi @@ -1053,7 +1053,7 @@ configure_remote_domain_or_subnet_list_handler() { config=$(sing_box_cm_patch_dns_route_rule "$config" "$SB_FAKEIP_DNS_RULE_TAG" "rule_set" "$ruleset_tag") ;; subnets) ;; - *) log "Unsupported remote rule set type: $type" "warn" ;; + *) log "Unsupported remote rule set type: $type" "error" ;; esac ;; *) @@ -1152,7 +1152,7 @@ sing_box_config_check() { local config_path="$1" if ! sing-box -c "$config_path" check > /dev/null 2>&1; then - log "Sing-box configuration $config_path is invalid" "fatal" + log "Sing-box configuration $config_path is invalid. Aborted." "fatal" exit 1 fi } @@ -1308,7 +1308,7 @@ import_domains_or_subnets_from_remote_file() { rm -f "$tmpfile" if [ -z "$items" ]; then - log "No valid $type found in $url" + log "No valid $type found in $url" "warn" return 0 fi diff --git a/podkop/files/usr/lib/helpers.sh b/podkop/files/usr/lib/helpers.sh index a8d035a..7af83f2 100644 --- a/podkop/files/usr/lib/helpers.sh +++ b/podkop/files/usr/lib/helpers.sh @@ -128,7 +128,7 @@ get_ruleset_format_by_file_extension() { json) format="source" ;; srs) format="binary" ;; *) - log "Unsupported file extension: .$file_extension" + log "Unsupported file extension: .$file_extension" "error" return 1 ;; esac diff --git a/podkop/files/usr/lib/sing_box_config_facade.sh b/podkop/files/usr/lib/sing_box_config_facade.sh index bdff029..1c3fb04 100644 --- a/podkop/files/usr/lib/sing_box_config_facade.sh +++ b/podkop/files/usr/lib/sing_box_config_facade.sh @@ -34,7 +34,7 @@ sing_box_cf_add_dns_server() { "$domain_resolver" "$detour") ;; *) - log "Unsupported DNS server type: $type" + log "Unsupported DNS server type: $type. Aborted." "fatal" exit 1 ;; esac @@ -121,7 +121,7 @@ sing_box_cf_add_proxy_outbound() { config=$(_add_outbound_transport "$config" "$tag" "$url") ;; *) - log "Unsupported proxy $scheme type" + log "Unsupported proxy $scheme type. Aborted." "fatal" exit 1 ;; esac From 9d5cdc3e9035256c82ec7fe96a47523de097a9c5 Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 20 Oct 2025 16:17:57 +0300 Subject: [PATCH 089/121] fix: change new log trigger for output --- fe-app-podkop/src/podkop/services/core.service.ts | 4 ++-- .../htdocs/luci-static/resources/view/podkop/main.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fe-app-podkop/src/podkop/services/core.service.ts b/fe-app-podkop/src/podkop/services/core.service.ts index b9a52b0..b2d44b3 100644 --- a/fe-app-podkop/src/podkop/services/core.service.ts +++ b/fe-app-podkop/src/podkop/services/core.service.ts @@ -30,8 +30,8 @@ export function coreService() { { intervalMs: 3000, onNewLog: (line) => { - if (line.includes('[critical]')) { - ui.addNotification('Podkop Critical', E('div', {}, line), 'error'); + if (line.toLowerCase().includes('[error]') || line.toLowerCase().includes('[fatal]')) { + ui.addNotification('Podkop Error', E('div', {}, line), 'error'); } }, }, diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 27c52c5..e0d6800 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1348,8 +1348,8 @@ function coreService() { { intervalMs: 3e3, onNewLog: (line) => { - if (line.includes("[critical]")) { - ui.addNotification("Podkop Critical", E("div", {}, line), "error"); + if (line.toLowerCase().includes("[error]") || line.toLowerCase().includes("[fatal]")) { + ui.addNotification("Podkop Error", E("div", {}, line), "error"); } } } From 8ff9562dcf27537abe0a8d11c154102755ad01d5 Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 20 Oct 2025 17:50:05 +0300 Subject: [PATCH 090/121] feat: rename enable_shadowsocks_udp_over_tcp --- .../htdocs/luci-static/resources/view/podkop/section.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 af69b10..503a96d 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 @@ -121,9 +121,9 @@ function createSectionContent(section) { o = section.option( form.Flag, - 'enable_shadowsocks_udp_over_tcp', - _('Shadowsocks UDP over TCP'), - _('Apply for SS2022'), + 'enable_udp_over_tcp', + _('Shadowsocks/Socks UDP over TCP'), + _('Apply for socks/Shadowsocks 2022'), ); o.default = '0'; o.depends('connection_type', 'proxy'); From 33b44fd9b342a006912d4a70201800f3ecca051e Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Mon, 20 Oct 2025 20:00:21 +0500 Subject: [PATCH 091/121] chore: add SOCKS proxy examples to String-example.md --- String-example.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/String-example.md b/String-example.md index d694504..6e6b148 100644 --- a/String-example.md +++ b/String-example.md @@ -1,3 +1,11 @@ +## Socks +``` +socks4://127.0.0.1:1080 +socks4a://127.0.0.1:1080 +socks5://127.0.0.1:1080 +socks5://username:password@127.0.0.1:1080 +``` + ## Shadowsocks ``` ss://MjAyMi1ibGFrZTMtYWVzLTI1Ni1nY206ZG1DbHkvWmgxNVd3OStzK0dGWGlGVElrcHc3Yy9xQ0lTYUJyYWk3V2hoWT0@127.0.0.1:25144?type=tcp#shadowsocks-no-client From 024c258d9229b9c374d22761dce82099af499489 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Mon, 20 Oct 2025 20:02:34 +0500 Subject: [PATCH 092/121] chore: rename SOCKS5 outbound function/comments to generic SOCKS --- podkop/files/usr/lib/sing_box_config_manager.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/podkop/files/usr/lib/sing_box_config_manager.sh b/podkop/files/usr/lib/sing_box_config_manager.sh index ce66424..87618aa 100644 --- a/podkop/files/usr/lib/sing_box_config_manager.sh +++ b/podkop/files/usr/lib/sing_box_config_manager.sh @@ -449,12 +449,12 @@ sing_box_cm_add_direct_outbound() { } ####################################### -# Add a SOCKS5 outbound to the outbounds section of a sing-box JSON configuration. +# Add a SOCKS outbound to the outbounds section of a sing-box JSON configuration. # Arguments: # config: JSON configuration (string) # tag: string, identifier for the outbound -# server_address: string, IP address or hostname of the SOCKS5 server -# server_port: number, port of the SOCKS5 server +# server_address: string, IP address or hostname of the SOCKS server +# server_port: number, port of the SOCKS server # version: string, optional SOCKS version # username: string, optional username for authentication # password: string, optional password for authentication @@ -463,9 +463,9 @@ sing_box_cm_add_direct_outbound() { # Outputs: # Writes updated JSON configuration to stdout # Example: -# CONFIG=$(sing_box_cm_add_socks5_outbound "$CONFIG" "socks5-out" "192.168.1.10" 1080) +# CONFIG=$(sing_box_cm_add_socks_outbound "$CONFIG" "socks5-out" "192.168.1.10" 1080) ####################################### -sing_box_cm_add_socks5_outbound() { +sing_box_cm_add_socks_outbound() { local config="$1" local tag="$2" local server_address="$3" From d0b06dd829f2bfcd66776798e86ad72dfb9f8a2b Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Mon, 20 Oct 2025 20:03:13 +0500 Subject: [PATCH 093/121] refactor: rename enable_shadowsocks_udp_over_tcp to enable_udp_over_tcp in config and script --- podkop/files/etc/config/podkop | 2 +- podkop/files/usr/bin/podkop | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/podkop/files/etc/config/podkop b/podkop/files/etc/config/podkop index 406c2c7..b9aecdc 100644 --- a/podkop/files/etc/config/podkop +++ b/podkop/files/etc/config/podkop @@ -22,7 +22,7 @@ config section 'main' option connection_type 'proxy' option proxy_config_type 'url' option proxy_string '' - option enable_shadowsocks_udp_over_tcp '0' + option enable_udp_over_tcp '0' #list community_lists 'russia_inside' #option user_domain_list_type 'dynamic' #list user_domains '2ip.ru' diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 722a218..dd243c0 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -610,7 +610,7 @@ configure_outbound_handler() { log "Detected proxy configuration type: url" "debug" local proxy_string udp_over_tcp config_get proxy_string "$section" "proxy_string" - config_get udp_over_tcp "$section" "enable_shadowsocks_udp_over_tcp" + config_get udp_over_tcp "$section" "enable_udp_over_tcp" # Extract the first non-comment line as the active configuration active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1) @@ -631,7 +631,7 @@ configure_outbound_handler() { local urltest_proxy_links udp_over_tcp i urltest_tag selector_tag outbound_tag outbound_tags \ urltest_outbounds selector_outbounds config_get urltest_proxy_links "$section" "urltest_proxy_links" - config_get udp_over_tcp "$section" "enable_shadowsocks_udp_over_tcp" + config_get udp_over_tcp "$section" "enable_udp_over_tcp" if [ -z "$urltest_proxy_links" ]; then log "URLTest proxy links is not set. Aborted." "fatal" From 9c01c8e2dd141654e05786eefb47e79345c5d3a1 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Mon, 20 Oct 2025 20:09:15 +0500 Subject: [PATCH 094/121] feat: add socks4/socks4a/socks5 outbound support --- podkop/files/usr/lib/sing_box_config_facade.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/podkop/files/usr/lib/sing_box_config_facade.sh b/podkop/files/usr/lib/sing_box_config_facade.sh index 1c3fb04..86ee149 100644 --- a/podkop/files/usr/lib/sing_box_config_facade.sh +++ b/podkop/files/usr/lib/sing_box_config_facade.sh @@ -66,6 +66,23 @@ sing_box_cf_add_proxy_outbound() { local scheme="${url%%://*}" case "$scheme" in + socks4 | socks4a | socks5) + local tag host port version userinfo username password udp_over_tcp + + tag=$(get_outbound_tag_by_section "$section") + host=$(url_get_host "$url") + port=$(url_get_port "$url") + version="${scheme#socks}" + if [ "$scheme" = "socks5" ]; then + userinfo=$(url_get_userinfo "$url") + if [ -n "$userinfo" ]; then + username="${userinfo%%:*}" + password="${userinfo#*:}" + fi + fi + config="$(sing_box_cm_add_socks_outbound "$config" "$tag" "$host" "$port" "$version" "$username" "$password" \ + "" "$udp_over_tcp")" + ;; vless) local tag host port uuid flow packet_encoding tag=$(get_outbound_tag_by_section "$section") From 49dd1d608f90ff1a1decd6b2174e4ba39522d653 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Mon, 20 Oct 2025 20:16:07 +0500 Subject: [PATCH 095/121] feat: enable UDP-over-TCP (mode 2) for SOCKS outbound when udp_over_tcp=1 and split args for readability --- podkop/files/usr/lib/sing_box_config_facade.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/podkop/files/usr/lib/sing_box_config_facade.sh b/podkop/files/usr/lib/sing_box_config_facade.sh index 86ee149..ffbe854 100644 --- a/podkop/files/usr/lib/sing_box_config_facade.sh +++ b/podkop/files/usr/lib/sing_box_config_facade.sh @@ -80,8 +80,17 @@ sing_box_cf_add_proxy_outbound() { password="${userinfo#*:}" fi fi - config="$(sing_box_cm_add_socks_outbound "$config" "$tag" "$host" "$port" "$version" "$username" "$password" \ - "" "$udp_over_tcp")" + config="$(sing_box_cm_add_socks_outbound \ + "$config" \ + "$tag" \ + "$host" \ + "$port" \ + "$version" \ + "$username" \ + "$password" \ + "" \ + "$([ "$udp_over_tcp" == "1" ] && echo 2)" # if udp_over_tcp is enabled, enable version 2 + )" ;; vless) local tag host port uuid flow packet_encoding From f0290fcc9e6fcc9fe02986e963316174939ae861 Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 20 Oct 2025 21:24:08 +0300 Subject: [PATCH 096/121] feat: add socks support --- .../src/podkop/services/core.service.ts | 5 +- fe-app-podkop/src/validators/index.ts | 1 + .../validators/tests/validateSocksUrl.test.js | 52 ++++++++++++ .../src/validators/validateProxyUrl.ts | 9 ++- .../src/validators/validateSocksUrl.ts | 81 +++++++++++++++++++ .../luci-static/resources/view/podkop/main.js | 74 ++++++++++++++++- .../resources/view/podkop/section.js | 4 +- 7 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 fe-app-podkop/src/validators/tests/validateSocksUrl.test.js create mode 100644 fe-app-podkop/src/validators/validateSocksUrl.ts diff --git a/fe-app-podkop/src/podkop/services/core.service.ts b/fe-app-podkop/src/podkop/services/core.service.ts index b2d44b3..79b63db 100644 --- a/fe-app-podkop/src/podkop/services/core.service.ts +++ b/fe-app-podkop/src/podkop/services/core.service.ts @@ -30,7 +30,10 @@ export function coreService() { { intervalMs: 3000, onNewLog: (line) => { - if (line.toLowerCase().includes('[error]') || line.toLowerCase().includes('[fatal]')) { + if ( + line.toLowerCase().includes('[error]') || + line.toLowerCase().includes('[fatal]') + ) { ui.addNotification('Podkop Error', E('div', {}, line), 'error'); } }, diff --git a/fe-app-podkop/src/validators/index.ts b/fe-app-podkop/src/validators/index.ts index 88e6b03..9bdad27 100644 --- a/fe-app-podkop/src/validators/index.ts +++ b/fe-app-podkop/src/validators/index.ts @@ -10,3 +10,4 @@ export * from './validateVlessUrl'; export * from './validateOutboundJson'; export * from './validateTrojanUrl'; export * from './validateProxyUrl'; +export * from './validateSocksUrl'; diff --git a/fe-app-podkop/src/validators/tests/validateSocksUrl.test.js b/fe-app-podkop/src/validators/tests/validateSocksUrl.test.js new file mode 100644 index 0000000..e9c4da4 --- /dev/null +++ b/fe-app-podkop/src/validators/tests/validateSocksUrl.test.js @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest'; +import { validateSocksUrl } from '../validateSocksUrl'; + +const validUrls = [ + ['socks4 basic', 'socks4://127.0.0.1:1080'], + ['socks4a basic', 'socks4a://127.0.0.1:1080'], + ['socks5 basic', 'socks5://127.0.0.1:1080'], + ['socks5 with username', 'socks5://user@127.0.0.1:1080'], + ['socks5 with username/password', 'socks5://user:pass@127.0.0.1:1080'], + ['socks5 with domain', 'socks5://user:pass@my.proxy.com:1080'], + ['socks5 with dash in domain', 'socks5://user:pass@fast-proxy.net:8080'], + ['socks5 with uppercase domain', 'socks5://USER:PASSWORD@Example.COM:1080'], +]; + +const invalidUrls = [ + ['no prefix', '127.0.0.1:1080'], + ['wrong prefix', 'http://127.0.0.1:1080'], + ['missing host', 'socks5://user:pass@:1080'], + ['missing port', 'socks5://127.0.0.1'], + ['invalid port (non-numeric)', 'socks5://127.0.0.1:abc'], + ['invalid port (too high)', 'socks5://127.0.0.1:99999'], + ['space in url', 'socks5://127.0. 0.1:1080'], + ['missing username when auth provided', 'socks5://:pass@127.0.0.1:1080'], + ['invalid domain chars', 'socks5://user:pass@exa_mple.com:1080'], + ['extra symbol', 'socks5:///127.0.0.1:1080'], +]; + +describe('validateSocksUrl', () => { + describe.each(validUrls)('Valid URL: %s', (_desc, url) => { + it(`returns valid=true for "${url}"`, () => { + const res = validateSocksUrl(url); + expect(res.valid).toBe(true); + }); + }); + + describe.each(invalidUrls)('Invalid URL: %s', (_desc, url) => { + it(`returns valid=false for "${url}"`, () => { + const res = validateSocksUrl(url); + expect(res.valid).toBe(false); + }); + }); + + it('detects invalid port range (0)', () => { + const res = validateSocksUrl('socks5://127.0.0.1:0'); + expect(res.valid).toBe(false); + }); + + it('detects invalid port range (65536)', () => { + const res = validateSocksUrl('socks5://127.0.0.1:65536'); + expect(res.valid).toBe(false); + }); +}); diff --git a/fe-app-podkop/src/validators/validateProxyUrl.ts b/fe-app-podkop/src/validators/validateProxyUrl.ts index ec3fe47..15a3003 100644 --- a/fe-app-podkop/src/validators/validateProxyUrl.ts +++ b/fe-app-podkop/src/validators/validateProxyUrl.ts @@ -2,6 +2,7 @@ import { ValidationResult } from './types'; import { validateShadowsocksUrl } from './validateShadowsocksUrl'; import { validateVlessUrl } from './validateVlessUrl'; import { validateTrojanUrl } from './validateTrojanUrl'; +import { validateSocksUrl } from './validateSocksUrl'; // TODO refactor current validation and add tests export function validateProxyUrl(url: string): ValidationResult { @@ -17,8 +18,14 @@ export function validateProxyUrl(url: string): ValidationResult { return validateTrojanUrl(url); } + if (/^socks(4|4a|5):\/\//.test(url)) { + return validateSocksUrl(url); + } + return { valid: false, - message: _('URL must start with vless:// or ss:// or trojan://'), + message: _( + 'URL must start with vless://, ss://, trojan://, or socks4/5://', + ), }; } diff --git a/fe-app-podkop/src/validators/validateSocksUrl.ts b/fe-app-podkop/src/validators/validateSocksUrl.ts new file mode 100644 index 0000000..b59ae6c --- /dev/null +++ b/fe-app-podkop/src/validators/validateSocksUrl.ts @@ -0,0 +1,81 @@ +import { ValidationResult } from './types'; +import { validateDomain } from './validateDomain'; +import { validateIPV4 } from './validateIp'; + +export function validateSocksUrl(url: string): ValidationResult { + try { + if (!/^socks(4|4a|5):\/\//.test(url)) { + return { + valid: false, + message: _( + 'Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://', + ), + }; + } + + if (!url || /\s/.test(url)) { + return { + valid: false, + message: _('Invalid SOCKS URL: must not contain spaces'), + }; + } + + const body = url.replace(/^socks(4|4a|5):\/\//, ''); + const [authAndHost] = body.split('#'); // отбрасываем hash, если есть + const [credentials, hostPortPart] = authAndHost.includes('@') + ? authAndHost.split('@') + : [null, authAndHost]; + + if (credentials) { + const [username, _password] = credentials.split(':'); + if (!username) { + return { + valid: false, + message: _('Invalid SOCKS URL: missing username'), + }; + } + } + + if (!hostPortPart) { + return { + valid: false, + message: _('Invalid SOCKS URL: missing host and port'), + }; + } + + const [host, port] = hostPortPart.split(':'); + + if (!host) { + return { + valid: false, + message: _('Invalid SOCKS URL: missing hostname or IP'), + }; + } + + if (!port) { + return { valid: false, message: _('Invalid SOCKS URL: missing port') }; + } + + const portNum = Number(port); + if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) { + return { + valid: false, + message: _('Invalid SOCKS URL: invalid port number'), + }; + } + + const ipv4Result = validateIPV4(host); + const domainResult = validateDomain(host); + + if (!ipv4Result.valid && !domainResult.valid) { + return { + valid: false, + message: _('Invalid SOCKS URL: invalid host format'), + }; + } + } catch (_e) { + return { valid: false, message: _('Invalid SOCKS URL: parsing failed') }; + } + + return { valid: true, message: _('Valid') }; +} diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index e0d6800..9f60cd7 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -383,6 +383,72 @@ function validateTrojanUrl(url) { return { valid: true, message: _("Valid") }; } +// src/validators/validateSocksUrl.ts +function validateSocksUrl(url) { + try { + if (!/^socks(4|4a|5):\/\//.test(url)) { + return { + valid: false, + message: _( + "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" + ) + }; + } + if (!url || /\s/.test(url)) { + return { + valid: false, + message: _("Invalid SOCKS URL: must not contain spaces") + }; + } + const body = url.replace(/^socks(4|4a|5):\/\//, ""); + const [authAndHost] = body.split("#"); + const [credentials, hostPortPart] = authAndHost.includes("@") ? authAndHost.split("@") : [null, authAndHost]; + if (credentials) { + const [username, _password] = credentials.split(":"); + if (!username) { + return { + valid: false, + message: _("Invalid SOCKS URL: missing username") + }; + } + } + if (!hostPortPart) { + return { + valid: false, + message: _("Invalid SOCKS URL: missing host and port") + }; + } + const [host, port] = hostPortPart.split(":"); + if (!host) { + return { + valid: false, + message: _("Invalid SOCKS URL: missing hostname or IP") + }; + } + if (!port) { + return { valid: false, message: _("Invalid SOCKS URL: missing port") }; + } + const portNum = Number(port); + if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) { + return { + valid: false, + message: _("Invalid SOCKS URL: invalid port number") + }; + } + const ipv4Result = validateIPV4(host); + const domainResult = validateDomain(host); + if (!ipv4Result.valid && !domainResult.valid) { + return { + valid: false, + message: _("Invalid SOCKS URL: invalid host format") + }; + } + } catch (_e) { + return { valid: false, message: _("Invalid SOCKS URL: parsing failed") }; + } + return { valid: true, message: _("Valid") }; +} + // src/validators/validateProxyUrl.ts function validateProxyUrl(url) { if (url.startsWith("ss://")) { @@ -394,9 +460,14 @@ function validateProxyUrl(url) { if (url.startsWith("trojan://")) { return validateTrojanUrl(url); } + if (/^socks(4|4a|5):\/\//.test(url)) { + return validateSocksUrl(url); + } return { valid: false, - message: _("URL must start with vless:// or ss:// or trojan://") + message: _( + "URL must start with vless://, ss://, trojan://, or socks4/5://" + ) }; } @@ -4490,6 +4561,7 @@ return baseclass.extend({ validatePath, validateProxyUrl, validateShadowsocksUrl, + validateSocksUrl, validateSubnet, validateTrojanUrl, validateUrl, 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 503a96d..0443d0d 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 @@ -43,7 +43,7 @@ function createSectionContent(section) { 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'; + 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) { @@ -102,7 +102,7 @@ function createSectionContent(section) { _('URLTest Proxy Links'), ); o.depends('proxy_config_type', 'urltest'); - o.placeholder = 'vless://, ss://, trojan:// links'; + o.placeholder = 'vless://, ss://, trojan://, socks4/5:// links'; o.rmempty = false; o.validate = function (section_id, value) { // Optional From ef70f4e53dd029609efce84d1ba5a5f3a4ae3c09 Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 20 Oct 2025 21:58:28 +0300 Subject: [PATCH 097/121] feat: add output_network_interface --- .../htdocs/luci-static/resources/view/podkop/settings.js | 9 +++++++++ 1 file changed, 9 insertions(+) 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 f837a8b..189df1c 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 @@ -81,6 +81,15 @@ function createSettingsContent(section) { return true; }; + 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 = section.option( widgets.DeviceSelect, 'source_network_interfaces', From 4186292aa7f6328465d067c43866950f153127bd Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 20 Oct 2025 22:40:03 +0300 Subject: [PATCH 098/121] feat: update output_network_interface field logic --- fe-app-podkop/package.json | 1 + .../resources/view/podkop/settings.js | 69 +++++++++++++++++-- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/fe-app-podkop/package.json b/fe-app-podkop/package.json index 6241ec2..8eeb286 100644 --- a/fe-app-podkop/package.json +++ b/fe-app-podkop/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "format": "prettier --write src", + "format:js": "prettier --write ../luci-app-podkop/htdocs/luci-static/resources/view/podkop", "lint": "eslint src --ext .ts,.tsx", "lint:fix": "eslint src --ext .ts,.tsx --fix", "build": "tsup src/main.ts", 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 189df1c..59b37b0 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 @@ -43,7 +43,9 @@ function createSettingsContent(section) { form.Value, 'bootstrap_dns_server', _('Bootstrap DNS server'), - _('The DNS server used to look up the IP address of an upstream 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)); @@ -81,6 +83,15 @@ function createSettingsContent(section) { 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( widgets.DeviceSelect, 'output_network_interface', @@ -89,6 +100,44 @@ function createSettingsContent(section) { ); 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 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); + + // 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( widgets.DeviceSelect, @@ -188,7 +237,9 @@ function createSettingsContent(section) { form.Flag, 'disable_quic', _('Disable QUIC'), - _('Disable the QUIC protocol to improve compatibility or fix issues with video streaming'), + _( + 'Disable the QUIC protocol to improve compatibility or fix issues with video streaming', + ), ); o.default = '0'; o.rmempty = false; @@ -256,7 +307,9 @@ function createSettingsContent(section) { form.ListValue, 'config_path', _('Config File Path'), - _('Select path for sing-box config file. Change this ONLY if you know what you are doing'), + _( + '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)'); @@ -267,7 +320,9 @@ function createSettingsContent(section) { 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'), + _( + '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( @@ -301,7 +356,9 @@ function createSettingsContent(section) { form.Flag, 'exclude_ntp', _('Exclude NTP'), - _('Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN'), + _( + 'Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN', + ), ); o.default = '0'; o.rmempty = false; @@ -332,6 +389,6 @@ function createSettingsContent(section) { const EntryPoint = { createSettingsContent, -} +}; return baseclass.extend(EntryPoint); From f4be831b5e01582317fbffdf5110684e1099f462 Mon Sep 17 00:00:00 2001 From: divocat Date: Mon, 20 Oct 2025 22:41:07 +0300 Subject: [PATCH 099/121] fix: run prettier for all js files --- .../resources/view/podkop/dashboard.js | 28 +- .../resources/view/podkop/diagnostic.js | 28 +- .../resources/view/podkop/podkop.js | 73 +- .../resources/view/podkop/section.js | 1117 +++++++++-------- .../resources/view/podkop/settings.js | 672 +++++----- 5 files changed, 980 insertions(+), 938 deletions(-) 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); From ed7b7e9c6d5f89dca2728b56fee1111f16aee28c Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Tue, 21 Oct 2025 11:23:29 +0500 Subject: [PATCH 100/121] feat: add optional default_interface parameter and include it in route when provided --- .../files/usr/lib/sing_box_config_manager.sh | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/podkop/files/usr/lib/sing_box_config_manager.sh b/podkop/files/usr/lib/sing_box_config_manager.sh index 87618aa..f2b745a 100644 --- a/podkop/files/usr/lib/sing_box_config_manager.sh +++ b/podkop/files/usr/lib/sing_box_config_manager.sh @@ -644,12 +644,12 @@ sing_box_cm_add_trojan_outbound() { local network="$6" echo "$config" | jq \ - --arg tag "$tag" \ - --arg server_address "$server_address" \ - --arg server_port "$server_port" \ - --arg password "$password" \ - --arg network "$network" \ - '.outbounds += [( + --arg tag "$tag" \ + --arg server_address "$server_address" \ + --arg server_port "$server_port" \ + --arg password "$password" \ + --arg network "$network" \ + '.outbounds += [( { type: "trojan", tag: $tag, @@ -969,6 +969,7 @@ sing_box_cm_add_selector_outbound() { # final: string, final outbound tag for unmatched traffic # auto_detect_interface: boolean, enable or disable automatic interface detection # default_domain_resolver: string, default DNS resolver for domain-based routing +# default_interface: string, default network interface to use when auto detection is disabled # Outputs: # Writes updated JSON configuration to stdout # Example: @@ -979,18 +980,22 @@ sing_box_cm_configure_route() { local final="$2" local auto_detect_interface="$3" local default_domain_resolver="$4" + local default_interface="$5" echo "$config" | jq \ --arg final "$final" \ --argjson auto_detect_interface "$auto_detect_interface" \ --arg default_domain_resolver "$default_domain_resolver" \ + --arg default_interface "$default_interface" \ '.route = { rules: (.route.rules // []), rule_set: (.route.rule_set // []), final: $final, auto_detect_interface: $auto_detect_interface, default_domain_resolver: $default_domain_resolver - }' + } + + (if $default_interface != "" then { default_interface: $default_interface } else {} end) + ' } ####################################### From e8a5d3d5ccacdf869104a3e7371f9b88585e8558 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Tue, 21 Oct 2025 11:24:44 +0500 Subject: [PATCH 101/121] feat: pass default interface to sing-box route --- podkop/files/etc/config/podkop | 2 ++ podkop/files/usr/bin/podkop | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/podkop/files/etc/config/podkop b/podkop/files/etc/config/podkop index b9aecdc..efff886 100644 --- a/podkop/files/etc/config/podkop +++ b/podkop/files/etc/config/podkop @@ -4,6 +4,8 @@ config settings 'settings' option bootstrap_dns_server '77.88.8.8' option dns_rewrite_ttl '60' list source_network_interfaces 'br-lan' + option enable_output_network_interface '0' + #option output_network_interface 'eth1' option enable_badwan_interface_monitoring '0' #list badwan_monitored_interfaces 'wan' #option badwan_reload_delay '2000' diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index dd243c0..59d0e60 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -737,7 +737,10 @@ sing_box_configure_dns() { sing_box_configure_route() { log "Configure the route section of a sing-box JSON configuration" - config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" true "$SB_DNS_SERVER_TAG") + local output_network_interface + config_get output_network_interface "settings" "output_network_interface" + config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" true "$SB_DNS_SERVER_TAG" \ + "$output_network_interface") local sniff_inbounds sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG") From b78682919a9fcab60f7337ac11b7f518c79cb7a1 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 21 Oct 2025 11:32:29 +0300 Subject: [PATCH 102/121] chore: remove comments support for proxy url --- .../luci-static/resources/view/podkop/main.js | 1560 +++++++++-------- .../resources/view/podkop/section.js | 31 +- .../resources/view/podkop/settings.js | 74 +- 3 files changed, 874 insertions(+), 791 deletions(-) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 9f60cd7..81d7460 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1,4 +1,4 @@ -// This file is autogenerated, please don't change manually +// This file is autogenerated, please don't change manually "use strict"; "require baseclass"; "require fs"; @@ -7,7 +7,8 @@ // src/validators/validateIp.ts function validateIPV4(ip) { - const ipRegex = /^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/; + const ipRegex = + /^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/; if (ipRegex.test(ip)) { return { valid: true, message: _("Valid") }; } @@ -16,7 +17,8 @@ function validateIPV4(ip) { // src/validators/validateDomain.ts function validateDomain(domain, allowDotTLD = false) { - const domainRegex = /^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/; + const domainRegex = + /^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/; if (allowDotTLD) { const dotTLD = /^\.[a-zA-Z]{2,}$/; if (dotTLD.test(domain)) { @@ -49,8 +51,8 @@ function validateDNS(value) { return { valid: false, message: _( - "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" - ) + "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH", + ), }; } @@ -61,7 +63,7 @@ function validateUrl(url, protocols = ["http:", "https:"]) { if (!protocols.includes(parsedUrl.protocol)) { return { valid: false, - message: `${_("URL must use one of the following protocols:")} ${protocols.join(", ")}` + message: `${_("URL must use one of the following protocols:")} ${protocols.join(", ")}`, }; } return { valid: true, message: _("Valid") }; @@ -75,21 +77,21 @@ function validatePath(value) { if (!value) { return { valid: false, - message: _("Path cannot be empty") + message: _("Path cannot be empty"), }; } const pathRegex = /^\/[a-zA-Z0-9_\-/.]+$/; if (pathRegex.test(value)) { return { valid: true, - message: _("Valid") + message: _("Valid"), }; } return { valid: false, message: _( - 'Invalid path format. Path must start with "/" and contain valid characters' - ) + 'Invalid path format. Path must start with "/" and contain valid characters', + ), }; } @@ -99,7 +101,7 @@ function validateSubnet(value) { if (!subnetRegex.test(value)) { return { valid: false, - message: _("Invalid format. Use X.X.X.X or X.X.X.X/Y") + message: _("Invalid format. Use X.X.X.X or X.X.X.X/Y"), }; } const [ip, cidr] = value.split("/"); @@ -115,7 +117,7 @@ function validateSubnet(value) { if (cidrNum < 0 || cidrNum > 32) { return { valid: false, - message: _("CIDR must be between 0 and 32") + message: _("CIDR must be between 0 and 32"), }; } } @@ -127,7 +129,7 @@ function bulkValidate(values, validate) { const results = values.map((value) => ({ ...validate(value), value })); return { valid: results.every((r) => r.valid), - results + results, }; } @@ -136,14 +138,14 @@ function validateShadowsocksUrl(url) { if (!url.startsWith("ss://")) { return { valid: false, - message: _("Invalid Shadowsocks URL: must start with ss://") + message: _("Invalid Shadowsocks URL: must start with ss://"), }; } try { if (!url || /\s/.test(url)) { return { valid: false, - message: _("Invalid Shadowsocks URL: must not contain spaces") + message: _("Invalid Shadowsocks URL: must not contain spaces"), }; } const mainPart = url.includes("?") ? url.split("?")[0] : url.split("#")[0]; @@ -151,7 +153,7 @@ function validateShadowsocksUrl(url) { if (!encryptedPart) { return { valid: false, - message: _("Invalid Shadowsocks URL: missing credentials") + message: _("Invalid Shadowsocks URL: missing credentials"), }; } try { @@ -160,8 +162,8 @@ function validateShadowsocksUrl(url) { return { valid: false, message: _( - "Invalid Shadowsocks URL: decoded credentials must contain method:password" - ) + "Invalid Shadowsocks URL: decoded credentials must contain method:password", + ), }; } } catch (_e) { @@ -169,8 +171,8 @@ function validateShadowsocksUrl(url) { return { valid: false, message: _( - 'Invalid Shadowsocks URL: missing method and password separator ":"' - ) + 'Invalid Shadowsocks URL: missing method and password separator ":"', + ), }; } } @@ -178,34 +180,34 @@ function validateShadowsocksUrl(url) { if (!serverPart) { return { valid: false, - message: _("Invalid Shadowsocks URL: missing server address") + message: _("Invalid Shadowsocks URL: missing server address"), }; } const [server, portAndRest] = serverPart.split(":"); if (!server) { return { valid: false, - message: _("Invalid Shadowsocks URL: missing server") + message: _("Invalid Shadowsocks URL: missing server"), }; } const port = portAndRest ? portAndRest.split(/[?#]/)[0] : null; if (!port) { return { valid: false, - message: _("Invalid Shadowsocks URL: missing port") + message: _("Invalid Shadowsocks URL: missing port"), }; } const portNum = parseInt(port, 10); if (isNaN(portNum) || portNum < 1 || portNum > 65535) { return { valid: false, - message: _("Invalid port number. Must be between 1 and 65535") + message: _("Invalid port number. Must be between 1 and 65535"), }; } } catch (_e) { return { valid: false, - message: _("Invalid Shadowsocks URL: parsing failed") + message: _("Invalid Shadowsocks URL: parsing failed"), }; } return { valid: true, message: _("Valid") }; @@ -214,8 +216,10 @@ function validateShadowsocksUrl(url) { // src/helpers/parseQueryString.ts function parseQueryString(query) { const clean = query.startsWith("?") ? query.slice(1) : query; - return clean.split("&").filter(Boolean).reduce( - (acc, pair) => { + return clean + .split("&") + .filter(Boolean) + .reduce((acc, pair) => { const [rawKey, rawValue = ""] = pair.split("="); if (!rawKey) { return acc; @@ -223,9 +227,7 @@ function parseQueryString(query) { const key = decodeURIComponent(rawKey); const value = decodeURIComponent(rawValue); return { ...acc, [key]: value }; - }, - {} - ); + }, {}); } // src/validators/validateVlessUrl.ts @@ -234,12 +236,12 @@ function validateVlessUrl(url) { if (!url.startsWith("vless://")) return { valid: false, - message: "Invalid VLESS URL: must start with vless://" + message: "Invalid VLESS URL: must start with vless://", }; if (/\s/.test(url)) return { valid: false, - message: "Invalid VLESS URL: must not contain spaces" + message: "Invalid VLESS URL: must not contain spaces", }; const body = url.slice("vless://".length); const [mainPart] = body.split("#"); @@ -247,7 +249,7 @@ function validateVlessUrl(url) { if (!userHostPort) return { valid: false, - message: "Invalid VLESS URL: missing host and UUID" + message: "Invalid VLESS URL: missing host and UUID", }; const [userPart, hostPortPart] = userHostPort.split("@"); if (!userPart) @@ -263,12 +265,12 @@ function validateVlessUrl(url) { if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) return { valid: false, - message: "Invalid VLESS URL: invalid port number" + message: "Invalid VLESS URL: invalid port number", }; if (!queryString) return { valid: false, - message: "Invalid VLESS URL: missing query parameters" + message: "Invalid VLESS URL: missing query parameters", }; const params = parseQueryString(queryString); const validTypes = [ @@ -280,35 +282,36 @@ function validateVlessUrl(url) { "httpupgrade", "xhttp", "ws", - "kcp" + "kcp", ]; const validSecurities = ["tls", "reality", "none"]; if (!params.type || !validTypes.includes(params.type)) return { valid: false, - message: "Invalid VLESS URL: unsupported or missing type" + message: "Invalid VLESS URL: unsupported or missing type", }; if (!params.security || !validSecurities.includes(params.security)) return { valid: false, - message: "Invalid VLESS URL: unsupported or missing security" + message: "Invalid VLESS URL: unsupported or missing security", }; if (params.security === "reality") { if (!params.pbk) return { valid: false, - message: "Invalid VLESS URL: missing pbk for reality" + message: "Invalid VLESS URL: missing pbk for reality", }; if (!params.fp) return { valid: false, - message: "Invalid VLESS URL: missing fp for reality" + message: "Invalid VLESS URL: missing fp for reality", }; } if (params.flow === "xtls-rprx-vision-udp443") { return { valid: false, - message: "Invalid VLESS URL: flow xtls-rprx-vision-udp443 does not supported" + message: + "Invalid VLESS URL: flow xtls-rprx-vision-udp443 does not supported", }; } return { valid: true, message: _("Valid") }; @@ -325,8 +328,8 @@ function validateOutboundJson(value) { return { valid: false, message: _( - 'Outbound JSON must contain at least "type", "server" and "server_port" fields' - ) + 'Outbound JSON must contain at least "type", "server" and "server_port" fields', + ), }; } return { valid: true, message: _("Valid") }; @@ -341,13 +344,13 @@ function validateTrojanUrl(url) { if (!url.startsWith("trojan://")) { return { valid: false, - message: _("Invalid Trojan URL: must start with trojan://") + message: _("Invalid Trojan URL: must start with trojan://"), }; } if (!url || /\s/.test(url)) { return { valid: false, - message: _("Invalid Trojan URL: must not contain spaces") + message: _("Invalid Trojan URL: must not contain spaces"), }; } const body = url.slice("trojan://".length); @@ -357,14 +360,14 @@ function validateTrojanUrl(url) { if (!userHostPort) return { valid: false, - message: "Invalid Trojan URL: missing credentials and host" + message: "Invalid Trojan URL: missing credentials and host", }; if (!userPart) return { valid: false, message: "Invalid Trojan URL: missing password" }; if (!hostPortPart) return { valid: false, - message: "Invalid Trojan URL: missing hostname and port" + message: "Invalid Trojan URL: missing hostname and port", }; const [host, port] = hostPortPart.split(":"); if (!host) @@ -375,7 +378,7 @@ function validateTrojanUrl(url) { if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) return { valid: false, - message: "Invalid Trojan URL: invalid port number" + message: "Invalid Trojan URL: invalid port number", }; } catch (_e) { return { valid: false, message: _("Invalid Trojan URL: parsing failed") }; @@ -390,39 +393,41 @@ function validateSocksUrl(url) { return { valid: false, message: _( - "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" - ) + "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://", + ), }; } if (!url || /\s/.test(url)) { return { valid: false, - message: _("Invalid SOCKS URL: must not contain spaces") + message: _("Invalid SOCKS URL: must not contain spaces"), }; } const body = url.replace(/^socks(4|4a|5):\/\//, ""); const [authAndHost] = body.split("#"); - const [credentials, hostPortPart] = authAndHost.includes("@") ? authAndHost.split("@") : [null, authAndHost]; + const [credentials, hostPortPart] = authAndHost.includes("@") + ? authAndHost.split("@") + : [null, authAndHost]; if (credentials) { const [username, _password] = credentials.split(":"); if (!username) { return { valid: false, - message: _("Invalid SOCKS URL: missing username") + message: _("Invalid SOCKS URL: missing username"), }; } } if (!hostPortPart) { return { valid: false, - message: _("Invalid SOCKS URL: missing host and port") + message: _("Invalid SOCKS URL: missing host and port"), }; } const [host, port] = hostPortPart.split(":"); if (!host) { return { valid: false, - message: _("Invalid SOCKS URL: missing hostname or IP") + message: _("Invalid SOCKS URL: missing hostname or IP"), }; } if (!port) { @@ -432,7 +437,7 @@ function validateSocksUrl(url) { if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) { return { valid: false, - message: _("Invalid SOCKS URL: invalid port number") + message: _("Invalid SOCKS URL: invalid port number"), }; } const ipv4Result = validateIPV4(host); @@ -440,7 +445,7 @@ function validateSocksUrl(url) { if (!ipv4Result.valid && !domainResult.valid) { return { valid: false, - message: _("Invalid SOCKS URL: invalid host format") + message: _("Invalid SOCKS URL: invalid host format"), }; } } catch (_e) { @@ -466,14 +471,20 @@ function validateProxyUrl(url) { return { valid: false, message: _( - "URL must start with vless://, ss://, trojan://, or socks4/5://" - ) + "URL must start with vless://, ss://, trojan://, or socks4/5://", + ), }; } // src/helpers/parseValueList.ts function parseValueList(value) { - return value.split(/\n/).map((line) => line.split("//")[0]).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean); + return value + .split(/\n/) + .map((line) => line.split("//")[0]) + .join(" ") + .split(/[,\s]+/) + .map((s) => s.trim()) + .filter(Boolean); } // src/podkop/methods/custom/getConfigSections.ts @@ -486,24 +497,24 @@ async function callBaseMethod(method, args = [], command = "/usr/bin/podkop") { const response = await executeShellCommand({ command, args: [method, ...args], - timeout: 1e4 + timeout: 1e4, }); if (response.stdout) { try { return { success: true, - data: JSON.parse(response.stdout) + data: JSON.parse(response.stdout), }; } catch (_e) { return { success: true, - data: response.stdout + data: response.stdout, }; } } return { success: false, - error: "" + error: "", }; } @@ -528,81 +539,71 @@ var Podkop; AvailableMethods2["SHOW_SING_BOX_CONFIG"] = "show_sing_box_config"; AvailableMethods2["CHECK_LOGS"] = "check_logs"; AvailableMethods2["GET_SYSTEM_INFO"] = "get_system_info"; - })(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})); + })( + (AvailableMethods = + Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})), + ); let AvailableClashAPIMethods; ((AvailableClashAPIMethods2) => { AvailableClashAPIMethods2["GET_PROXIES"] = "get_proxies"; AvailableClashAPIMethods2["GET_PROXY_LATENCY"] = "get_proxy_latency"; AvailableClashAPIMethods2["GET_GROUP_LATENCY"] = "get_group_latency"; AvailableClashAPIMethods2["SET_GROUP_PROXY"] = "set_group_proxy"; - })(AvailableClashAPIMethods = Podkop2.AvailableClashAPIMethods || (Podkop2.AvailableClashAPIMethods = {})); + })( + (AvailableClashAPIMethods = + Podkop2.AvailableClashAPIMethods || + (Podkop2.AvailableClashAPIMethods = {})), + ); })(Podkop || (Podkop = {})); // src/podkop/methods/shell/index.ts var PodkopShellMethods = { - checkDNSAvailable: async () => callBaseMethod( - Podkop.AvailableMethods.CHECK_DNS_AVAILABLE - ), - checkFakeIP: async () => callBaseMethod( - Podkop.AvailableMethods.CHECK_FAKEIP - ), - checkNftRules: async () => callBaseMethod( - Podkop.AvailableMethods.CHECK_NFT_RULES - ), + checkDNSAvailable: async () => + callBaseMethod(Podkop.AvailableMethods.CHECK_DNS_AVAILABLE), + checkFakeIP: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_FAKEIP), + checkNftRules: async () => + callBaseMethod(Podkop.AvailableMethods.CHECK_NFT_RULES), getStatus: async () => callBaseMethod(Podkop.AvailableMethods.GET_STATUS), - checkSingBox: async () => callBaseMethod( - Podkop.AvailableMethods.CHECK_SING_BOX - ), - getSingBoxStatus: async () => callBaseMethod( - Podkop.AvailableMethods.GET_SING_BOX_STATUS - ), - getClashApiProxies: async () => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ - Podkop.AvailableClashAPIMethods.GET_PROXIES - ]), - getClashApiProxyLatency: async (tag) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ - Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, - tag - ]), - getClashApiGroupLatency: async (tag) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ - Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, - tag - ]), - setClashApiGroupProxy: async (group, proxy) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ - Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY, - group, - proxy - ]), - restart: async () => callBaseMethod( - Podkop.AvailableMethods.RESTART, - [], - "/etc/init.d/podkop" - ), - start: async () => callBaseMethod( - Podkop.AvailableMethods.START, - [], - "/etc/init.d/podkop" - ), - stop: async () => callBaseMethod( - Podkop.AvailableMethods.STOP, - [], - "/etc/init.d/podkop" - ), - enable: async () => callBaseMethod( - Podkop.AvailableMethods.ENABLE, - [], - "/etc/init.d/podkop" - ), - disable: async () => callBaseMethod( - Podkop.AvailableMethods.DISABLE, - [], - "/etc/init.d/podkop" - ), + checkSingBox: async () => + callBaseMethod(Podkop.AvailableMethods.CHECK_SING_BOX), + getSingBoxStatus: async () => + callBaseMethod(Podkop.AvailableMethods.GET_SING_BOX_STATUS), + getClashApiProxies: async () => + callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_PROXIES, + ]), + getClashApiProxyLatency: async (tag) => + callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, + tag, + ]), + getClashApiGroupLatency: async (tag) => + callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, + tag, + ]), + setClashApiGroupProxy: async (group, proxy) => + callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY, + group, + proxy, + ]), + restart: async () => + callBaseMethod(Podkop.AvailableMethods.RESTART, [], "/etc/init.d/podkop"), + start: async () => + callBaseMethod(Podkop.AvailableMethods.START, [], "/etc/init.d/podkop"), + stop: async () => + callBaseMethod(Podkop.AvailableMethods.STOP, [], "/etc/init.d/podkop"), + enable: async () => + callBaseMethod(Podkop.AvailableMethods.ENABLE, [], "/etc/init.d/podkop"), + disable: async () => + callBaseMethod(Podkop.AvailableMethods.DISABLE, [], "/etc/init.d/podkop"), globalCheck: async () => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), - showSingBoxConfig: async () => callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), + showSingBoxConfig: async () => + callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), - getSystemInfo: async () => callBaseMethod( - Podkop.AvailableMethods.GET_SYSTEM_INFO - ) + getSystemInfo: async () => + callBaseMethod(Podkop.AvailableMethods.GET_SYSTEM_INFO), }; // src/podkop/methods/custom/getDashboardSections.ts @@ -612,25 +613,108 @@ async function getDashboardSections() { if (!clashProxies.success) { return { success: false, - data: [] + data: [], }; } const proxies = Object.entries(clashProxies.data.proxies).map( ([key, value]) => ({ code: key, - value - }) + value, + }), ); - const data = configSections.filter( - (section) => section.connection_type !== "block" && section[".type"] !== "settings" - ).map((section) => { - if (section.connection_type === "proxy") { - if (section.proxy_config_type === "url") { + const data = configSections + .filter( + (section) => + section.connection_type !== "block" && section[".type"] !== "settings", + ) + .map((section) => { + if (section.connection_type === "proxy") { + if (section.proxy_config_type === "url") { + const outbound = proxies.find( + (proxy) => proxy.code === `${section[".name"]}-out`, + ); + const activeConfigs = splitProxyString(section.proxy_string); + const proxyDisplayName = + getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || ""; + return { + withTagSelect: false, + code: outbound?.code || section[".name"], + displayName: section[".name"], + outbounds: [ + { + code: outbound?.code || section[".name"], + displayName: proxyDisplayName, + latency: outbound?.value?.history?.[0]?.delay || 0, + type: outbound?.value?.type || "", + selected: true, + }, + ], + }; + } + if (section.proxy_config_type === "outbound") { + const outbound = proxies.find( + (proxy) => proxy.code === `${section[".name"]}-out`, + ); + const parsedOutbound = JSON.parse(section.outbound_json); + const parsedTag = parsedOutbound?.tag + ? decodeURIComponent(parsedOutbound?.tag) + : void 0; + const proxyDisplayName = parsedTag || outbound?.value?.name || ""; + return { + withTagSelect: false, + code: outbound?.code || section[".name"], + displayName: section[".name"], + outbounds: [ + { + code: outbound?.code || section[".name"], + displayName: proxyDisplayName, + latency: outbound?.value?.history?.[0]?.delay || 0, + type: outbound?.value?.type || "", + selected: true, + }, + ], + }; + } + if (section.proxy_config_type === "urltest") { + const selector = proxies.find( + (proxy) => proxy.code === `${section[".name"]}-out`, + ); + const outbound = proxies.find( + (proxy) => proxy.code === `${section[".name"]}-urltest-out`, + ); + const outbounds = (outbound?.value?.all ?? []) + .map((code) => proxies.find((item) => item.code === code)) + .map((item, index) => ({ + code: item?.code || "", + displayName: + getProxyUrlName(section.urltest_proxy_links?.[index]) || + item?.value?.name || + "", + latency: item?.value?.history?.[0]?.delay || 0, + type: item?.value?.type || "", + selected: selector?.value?.now === item?.code, + })); + return { + withTagSelect: true, + code: selector?.code || section[".name"], + displayName: section[".name"], + outbounds: [ + { + code: outbound?.code || "", + displayName: _("Fastest"), + latency: outbound?.value?.history?.[0]?.delay || 0, + type: outbound?.value?.type || "", + selected: selector?.value?.now === outbound?.code, + }, + ...outbounds, + ], + }; + } + } + if (section.connection_type === "vpn") { const outbound = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-out` + (proxy) => proxy.code === `${section[".name"]}-out`, ); - const activeConfigs = splitProxyString(section.proxy_string); - const proxyDisplayName = getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || ""; return { withTagSelect: false, code: outbound?.code || section[".name"], @@ -638,119 +722,43 @@ async function getDashboardSections() { outbounds: [ { code: outbound?.code || section[".name"], - displayName: proxyDisplayName, + displayName: section.interface || outbound?.value?.name || "", latency: outbound?.value?.history?.[0]?.delay || 0, type: outbound?.value?.type || "", - selected: true - } - ] - }; - } - if (section.proxy_config_type === "outbound") { - const outbound = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-out` - ); - const parsedOutbound = JSON.parse(section.outbound_json); - const parsedTag = parsedOutbound?.tag ? decodeURIComponent(parsedOutbound?.tag) : void 0; - const proxyDisplayName = parsedTag || outbound?.value?.name || ""; - return { - withTagSelect: false, - code: outbound?.code || section[".name"], - displayName: section[".name"], - outbounds: [ - { - code: outbound?.code || section[".name"], - displayName: proxyDisplayName, - latency: outbound?.value?.history?.[0]?.delay || 0, - type: outbound?.value?.type || "", - selected: true - } - ] - }; - } - if (section.proxy_config_type === "urltest") { - const selector = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-out` - ); - const outbound = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-urltest-out` - ); - const outbounds = (outbound?.value?.all ?? []).map((code) => proxies.find((item) => item.code === code)).map((item, index) => ({ - code: item?.code || "", - displayName: getProxyUrlName(section.urltest_proxy_links?.[index]) || item?.value?.name || "", - latency: item?.value?.history?.[0]?.delay || 0, - type: item?.value?.type || "", - selected: selector?.value?.now === item?.code - })); - return { - withTagSelect: true, - code: selector?.code || section[".name"], - displayName: section[".name"], - outbounds: [ - { - code: outbound?.code || "", - displayName: _("Fastest"), - latency: outbound?.value?.history?.[0]?.delay || 0, - type: outbound?.value?.type || "", - selected: selector?.value?.now === outbound?.code + selected: true, }, - ...outbounds - ] + ], }; } - } - if (section.connection_type === "vpn") { - const outbound = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-out` - ); return { withTagSelect: false, - code: outbound?.code || section[".name"], + code: section[".name"], displayName: section[".name"], - outbounds: [ - { - code: outbound?.code || section[".name"], - displayName: section.interface || outbound?.value?.name || "", - latency: outbound?.value?.history?.[0]?.delay || 0, - type: outbound?.value?.type || "", - selected: true - } - ] + outbounds: [], }; - } - return { - withTagSelect: false, - code: section[".name"], - displayName: section[".name"], - outbounds: [] - }; - }); + }); return { success: true, - data + data, }; } // src/podkop/methods/custom/index.ts var CustomPodkopMethods = { getConfigSections, - getDashboardSections + getDashboardSections, }; // src/constants.ts var STATUS_COLORS = { SUCCESS: "#4caf50", ERROR: "#f44336", - WARNING: "#ff9800" + WARNING: "#ff9800", }; var PODKOP_LUCI_APP_VERSION = "__COMPILED_VERSION_VARIABLE__"; var FAKEIP_CHECK_DOMAIN = "fakeip.podkop.fyi"; var IP_CHECK_DOMAIN = "ip.podkop.fyi"; -var REGIONAL_OPTIONS = [ - "russia_inside", - "russia_outside", - "ukraine_inside" -]; +var REGIONAL_OPTIONS = ["russia_inside", "russia_outside", "ukraine_inside"]; var ALLOWED_WITH_RUSSIA_INSIDE = [ "russia_inside", "meta", @@ -764,7 +772,7 @@ var ALLOWED_WITH_RUSSIA_INSIDE = [ "ovh", "hodca", "digitalocean", - "cloudfront" + "cloudfront", ]; var DOMAIN_LIST_OPTIONS = { russia_inside: "Russia inside", @@ -789,22 +797,23 @@ var DOMAIN_LIST_OPTIONS = { hetzner: "Hetzner ASN", ovh: "OVH ASN", digitalocean: "Digital Ocean ASN", - cloudfront: "CloudFront ASN" + cloudfront: "CloudFront ASN", }; var UPDATE_INTERVAL_OPTIONS = { "1h": "Every hour", "3h": "Every 3 hours", "12h": "Every 12 hours", "1d": "Every day", - "3d": "Every 3 days" + "3d": "Every 3 days", }; var DNS_SERVER_OPTIONS = { "1.1.1.1": "1.1.1.1 (Cloudflare)", "8.8.8.8": "8.8.8.8 (Google)", "9.9.9.9": "9.9.9.9 (Quad9)", "dns.adguard-dns.com": "dns.adguard-dns.com (AdGuard Default)", - "unfiltered.adguard-dns.com": "unfiltered.adguard-dns.com (AdGuard Unfiltered)", - "family.adguard-dns.com": "family.adguard-dns.com (AdGuard Family)" + "unfiltered.adguard-dns.com": + "unfiltered.adguard-dns.com (AdGuard Unfiltered)", + "family.adguard-dns.com": "family.adguard-dns.com (AdGuard Family)", }; var BOOTSTRAP_DNS_SERVER_OPTIONS = { "77.88.8.8": "77.88.8.8 (Yandex DNS)", @@ -814,7 +823,7 @@ var BOOTSTRAP_DNS_SERVER_OPTIONS = { "8.8.8.8": "8.8.8.8 (Google DNS)", "8.8.4.4": "8.8.4.4 (Google DNS)", "9.9.9.9": "9.9.9.9 (Quad9 DNS)", - "9.9.9.11": "9.9.9.11 (Quad9 DNS)" + "9.9.9.11": "9.9.9.11 (Quad9 DNS)", }; var DIAGNOSTICS_UPDATE_INTERVAL = 1e4; var CACHE_TIMEOUT = DIAGNOSTICS_UPDATE_INTERVAL - 1e3; @@ -844,35 +853,38 @@ var COMMAND_SCHEDULING = { // Background execution P9_PRIORITY: 1700, // Idle mode execution - P10_PRIORITY: 1900 + P10_PRIORITY: 1900, // Lowest priority }; // src/podkop/api.ts async function createBaseApiRequest(fetchFn, options) { - const wrappedFn = () => options?.timeoutMs && options?.operationName ? withTimeout( - fetchFn(), - options.timeoutMs, - options.operationName, - options.timeoutMessage - ) : fetchFn(); + const wrappedFn = () => + options?.timeoutMs && options?.operationName + ? withTimeout( + fetchFn(), + options.timeoutMs, + options.operationName, + options.timeoutMessage, + ) + : fetchFn(); try { const response = await wrappedFn(); if (!response.ok) { return { success: false, - message: `${_("HTTP error")} ${response.status}: ${response.statusText}` + message: `${_("HTTP error")} ${response.status}: ${response.statusText}`, }; } const data = await response.json(); return { success: true, - data + data, }; } catch (e) { return { success: false, - message: e instanceof Error ? e.message : _("Unknown error") + message: e instanceof Error ? e.message : _("Unknown error"), }; } } @@ -880,35 +892,37 @@ async function createBaseApiRequest(fetchFn, options) { // src/podkop/methods/fakeip/getFakeIpCheck.ts async function getFakeIpCheck() { return createBaseApiRequest( - () => fetch(`https://${FAKEIP_CHECK_DOMAIN}/check`, { - method: "GET", - headers: { "Content-Type": "application/json" } - }), + () => + fetch(`https://${FAKEIP_CHECK_DOMAIN}/check`, { + method: "GET", + headers: { "Content-Type": "application/json" }, + }), { operationName: "getFakeIpCheck", - timeoutMs: 5e3 - } + timeoutMs: 5e3, + }, ); } // src/podkop/methods/fakeip/getIpCheck.ts async function getIpCheck() { return createBaseApiRequest( - () => fetch(`https://${IP_CHECK_DOMAIN}/check`, { - method: "GET", - headers: { "Content-Type": "application/json" } - }), + () => + fetch(`https://${IP_CHECK_DOMAIN}/check`, { + method: "GET", + headers: { "Content-Type": "application/json" }, + }), { operationName: "getIpCheck", - timeoutMs: 5e3 - } + timeoutMs: 5e3, + }, ); } // src/podkop/methods/fakeip/index.ts var RemoteFakeIPMethods = { getFakeIpCheck, - getIpCheck + getIpCheck, }; // src/podkop/services/tab.service.ts @@ -930,7 +944,7 @@ var TabService = class _TabService { subtree: true, childList: true, attributes: true, - attributeFilter: ["class"] + attributeFilter: ["class"], }); this.notify(); } @@ -939,18 +953,18 @@ var TabService = class _TabService { } getTabsInfo() { const tabs = Array.from( - document.querySelectorAll(".cbi-tab, .cbi-tab-disabled") + document.querySelectorAll(".cbi-tab, .cbi-tab-disabled"), ); return tabs.map((el) => ({ el, id: el.dataset.tab || "", - active: el.classList.contains("cbi-tab") && !el.classList.contains("cbi-tab-disabled") + active: + el.classList.contains("cbi-tab") && + !el.classList.contains("cbi-tab-disabled"), })); } getActiveTabId() { - const active = document.querySelector( - ".cbi-tab:not(.cbi-tab-disabled)" - ); + const active = document.querySelector(".cbi-tab:not(.cbi-tab-disabled)"); return active?.dataset.tab || null; } notify() { @@ -983,23 +997,23 @@ var DIAGNOSTICS_CHECKS_MAP = { ["DNS" /* DNS */]: { order: 1, title: _("DNS checks"), - code: "DNS" /* DNS */ + code: "DNS" /* DNS */, }, ["SINGBOX" /* SINGBOX */]: { order: 2, title: _("Sing-box checks"), - code: "SINGBOX" /* SINGBOX */ + code: "SINGBOX" /* SINGBOX */, }, ["NFT" /* NFT */]: { order: 3, title: _("Nftables checks"), - code: "NFT" /* NFT */ + code: "NFT" /* NFT */, }, ["FAKEIP" /* FAKEIP */]: { order: 4, title: _("FakeIP checks"), - code: "FAKEIP" /* FAKEIP */ - } + code: "FAKEIP" /* FAKEIP */, + }, }; // src/podkop/tabs/diagnostic/diagnostic.store.ts @@ -1011,33 +1025,33 @@ var initialDiagnosticStore = { luci_app_version: "loading", sing_box_version: "loading", openwrt_version: "loading", - device_model: "loading" + device_model: "loading", }, diagnosticsActions: { restart: { - loading: false + loading: false, }, start: { - loading: false + loading: false, }, stop: { - loading: false + loading: false, }, enable: { - loading: false + loading: false, }, disable: { - loading: false + loading: false, }, globalCheck: { - loading: false + loading: false, }, viewLogs: { - loading: false + loading: false, }, showSingBoxConfig: { - loading: false - } + loading: false, + }, }, diagnosticsRunAction: { loading: false }, diagnosticsChecks: [ @@ -1047,7 +1061,7 @@ var initialDiagnosticStore = { order: DIAGNOSTICS_CHECKS_MAP.DNS.order, description: _("Not running"), items: [], - state: "skipped" + state: "skipped", }, { code: "SINGBOX" /* SINGBOX */, @@ -1055,7 +1069,7 @@ var initialDiagnosticStore = { order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, description: _("Not running"), items: [], - state: "skipped" + state: "skipped", }, { code: "NFT" /* NFT */, @@ -1063,7 +1077,7 @@ var initialDiagnosticStore = { order: DIAGNOSTICS_CHECKS_MAP.NFT.order, description: _("Not running"), items: [], - state: "skipped" + state: "skipped", }, { code: "FAKEIP" /* FAKEIP */, @@ -1071,9 +1085,9 @@ var initialDiagnosticStore = { order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, description: _("Not running"), items: [], - state: "skipped" - } - ] + state: "skipped", + }, + ], }; var loadingDiagnosticsChecksStore = { diagnosticsChecks: [ @@ -1083,7 +1097,7 @@ var loadingDiagnosticsChecksStore = { order: DIAGNOSTICS_CHECKS_MAP.DNS.order, description: _("Queued"), items: [], - state: "skipped" + state: "skipped", }, { code: "SINGBOX" /* SINGBOX */, @@ -1091,7 +1105,7 @@ var loadingDiagnosticsChecksStore = { order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, description: _("Queued"), items: [], - state: "skipped" + state: "skipped", }, { code: "NFT" /* NFT */, @@ -1099,7 +1113,7 @@ var loadingDiagnosticsChecksStore = { order: DIAGNOSTICS_CHECKS_MAP.NFT.order, description: _("Queued"), items: [], - state: "skipped" + state: "skipped", }, { code: "FAKEIP" /* FAKEIP */, @@ -1107,22 +1121,21 @@ var loadingDiagnosticsChecksStore = { order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, description: _("Queued"), items: [], - state: "skipped" - } - ] + state: "skipped", + }, + ], }; // src/podkop/services/store.service.ts function jsonStableStringify(obj) { return JSON.stringify(obj, (_2, value) => { if (value && typeof value === "object" && !Array.isArray(value)) { - return Object.keys(value).sort().reduce( - (acc, key) => { + return Object.keys(value) + .sort() + .reduce((acc, key) => { acc[key] = value[key]; return acc; - }, - {} - ); + }, {}); } return value; }); @@ -1205,35 +1218,35 @@ var StoreService = class { var initialStore = { tabService: { current: "", - all: [] + all: [], }, bandwidthWidget: { loading: true, failed: false, - data: { up: 0, down: 0 } + data: { up: 0, down: 0 }, }, trafficTotalWidget: { loading: true, failed: false, - data: { downloadTotal: 0, uploadTotal: 0 } + data: { downloadTotal: 0, uploadTotal: 0 }, }, systemInfoWidget: { loading: true, failed: false, - data: { connections: 0, memory: 0 } + data: { connections: 0, memory: 0 }, }, servicesInfoWidget: { loading: true, failed: false, - data: { singbox: 0, podkop: 0 } + data: { singbox: 0, podkop: 0 }, }, sectionsWidget: { loading: true, failed: false, latencyFetching: false, - data: [] + data: [], }, - ...initialDiagnosticStore + ...initialDiagnosticStore, }; var store = new StoreService(initialStore); @@ -1297,7 +1310,9 @@ var Logger = class { } download(filename = "logs.txt") { if (typeof document === "undefined") { - console.warn("Logger.download() \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435"); + console.warn( + "Logger.download() \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435", + ); return; } downloadAsTxt(this.getLogs(), filename); @@ -1331,7 +1346,7 @@ var PodkopLogWatcher = class _PodkopLogWatcher { this.intervalMs = options?.intervalMs ?? 5e3; logger.info( "[PodkopLogWatcher]", - `initialized (interval: ${this.intervalMs}ms)` + `initialized (interval: ${this.intervalMs}ms)`, ); } async checkOnce() { @@ -1340,7 +1355,10 @@ var PodkopLogWatcher = class _PodkopLogWatcher { return; } if (this.paused) { - logger.debug("[PodkopLogWatcher]", "skipped check \u2014 tab not visible"); + logger.debug( + "[PodkopLogWatcher]", + "skipped check \u2014 tab not visible", + ); return; } try { @@ -1370,7 +1388,7 @@ var PodkopLogWatcher = class _PodkopLogWatcher { this.timer = setInterval(() => this.checkOnce(), this.intervalMs); logger.info( "[PodkopLogWatcher]", - `started (interval: ${this.intervalMs}ms)` + `started (interval: ${this.intervalMs}ms)`, ); } stop() { @@ -1403,8 +1421,8 @@ function coreService() { store.set({ tabService: { current: activeId || "", - all: tabs.map((tab) => tab.id) - } + all: tabs.map((tab) => tab.id), + }, }); }); const watcher = PodkopLogWatcher.getInstance(); @@ -1419,11 +1437,14 @@ function coreService() { { intervalMs: 3e3, onNewLog: (line) => { - if (line.toLowerCase().includes("[error]") || line.toLowerCase().includes("[fatal]")) { + if ( + line.toLowerCase().includes("[error]") || + line.toLowerCase().includes("[fatal]") + ) { ui.addNotification("Podkop Error", E("div", {}, line), "error"); } - } - } + }, + }, ); watcher.start(); } @@ -1445,14 +1466,17 @@ var SocketManager = class _SocketManager { resetAll() { for (const [url, ws] of this.sockets.entries()) { try { - if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) { + if ( + ws.readyState === WebSocket.OPEN || + ws.readyState === WebSocket.CONNECTING + ) { ws.close(); } } catch (err) { logger.error( "[SOCKET]", `resetAll: failed to close socket ${url}`, - err + err, ); } } @@ -1471,7 +1495,7 @@ var SocketManager = class _SocketManager { logger.error( "[SOCKET]", `failed to construct WebSocket for ${url}:`, - err + err, ); this.triggerError(url, err instanceof Event ? err : String(err)); return; @@ -1573,23 +1597,23 @@ function renderFailedState() { "div", { class: "pdk_dashboard-page__outbound-section centered", - style: "height: 127px" + style: "height: 127px", }, - E("span", {}, [E("span", {}, _("Dashboard currently unavailable"))]) + E("span", {}, [E("span", {}, _("Dashboard currently unavailable"))]), ); } function renderLoadingState() { return E("div", { id: "dashboard-sections-grid-skeleton", class: "pdk_dashboard-page__outbound-section skeleton", - style: "height: 127px" + style: "height: 127px", }); } function renderDefaultState({ section, onChooseOutbound, onTestLatency, - latencyFetching + latencyFetching, }) { function testLatency() { if (section.withTagSelect) { @@ -1616,7 +1640,9 @@ function renderDefaultState({ "div", { class: `pdk_dashboard-page__outbound-grid__item ${outbound.selected ? "pdk_dashboard-page__outbound-grid__item--active" : ""} ${section.withTagSelect ? "pdk_dashboard-page__outbound-grid__item--selectable" : ""}`, - click: () => section.withTagSelect && onChooseOutbound(section.code, outbound.code) + click: () => + section.withTagSelect && + onChooseOutbound(section.code, outbound.code), }, [ E("b", {}, outbound.displayName), @@ -1624,15 +1650,15 @@ function renderDefaultState({ E( "div", { class: "pdk_dashboard-page__outbound-grid__item__type" }, - outbound.type + outbound.type, ), E( "div", { class: getLatencyClass() }, - outbound.latency ? `${outbound.latency}ms` : "N/A" - ) - ]) - ] + outbound.latency ? `${outbound.latency}ms` : "N/A", + ), + ]), + ], ); } return E("div", { class: "pdk_dashboard-page__outbound-section" }, [ @@ -1641,24 +1667,26 @@ function renderDefaultState({ E( "div", { - class: "pdk_dashboard-page__outbound-section__title-section__title" + class: "pdk_dashboard-page__outbound-section__title-section__title", }, - section.displayName + section.displayName, ), - latencyFetching ? E("div", { class: "skeleton", style: "width: 99px; height: 28px" }) : E( - "button", - { - class: "btn dashboard-sections-grid-item-test-latency", - click: () => testLatency() - }, - _("Test latency") - ) + latencyFetching + ? E("div", { class: "skeleton", style: "width: 99px; height: 28px" }) + : E( + "button", + { + class: "btn dashboard-sections-grid-item-test-latency", + click: () => testLatency(), + }, + _("Test latency"), + ), ]), E( "div", { class: "pdk_dashboard-page__outbound-grid" }, - section.outbounds.map((outbound) => renderOutbound(outbound)) - ) + section.outbounds.map((outbound) => renderOutbound(outbound)), + ), ]); } function renderSections(props) { @@ -1678,9 +1706,9 @@ function renderFailedState2() { { id: "", style: "height: 78px", - class: "pdk_dashboard-page__widgets-section__item centered" + class: "pdk_dashboard-page__widgets-section__item centered", }, - _("Currently unavailable") + _("Currently unavailable"), ); } function renderLoadingState2() { @@ -1689,9 +1717,9 @@ function renderLoadingState2() { { id: "", style: "height: 78px", - class: "pdk_dashboard-page__widgets-section__item skeleton" + class: "pdk_dashboard-page__widgets-section__item skeleton", }, - "" + "", ); } function renderDefaultState2({ title, items }) { @@ -1699,28 +1727,28 @@ function renderDefaultState2({ title, items }) { E( "b", { class: "pdk_dashboard-page__widgets-section__item__title" }, - title + title, ), - ...items.map( - (item) => E( + ...items.map((item) => + E( "div", { - class: `pdk_dashboard-page__widgets-section__item__row ${item?.attributes?.class || ""}` + class: `pdk_dashboard-page__widgets-section__item__row ${item?.attributes?.class || ""}`, }, [ E( "span", { class: "pdk_dashboard-page__widgets-section__item__row__key" }, - `${item.key}: ` + `${item.key}: `, ), E( "span", { class: "pdk_dashboard-page__widgets-section__item__row__value" }, - item.value - ) - ] - ) - ) + item.value, + ), + ], + ), + ), ]); } function renderWidget(props) { @@ -1739,7 +1767,7 @@ function render() { "div", { id: "dashboard-status", - class: "pdk_dashboard-page" + class: "pdk_dashboard-page", }, [ // Widgets section @@ -1747,23 +1775,23 @@ function render() { E( "div", { id: "dashboard-widget-traffic" }, - renderWidget({ loading: true, failed: false, title: "", items: [] }) + renderWidget({ loading: true, failed: false, title: "", items: [] }), ), E( "div", { id: "dashboard-widget-traffic-total" }, - renderWidget({ loading: true, failed: false, title: "", items: [] }) + renderWidget({ loading: true, failed: false, title: "", items: [] }), ), E( "div", { id: "dashboard-widget-system-info" }, - renderWidget({ loading: true, failed: false, title: "", items: [] }) + renderWidget({ loading: true, failed: false, title: "", items: [] }), ), E( "div", { id: "dashboard-widget-service-info" }, - renderWidget({ loading: true, failed: false, title: "", items: [] }) - ) + renderWidget({ loading: true, failed: false, title: "", items: [] }), + ), ]), // All outbounds E( @@ -1776,16 +1804,14 @@ function render() { code: "", displayName: "", outbounds: [], - withTagSelect: false + withTagSelect: false, }, - onTestLatency: () => { - }, - onChooseOutbound: () => { - }, - latencyFetching: false - }) - ) - ] + onTestLatency: () => {}, + onChooseOutbound: () => {}, + latencyFetching: false, + }), + ), + ], ); } @@ -1805,15 +1831,15 @@ function prettyBytes(n) { async function fetchServicesInfo() { const [podkop, singbox] = await Promise.all([ PodkopShellMethods.getStatus(), - PodkopShellMethods.getSingBoxStatus() + PodkopShellMethods.getSingBoxStatus(), ]); if (!podkop.success || !singbox.success) { store.set({ servicesInfoWidget: { loading: false, failed: true, - data: { singbox: 0, podkop: 0 } - } + data: { singbox: 0, podkop: 0 }, + }, }); } if (podkop.success && singbox.success) { @@ -1821,8 +1847,8 @@ async function fetchServicesInfo() { servicesInfoWidget: { loading: false, failed: false, - data: { singbox: singbox.data.running, podkop: podkop.data.enabled } - } + data: { singbox: singbox.data.running, podkop: podkop.data.enabled }, + }, }); } } @@ -1833,8 +1859,8 @@ async function fetchDashboardSections() { store.set({ sectionsWidget: { ...prev, - failed: false - } + failed: false, + }, }); const { data, success } = await CustomPodkopMethods.getDashboardSections(); if (!success) { @@ -1845,8 +1871,8 @@ async function fetchDashboardSections() { latencyFetching: false, loading: false, failed: !success, - data - } + data, + }, }); } async function connectToClashSockets() { @@ -1858,24 +1884,24 @@ async function connectToClashSockets() { bandwidthWidget: { loading: false, failed: false, - data: { up: parsedMsg.up, down: parsedMsg.down } - } + data: { up: parsedMsg.up, down: parsedMsg.down }, + }, }); }, (_err) => { logger.error( "[DASHBOARD]", "connectToClashSockets - traffic: failed to connect to", - getClashWsUrl() + getClashWsUrl(), ); store.set({ bandwidthWidget: { loading: false, failed: true, - data: { up: 0, down: 0 } - } + data: { up: 0, down: 0 }, + }, }); - } + }, ); socket.subscribe( `${getClashWsUrl()}/connections?token=`, @@ -1887,41 +1913,41 @@ async function connectToClashSockets() { failed: false, data: { downloadTotal: parsedMsg.downloadTotal, - uploadTotal: parsedMsg.uploadTotal - } + uploadTotal: parsedMsg.uploadTotal, + }, }, systemInfoWidget: { loading: false, failed: false, data: { connections: parsedMsg.connections?.length, - memory: parsedMsg.memory - } - } + memory: parsedMsg.memory, + }, + }, }); }, (_err) => { logger.error( "[DASHBOARD]", "connectToClashSockets - connections: failed to connect to", - getClashWsUrl() + getClashWsUrl(), ); store.set({ trafficTotalWidget: { loading: false, failed: true, - data: { downloadTotal: 0, uploadTotal: 0 } + data: { downloadTotal: 0, uploadTotal: 0 }, }, systemInfoWidget: { loading: false, failed: true, data: { connections: 0, - memory: 0 - } - } + memory: 0, + }, + }, }); - } + }, ); } async function handleChooseOutbound(selector, tag) { @@ -1932,32 +1958,32 @@ async function handleTestGroupLatency(tag) { store.set({ sectionsWidget: { ...store.get().sectionsWidget, - latencyFetching: true - } + latencyFetching: true, + }, }); await PodkopShellMethods.getClashApiGroupLatency(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { ...store.get().sectionsWidget, - latencyFetching: false - } + latencyFetching: false, + }, }); } async function handleTestProxyLatency(tag) { store.set({ sectionsWidget: { ...store.get().sectionsWidget, - latencyFetching: true - } + latencyFetching: true, + }, }); await PodkopShellMethods.getClashApiProxyLatency(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { ...store.get().sectionsWidget, - latencyFetching: false - } + latencyFetching: false, + }, }); } async function renderSectionsWidget() { @@ -1972,20 +1998,18 @@ async function renderSectionsWidget() { code: "", displayName: "", outbounds: [], - withTagSelect: false + withTagSelect: false, }, - onTestLatency: () => { - }, - onChooseOutbound: () => { - }, - latencyFetching: sectionsWidget.latencyFetching + onTestLatency: () => {}, + onChooseOutbound: () => {}, + latencyFetching: sectionsWidget.latencyFetching, }); return preserveScrollForPage(() => { container.replaceChildren(renderedWidget); }); } - const renderedWidgets = sectionsWidget.data.map( - (section) => renderSections({ + const renderedWidgets = sectionsWidget.data.map((section) => + renderSections({ loading: sectionsWidget.loading, failed: sectionsWidget.failed, section, @@ -1998,8 +2022,8 @@ async function renderSectionsWidget() { }, onChooseOutbound: (selector, tag) => { handleChooseOutbound(selector, tag); - } - }) + }, + }), ); return preserveScrollForPage(() => { container.replaceChildren(...renderedWidgets); @@ -2014,7 +2038,7 @@ async function renderBandwidthWidget() { loading: traffic.loading, failed: traffic.failed, title: "", - items: [] + items: [], }); return container.replaceChildren(renderedWidget2); } @@ -2024,8 +2048,8 @@ async function renderBandwidthWidget() { title: _("Traffic"), items: [ { key: _("Uplink"), value: `${prettyBytes(traffic.data.up)}/s` }, - { key: _("Downlink"), value: `${prettyBytes(traffic.data.down)}/s` } - ] + { key: _("Downlink"), value: `${prettyBytes(traffic.data.down)}/s` }, + ], }); container.replaceChildren(renderedWidget); } @@ -2038,7 +2062,7 @@ async function renderTrafficTotalWidget() { loading: trafficTotalWidget.loading, failed: trafficTotalWidget.failed, title: "", - items: [] + items: [], }); return container.replaceChildren(renderedWidget2); } @@ -2049,13 +2073,13 @@ async function renderTrafficTotalWidget() { items: [ { key: _("Uplink"), - value: String(prettyBytes(trafficTotalWidget.data.uploadTotal)) + value: String(prettyBytes(trafficTotalWidget.data.uploadTotal)), }, { key: _("Downlink"), - value: String(prettyBytes(trafficTotalWidget.data.downloadTotal)) - } - ] + value: String(prettyBytes(trafficTotalWidget.data.downloadTotal)), + }, + ], }); container.replaceChildren(renderedWidget); } @@ -2068,7 +2092,7 @@ async function renderSystemInfoWidget() { loading: systemInfoWidget.loading, failed: systemInfoWidget.failed, title: "", - items: [] + items: [], }); return container.replaceChildren(renderedWidget2); } @@ -2079,13 +2103,13 @@ async function renderSystemInfoWidget() { items: [ { key: _("Active Connections"), - value: String(systemInfoWidget.data.connections) + value: String(systemInfoWidget.data.connections), }, { key: _("Memory Usage"), - value: String(prettyBytes(systemInfoWidget.data.memory)) - } - ] + value: String(prettyBytes(systemInfoWidget.data.memory)), + }, + ], }); container.replaceChildren(renderedWidget); } @@ -2098,7 +2122,7 @@ async function renderServicesInfoWidget() { loading: servicesInfoWidget.loading, failed: servicesInfoWidget.failed, title: "", - items: [] + items: [], }); return container.replaceChildren(renderedWidget2); } @@ -2109,19 +2133,27 @@ async function renderServicesInfoWidget() { items: [ { key: _("Podkop"), - value: servicesInfoWidget.data.podkop ? _("\u2714 Enabled") : _("\u2718 Disabled"), + value: servicesInfoWidget.data.podkop + ? _("\u2714 Enabled") + : _("\u2718 Disabled"), attributes: { - class: servicesInfoWidget.data.podkop ? "pdk_dashboard-page__widgets-section__item__row--success" : "pdk_dashboard-page__widgets-section__item__row--error" - } + class: servicesInfoWidget.data.podkop + ? "pdk_dashboard-page__widgets-section__item__row--success" + : "pdk_dashboard-page__widgets-section__item__row--error", + }, }, { key: _("Sing-box"), - value: servicesInfoWidget.data.singbox ? _("\u2714 Running") : _("\u2718 Stopped"), + value: servicesInfoWidget.data.singbox + ? _("\u2714 Running") + : _("\u2718 Stopped"), attributes: { - class: servicesInfoWidget.data.singbox ? "pdk_dashboard-page__widgets-section__item__row--success" : "pdk_dashboard-page__widgets-section__item__row--error" - } - } - ] + class: servicesInfoWidget.data.singbox + ? "pdk_dashboard-page__widgets-section__item__row--success" + : "pdk_dashboard-page__widgets-section__item__row--error", + }, + }, + ], }); container.replaceChildren(renderedWidget); } @@ -2156,24 +2188,27 @@ function onPageUnmount() { "trafficTotalWidget", "systemInfoWidget", "servicesInfoWidget", - "sectionsWidget" + "sectionsWidget", ]); socket.resetAll(); } function registerLifecycleListeners() { store.subscribe((next, prev, diff) => { - if (diff.tabService && next.tabService.current !== prev.tabService.current) { + if ( + diff.tabService && + next.tabService.current !== prev.tabService.current + ) { logger.debug( "[DASHBOARD]", "active tab diff event, active tab:", - diff.tabService.current + diff.tabService.current, ); const isDashboardVisible = next.tabService.current === "dashboard"; if (isDashboardVisible) { logger.debug( "[DASHBOARD]", "registerLifecycleListeners", - "onPageMount" + "onPageMount", ); return onPageMount(); } @@ -2181,7 +2216,7 @@ function registerLifecycleListeners() { logger.debug( "[DASHBOARD]", "registerLifecycleListeners", - "onPageUnmount" + "onPageUnmount", ); return onPageUnmount(); } @@ -2321,7 +2356,7 @@ var styles = ` var DashboardTab = { render, initController, - styles + styles, }; // src/podkop/tabs/diagnostic/renderDiagnostic.ts @@ -2331,13 +2366,13 @@ function render2() { E("div", { id: "pdk_diagnostic-page-run-check" }), E("div", { class: "pdk_diagnostic-page__checks", - id: "pdk_diagnostic-page-checks" - }) + id: "pdk_diagnostic-page-checks", + }), ]), E("div", { class: "pdk_diagnostic-page__right-bar" }, [ E("div", { id: "pdk_diagnostic-page-actions" }), - E("div", { id: "pdk_diagnostic-page-system-info" }) - ]) + E("div", { id: "pdk_diagnostic-page-system-info" }), + ]), ]); } @@ -2347,11 +2382,11 @@ function updateCheckStore(check, minified) { const other = diagnosticsChecks.filter((item) => item.code !== check.code); const smallCheck = { ...check, - items: check.items.filter((item) => item.state !== "success") + items: check.items.filter((item) => item.state !== "success"), }; const targetCheck = minified ? smallCheck : check; store.set({ - diagnosticsChecks: [...other, targetCheck] + diagnosticsChecks: [...other, targetCheck], }); } @@ -2364,7 +2399,7 @@ async function runDnsCheck() { title, description: _("Checking dns, please wait"), state: "loading", - items: [] + items: [], }); const dnsChecks = await PodkopShellMethods.checkDNSAvailable(); if (!dnsChecks.success) { @@ -2374,13 +2409,21 @@ async function runDnsCheck() { title, description: _("Cannot receive DNS checks result"), state: "error", - items: [] + items: [], }); throw new Error("DNS checks failed"); } const data = dnsChecks.data; - const allGood = Boolean(data.dns_on_router) && Boolean(data.dhcp_config_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); - const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.dhcp_config_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); + const allGood = + Boolean(data.dns_on_router) && + Boolean(data.dhcp_config_status) && + Boolean(data.bootstrap_dns_status) && + Boolean(data.dns_status); + const atLeastOneGood = + Boolean(data.dns_on_router) || + Boolean(data.dhcp_config_status) || + Boolean(data.bootstrap_dns_status) || + Boolean(data.dns_status); function getStatus() { if (allGood) { return "success"; @@ -2397,32 +2440,29 @@ async function runDnsCheck() { description: _("DNS checks passed"), state: getStatus(), items: [ - ...insertIf( - data.dns_type === "doh" || data.dns_type === "dot", - [ - { - state: data.bootstrap_dns_status ? "success" : "error", - key: _("Bootsrap DNS"), - value: data.bootstrap_dns_server - } - ] - ), + ...insertIf(data.dns_type === "doh" || data.dns_type === "dot", [ + { + state: data.bootstrap_dns_status ? "success" : "error", + key: _("Bootsrap DNS"), + value: data.bootstrap_dns_server, + }, + ]), { state: data.dns_status ? "success" : "error", key: _("Main DNS"), - value: `${data.dns_server} [${data.dns_type}]` + value: `${data.dns_server} [${data.dns_type}]`, }, { state: data.dns_on_router ? "success" : "error", key: _("DNS on router"), - value: "" + value: "", }, { state: data.dhcp_config_status ? "success" : "error", key: _("DHCP has DNS server"), - value: "" - } - ] + value: "", + }, + ], }); if (!atLeastOneGood) { throw new Error("DNS checks failed"); @@ -2438,7 +2478,7 @@ async function runSingBoxCheck() { title, description: _("Checking sing-box, please wait"), state: "loading", - items: [] + items: [], }); const singBoxChecks = await PodkopShellMethods.checkSingBox(); if (!singBoxChecks.success) { @@ -2448,13 +2488,25 @@ async function runSingBoxCheck() { title, description: _("Cannot receive Sing-box checks result"), state: "error", - items: [] + items: [], }); throw new Error("Sing-box checks failed"); } const data = singBoxChecks.data; - const allGood = Boolean(data.sing_box_installed) && Boolean(data.sing_box_version_ok) && Boolean(data.sing_box_service_exist) && Boolean(data.sing_box_autostart_disabled) && Boolean(data.sing_box_process_running) && Boolean(data.sing_box_ports_listening); - const atLeastOneGood = Boolean(data.sing_box_installed) || Boolean(data.sing_box_version_ok) || Boolean(data.sing_box_service_exist) || Boolean(data.sing_box_autostart_disabled) || Boolean(data.sing_box_process_running) || Boolean(data.sing_box_ports_listening); + const allGood = + Boolean(data.sing_box_installed) && + Boolean(data.sing_box_version_ok) && + Boolean(data.sing_box_service_exist) && + Boolean(data.sing_box_autostart_disabled) && + Boolean(data.sing_box_process_running) && + Boolean(data.sing_box_ports_listening); + const atLeastOneGood = + Boolean(data.sing_box_installed) || + Boolean(data.sing_box_version_ok) || + Boolean(data.sing_box_service_exist) || + Boolean(data.sing_box_autostart_disabled) || + Boolean(data.sing_box_process_running) || + Boolean(data.sing_box_ports_listening); function getStatus() { if (allGood) { return "success"; @@ -2474,34 +2526,34 @@ async function runSingBoxCheck() { { state: data.sing_box_installed ? "success" : "error", key: _("Sing-box installed"), - value: "" + value: "", }, { state: data.sing_box_version_ok ? "success" : "error", key: _("Sing-box version >= 1.12.4"), - value: "" + value: "", }, { state: data.sing_box_service_exist ? "success" : "error", key: _("Sing-box service exist"), - value: "" + value: "", }, { state: data.sing_box_autostart_disabled ? "success" : "error", key: _("Sing-box autostart disabled"), - value: "" + value: "", }, { state: data.sing_box_process_running ? "success" : "error", key: _("Sing-box process running"), - value: "" + value: "", }, { state: data.sing_box_ports_listening ? "success" : "error", key: _("Sing-box listening ports"), - value: "" - } - ] + value: "", + }, + ], }); if (!atLeastOneGood || !data.sing_box_process_running) { throw new Error("Sing-box checks failed"); @@ -2517,7 +2569,7 @@ async function runNftCheck() { title, description: _("Checking nftables, please wait"), state: "loading", - items: [] + items: [], }); await RemoteFakeIPMethods.getFakeIpCheck(); await RemoteFakeIPMethods.getIpCheck(); @@ -2529,13 +2581,29 @@ async function runNftCheck() { title, description: _("Cannot receive nftables checks result"), state: "error", - items: [] + items: [], }); throw new Error("Nftables checks failed"); } const data = nftablesChecks.data; - const allGood = Boolean(data.table_exist) && Boolean(data.rules_mangle_exist) && Boolean(data.rules_mangle_counters) && Boolean(data.rules_mangle_output_exist) && Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && Boolean(data.rules_other_mark_exist); - const atLeastOneGood = Boolean(data.table_exist) || Boolean(data.rules_mangle_exist) || Boolean(data.rules_mangle_counters) || Boolean(data.rules_mangle_output_exist) || Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || Boolean(data.rules_other_mark_exist); + const allGood = + Boolean(data.table_exist) && + Boolean(data.rules_mangle_exist) && + Boolean(data.rules_mangle_counters) && + Boolean(data.rules_mangle_output_exist) && + Boolean(data.rules_mangle_output_counters) && + Boolean(data.rules_proxy_exist) && + Boolean(data.rules_proxy_counters) && + Boolean(data.rules_other_mark_exist); + const atLeastOneGood = + Boolean(data.table_exist) || + Boolean(data.rules_mangle_exist) || + Boolean(data.rules_mangle_counters) || + Boolean(data.rules_mangle_output_exist) || + Boolean(data.rules_mangle_output_counters) || + Boolean(data.rules_proxy_exist) || + Boolean(data.rules_proxy_counters) || + Boolean(data.rules_other_mark_exist); function getStatus() { if (allGood) { return "success"; @@ -2549,50 +2617,54 @@ async function runNftCheck() { order, code, title, - description: allGood ? _("Nftables checks passed") : _("Nftables checks partially passed"), + description: allGood + ? _("Nftables checks passed") + : _("Nftables checks partially passed"), state: getStatus(), items: [ { state: data.table_exist ? "success" : "error", key: _("Table exist"), - value: "" + value: "", }, { state: data.rules_mangle_exist ? "success" : "error", key: _("Rules mangle exist"), - value: "" + value: "", }, { state: data.rules_mangle_counters ? "success" : "error", key: _("Rules mangle counters"), - value: "" + value: "", }, { state: data.rules_mangle_output_exist ? "success" : "error", key: _("Rules mangle output exist"), - value: "" + value: "", }, { state: data.rules_mangle_output_counters ? "success" : "error", key: _("Rules mangle output counters"), - value: "" + value: "", }, { state: data.rules_proxy_exist ? "success" : "error", key: _("Rules proxy exist"), - value: "" + value: "", }, { state: data.rules_proxy_counters ? "success" : "error", key: _("Rules proxy counters"), - value: "" + value: "", }, { state: !data.rules_other_mark_exist ? "success" : "warning", - key: !data.rules_other_mark_exist ? _("No other marking rules found") : _("Additional marking rules found"), - value: "" - } - ] + key: !data.rules_other_mark_exist + ? _("No other marking rules found") + : _("Additional marking rules found"), + value: "", + }, + ], }); if (!atLeastOneGood) { throw new Error("Nftables checks failed"); @@ -2608,34 +2680,39 @@ async function runFakeIPCheck() { title, description: _("Checking FakeIP, please wait"), state: "loading", - items: [] + items: [], }); const routerFakeIPResponse = await PodkopShellMethods.checkFakeIP(); const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); const checks = { router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, - browserFakeIP: checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, - differentIP: checkFakeIPResponse.success && checkIPResponse.success && checkFakeIPResponse.data.IP !== checkIPResponse.data.IP + browserFakeIP: + checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, + differentIP: + checkFakeIPResponse.success && + checkIPResponse.success && + checkFakeIPResponse.data.IP !== checkIPResponse.data.IP, }; const allGood = checks.router || checks.browserFakeIP || checks.differentIP; - const atLeastOneGood = checks.router && checks.browserFakeIP && checks.differentIP; + const atLeastOneGood = + checks.router && checks.browserFakeIP && checks.differentIP; function getMeta() { if (allGood) { return { state: "success", - description: _("FakeIP checks passed") + description: _("FakeIP checks passed"), }; } if (atLeastOneGood) { return { state: "warning", - description: _("FakeIP checks partially passed") + description: _("FakeIP checks partially passed"), }; } return { state: "error", - description: _("FakeIP checks failed") + description: _("FakeIP checks failed"), }; } const { state, description } = getMeta(); @@ -2648,22 +2725,28 @@ async function runFakeIPCheck() { items: [ { state: checks.router ? "success" : "warning", - key: checks.router ? _("Router DNS is routed through sing-box") : _("Router DNS is not routed through sing-box"), - value: "" + key: checks.router + ? _("Router DNS is routed through sing-box") + : _("Router DNS is not routed through sing-box"), + value: "", }, { state: checks.browserFakeIP ? "success" : "error", - key: checks.browserFakeIP ? _("Browser is using FakeIP correctly") : _("Browser is not using FakeIP"), - value: "" + key: checks.browserFakeIP + ? _("Browser is using FakeIP correctly") + : _("Browser is not using FakeIP"), + value: "", }, ...insertIf(checks.browserFakeIP, [ { state: checks.differentIP ? "success" : "error", - key: checks.differentIP ? _("Proxy traffic is routed via FakeIP") : _("Proxy traffic is not routed via FakeIP"), - value: "" - } - ]) - ] + key: checks.differentIP + ? _("Proxy traffic is routed via FakeIP") + : _("Proxy traffic is not routed via FakeIP"), + value: "", + }, + ]), + ], }); } @@ -2735,11 +2818,11 @@ function renderLoaderCircleIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-loader-circle rotate" + class: "lucide lucide-loader-circle rotate", }, [ svgEl("path", { - d: "M21 12a9 9 0 1 1-6.219-8.56" + d: "M21 12a9 9 0 1 1-6.219-8.56", }), svgEl("animateTransform", { attributeName: "transform", @@ -2748,9 +2831,9 @@ function renderLoaderCircleIcon24() { from: "0 12 12", to: "360 12 12", dur: "1s", - repeatCount: "indefinite" - }) - ] + repeatCount: "indefinite", + }), + ], ); } @@ -2769,27 +2852,27 @@ function renderCircleAlertIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-alert-icon lucide-circle-alert" + class: "lucide lucide-circle-alert-icon lucide-circle-alert", }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10" + r: "10", }), svgEl("line", { x1: "12", y1: "8", x2: "12", - y2: "12" + y2: "12", }), svgEl("line", { x1: "12", y1: "16", x2: "12.01", - y2: "16" - }) - ] + y2: "16", + }), + ], ); } @@ -2808,18 +2891,18 @@ function renderCircleCheckIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-check-icon lucide-circle-check" + class: "lucide lucide-circle-check-icon lucide-circle-check", }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10" + r: "10", }), svgEl("path", { - d: "M9 12l2 2 4-4" - }) - ] + d: "M9 12l2 2 4-4", + }), + ], ); } @@ -2838,21 +2921,21 @@ function renderCircleSlashIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-slash-icon lucide-circle-slash" + class: "lucide lucide-circle-slash-icon lucide-circle-slash", }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10" + r: "10", }), svgEl("line", { x1: "9", y1: "15", x2: "15", - y2: "9" - }) - ] + y2: "9", + }), + ], ); } @@ -2871,21 +2954,21 @@ function renderCircleXIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-x-icon lucide-circle-x" + class: "lucide lucide-circle-x-icon lucide-circle-x", }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10" + r: "10", }), svgEl("path", { - d: "M15 9L9 15" + d: "M15 9L9 15", }), svgEl("path", { - d: "M9 9L15 15" - }) - ] + d: "M9 9L15 15", + }), + ], ); } @@ -2902,13 +2985,13 @@ function renderCheckIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-check-icon lucide-check" + class: "lucide lucide-check-icon lucide-check", }, [ svgEl("path", { - d: "M20 6 9 17l-5-5" - }) - ] + d: "M20 6 9 17l-5-5", + }), + ], ); } @@ -2925,9 +3008,9 @@ function renderXIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-x-icon lucide-x" + class: "lucide lucide-x-icon lucide-x", }, - [svgEl("path", { d: "M18 6 6 18" }), svgEl("path", { d: "m6 6 12 12" })] + [svgEl("path", { d: "M18 6 6 18" }), svgEl("path", { d: "m6 6 12 12" })], ); } @@ -2944,15 +3027,15 @@ function renderTriangleAlertIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-triangle-alert-icon lucide-triangle-alert" + class: "lucide lucide-triangle-alert-icon lucide-triangle-alert", }, [ svgEl("path", { - d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" + d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3", }), svgEl("path", { d: "M12 9v4" }), - svgEl("path", { d: "M12 17h.01" }) - ] + svgEl("path", { d: "M12 17h.01" }), + ], ); } @@ -2969,7 +3052,7 @@ function renderPauseIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-pause-icon lucide-pause" + class: "lucide lucide-pause-icon lucide-pause", }, [ svgEl("rect", { @@ -2977,16 +3060,16 @@ function renderPauseIcon24() { y: "3", width: "5", height: "18", - rx: "1" + rx: "1", }), svgEl("rect", { x: "5", y: "3", width: "5", height: "18", - rx: "1" - }) - ] + rx: "1", + }), + ], ); } @@ -3003,13 +3086,13 @@ function renderPlayIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-play-icon lucide-play" + class: "lucide lucide-play-icon lucide-play", }, [ svgEl("path", { - d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" - }) - ] + d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z", + }), + ], ); } @@ -3026,16 +3109,16 @@ function renderRotateCcwIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-rotate-ccw-icon lucide-rotate-ccw" + class: "lucide lucide-rotate-ccw-icon lucide-rotate-ccw", }, [ svgEl("path", { - d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" + d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", }), svgEl("path", { - d: "M3 3v5h5" - }) - ] + d: "M3 3v5h5", + }), + ], ); } @@ -3052,22 +3135,22 @@ function renderCircleStopIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-stop-icon lucide-circle-stop" + class: "lucide lucide-circle-stop-icon lucide-circle-stop", }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10" + r: "10", }), svgEl("rect", { x: "9", y: "9", width: "6", height: "6", - rx: "1" - }) - ] + rx: "1", + }), + ], ); } @@ -3084,18 +3167,18 @@ function renderCirclePlayIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-play-icon lucide-circle-play" + class: "lucide lucide-circle-play-icon lucide-circle-play", }, [ svgEl("path", { - d: "M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z" + d: "M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z", }), svgEl("circle", { cx: "12", cy: "12", - r: "10" - }) - ] + r: "10", + }), + ], ); } @@ -3112,16 +3195,16 @@ function renderCircleCheckBigIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-check-big-icon lucide-circle-check-big" + class: "lucide lucide-circle-check-big-icon lucide-circle-check-big", }, [ svgEl("path", { - d: "M21.801 10A10 10 0 1 1 17 3.335" + d: "M21.801 10A10 10 0 1 1 17 3.335", }), svgEl("path", { - d: "m9 11 3 3L22 4" - }) - ] + d: "m9 11 3 3L22 4", + }), + ], ); } @@ -3138,7 +3221,7 @@ function renderSquareChartGanttIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-square-chart-gantt-icon lucide-square-chart-gantt" + class: "lucide lucide-square-chart-gantt-icon lucide-square-chart-gantt", }, [ svgEl("rect", { @@ -3146,12 +3229,12 @@ function renderSquareChartGanttIcon24() { height: "18", x: "3", y: "3", - rx: "2" + rx: "2", }), svgEl("path", { d: "M9 8h7" }), svgEl("path", { d: "M8 12h6" }), - svgEl("path", { d: "M11 16h5" }) - ] + svgEl("path", { d: "M11 16h5" }), + ], ); } @@ -3168,7 +3251,7 @@ function renderCogIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-cog-icon lucide-cog" + class: "lucide lucide-cog-icon lucide-cog", }, [ svgEl("path", { d: "M11 10.27 7 3.34" }), @@ -3184,8 +3267,8 @@ function renderCogIcon24() { svgEl("path", { d: "m3.34 17 1.73-1" }), svgEl("path", { d: "m3.34 7 1.73 1" }), svgEl("circle", { cx: "12", cy: "12", r: "2" }), - svgEl("circle", { cx: "12", cy: "12", r: "8" }) - ] + svgEl("circle", { cx: "12", cy: "12", r: "8" }), + ], ); } @@ -3202,12 +3285,12 @@ function renderSearchIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-search-icon lucide-search" + class: "lucide lucide-search-icon lucide-search", }, [ svgEl("path", { d: "m21 21-4.34-4.34" }), - svgEl("circle", { cx: "11", cy: "11", r: "8" }) - ] + svgEl("circle", { cx: "11", cy: "11", r: "8" }), + ], ); } @@ -3218,12 +3301,12 @@ function renderButton({ loading, onClick, text, - icon + icon, }) { const hasIcon = !!loading || !!icon; function getWrappedIcon() { const iconWrap = E("span", { - class: "pdk-partial-button__icon" + class: "pdk-partial-button__icon", }); if (loading) { iconWrap.appendChild(renderLoaderCircleIcon24()); @@ -3242,8 +3325,10 @@ function renderButton({ ...insertIf(Boolean(disabled), ["pdk-partial-button--disabled"]), ...insertIf(Boolean(loading), ["pdk-partial-button--loading"]), ...insertIf(Boolean(hasIcon), ["pdk-partial-button--with-icon"]), - ...classNames - ].filter(Boolean).join(" "); + ...classNames, + ] + .filter(Boolean) + .join(" "); } function getDisabled() { if (loading || disabled) { @@ -3254,7 +3339,7 @@ function renderButton({ return E( "button", { class: getClass(), disabled: getDisabled(), click: onClick }, - [...insertIf(hasIcon, [getWrappedIcon()]), E("span", {}, text)] + [...insertIf(hasIcon, [getWrappedIcon()]), E("span", {}, text)], ); } @@ -3304,22 +3389,23 @@ function renderModal(text, name) { renderButton({ classNames: ["cbi-button-apply"], text: _("Download"), - onClick: () => downloadAsTxt(text, name) + onClick: () => downloadAsTxt(text, name), }), renderButton({ classNames: ["cbi-button-apply"], text: _("Copy"), - onClick: () => copyToClipboard(` \`\`\`${name} + onClick: () => + copyToClipboard(` \`\`\`${name} ${text} - \`\`\``) + \`\`\``), }), renderButton({ classNames: ["cbi-button-remove"], text: _("Close"), - onClick: ui.hideModal - }) - ]) - ]) + onClick: ui.hideModal, + }), + ]), + ]), ); } @@ -3338,7 +3424,7 @@ function renderAvailableActions({ disable, globalCheck, viewLogs, - showSingBoxConfig + showSingBoxConfig, }) { return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [ E("b", {}, "Available actions"), @@ -3349,8 +3435,8 @@ function renderAvailableActions({ icon: renderRotateCcwIcon24, text: _("Restart podkop"), loading: restart.loading, - disabled: restart.disabled - }) + disabled: restart.disabled, + }), ]), ...insertIf(stop.visible, [ renderButton({ @@ -3359,8 +3445,8 @@ function renderAvailableActions({ icon: renderCircleStopIcon24, text: _("Stop podkop"), loading: stop.loading, - disabled: stop.disabled - }) + disabled: stop.disabled, + }), ]), ...insertIf(start.visible, [ renderButton({ @@ -3369,8 +3455,8 @@ function renderAvailableActions({ icon: renderCirclePlayIcon24, text: _("Start podkop"), loading: start.loading, - disabled: start.disabled - }) + disabled: start.disabled, + }), ]), ...insertIf(disable.visible, [ renderButton({ @@ -3379,8 +3465,8 @@ function renderAvailableActions({ icon: renderPauseIcon24, text: _("Disable autostart"), loading: disable.loading, - disabled: disable.disabled - }) + disabled: disable.disabled, + }), ]), ...insertIf(enable.visible, [ renderButton({ @@ -3389,8 +3475,8 @@ function renderAvailableActions({ icon: renderPlayIcon24, text: _("Enable autostart"), loading: enable.loading, - disabled: enable.disabled - }) + disabled: enable.disabled, + }), ]), ...insertIf(globalCheck.visible, [ renderButton({ @@ -3398,8 +3484,8 @@ function renderAvailableActions({ icon: renderCircleCheckBigIcon24, text: _("Get global check"), loading: globalCheck.loading, - disabled: globalCheck.disabled - }) + disabled: globalCheck.disabled, + }), ]), ...insertIf(viewLogs.visible, [ renderButton({ @@ -3407,8 +3493,8 @@ function renderAvailableActions({ icon: renderSquareChartGanttIcon24, text: _("View logs"), loading: viewLogs.loading, - disabled: viewLogs.disabled - }) + disabled: viewLogs.disabled, + }), ]), ...insertIf(showSingBoxConfig.visible, [ renderButton({ @@ -3416,9 +3502,9 @@ function renderAvailableActions({ icon: renderCogIcon24, text: _("Show sing-box config"), loading: showSingBoxConfig.loading, - disabled: showSingBoxConfig.disabled - }) - ]) + disabled: showSingBoxConfig.disabled, + }), + ]), ]); } @@ -3430,7 +3516,7 @@ function renderCheckSummary(items) { const renderedItems = items.map((item) => { function getIcon() { const iconWrap = E("span", { - class: "pdk_diagnostic_alert__summary__item__icon" + class: "pdk_diagnostic_alert__summary__item__icon", }); if (item.state === "success") { iconWrap.appendChild(renderCheckIcon24()); @@ -3446,9 +3532,9 @@ function renderCheckSummary(items) { return E( "div", { - class: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}` + class: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}`, }, - [getIcon(), E("b", {}, item.key), E("div", {}, item.value)] + [getIcon(), E("b", {}, item.key), E("div", {}, item.value)], ); }); return E("div", { class: "pdk_diagnostic_alert__summary" }, renderedItems); @@ -3466,12 +3552,12 @@ function renderLoadingState3(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description - ) + props.description, + ), ]), E("div", {}, ""), - renderCheckSummary(props.items) - ] + renderCheckSummary(props.items), + ], ); } function renderWarningState(props) { @@ -3487,12 +3573,12 @@ function renderWarningState(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description - ) + props.description, + ), ]), E("div", {}, ""), - renderCheckSummary(props.items) - ] + renderCheckSummary(props.items), + ], ); } function renderErrorState(props) { @@ -3508,12 +3594,12 @@ function renderErrorState(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description - ) + props.description, + ), ]), E("div", {}, ""), - renderCheckSummary(props.items) - ] + renderCheckSummary(props.items), + ], ); } function renderSuccessState(props) { @@ -3529,12 +3615,12 @@ function renderSuccessState(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description - ) + props.description, + ), ]), E("div", {}, ""), - renderCheckSummary(props.items) - ] + renderCheckSummary(props.items), + ], ); } function renderSkippedState(props) { @@ -3550,12 +3636,12 @@ function renderSkippedState(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description - ) + props.description, + ), ]), E("div", {}, ""), - renderCheckSummary(props.items) - ] + renderCheckSummary(props.items), + ], ); } function renderCheckSection(props) { @@ -3578,18 +3664,15 @@ function renderCheckSection(props) { } // src/podkop/tabs/diagnostic/partials/renderRunAction.ts -function renderRunAction({ - loading, - click -}) { +function renderRunAction({ loading, click }) { return E("div", { class: "pdk_diagnostic-page__run_check_wrapper" }, [ renderButton({ text: _("Run Diagnostic"), onClick: click, icon: renderSearchIcon24, loading, - classNames: ["cbi-button-apply"] - }) + classNames: ["cbi-button-apply"], + }), ]); } @@ -3599,18 +3682,20 @@ function renderSystemInfo({ items }) { E( "b", { class: "pdk_diagnostic-page__right-bar__system-info__title" }, - "System information" + "System information", ), ...items.map((item) => { const tagClass = [ "pdk_diagnostic-page__right-bar__system-info__row__tag", ...insertIf(item.tag?.kind === "warning", [ - "pdk_diagnostic-page__right-bar__system-info__row__tag--warning" + "pdk_diagnostic-page__right-bar__system-info__row__tag--warning", ]), ...insertIf(item.tag?.kind === "success", [ - "pdk_diagnostic-page__right-bar__system-info__row__tag--success" - ]) - ].filter(Boolean).join(" "); + "pdk_diagnostic-page__right-bar__system-info__row__tag--success", + ]), + ] + .filter(Boolean) + .join(" "); return E( "div", { class: "pdk_diagnostic-page__right-bar__system-info__row" }, @@ -3618,11 +3703,11 @@ function renderSystemInfo({ items }) { E("b", {}, item.key), E("div", {}, [ E("span", {}, item.value), - E("span", { class: tagClass }, item?.tag?.label) - ]) - ] + E("span", { class: tagClass }, item?.tag?.label), + ]), + ], ); - }) + }), ]); } @@ -3641,8 +3726,8 @@ async function fetchSystemInfo() { store.set({ diagnosticsSystemInfo: { loading: false, - ...systemInfo.data - } + ...systemInfo.data, + }, }); } else { store.set({ @@ -3653,17 +3738,19 @@ async function fetchSystemInfo() { luci_app_version: _("unknown"), sing_box_version: _("unknown"), openwrt_version: _("unknown"), - device_model: _("unknown") - } + device_model: _("unknown"), + }, }); } } function renderDiagnosticsChecks() { logger.debug("[DIAGNOSTIC]", "renderDiagnosticsChecks"); - const diagnosticsChecks = store.get().diagnosticsChecks.sort((a, b) => a.order - b.order); + const diagnosticsChecks = store + .get() + .diagnosticsChecks.sort((a, b) => a.order - b.order); const container = document.getElementById("pdk_diagnostic-page-checks"); - const renderedDiagnosticsChecks = diagnosticsChecks.map( - (check) => renderCheckSection(check) + const renderedDiagnosticsChecks = diagnosticsChecks.map((check) => + renderCheckSection(check), ); return preserveScrollForPage(() => { container.replaceChildren(...renderedDiagnosticsChecks); @@ -3675,7 +3762,7 @@ function renderDiagnosticRunActionWidget() { const container = document.getElementById("pdk_diagnostic-page-run-check"); const renderedAction = renderRunAction({ loading, - click: () => runChecks() + click: () => runChecks(), }); return preserveScrollForPage(() => { container.replaceChildren(renderedAction); @@ -3686,8 +3773,8 @@ async function handleRestart() { store.set({ diagnosticsActions: { ...diagnosticsActions, - restart: { loading: true } - } + restart: { loading: true }, + }, }); try { await PodkopShellMethods.restart(); @@ -3699,8 +3786,8 @@ async function handleRestart() { store.set({ diagnosticsActions: { ...diagnosticsActions, - restart: { loading: false } - } + restart: { loading: false }, + }, }); store.reset(["diagnosticsChecks"]); }, 5e3); @@ -3711,8 +3798,8 @@ async function handleStop() { store.set({ diagnosticsActions: { ...diagnosticsActions, - stop: { loading: true } - } + stop: { loading: true }, + }, }); try { await PodkopShellMethods.stop(); @@ -3723,8 +3810,8 @@ async function handleStop() { store.set({ diagnosticsActions: { ...diagnosticsActions, - stop: { loading: false } - } + stop: { loading: false }, + }, }); store.reset(["diagnosticsChecks"]); } @@ -3734,8 +3821,8 @@ async function handleStart() { store.set({ diagnosticsActions: { ...diagnosticsActions, - start: { loading: true } - } + start: { loading: true }, + }, }); try { await PodkopShellMethods.start(); @@ -3747,8 +3834,8 @@ async function handleStart() { store.set({ diagnosticsActions: { ...diagnosticsActions, - start: { loading: false } - } + start: { loading: false }, + }, }); store.reset(["diagnosticsChecks"]); }, 5e3); @@ -3759,8 +3846,8 @@ async function handleEnable() { store.set({ diagnosticsActions: { ...diagnosticsActions, - enable: { loading: true } - } + enable: { loading: true }, + }, }); try { await PodkopShellMethods.enable(); @@ -3771,8 +3858,8 @@ async function handleEnable() { store.set({ diagnosticsActions: { ...diagnosticsActions, - enable: { loading: false } - } + enable: { loading: false }, + }, }); } } @@ -3781,8 +3868,8 @@ async function handleDisable() { store.set({ diagnosticsActions: { ...diagnosticsActions, - disable: { loading: true } - } + disable: { loading: true }, + }, }); try { await PodkopShellMethods.disable(); @@ -3793,8 +3880,8 @@ async function handleDisable() { store.set({ diagnosticsActions: { ...diagnosticsActions, - disable: { loading: false } - } + disable: { loading: false }, + }, }); } } @@ -3803,15 +3890,15 @@ async function handleShowGlobalCheck() { store.set({ diagnosticsActions: { ...diagnosticsActions, - globalCheck: { loading: true } - } + globalCheck: { loading: true }, + }, }); try { const globalCheck = await PodkopShellMethods.globalCheck(); if (globalCheck.success) { ui.showModal( _("Global check"), - renderModal(globalCheck.data, "global_check") + renderModal(globalCheck.data, "global_check"), ); } } catch (e) { @@ -3820,8 +3907,8 @@ async function handleShowGlobalCheck() { store.set({ diagnosticsActions: { ...diagnosticsActions, - globalCheck: { loading: false } - } + globalCheck: { loading: false }, + }, }); } } @@ -3830,16 +3917,13 @@ async function handleViewLogs() { store.set({ diagnosticsActions: { ...diagnosticsActions, - viewLogs: { loading: true } - } + viewLogs: { loading: true }, + }, }); try { const viewLogs = await PodkopShellMethods.checkLogs(); if (viewLogs.success) { - ui.showModal( - _("View logs"), - renderModal(viewLogs.data, "view_logs") - ); + ui.showModal(_("View logs"), renderModal(viewLogs.data, "view_logs")); } } catch (e) { logger.error("[DIAGNOSTIC]", "handleViewLogs - e", e); @@ -3847,8 +3931,8 @@ async function handleViewLogs() { store.set({ diagnosticsActions: { ...diagnosticsActions, - viewLogs: { loading: false } - } + viewLogs: { loading: false }, + }, }); } } @@ -3857,15 +3941,15 @@ async function handleShowSingBoxConfig() { store.set({ diagnosticsActions: { ...diagnosticsActions, - showSingBoxConfig: { loading: true } - } + showSingBoxConfig: { loading: true }, + }, }); try { const showSingBoxConfig = await PodkopShellMethods.showSingBoxConfig(); if (showSingBoxConfig.success) { ui.showModal( _("Show sing-box config"), - renderModal(showSingBoxConfig.data, "show_sing_box_config") + renderModal(showSingBoxConfig.data, "show_sing_box_config"), ); } } catch (e) { @@ -3874,8 +3958,8 @@ async function handleShowSingBoxConfig() { store.set({ diagnosticsActions: { ...diagnosticsActions, - showSingBoxConfig: { loading: false } - } + showSingBoxConfig: { loading: false }, + }, }); } } @@ -3885,57 +3969,61 @@ function renderDiagnosticAvailableActionsWidget() { logger.debug("[DIAGNOSTIC]", "renderDiagnosticAvailableActionsWidget"); const podkopEnabled = Boolean(servicesInfoWidget.data.podkop); const singBoxRunning = Boolean(servicesInfoWidget.data.singbox); - const atLeastOneServiceCommandLoading = servicesInfoWidget.loading || diagnosticsActions.restart.loading || diagnosticsActions.start.loading || diagnosticsActions.stop.loading; + const atLeastOneServiceCommandLoading = + servicesInfoWidget.loading || + diagnosticsActions.restart.loading || + diagnosticsActions.start.loading || + diagnosticsActions.stop.loading; const container = document.getElementById("pdk_diagnostic-page-actions"); const renderedActions = renderAvailableActions({ restart: { loading: diagnosticsActions.restart.loading, visible: true, onClick: handleRestart, - disabled: atLeastOneServiceCommandLoading + disabled: atLeastOneServiceCommandLoading, }, start: { loading: diagnosticsActions.start.loading, visible: !singBoxRunning, onClick: handleStart, - disabled: atLeastOneServiceCommandLoading + disabled: atLeastOneServiceCommandLoading, }, stop: { loading: diagnosticsActions.stop.loading, visible: singBoxRunning, onClick: handleStop, - disabled: atLeastOneServiceCommandLoading + disabled: atLeastOneServiceCommandLoading, }, enable: { loading: diagnosticsActions.enable.loading, visible: !podkopEnabled, onClick: handleEnable, - disabled: atLeastOneServiceCommandLoading + disabled: atLeastOneServiceCommandLoading, }, disable: { loading: diagnosticsActions.disable.loading, visible: podkopEnabled, onClick: handleDisable, - disabled: atLeastOneServiceCommandLoading + disabled: atLeastOneServiceCommandLoading, }, globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, onClick: handleShowGlobalCheck, - disabled: atLeastOneServiceCommandLoading + disabled: atLeastOneServiceCommandLoading, }, viewLogs: { loading: diagnosticsActions.viewLogs.loading, visible: true, onClick: handleViewLogs, - disabled: atLeastOneServiceCommandLoading + disabled: atLeastOneServiceCommandLoading, }, showSingBoxConfig: { loading: diagnosticsActions.showSingBoxConfig.loading, visible: true, onClick: handleShowSingBoxConfig, - disabled: atLeastOneServiceCommandLoading - } + disabled: atLeastOneServiceCommandLoading, + }, }); return preserveScrollForPage(() => { container.replaceChildren(renderedActions); @@ -3949,16 +4037,16 @@ function renderDiagnosticSystemInfoWidget() { const loading = diagnosticsSystemInfo.loading; const unknown = diagnosticsSystemInfo.podkop_version === _("unknown"); const hasActualVersion = Boolean( - diagnosticsSystemInfo.podkop_latest_version + diagnosticsSystemInfo.podkop_latest_version, ); const version = normalizeCompiledVersion( - diagnosticsSystemInfo.podkop_version + diagnosticsSystemInfo.podkop_version, ); const isDevVersion = version === "dev"; if (loading || unknown || !hasActualVersion || isDevVersion) { return { key: "Podkop", - value: version + value: version, }; } if (version !== diagnosticsSystemInfo.podkop_latest_version) { @@ -3967,8 +4055,8 @@ function renderDiagnosticSystemInfoWidget() { value: version, tag: { label: _("Outdated"), - kind: "warning" - } + kind: "warning", + }, }; } return { @@ -3976,8 +4064,8 @@ function renderDiagnosticSystemInfoWidget() { value: version, tag: { label: _("Latest"), - kind: "success" - } + kind: "success", + }, }; } const renderedSystemInfo = renderSystemInfo({ @@ -3985,21 +4073,21 @@ function renderDiagnosticSystemInfoWidget() { getPodkopVersionRow(), { key: "Luci App", - value: normalizeCompiledVersion(diagnosticsSystemInfo.luci_app_version) + value: normalizeCompiledVersion(diagnosticsSystemInfo.luci_app_version), }, { key: "Sing-box", - value: diagnosticsSystemInfo.sing_box_version + value: diagnosticsSystemInfo.sing_box_version, }, { key: "OS", - value: diagnosticsSystemInfo.openwrt_version + value: diagnosticsSystemInfo.openwrt_version, }, { key: "Device", - value: diagnosticsSystemInfo.device_model - } - ] + value: diagnosticsSystemInfo.device_model, + }, + ], }); return preserveScrollForPage(() => { container.replaceChildren(renderedSystemInfo); @@ -4023,7 +4111,7 @@ async function runChecks() { try { store.set({ diagnosticsRunAction: { loading: true }, - diagnosticsChecks: loadingDiagnosticsChecksStore.diagnosticsChecks + diagnosticsChecks: loadingDiagnosticsChecksStore.diagnosticsChecks, }); await runDnsCheck(); await runSingBoxCheck(); @@ -4051,23 +4139,26 @@ function onPageUnmount2() { "diagnosticsActions", "diagnosticsSystemInfo", "diagnosticsChecks", - "diagnosticsRunAction" + "diagnosticsRunAction", ]); } function registerLifecycleListeners2() { store.subscribe((next, prev, diff) => { - if (diff.tabService && next.tabService.current !== prev.tabService.current) { + if ( + diff.tabService && + next.tabService.current !== prev.tabService.current + ) { logger.debug( "[DIAGNOSTIC]", "active tab diff event, active tab:", - diff.tabService.current + diff.tabService.current, ); const isDIAGNOSTICVisible = next.tabService.current === "diagnostic"; if (isDIAGNOSTICVisible) { logger.debug( "[DIAGNOSTIC]", "registerLifecycleListeners", - "onPageMount" + "onPageMount", ); return onPageMount2(); } @@ -4075,7 +4166,7 @@ function registerLifecycleListeners2() { logger.debug( "[DIAGNOSTIC]", "registerLifecycleListeners", - "onPageUnmount" + "onPageUnmount", ); return onPageUnmount2(); } @@ -4260,7 +4351,7 @@ var styles4 = ` var DiagnosticTab = { render: render2, initController: initController2, - styles: styles4 + styles: styles4, }; // src/styles.ts @@ -4381,12 +4472,17 @@ function injectGlobalStyles() { - ` + `, ); } // src/helpers/withTimeout.ts -async function withTimeout(promise, timeoutMs, operationName, timeoutMessage = _("Operation timed out")) { +async function withTimeout( + promise, + timeoutMs, + operationName, + timeoutMessage = _("Operation timed out"), +) { let timeoutId; const start = performance.now(); const timeoutPromise = new Promise((_2, reject) => { @@ -4405,13 +4501,13 @@ async function withTimeout(promise, timeoutMs, operationName, timeoutMessage = _ async function executeShellCommand({ command, args, - timeout = COMMAND_TIMEOUT + timeout = COMMAND_TIMEOUT, }) { try { return withTimeout( fs.exec(command, args), timeout, - [command, ...args].join(" ") + [command, ...args].join(" "), ); } catch (err) { const error = err; @@ -4461,7 +4557,7 @@ async function onMount(id) { }); observer.observe(document.body, { childList: true, - subtree: true + subtree: true, }); }); } @@ -4478,7 +4574,11 @@ function getClashUIUrl() { // src/helpers/splitProxyString.ts function splitProxyString(str) { - return str.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("//")).filter(Boolean); + return str + .split("\n") + .map((line) => line.trim()) + .filter((line) => !line.startsWith("//")) + .filter(Boolean); } // src/helpers/preserveScrollForPage.ts @@ -4497,7 +4597,9 @@ function svgEl(tag, attrs = {}, children = []) { for (const [k, v] of Object.entries(attrs)) { if (v != null) el.setAttribute(k, String(v)); } - (Array.isArray(children) ? children : [children]).filter(Boolean).forEach((ch) => el.appendChild(ch)); + (Array.isArray(children) ? children : [children]) + .filter(Boolean) + .forEach((ch) => el.appendChild(ch)); return el; } @@ -4566,5 +4668,5 @@ return baseclass.extend({ validateTrojanUrl, validateUrl, validateVlessUrl, - withTimeout + withTimeout, }); 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 7903b96..c03a3e0 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 @@ -42,39 +42,20 @@ function createSectionContent(section) { 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.placeholder = "vless://uuid@server:port?type=tcp&security=tls#main"; o.validate = function (section_id, value) { // Optional if (!value || value.length === 0) { return true; } - try { - const activeConfigs = main.splitProxyString(value); + const validation = main.validateProxyUrl(value); - 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.", - ); - } - - const validation = main.validateProxyUrl(activeConfigs[0]); - - if (validation.valid) { - return true; - } - - return validation.message; - } catch (e) { - return `${_("Invalid URL format:")} ${e?.message}`; + if (validation.valid) { + return true; } + + return validation.message; }; o = section.option( 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 b36e70a..3b735bc 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 @@ -83,6 +83,43 @@ function createSettingsContent(section) { return true; }; + 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); + + // If no device is found, allow the value + if (!device) { + return true; + } + + // 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"); + + // Allow only non-wireless devices + return !isWireless; + }; + o = section.option( form.Flag, "enable_output_network_interface", @@ -139,43 +176,6 @@ function createSettingsContent(section) { 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; - } - - // 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; - } - - // 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"); - - // Allow only non-wireless devices - return !isWireless; - }; - o = section.option( form.Flag, "enable_badwan_interface_monitoring", From 8384e18a221cb9f9be33b37f27993786e9bde8a8 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Tue, 21 Oct 2025 15:25:07 +0500 Subject: [PATCH 103/121] chore: remove comments support for proxy url --- podkop/files/usr/bin/podkop | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 59d0e60..8ec9ade 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -611,14 +611,12 @@ configure_outbound_handler() { local proxy_string udp_over_tcp config_get proxy_string "$section" "proxy_string" config_get udp_over_tcp "$section" "enable_udp_over_tcp" - # Extract the first non-comment line as the active configuration - active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1) - if [ -z "$active_proxy_string" ]; then + if [ -z "$proxy_string" ]; then log "Proxy string is not set. Aborted." "fatal" exit 1 fi - config=$(sing_box_cf_add_proxy_outbound "$config" "$section" "$active_proxy_string" "$udp_over_tcp") + config=$(sing_box_cf_add_proxy_outbound "$config" "$section" "$proxy_string" "$udp_over_tcp") ;; outbound) log "Detected proxy configuration type: outbound" "debug" From 3bccf8d6171f2bd3bdd79101d4986e7ac1177fa7 Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Tue, 21 Oct 2025 16:24:24 +0500 Subject: [PATCH 104/121] chore: Update UDP-over-TCP option label and description to clarify SOCKS/Shadowsocks applicability --- .../htdocs/luci-static/resources/view/podkop/section.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c03a3e0..14b23ba 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 @@ -107,8 +107,8 @@ function createSectionContent(section) { o = section.option( form.Flag, "enable_udp_over_tcp", - _("Shadowsocks/Socks UDP over TCP"), - _("Apply for socks/Shadowsocks 2022"), + _("UDP over TCP"), + _("Applicable for SOCKS and Shadowsocks proxy"), ); o.default = "0"; o.depends("connection_type", "proxy"); From 1acdbe67a23f94ec42979dd00a1c03433fdd2050 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 21 Oct 2025 21:33:51 +0300 Subject: [PATCH 105/121] feat: implement locales scripts --- fe-app-podkop/distribute-locales.js | 38 + fe-app-podkop/extract-calls.js | 93 + fe-app-podkop/generate-po.js | 113 + fe-app-podkop/generate-pot.js | 73 + fe-app-podkop/locales/calls.json | 2570 +++++++++++++++++ fe-app-podkop/locales/podkop.pot | 1517 ++++++++++ fe-app-podkop/locales/podkop.ru.po | 1086 +++++++ fe-app-podkop/package.json | 8 +- fe-app-podkop/yarn.lock | 11 + .../luci-static/resources/view/podkop/main.js | 1562 +++++----- luci-app-podkop/po/ru/podkop.po | 1337 ++++++--- luci-app-podkop/po/templates/podkop.pot | 2011 ++++++++----- 12 files changed, 8387 insertions(+), 2032 deletions(-) create mode 100644 fe-app-podkop/distribute-locales.js create mode 100644 fe-app-podkop/extract-calls.js create mode 100644 fe-app-podkop/generate-po.js create mode 100644 fe-app-podkop/generate-pot.js create mode 100644 fe-app-podkop/locales/calls.json create mode 100644 fe-app-podkop/locales/podkop.pot create mode 100644 fe-app-podkop/locales/podkop.ru.po diff --git a/fe-app-podkop/distribute-locales.js b/fe-app-podkop/distribute-locales.js new file mode 100644 index 0000000..0bc27b2 --- /dev/null +++ b/fe-app-podkop/distribute-locales.js @@ -0,0 +1,38 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const sourceDir = path.resolve(__dirname, 'locales'); +const targetRoot = path.resolve(__dirname, '../luci-app-podkop/po'); + +async function main() { + const files = await fs.readdir(sourceDir); + + for (const file of files) { + const filePath = path.join(sourceDir, file); + + if (file === 'podkop.pot') { + const potTarget = path.join(targetRoot, 'templates', 'podkop.pot'); + await fs.mkdir(path.dirname(potTarget), { recursive: true }); + await fs.copyFile(filePath, potTarget); + console.log(`✅ Copied POT: ${filePath} → ${potTarget}`); + } + + const match = file.match(/^podkop\.([a-zA-Z_]+)\.po$/); + if (match) { + const lang = match[1]; + const poTarget = path.join(targetRoot, lang, 'podkop.po'); + await fs.mkdir(path.dirname(poTarget), { recursive: true }); + await fs.copyFile(filePath, poTarget); + console.log(`✅ Copied ${lang.toUpperCase()}: ${filePath} → ${poTarget}`); + } + } +} + +main().catch((err) => { + console.error('❌ Ошибка при распространении переводов:', err); + process.exit(1); +}); diff --git a/fe-app-podkop/extract-calls.js b/fe-app-podkop/extract-calls.js new file mode 100644 index 0000000..4bc88d1 --- /dev/null +++ b/fe-app-podkop/extract-calls.js @@ -0,0 +1,93 @@ +import fg from 'fast-glob'; +import fs from 'fs/promises'; +import path from 'path'; + +const outputFile = 'locales/calls.json'; + +const tsSearchGlob = 'src/**/*.ts'; +const jsSearchGlob = '../luci-app-podkop/htdocs/luci-static/resources/view/podkop/**/*.js'; + +function extractAllUnderscoreCallsFromContent(content) { + const results = []; + let index = 0; + + while (index < content.length) { + const start = content.indexOf('_(', index); + if (start === -1) break; + + let i = start + 2; + let depth = 1; + + while (i < content.length && depth > 0) { + if (content[i] === '(') depth++; + else if (content[i] === ')') depth--; + i++; + } + + const raw = content.slice(start, i); + results.push({ raw, index: start }); + index = i; + } + + return results; +} + +function getLineNumber(content, charIndex) { + return content.slice(0, charIndex).split('\n').length; +} + +function extractKey(call) { + const match = call.match(/^_\(\s*(['"`])((?:\\\1|.)*?)\1\s*\)$/); + return match ? match[2].trim() : null; +} + +function normalizeCall(call) { + return call + .replace(/\s*\n\s*/g, ' ') + .replace(/\s+/g, ' ') + .replace(/\(\s+/g, '(') + .replace(/\s+\)/g, ')') + .replace(/,\s*\)$/, ')') + .trim(); +} + +async function extractAllUnderscoreCallsWithLocations() { + const files = [ + ...(await fg(tsSearchGlob, { ignore: ['**/*test.ts'], absolute: true })), + ...(await fg(jsSearchGlob, { ignore: ['**/main.js'], absolute: true })), + ]; + + const callMap = new Map(); + + for (const file of files) { + const content = await fs.readFile(file, 'utf8'); + const relativePath = path.relative(process.cwd(), file); + const extracted = extractAllUnderscoreCallsFromContent(content); + + for (const { raw, index } of extracted) { + const line = getLineNumber(content, index); + const location = `${relativePath}:${line}`; + + const normalized = normalizeCall(raw); + const key = extractKey(normalized); + + if (!callMap.has(normalized)) { + callMap.set(normalized, { + call: normalized, + key: key ?? '', + places: [], + }); + } + + callMap.get(normalized).places.push(location); + } + } + + const result = [...callMap.values()]; + await fs.mkdir(path.dirname(outputFile), { recursive: true }); + await fs.writeFile(outputFile, JSON.stringify(result, null, 2), 'utf8'); + + console.log(`✅ Найдено ${result.length} уникальных вызовов _(...). Сохранено в ${outputFile}`); +} + +extractAllUnderscoreCallsWithLocations().catch(console.error); diff --git a/fe-app-podkop/generate-po.js b/fe-app-podkop/generate-po.js new file mode 100644 index 0000000..234d6b8 --- /dev/null +++ b/fe-app-podkop/generate-po.js @@ -0,0 +1,113 @@ +import fs from 'fs/promises'; +import { execSync } from 'child_process'; + +const lang = process.argv[2]; +if (!lang) { + console.error('❌ Укажи язык, например: node generate-po.js ru'); + process.exit(1); +} + +const callsPath = 'locales/calls.json'; +const poPath = `locales/podkop.${lang}.po`; + +function getGitUser() { + try { + return execSync('git config user.name').toString().trim(); + } catch { + return 'Automatically generated'; + } +} + +function getHeader(lang) { + const now = new Date(); + const date = now.toISOString().split('T')[0]; + const time = now.toTimeString().split(' ')[0].slice(0, 5); + const tzOffset = (() => { + const offset = -now.getTimezoneOffset(); + const sign = offset >= 0 ? '+' : '-'; + const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0'); + const minutes = String(Math.abs(offset) % 60).padStart(2, '0'); + return `${sign}${hours}${minutes}`; + })(); + + const translator = getGitUser(); + const pluralForms = lang === 'ru' + ? 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);' + : 'nplurals=2; plural=(n != 1);'; + + return [ + `# ${lang.toUpperCase()} translations for PODKOP package.`, + `# Copyright (C) ${now.getFullYear()} THE PODKOP'S COPYRIGHT HOLDER`, + `# This file is distributed under the same license as the PODKOP package.`, + `# ${translator}, ${now.getFullYear()}.`, + '#', + 'msgid ""', + 'msgstr ""', + `"Project-Id-Version: PODKOP\\n"`, + `"Report-Msgid-Bugs-To: \\n"`, + `"POT-Creation-Date: ${date} ${time}${tzOffset}\\n"`, + `"PO-Revision-Date: ${date} ${time}${tzOffset}\\n"`, + `"Last-Translator: ${translator}\\n"`, + `"Language-Team: none\\n"`, + `"Language: ${lang}\\n"`, + `"MIME-Version: 1.0\\n"`, + `"Content-Type: text/plain; charset=UTF-8\\n"`, + `"Content-Transfer-Encoding: 8bit\\n"`, + `"Plural-Forms: ${pluralForms}\\n"`, + '', + ]; +} + +function parsePo(content) { + const lines = content.split('\n'); + const translations = new Map(); + let msgid = null; + let msgstr = null; + for (const line of lines) { + if (line.startsWith('msgid ')) { + msgid = JSON.parse(line.slice(6)); + } else if (line.startsWith('msgstr ') && msgid !== null) { + msgstr = JSON.parse(line.slice(7)); + translations.set(msgid, msgstr); + msgid = null; + msgstr = null; + } + } + return translations; +} + +function escapePoString(str) { + return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); +} + +async function generatePo() { + const [callsRaw, oldPoRaw] = await Promise.all([ + fs.readFile(callsPath, 'utf8'), + fs.readFile(poPath, 'utf8').catch(() => ''), + ]); + + const calls = JSON.parse(callsRaw); + const oldTranslations = parsePo(oldPoRaw); + const header = getHeader(lang); + + const body = calls + .map(({ key }) => { + const msgid = key; + const msgstr = oldTranslations.get(msgid) || ''; + return [ + `msgid "${escapePoString(msgid)}"`, + `msgstr "${escapePoString(msgstr)}"`, + '' + ].join('\n'); + }) + .join('\n'); + + const finalPo = header.join('\n') + '\n' + body; + + await fs.writeFile(poPath, finalPo, 'utf8'); + console.log(`✅ Файл ${poPath} успешно сгенерирован. Переведено ${[...oldTranslations.keys()].length}/${calls.length}`); +} + +generatePo().catch((err) => { + console.error('Ошибка генерации PO файла:', err); +}); diff --git a/fe-app-podkop/generate-pot.js b/fe-app-podkop/generate-pot.js new file mode 100644 index 0000000..b739044 --- /dev/null +++ b/fe-app-podkop/generate-pot.js @@ -0,0 +1,73 @@ +import fs from 'fs/promises'; +import { execSync } from 'child_process'; + +const inputFile = 'locales/calls.json'; +const outputFile = 'locales/podkop.pot'; +const projectId = 'PODKOP'; + +function getGitUser() { + const name = execSync('git config user.name').toString().trim(); + const email = execSync('git config user.email').toString().trim(); + return { name, email }; +} + +function getPotHeader({ name, email }) { + const now = new Date(); + const date = now.toISOString().replace('T', ' ').slice(0, 16); + const offset = -now.getTimezoneOffset(); + const sign = offset >= 0 ? '+' : '-'; + const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0'); + const minutes = String(Math.abs(offset) % 60).padStart(2, '0'); + const timezone = `${sign}${hours}${minutes}`; + + return [ + '# SOME DESCRIPTIVE TITLE.', + `# Copyright (C) ${now.getFullYear()} THE PACKAGE'S COPYRIGHT HOLDER`, + `# This file is distributed under the same license as the ${projectId} package.`, + `# ${name} <${email}>, ${now.getFullYear()}.`, + '#, fuzzy', + 'msgid ""', + 'msgstr ""', + `"Project-Id-Version: ${projectId}\\n"`, + `"Report-Msgid-Bugs-To: \\n"`, + `"POT-Creation-Date: ${date}${timezone}\\n"`, + `"PO-Revision-Date: ${date}${timezone}\\n"`, + `"Last-Translator: ${name} <${email}>\\n"`, + `"Language-Team: LANGUAGE \\n"`, + `"Language: \\n"`, + `"MIME-Version: 1.0\\n"`, + `"Content-Type: text/plain; charset=UTF-8\\n"`, + `"Content-Transfer-Encoding: 8bit\\n"`, + '', + ].join('\n'); +} + +function escapePoString(str) { + return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); +} + +function generateEntry(item) { + const locations = item.places.map(loc => `#: ${loc}`).join('\n'); + const msgid = escapePoString(item.key); + return [ + locations, + `msgid "${msgid}"`, + `msgstr ""`, + '' + ].join('\n'); +} + +async function generatePot() { + const gitUser = getGitUser(); + const raw = await fs.readFile(inputFile, 'utf8'); + const entries = JSON.parse(raw); + + const header = getPotHeader(gitUser); + const body = entries.map(generateEntry).join('\n'); + + await fs.writeFile(outputFile, `${header}\n${body}`, 'utf8'); + + console.log(`✅ POT-файл успешно создан: ${outputFile}`); +} + +generatePot().catch(console.error); diff --git a/fe-app-podkop/locales/calls.json b/fe-app-podkop/locales/calls.json new file mode 100644 index 0000000..9f4c445 --- /dev/null +++ b/fe-app-podkop/locales/calls.json @@ -0,0 +1,2570 @@ +[ + { + "call": "_('Successfully copied!')", + "key": "Successfully copied!", + "places": [ + "src/helpers/copyToClipboard.ts:10" + ] + }, + { + "call": "_('Failed to copy!')", + "key": "Failed to copy!", + "places": [ + "src/helpers/copyToClipboard.ts:12" + ] + }, + { + "call": "_('Operation timed out')", + "key": "Operation timed out", + "places": [ + "src/helpers/withTimeout.ts:7" + ] + }, + { + "call": "_('HTTP error')", + "key": "HTTP error", + "places": [ + "src/podkop/api.ts:27" + ] + }, + { + "call": "_('Unknown error')", + "key": "Unknown error", + "places": [ + "src/podkop/api.ts:40" + ] + }, + { + "call": "_('DNS server address cannot be empty')", + "key": "DNS server address cannot be empty", + "places": [ + "src/validators/validateDns.ts:7" + ] + }, + { + "call": "_('Valid')", + "key": "Valid", + "places": [ + "src/validators/validateDns.ts:11", + "src/validators/validateDns.ts:15", + "src/validators/validateDomain.ts:13", + "src/validators/validateDomain.ts:30", + "src/validators/validateIp.ts:8", + "src/validators/validateOutboundJson.ts:17", + "src/validators/validatePath.ts:16", + "src/validators/validateShadowsocksUrl.ts:95", + "src/validators/validateSocksUrl.ts:80", + "src/validators/validateSubnet.ts:38", + "src/validators/validateTrojanUrl.ts:59", + "src/validators/validateUrl.ts:16", + "src/validators/validateVlessUrl.ts:107" + ] + }, + { + "call": "_('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH')", + "key": "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH", + "places": [ + "src/validators/validateDns.ts:20" + ] + }, + { + "call": "_('Invalid domain address')", + "key": "Invalid domain address", + "places": [ + "src/validators/validateDomain.ts:18", + "src/validators/validateDomain.ts:27" + ] + }, + { + "call": "_('Invalid IP address')", + "key": "Invalid IP address", + "places": [ + "src/validators/validateIp.ts:11" + ] + }, + { + "call": "_('Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields')", + "key": "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields", + "places": [ + "src/validators/validateOutboundJson.ts:11", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:327" + ] + }, + { + "call": "_('Invalid JSON format')", + "key": "Invalid JSON format", + "places": [ + "src/validators/validateOutboundJson.ts:19" + ] + }, + { + "call": "_('Path cannot be empty')", + "key": "Path cannot be empty", + "places": [ + "src/validators/validatePath.ts:7" + ] + }, + { + "call": "_('Invalid path format. Path must start with \"/\" and contain valid characters')", + "key": "Invalid path format. Path must start with \"/\" and contain valid characters", + "places": [ + "src/validators/validatePath.ts:22", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:90" + ] + }, + { + "call": "_('URL must start with vless://, ss://, trojan://, or socks4/5://')", + "key": "URL must start with vless://, ss://, trojan://, or socks4/5://", + "places": [ + "src/validators/validateProxyUrl.ts:27" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: must start with ss://')", + "key": "Invalid Shadowsocks URL: must start with ss://", + "places": [ + "src/validators/validateShadowsocksUrl.ts:8" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: must not contain spaces')", + "key": "Invalid Shadowsocks URL: must not contain spaces", + "places": [ + "src/validators/validateShadowsocksUrl.ts:16" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: missing credentials')", + "key": "Invalid Shadowsocks URL: missing credentials", + "places": [ + "src/validators/validateShadowsocksUrl.ts:27" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: decoded credentials must contain method:password')", + "key": "Invalid Shadowsocks URL: decoded credentials must contain method:password", + "places": [ + "src/validators/validateShadowsocksUrl.ts:37" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: missing method and password separator \":\"')", + "key": "Invalid Shadowsocks URL: missing method and password separator \":\"", + "places": [ + "src/validators/validateShadowsocksUrl.ts:46", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:171" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: missing server address')", + "key": "Invalid Shadowsocks URL: missing server address", + "places": [ + "src/validators/validateShadowsocksUrl.ts:58" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: missing server')", + "key": "Invalid Shadowsocks URL: missing server", + "places": [ + "src/validators/validateShadowsocksUrl.ts:67" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: missing port')", + "key": "Invalid Shadowsocks URL: missing port", + "places": [ + "src/validators/validateShadowsocksUrl.ts:76" + ] + }, + { + "call": "_('Invalid port number. Must be between 1 and 65535')", + "key": "Invalid port number. Must be between 1 and 65535", + "places": [ + "src/validators/validateShadowsocksUrl.ts:85" + ] + }, + { + "call": "_('Invalid Shadowsocks URL: parsing failed')", + "key": "Invalid Shadowsocks URL: parsing failed", + "places": [ + "src/validators/validateShadowsocksUrl.ts:91" + ] + }, + { + "call": "_('Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://')", + "key": "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://", + "places": [ + "src/validators/validateSocksUrl.ts:10" + ] + }, + { + "call": "_('Invalid SOCKS URL: must not contain spaces')", + "key": "Invalid SOCKS URL: must not contain spaces", + "places": [ + "src/validators/validateSocksUrl.ts:19" + ] + }, + { + "call": "_('Invalid SOCKS URL: missing username')", + "key": "Invalid SOCKS URL: missing username", + "places": [ + "src/validators/validateSocksUrl.ts:34" + ] + }, + { + "call": "_('Invalid SOCKS URL: missing host and port')", + "key": "Invalid SOCKS URL: missing host and port", + "places": [ + "src/validators/validateSocksUrl.ts:42" + ] + }, + { + "call": "_('Invalid SOCKS URL: missing hostname or IP')", + "key": "Invalid SOCKS URL: missing hostname or IP", + "places": [ + "src/validators/validateSocksUrl.ts:51" + ] + }, + { + "call": "_('Invalid SOCKS URL: missing port')", + "key": "Invalid SOCKS URL: missing port", + "places": [ + "src/validators/validateSocksUrl.ts:56" + ] + }, + { + "call": "_('Invalid SOCKS URL: invalid port number')", + "key": "Invalid SOCKS URL: invalid port number", + "places": [ + "src/validators/validateSocksUrl.ts:63" + ] + }, + { + "call": "_('Invalid SOCKS URL: invalid host format')", + "key": "Invalid SOCKS URL: invalid host format", + "places": [ + "src/validators/validateSocksUrl.ts:73" + ] + }, + { + "call": "_('Invalid SOCKS URL: parsing failed')", + "key": "Invalid SOCKS URL: parsing failed", + "places": [ + "src/validators/validateSocksUrl.ts:77" + ] + }, + { + "call": "_('Invalid format. Use X.X.X.X or X.X.X.X/Y')", + "key": "Invalid format. Use X.X.X.X or X.X.X.X/Y", + "places": [ + "src/validators/validateSubnet.ts:11" + ] + }, + { + "call": "_('IP address 0.0.0.0 is not allowed')", + "key": "IP address 0.0.0.0 is not allowed", + "places": [ + "src/validators/validateSubnet.ts:18" + ] + }, + { + "call": "_('CIDR must be between 0 and 32')", + "key": "CIDR must be between 0 and 32", + "places": [ + "src/validators/validateSubnet.ts:33" + ] + }, + { + "call": "_('Invalid Trojan URL: must start with trojan://')", + "key": "Invalid Trojan URL: must start with trojan://", + "places": [ + "src/validators/validateTrojanUrl.ts:8" + ] + }, + { + "call": "_('Invalid Trojan URL: must not contain spaces')", + "key": "Invalid Trojan URL: must not contain spaces", + "places": [ + "src/validators/validateTrojanUrl.ts:15" + ] + }, + { + "call": "_('Invalid Trojan URL: parsing failed')", + "key": "Invalid Trojan URL: parsing failed", + "places": [ + "src/validators/validateTrojanUrl.ts:56" + ] + }, + { + "call": "_('URL must use one of the following protocols:')", + "key": "URL must use one of the following protocols:", + "places": [ + "src/validators/validateUrl.ts:13" + ] + }, + { + "call": "_('Invalid URL format')", + "key": "Invalid URL format", + "places": [ + "src/validators/validateUrl.ts:18" + ] + }, + { + "call": "_('Invalid VLESS URL: parsing failed')", + "key": "Invalid VLESS URL: parsing failed", + "places": [ + "src/validators/validateVlessUrl.ts:109" + ] + }, + { + "call": "_('Download')", + "key": "Download", + "places": [ + "src/partials/modal/renderModal.ts:15" + ] + }, + { + "call": "_('Copy')", + "key": "Copy", + "places": [ + "src/partials/modal/renderModal.ts:20" + ] + }, + { + "call": "_('Close')", + "key": "Close", + "places": [ + "src/partials/modal/renderModal.ts:26" + ] + }, + { + "call": "_('Fastest')", + "key": "Fastest", + "places": [ + "src/podkop/methods/custom/getDashboardSections.ts:117" + ] + }, + { + "call": "_('Traffic')", + "key": "Traffic", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:235" + ] + }, + { + "call": "_('Uplink')", + "key": "Uplink", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:237", + "src/podkop/tabs/dashboard/initController.ts:268" + ] + }, + { + "call": "_('Downlink')", + "key": "Downlink", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:238", + "src/podkop/tabs/dashboard/initController.ts:272" + ] + }, + { + "call": "_('Traffic Total')", + "key": "Traffic Total", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:265" + ] + }, + { + "call": "_('System info')", + "key": "System info", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:301" + ] + }, + { + "call": "_('Active Connections')", + "key": "Active Connections", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:304" + ] + }, + { + "call": "_('Memory Usage')", + "key": "Memory Usage", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:308" + ] + }, + { + "call": "_('Services info')", + "key": "Services info", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:337" + ] + }, + { + "call": "_('Podkop')", + "key": "Podkop", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:340" + ] + }, + { + "call": "_('✔ Enabled')", + "key": "✔ Enabled", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:342" + ] + }, + { + "call": "_('✘ Disabled')", + "key": "✘ Disabled", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:343" + ] + }, + { + "call": "_('Sing-box')", + "key": "Sing-box", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:351" + ] + }, + { + "call": "_('✔ Running')", + "key": "✔ Running", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:353" + ] + }, + { + "call": "_('✘ Stopped')", + "key": "✘ Stopped", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:354" + ] + }, + { + "call": "_('Not running')", + "key": "Not running", + "places": [ + "src/podkop/tabs/diagnostic/diagnostic.store.ts:55", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:63", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:71", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:79" + ] + }, + { + "call": "_('Queued')", + "key": "Queued", + "places": [ + "src/podkop/tabs/diagnostic/diagnostic.store.ts:95", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:103", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:111", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:119" + ] + }, + { + "call": "_('unknown')", + "key": "unknown", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:34", + "src/podkop/tabs/diagnostic/initController.ts:35", + "src/podkop/tabs/diagnostic/initController.ts:36", + "src/podkop/tabs/diagnostic/initController.ts:37", + "src/podkop/tabs/diagnostic/initController.ts:38", + "src/podkop/tabs/diagnostic/initController.ts:39", + "src/podkop/tabs/diagnostic/initController.ts:373" + ] + }, + { + "call": "_('Global check')", + "key": "Global check", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:218" + ] + }, + { + "call": "_('View logs')", + "key": "View logs", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:248", + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:107" + ] + }, + { + "call": "_('Show sing-box config')", + "key": "Show sing-box config", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:278", + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116" + ] + }, + { + "call": "_('Outdated')", + "key": "Outdated", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:394" + ] + }, + { + "call": "_('Latest')", + "key": "Latest", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:404" + ] + }, + { + "call": "_('Dashboard currently unavailable')", + "key": "Dashboard currently unavailable", + "places": [ + "src/podkop/tabs/dashboard/partials/renderSections.ts:19" + ] + }, + { + "call": "_('Test latency')", + "key": "Test latency", + "places": [ + "src/podkop/tabs/dashboard/partials/renderSections.ts:108" + ] + }, + { + "call": "_('Currently unavailable')", + "key": "Currently unavailable", + "places": [ + "src/podkop/tabs/dashboard/partials/renderWidget.ts:22" + ] + }, + { + "call": "_('DNS checks')", + "key": "DNS checks", + "places": [ + "src/podkop/tabs/diagnostic/checks/contstants.ts:14" + ] + }, + { + "call": "_('Sing-box checks')", + "key": "Sing-box checks", + "places": [ + "src/podkop/tabs/diagnostic/checks/contstants.ts:19" + ] + }, + { + "call": "_('Nftables checks')", + "key": "Nftables checks", + "places": [ + "src/podkop/tabs/diagnostic/checks/contstants.ts:24" + ] + }, + { + "call": "_('FakeIP checks')", + "key": "FakeIP checks", + "places": [ + "src/podkop/tabs/diagnostic/checks/contstants.ts:29" + ] + }, + { + "call": "_('Checking dns, please wait')", + "key": "Checking dns, please wait", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14" + ] + }, + { + "call": "_('Cannot receive DNS checks result')", + "key": "Cannot receive DNS checks result", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26" + ] + }, + { + "call": "_('DNS checks passed')", + "key": "DNS checks passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64" + ] + }, + { + "call": "_('Bootsrap DNS')", + "key": "Bootsrap DNS", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72" + ] + }, + { + "call": "_('Main DNS')", + "key": "Main DNS", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79" + ] + }, + { + "call": "_('DNS on router')", + "key": "DNS on router", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84" + ] + }, + { + "call": "_('DHCP has DNS server')", + "key": "DHCP has DNS server", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89" + ] + }, + { + "call": "_('Checking FakeIP, please wait')", + "key": "Checking FakeIP, please wait", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14" + ] + }, + { + "call": "_('FakeIP checks passed')", + "key": "FakeIP checks passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44" + ] + }, + { + "call": "_('FakeIP checks partially passed')", + "key": "FakeIP checks partially passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51" + ] + }, + { + "call": "_('FakeIP checks failed')", + "key": "FakeIP checks failed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57" + ] + }, + { + "call": "_('Router DNS is routed through sing-box')", + "key": "Router DNS is routed through sing-box", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73" + ] + }, + { + "call": "_('Router DNS is not routed through sing-box')", + "key": "Router DNS is not routed through sing-box", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74" + ] + }, + { + "call": "_('Browser is using FakeIP correctly')", + "key": "Browser is using FakeIP correctly", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80" + ] + }, + { + "call": "_('Browser is not using FakeIP')", + "key": "Browser is not using FakeIP", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81" + ] + }, + { + "call": "_('Proxy traffic is routed via FakeIP')", + "key": "Proxy traffic is routed via FakeIP", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88" + ] + }, + { + "call": "_('Proxy traffic is not routed via FakeIP')", + "key": "Proxy traffic is not routed via FakeIP", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89" + ] + }, + { + "call": "_('Checking nftables, please wait')", + "key": "Checking nftables, please wait", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12" + ] + }, + { + "call": "_('Cannot receive nftables checks result')", + "key": "Cannot receive nftables checks result", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27" + ] + }, + { + "call": "_('Nftables checks passed')", + "key": "Nftables checks passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74" + ] + }, + { + "call": "_('Nftables checks partially passed')", + "key": "Nftables checks partially passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75" + ] + }, + { + "call": "_('Table exist')", + "key": "Table exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80" + ] + }, + { + "call": "_('Rules mangle exist')", + "key": "Rules mangle exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85" + ] + }, + { + "call": "_('Rules mangle counters')", + "key": "Rules mangle counters", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90" + ] + }, + { + "call": "_('Rules mangle output exist')", + "key": "Rules mangle output exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95" + ] + }, + { + "call": "_('Rules mangle output counters')", + "key": "Rules mangle output counters", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100" + ] + }, + { + "call": "_('Rules proxy exist')", + "key": "Rules proxy exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105" + ] + }, + { + "call": "_('Rules proxy counters')", + "key": "Rules proxy counters", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110" + ] + }, + { + "call": "_('No other marking rules found')", + "key": "No other marking rules found", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116" + ] + }, + { + "call": "_('Additional marking rules found')", + "key": "Additional marking rules found", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117" + ] + }, + { + "call": "_('Checking sing-box, please wait')", + "key": "Checking sing-box, please wait", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12" + ] + }, + { + "call": "_('Cannot receive Sing-box checks result')", + "key": "Cannot receive Sing-box checks result", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24" + ] + }, + { + "call": "_('Sing-box checks passed')", + "key": "Sing-box checks passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66" + ] + }, + { + "call": "_('Sing-box installed')", + "key": "Sing-box installed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71" + ] + }, + { + "call": "_('Sing-box version >= 1.12.4')", + "key": "Sing-box version >= 1.12.4", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76" + ] + }, + { + "call": "_('Sing-box service exist')", + "key": "Sing-box service exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81" + ] + }, + { + "call": "_('Sing-box autostart disabled')", + "key": "Sing-box autostart disabled", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86" + ] + }, + { + "call": "_('Sing-box process running')", + "key": "Sing-box process running", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91" + ] + }, + { + "call": "_('Sing-box listening ports')", + "key": "Sing-box listening ports", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96" + ] + }, + { + "call": "_('Restart podkop')", + "key": "Restart podkop", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49" + ] + }, + { + "call": "_('Stop podkop')", + "key": "Stop podkop", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59" + ] + }, + { + "call": "_('Start podkop')", + "key": "Start podkop", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69" + ] + }, + { + "call": "_('Disable autostart')", + "key": "Disable autostart", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79" + ] + }, + { + "call": "_('Enable autostart')", + "key": "Enable autostart", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89" + ] + }, + { + "call": "_('Get global check')", + "key": "Get global check", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98" + ] + }, + { + "call": "_('Not implement yet')", + "key": "Not implement yet", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189" + ] + }, + { + "call": "_('Run Diagnostic')", + "key": "Run Diagnostic", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15" + ] + }, + { + "call": "_(\"Valid\")", + "key": "Valid", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:12", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:23", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:35", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:44", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:47", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:67", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:85", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:122", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:211", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:314", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:332", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:383", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:449" + ] + }, + { + "call": "_(\"Invalid IP address\")", + "key": "Invalid IP address", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:14" + ] + }, + { + "call": "_(\"Invalid domain address\")", + "key": "Invalid domain address", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:27", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:33" + ] + }, + { + "call": "_(\"DNS server address cannot be empty\")", + "key": "DNS server address cannot be empty", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:41" + ] + }, + { + "call": "_(\"Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH\")", + "key": "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:51" + ] + }, + { + "call": "_(\"URL must use one of the following protocols:\")", + "key": "URL must use one of the following protocols:", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:64" + ] + }, + { + "call": "_(\"Invalid URL format\")", + "key": "Invalid URL format", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:69" + ] + }, + { + "call": "_(\"Path cannot be empty\")", + "key": "Path cannot be empty", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:78" + ] + }, + { + "call": "_(\"Invalid format. Use X.X.X.X or X.X.X.X/Y\")", + "key": "Invalid format. Use X.X.X.X or X.X.X.X/Y", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:102" + ] + }, + { + "call": "_(\"IP address 0.0.0.0 is not allowed\")", + "key": "IP address 0.0.0.0 is not allowed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:107" + ] + }, + { + "call": "_(\"CIDR must be between 0 and 32\")", + "key": "CIDR must be between 0 and 32", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:118" + ] + }, + { + "call": "_(\"Invalid Shadowsocks URL: must start with ss://\")", + "key": "Invalid Shadowsocks URL: must start with ss://", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:139" + ] + }, + { + "call": "_(\"Invalid Shadowsocks URL: must not contain spaces\")", + "key": "Invalid Shadowsocks URL: must not contain spaces", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:146" + ] + }, + { + "call": "_(\"Invalid Shadowsocks URL: missing credentials\")", + "key": "Invalid Shadowsocks URL: missing credentials", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:154" + ] + }, + { + "call": "_(\"Invalid Shadowsocks URL: decoded credentials must contain method:password\")", + "key": "Invalid Shadowsocks URL: decoded credentials must contain method:password", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:162" + ] + }, + { + "call": "_(\"Invalid Shadowsocks URL: missing server address\")", + "key": "Invalid Shadowsocks URL: missing server address", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:181" + ] + }, + { + "call": "_(\"Invalid Shadowsocks URL: missing server\")", + "key": "Invalid Shadowsocks URL: missing server", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:188" + ] + }, + { + "call": "_(\"Invalid Shadowsocks URL: missing port\")", + "key": "Invalid Shadowsocks URL: missing port", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:195" + ] + }, + { + "call": "_(\"Invalid port number. Must be between 1 and 65535\")", + "key": "Invalid port number. Must be between 1 and 65535", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:202" + ] + }, + { + "call": "_(\"Invalid Shadowsocks URL: parsing failed\")", + "key": "Invalid Shadowsocks URL: parsing failed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:208" + ] + }, + { + "call": "_(\"Invalid VLESS URL: parsing failed\")", + "key": "Invalid VLESS URL: parsing failed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:316" + ] + }, + { + "call": "_(\"Invalid JSON format\")", + "key": "Invalid JSON format", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:334" + ] + }, + { + "call": "_(\"Invalid Trojan URL: must start with trojan://\")", + "key": "Invalid Trojan URL: must start with trojan://", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:344" + ] + }, + { + "call": "_(\"Invalid Trojan URL: must not contain spaces\")", + "key": "Invalid Trojan URL: must not contain spaces", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:350" + ] + }, + { + "call": "_(\"Invalid Trojan URL: parsing failed\")", + "key": "Invalid Trojan URL: parsing failed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:381" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://\")", + "key": "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:392" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: must not contain spaces\")", + "key": "Invalid SOCKS URL: must not contain spaces", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:400" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: missing username\")", + "key": "Invalid SOCKS URL: missing username", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:411" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: missing host and port\")", + "key": "Invalid SOCKS URL: missing host and port", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:418" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: missing hostname or IP\")", + "key": "Invalid SOCKS URL: missing hostname or IP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:425" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: missing port\")", + "key": "Invalid SOCKS URL: missing port", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:429" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: invalid port number\")", + "key": "Invalid SOCKS URL: invalid port number", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:435" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: invalid host format\")", + "key": "Invalid SOCKS URL: invalid host format", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:443" + ] + }, + { + "call": "_(\"Invalid SOCKS URL: parsing failed\")", + "key": "Invalid SOCKS URL: parsing failed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:447" + ] + }, + { + "call": "_(\"URL must start with vless://, ss://, trojan://, or socks4/5://\")", + "key": "URL must start with vless://, ss://, trojan://, or socks4/5://", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:468" + ] + }, + { + "call": "_(\"Fastest\")", + "key": "Fastest", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:692" + ] + }, + { + "call": "_(\"HTTP error\")", + "key": "HTTP error", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:864" + ] + }, + { + "call": "_(\"Unknown error\")", + "key": "Unknown error", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:875" + ] + }, + { + "call": "_(\"DNS checks\")", + "key": "DNS checks", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:985" + ] + }, + { + "call": "_(\"Sing-box checks\")", + "key": "Sing-box checks", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:990" + ] + }, + { + "call": "_(\"Nftables checks\")", + "key": "Nftables checks", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:995" + ] + }, + { + "call": "_(\"FakeIP checks\")", + "key": "FakeIP checks", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1000" + ] + }, + { + "call": "_(\"Not running\")", + "key": "Not running", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1048", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1056", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1064", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1072" + ] + }, + { + "call": "_(\"Queued\")", + "key": "Queued", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1084", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1092", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1100", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1108" + ] + }, + { + "call": "_(\"Dashboard currently unavailable\")", + "key": "Dashboard currently unavailable", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1578" + ] + }, + { + "call": "_(\"Test latency\")", + "key": "Test latency", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1654" + ] + }, + { + "call": "_(\"Currently unavailable\")", + "key": "Currently unavailable", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1683" + ] + }, + { + "call": "_(\"Traffic\")", + "key": "Traffic", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2024" + ] + }, + { + "call": "_(\"Uplink\")", + "key": "Uplink", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2026", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2051" + ] + }, + { + "call": "_(\"Downlink\")", + "key": "Downlink", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2027", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2055" + ] + }, + { + "call": "_(\"Traffic Total\")", + "key": "Traffic Total", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2048" + ] + }, + { + "call": "_(\"System info\")", + "key": "System info", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2078" + ] + }, + { + "call": "_(\"Active Connections\")", + "key": "Active Connections", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2081" + ] + }, + { + "call": "_(\"Memory Usage\")", + "key": "Memory Usage", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2085" + ] + }, + { + "call": "_(\"Services info\")", + "key": "Services info", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2108" + ] + }, + { + "call": "_(\"Podkop\")", + "key": "Podkop", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2111" + ] + }, + { + "call": "_(\"\\u2714 Enabled\")", + "key": "\\u2714 Enabled", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112" + ] + }, + { + "call": "_(\"\\u2718 Disabled\")", + "key": "\\u2718 Disabled", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112" + ] + }, + { + "call": "_(\"Sing-box\")", + "key": "Sing-box", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2118" + ] + }, + { + "call": "_(\"\\u2714 Running\")", + "key": "\\u2714 Running", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119" + ] + }, + { + "call": "_(\"\\u2718 Stopped\")", + "key": "\\u2718 Stopped", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119" + ] + }, + { + "call": "_(\"Checking dns, please wait\")", + "key": "Checking dns, please wait", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2365" + ] + }, + { + "call": "_(\"Cannot receive DNS checks result\")", + "key": "Cannot receive DNS checks result", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2375" + ] + }, + { + "call": "_(\"DNS checks passed\")", + "key": "DNS checks passed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2397" + ] + }, + { + "call": "_(\"Bootsrap DNS\")", + "key": "Bootsrap DNS", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2405" + ] + }, + { + "call": "_(\"Main DNS\")", + "key": "Main DNS", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2412" + ] + }, + { + "call": "_(\"DNS on router\")", + "key": "DNS on router", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2417" + ] + }, + { + "call": "_(\"DHCP has DNS server\")", + "key": "DHCP has DNS server", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2422" + ] + }, + { + "call": "_(\"Checking sing-box, please wait\")", + "key": "Checking sing-box, please wait", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2439" + ] + }, + { + "call": "_(\"Cannot receive Sing-box checks result\")", + "key": "Cannot receive Sing-box checks result", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2449" + ] + }, + { + "call": "_(\"Sing-box checks passed\")", + "key": "Sing-box checks passed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2471" + ] + }, + { + "call": "_(\"Sing-box installed\")", + "key": "Sing-box installed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2476" + ] + }, + { + "call": "_(\"Sing-box version >= 1.12.4\")", + "key": "Sing-box version >= 1.12.4", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2481" + ] + }, + { + "call": "_(\"Sing-box service exist\")", + "key": "Sing-box service exist", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2486" + ] + }, + { + "call": "_(\"Sing-box autostart disabled\")", + "key": "Sing-box autostart disabled", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2491" + ] + }, + { + "call": "_(\"Sing-box process running\")", + "key": "Sing-box process running", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2496" + ] + }, + { + "call": "_(\"Sing-box listening ports\")", + "key": "Sing-box listening ports", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2501" + ] + }, + { + "call": "_(\"Checking nftables, please wait\")", + "key": "Checking nftables, please wait", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2518" + ] + }, + { + "call": "_(\"Cannot receive nftables checks result\")", + "key": "Cannot receive nftables checks result", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2530" + ] + }, + { + "call": "_(\"Nftables checks passed\")", + "key": "Nftables checks passed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552" + ] + }, + { + "call": "_(\"Nftables checks partially passed\")", + "key": "Nftables checks partially passed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552" + ] + }, + { + "call": "_(\"Table exist\")", + "key": "Table exist", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2557" + ] + }, + { + "call": "_(\"Rules mangle exist\")", + "key": "Rules mangle exist", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2562" + ] + }, + { + "call": "_(\"Rules mangle counters\")", + "key": "Rules mangle counters", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2567" + ] + }, + { + "call": "_(\"Rules mangle output exist\")", + "key": "Rules mangle output exist", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2572" + ] + }, + { + "call": "_(\"Rules mangle output counters\")", + "key": "Rules mangle output counters", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2577" + ] + }, + { + "call": "_(\"Rules proxy exist\")", + "key": "Rules proxy exist", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2582" + ] + }, + { + "call": "_(\"Rules proxy counters\")", + "key": "Rules proxy counters", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2587" + ] + }, + { + "call": "_(\"No other marking rules found\")", + "key": "No other marking rules found", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592" + ] + }, + { + "call": "_(\"Additional marking rules found\")", + "key": "Additional marking rules found", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592" + ] + }, + { + "call": "_(\"Checking FakeIP, please wait\")", + "key": "Checking FakeIP, please wait", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2609" + ] + }, + { + "call": "_(\"FakeIP checks passed\")", + "key": "FakeIP checks passed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2627" + ] + }, + { + "call": "_(\"FakeIP checks partially passed\")", + "key": "FakeIP checks partially passed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2633" + ] + }, + { + "call": "_(\"FakeIP checks failed\")", + "key": "FakeIP checks failed", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2638" + ] + }, + { + "call": "_(\"Router DNS is routed through sing-box\")", + "key": "Router DNS is routed through sing-box", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651" + ] + }, + { + "call": "_(\"Router DNS is not routed through sing-box\")", + "key": "Router DNS is not routed through sing-box", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651" + ] + }, + { + "call": "_(\"Browser is using FakeIP correctly\")", + "key": "Browser is using FakeIP correctly", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656" + ] + }, + { + "call": "_(\"Browser is not using FakeIP\")", + "key": "Browser is not using FakeIP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656" + ] + }, + { + "call": "_(\"Proxy traffic is routed via FakeIP\")", + "key": "Proxy traffic is routed via FakeIP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662" + ] + }, + { + "call": "_(\"Proxy traffic is not routed via FakeIP\")", + "key": "Proxy traffic is not routed via FakeIP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662" + ] + }, + { + "call": "_(\"Successfully copied!\")", + "key": "Successfully copied!", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3288" + ] + }, + { + "call": "_(\"Failed to copy!\")", + "key": "Failed to copy!", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3290" + ] + }, + { + "call": "_(\"Download\")", + "key": "Download", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3306" + ] + }, + { + "call": "_(\"Copy\")", + "key": "Copy", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3311" + ] + }, + { + "call": "_(\"Close\")", + "key": "Close", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3318" + ] + }, + { + "call": "_(\"Restart podkop\")", + "key": "Restart podkop", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3350" + ] + }, + { + "call": "_(\"Stop podkop\")", + "key": "Stop podkop", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3360" + ] + }, + { + "call": "_(\"Start podkop\")", + "key": "Start podkop", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3370" + ] + }, + { + "call": "_(\"Disable autostart\")", + "key": "Disable autostart", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3380" + ] + }, + { + "call": "_(\"Enable autostart\")", + "key": "Enable autostart", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3390" + ] + }, + { + "call": "_(\"Get global check\")", + "key": "Get global check", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3399" + ] + }, + { + "call": "_(\"View logs\")", + "key": "View logs", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3408", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3840" + ] + }, + { + "call": "_(\"Show sing-box config\")", + "key": "Show sing-box config", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3417", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3867" + ] + }, + { + "call": "_(\"Not implement yet\")", + "key": "Not implement yet", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3577" + ] + }, + { + "call": "_(\"Run Diagnostic\")", + "key": "Run Diagnostic", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3587" + ] + }, + { + "call": "_(\"unknown\")", + "key": "unknown", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3651", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3652", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3653", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3654", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3655", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3656", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3950" + ] + }, + { + "call": "_(\"Global check\")", + "key": "Global check", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3813" + ] + }, + { + "call": "_(\"Outdated\")", + "key": "Outdated", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3969" + ] + }, + { + "call": "_(\"Latest\")", + "key": "Latest", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3978" + ] + }, + { + "call": "_(\"Operation timed out\")", + "key": "Operation timed out", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:4389" + ] + }, + { + "call": "_(\"Podkop Settings\")", + "key": "Podkop Settings", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26" + ] + }, + { + "call": "_(\"Configuration for Podkop service\")", + "key": "Configuration for Podkop service", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27" + ] + }, + { + "call": "_(\"Sections\")", + "key": "Sections", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36" + ] + }, + { + "call": "_(\"Settings\")", + "key": "Settings", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49" + ] + }, + { + "call": "_(\"Diagnostics\")", + "key": "Diagnostics", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65" + ] + }, + { + "call": "_(\"Dashboard\")", + "key": "Dashboard", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80" + ] + }, + { + "call": "_(\"Connection Type\")", + "key": "Connection Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12" + ] + }, + { + "call": "_(\"Select between VPN and Proxy connection methods for traffic routing\")", + "key": "Select between VPN and Proxy connection methods for traffic routing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13" + ] + }, + { + "call": "_(\"Configuration Type\")", + "key": "Configuration Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22" + ] + }, + { + "call": "_(\"Select how to configure the proxy\")", + "key": "Select how to configure the proxy", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23" + ] + }, + { + "call": "_(\"Connection URL\")", + "key": "Connection URL", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25" + ] + }, + { + "call": "_(\"Outbound Config\")", + "key": "Outbound Config", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26" + ] + }, + { + "call": "_(\"URLTest\")", + "key": "URLTest", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27" + ] + }, + { + "call": "_(\"Proxy Configuration URL\")", + "key": "Proxy Configuration URL", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34" + ] + }, + { + "call": "_(\"Outbound Configuration\")", + "key": "Outbound Configuration", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64" + ] + }, + { + "call": "_(\"Enter complete outbound configuration in JSON format\")", + "key": "Enter complete outbound configuration in JSON format", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65" + ] + }, + { + "call": "_(\"URLTest Proxy Links\")", + "key": "URLTest Proxy Links", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87" + ] + }, + { + "call": "_(\"UDP over TCP\")", + "key": "UDP over TCP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110" + ] + }, + { + "call": "_(\"Applicable for SOCKS and Shadowsocks proxy\")", + "key": "Applicable for SOCKS and Shadowsocks proxy", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111" + ] + }, + { + "call": "_(\"Network Interface\")", + "key": "Network Interface", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120" + ] + }, + { + "call": "_(\"Select network interface for VPN connection\")", + "key": "Select network interface for VPN connection", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121" + ] + }, + { + "call": "_(\"Domain Resolver\")", + "key": "Domain Resolver", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166" + ] + }, + { + "call": "_(\"Enable built-in DNS resolver for domains handled by this section\")", + "key": "Enable built-in DNS resolver for domains handled by this section", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167" + ] + }, + { + "call": "_(\"DNS Protocol Type\")", + "key": "DNS Protocol Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12" + ] + }, + { + "call": "_(\"Select the DNS protocol type for the domain resolver\")", + "key": "Select the DNS protocol type for the domain resolver", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177" + ] + }, + { + "call": "_(\"DNS over HTTPS (DoH)\")", + "key": "DNS over HTTPS (DoH)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15" + ] + }, + { + "call": "_(\"DNS over TLS (DoT)\")", + "key": "DNS over TLS (DoT)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16" + ] + }, + { + "call": "_(\"UDP (Unprotected DNS)\")", + "key": "UDP (Unprotected DNS)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17" + ] + }, + { + "call": "_(\"DNS Server\")", + "key": "DNS Server", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24" + ] + }, + { + "call": "_(\"Select or enter DNS server address\")", + "key": "Select or enter DNS server address", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25" + ] + }, + { + "call": "_(label)", + "key": "", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:193", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:217", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:28", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:51", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:254" + ] + }, + { + "call": "_(\"Community Lists\")", + "key": "Community Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211" + ] + }, + { + "call": "_(\"Select a predefined list for routing\")", + "key": "Select a predefined list for routing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212" + ] + }, + { + "call": "_(\"Regional options cannot be used together\")", + "key": "Regional options cannot be used together", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245" + ] + }, + { + "call": "_(\"Warning: %s cannot be used together with %s. Previous selections have been removed.\")", + "key": "Warning: %s cannot be used together with %s. Previous selections have been removed.", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247" + ] + }, + { + "call": "_(\"Russia inside restrictions\")", + "key": "Russia inside restrictions", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264" + ] + }, + { + "call": "_(\"Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.\")", + "key": "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266" + ] + }, + { + "call": "_(\"User Domain List Type\")", + "key": "User Domain List Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299" + ] + }, + { + "call": "_(\"Select the list type for adding custom domains\")", + "key": "Select the list type for adding custom domains", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300" + ] + }, + { + "call": "_(\"Disabled\")", + "key": "Disabled", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382" + ] + }, + { + "call": "_(\"Dynamic List\")", + "key": "Dynamic List", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383" + ] + }, + { + "call": "_(\"Text List\")", + "key": "Text List", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304" + ] + }, + { + "call": "_(\"User Domains\")", + "key": "User Domains", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311" + ] + }, + { + "call": "_(\"Enter domain names without protocols, e.g. example.com or sub.example.com\")", + "key": "Enter domain names without protocols, e.g. example.com or sub.example.com", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312" + ] + }, + { + "call": "_(\"User Domains List\")", + "key": "User Domains List", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337" + ] + }, + { + "call": "_(\"Enter domain names separated by commas, spaces, or newlines. You can add comments using //\")", + "key": "Enter domain names separated by commas, spaces, or newlines. You can add comments using //", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338" + ] + }, + { + "call": "_(\"At least one valid domain must be specified. Comments-only content is not allowed.\")", + "key": "At least one valid domain must be specified. Comments-only content is not allowed.", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356" + ] + }, + { + "call": "_(`${validation.value}: ${validation.message}`)", + "key": "${validation.value}: ${validation.message}", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:447" + ] + }, + { + "call": "_(\"Validation errors:\")", + "key": "Validation errors:", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449" + ] + }, + { + "call": "_(\"User Subnet List Type\")", + "key": "User Subnet List Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379" + ] + }, + { + "call": "_(\"Select the list type for adding custom subnets\")", + "key": "Select the list type for adding custom subnets", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380" + ] + }, + { + "call": "_(\"Text List (comma/space/newline separated)\")", + "key": "Text List (comma/space/newline separated)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384" + ] + }, + { + "call": "_(\"User Subnets\")", + "key": "User Subnets", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391" + ] + }, + { + "call": "_(\"Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses\")", + "key": "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392" + ] + }, + { + "call": "_(\"User Subnets List\")", + "key": "User Subnets List", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417" + ] + }, + { + "call": "_(\"Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //\")", + "key": "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:418" + ] + }, + { + "call": "_(\"At least one valid subnet or IP must be specified. Comments-only content is not allowed.\")", + "key": "At least one valid subnet or IP must be specified. Comments-only content is not allowed.", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437" + ] + }, + { + "call": "_(\"Local Domain Lists\")", + "key": "Local Domain Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458" + ] + }, + { + "call": "_(\"Specify the path to the list file located on the router filesystem\")", + "key": "Specify the path to the list file located on the router filesystem", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482" + ] + }, + { + "call": "_(\"Local Subnet Lists\")", + "key": "Local Subnet Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481" + ] + }, + { + "call": "_(\"Remote Domain Lists\")", + "key": "Remote Domain Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504" + ] + }, + { + "call": "_(\"Specify remote URLs to download and use domain lists\")", + "key": "Specify remote URLs to download and use domain lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505" + ] + }, + { + "call": "_(\"Remote Subnet Lists\")", + "key": "Remote Subnet Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527" + ] + }, + { + "call": "_(\"Specify remote URLs to download and use subnet lists\")", + "key": "Specify remote URLs to download and use subnet lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528" + ] + }, + { + "call": "_(\"Fully Routed IPs\")", + "key": "Fully Routed IPs", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550" + ] + }, + { + "call": "_(\"Specify local IP addresses or subnets whose traffic will always be routed through the configured route\")", + "key": "Specify local IP addresses or subnets whose traffic will always be routed through the configured route", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551" + ] + }, + { + "call": "_(\"Enable Mixed Proxy\")", + "key": "Enable Mixed Proxy", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575" + ] + }, + { + "call": "_(\"Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies\")", + "key": "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576" + ] + }, + { + "call": "_(\"Mixed Proxy Port\")", + "key": "Mixed Proxy Port", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586" + ] + }, + { + "call": "_(\"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\")", + "key": "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", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:587" + ] + }, + { + "call": "_(\"Select DNS protocol to use\")", + "key": "Select DNS protocol to use", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13" + ] + }, + { + "call": "_(\"Bootstrap DNS server\")", + "key": "Bootstrap DNS server", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45" + ] + }, + { + "call": "_(\"The DNS server used to look up the IP address of an upstream DNS server\")", + "key": "The DNS server used to look up the IP address of an upstream DNS server", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46" + ] + }, + { + "call": "_(\"DNS Rewrite TTL\")", + "key": "DNS Rewrite TTL", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68" + ] + }, + { + "call": "_(\"Time in seconds for DNS record caching (default: 60)\")", + "key": "Time in seconds for DNS record caching (default: 60)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69" + ] + }, + { + "call": "_(\"TTL value cannot be empty\")", + "key": "TTL value cannot be empty", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75" + ] + }, + { + "call": "_(\"TTL must be a positive number\")", + "key": "TTL must be a positive number", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80" + ] + }, + { + "call": "_(\"Source Network Interface\")", + "key": "Source Network Interface", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89" + ] + }, + { + "call": "_(\"Select the network interface from which the traffic will originate\")", + "key": "Select the network interface from which the traffic will originate", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90" + ] + }, + { + "call": "_(\"Enable Output Network Interface\")", + "key": "Enable Output Network Interface", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126" + ] + }, + { + "call": "_(\"You can select Output Network Interface, by default autodetect\")", + "key": "You can select Output Network Interface, by default autodetect", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:127" + ] + }, + { + "call": "_(\"Output Network Interface\")", + "key": "Output Network Interface", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135" + ] + }, + { + "call": "_(\"Select the network interface to which the traffic will originate\")", + "key": "Select the network interface to which the traffic will originate", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136" + ] + }, + { + "call": "_(\"Interface Monitoring\")", + "key": "Interface Monitoring", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182" + ] + }, + { + "call": "_(\"Interface monitoring for Bad WAN\")", + "key": "Interface monitoring for Bad WAN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183" + ] + }, + { + "call": "_(\"Monitored Interfaces\")", + "key": "Monitored Interfaces", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191" + ] + }, + { + "call": "_(\"Select the WAN interfaces to be monitored\")", + "key": "Select the WAN interfaces to be monitored", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192" + ] + }, + { + "call": "_(\"Interface Monitoring Delay\")", + "key": "Interface Monitoring Delay", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214" + ] + }, + { + "call": "_(\"Delay in milliseconds before reloading podkop after interface UP\")", + "key": "Delay in milliseconds before reloading podkop after interface UP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215" + ] + }, + { + "call": "_(\"Delay value cannot be empty\")", + "key": "Delay value cannot be empty", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222" + ] + }, + { + "call": "_(\"Enable YACD\")", + "key": "Enable YACD", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230" + ] + }, + { + "call": "_(\"Disable QUIC\")", + "key": "Disable QUIC", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239" + ] + }, + { + "call": "_(\"Disable the QUIC protocol to improve compatibility or fix issues with video streaming\")", + "key": "Disable the QUIC protocol to improve compatibility or fix issues with video streaming", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240" + ] + }, + { + "call": "_(\"List Update Frequency\")", + "key": "List Update Frequency", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250" + ] + }, + { + "call": "_(\"Select how often the domain or subnet lists are updated automatically\")", + "key": "Select how often the domain or subnet lists are updated automatically", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251" + ] + }, + { + "call": "_(\"Download Lists via Proxy/VPN\")", + "key": "Download Lists via Proxy/VPN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262" + ] + }, + { + "call": "_(\"Downloading all lists via main Proxy/VPN\")", + "key": "Downloading all lists via main Proxy/VPN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263" + ] + }, + { + "call": "_(\"Download Lists via specific proxy section\")", + "key": "Download Lists via specific proxy section", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271" + ] + }, + { + "call": "_(\"Downloading all lists via specific Proxy/VPN\")", + "key": "Downloading all lists via specific Proxy/VPN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272" + ] + }, + { + "call": "_(\"Dont Touch My DHCP!\")", + "key": "Dont Touch My DHCP!", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300" + ] + }, + { + "call": "_(\"Podkop will not modify your DHCP configuration\")", + "key": "Podkop will not modify your DHCP configuration", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301" + ] + }, + { + "call": "_(\"Config File Path\")", + "key": "Config File Path", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309" + ] + }, + { + "call": "_(\"Select path for sing-box config file. Change this ONLY if you know what you are doing\")", + "key": "Select path for sing-box config file. Change this ONLY if you know what you are doing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310" + ] + }, + { + "call": "_(\"Cache File Path\")", + "key": "Cache File Path", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322" + ] + }, + { + "call": "_(\"Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing\")", + "key": "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323" + ] + }, + { + "call": "_(\"Cache file path cannot be empty\")", + "key": "Cache file path cannot be empty", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336" + ] + }, + { + "call": "_(\"Path must be absolute (start with /)\")", + "key": "Path must be absolute (start with /)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340" + ] + }, + { + "call": "_(\"Path must end with cache.db\")", + "key": "Path must end with cache.db", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344" + ] + }, + { + "call": "_(\"Path must contain at least one directory (like /tmp/cache.db)\")", + "key": "Path must contain at least one directory (like /tmp/cache.db)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349" + ] + }, + { + "call": "_(\"Exclude NTP\")", + "key": "Exclude NTP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358" + ] + }, + { + "call": "_(\"Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN\")", + "key": "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359" + ] + }, + { + "call": "_(\"Routing Excluded IPs\")", + "key": "Routing Excluded IPs", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369" + ] + }, + { + "call": "_(\"Specify a local IP address to be excluded from routing\")", + "key": "Specify a local IP address to be excluded from routing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370" + ] + } +] \ No newline at end of file diff --git a/fe-app-podkop/locales/podkop.pot b/fe-app-podkop/locales/podkop.pot new file mode 100644 index 0000000..64f11d4 --- /dev/null +++ b/fe-app-podkop/locales/podkop.pot @@ -0,0 +1,1517 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PODKOP package. +# divocat , 2025. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PODKOP\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-21 18:31+0300\n" +"PO-Revision-Date: 2025-10-21 18:31+0300\n" +"Last-Translator: divocat \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/helpers/copyToClipboard.ts:10 +msgid "Successfully copied!" +msgstr "" + +#: src/helpers/copyToClipboard.ts:12 +msgid "Failed to copy!" +msgstr "" + +#: src/helpers/withTimeout.ts:7 +msgid "Operation timed out" +msgstr "" + +#: src/podkop/api.ts:27 +msgid "HTTP error" +msgstr "" + +#: src/podkop/api.ts:40 +msgid "Unknown error" +msgstr "" + +#: src/validators/validateDns.ts:7 +msgid "DNS server address cannot be empty" +msgstr "" + +#: src/validators/validateDns.ts:11 +#: src/validators/validateDns.ts:15 +#: src/validators/validateDomain.ts:13 +#: src/validators/validateDomain.ts:30 +#: src/validators/validateIp.ts:8 +#: src/validators/validateOutboundJson.ts:17 +#: src/validators/validatePath.ts:16 +#: src/validators/validateShadowsocksUrl.ts:95 +#: src/validators/validateSocksUrl.ts:80 +#: src/validators/validateSubnet.ts:38 +#: src/validators/validateTrojanUrl.ts:59 +#: src/validators/validateUrl.ts:16 +#: src/validators/validateVlessUrl.ts:107 +msgid "Valid" +msgstr "" + +#: src/validators/validateDns.ts:20 +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "" + +#: src/validators/validateDomain.ts:18 +#: src/validators/validateDomain.ts:27 +msgid "Invalid domain address" +msgstr "" + +#: src/validators/validateIp.ts:11 +msgid "Invalid IP address" +msgstr "" + +#: src/validators/validateOutboundJson.ts:11 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:327 +msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" +msgstr "" + +#: src/validators/validateOutboundJson.ts:19 +msgid "Invalid JSON format" +msgstr "" + +#: src/validators/validatePath.ts:7 +msgid "Path cannot be empty" +msgstr "" + +#: src/validators/validatePath.ts:22 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:90 +msgid "Invalid path format. Path must start with \"/\" and contain valid characters" +msgstr "" + +#: src/validators/validateProxyUrl.ts:27 +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:8 +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:16 +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:27 +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:37 +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:46 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:171 +msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:58 +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:67 +msgid "Invalid Shadowsocks URL: missing server" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:76 +msgid "Invalid Shadowsocks URL: missing port" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:85 +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:91 +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "" + +#: src/validators/validateSocksUrl.ts:10 +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +#: src/validators/validateSocksUrl.ts:19 +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +#: src/validators/validateSocksUrl.ts:34 +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +#: src/validators/validateSocksUrl.ts:42 +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +#: src/validators/validateSocksUrl.ts:51 +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +#: src/validators/validateSocksUrl.ts:56 +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +#: src/validators/validateSocksUrl.ts:63 +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +#: src/validators/validateSocksUrl.ts:73 +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +#: src/validators/validateSocksUrl.ts:77 +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +#: src/validators/validateSubnet.ts:11 +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "" + +#: src/validators/validateSubnet.ts:18 +msgid "IP address 0.0.0.0 is not allowed" +msgstr "" + +#: src/validators/validateSubnet.ts:33 +msgid "CIDR must be between 0 and 32" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:8 +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:15 +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:56 +msgid "Invalid Trojan URL: parsing failed" +msgstr "" + +#: src/validators/validateUrl.ts:13 +msgid "URL must use one of the following protocols:" +msgstr "" + +#: src/validators/validateUrl.ts:18 +msgid "Invalid URL format" +msgstr "" + +#: src/validators/validateVlessUrl.ts:109 +msgid "Invalid VLESS URL: parsing failed" +msgstr "" + +#: src/partials/modal/renderModal.ts:15 +msgid "Download" +msgstr "" + +#: src/partials/modal/renderModal.ts:20 +msgid "Copy" +msgstr "" + +#: src/partials/modal/renderModal.ts:26 +msgid "Close" +msgstr "" + +#: src/podkop/methods/custom/getDashboardSections.ts:117 +msgid "Fastest" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:235 +msgid "Traffic" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:237 +#: src/podkop/tabs/dashboard/initController.ts:268 +msgid "Uplink" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:238 +#: src/podkop/tabs/dashboard/initController.ts:272 +msgid "Downlink" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:265 +msgid "Traffic Total" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:301 +msgid "System info" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:304 +msgid "Active Connections" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:308 +msgid "Memory Usage" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:337 +msgid "Services info" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:340 +msgid "Podkop" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:342 +msgid "✔ Enabled" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:343 +msgid "✘ Disabled" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:351 +msgid "Sing-box" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:353 +msgid "✔ Running" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:354 +msgid "✘ Stopped" +msgstr "" + +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:55 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:63 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:71 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:79 +msgid "Not running" +msgstr "" + +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:95 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:103 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:111 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:119 +msgid "Queued" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:34 +#: src/podkop/tabs/diagnostic/initController.ts:35 +#: src/podkop/tabs/diagnostic/initController.ts:36 +#: src/podkop/tabs/diagnostic/initController.ts:37 +#: src/podkop/tabs/diagnostic/initController.ts:38 +#: src/podkop/tabs/diagnostic/initController.ts:39 +#: src/podkop/tabs/diagnostic/initController.ts:373 +msgid "unknown" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:218 +msgid "Global check" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:248 +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:107 +msgid "View logs" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:278 +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116 +msgid "Show sing-box config" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:394 +msgid "Outdated" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:404 +msgid "Latest" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderSections.ts:19 +msgid "Dashboard currently unavailable" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderSections.ts:108 +msgid "Test latency" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderWidget.ts:22 +msgid "Currently unavailable" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:14 +msgid "DNS checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:19 +msgid "Sing-box checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:24 +msgid "Nftables checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:29 +msgid "FakeIP checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14 +msgid "Checking dns, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26 +msgid "Cannot receive DNS checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64 +msgid "DNS checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72 +msgid "Bootsrap DNS" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79 +msgid "Main DNS" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84 +msgid "DNS on router" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89 +msgid "DHCP has DNS server" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14 +msgid "Checking FakeIP, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44 +msgid "FakeIP checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51 +msgid "FakeIP checks partially passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57 +msgid "FakeIP checks failed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73 +msgid "Router DNS is routed through sing-box" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74 +msgid "Router DNS is not routed through sing-box" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80 +msgid "Browser is using FakeIP correctly" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81 +msgid "Browser is not using FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88 +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89 +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12 +msgid "Checking nftables, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27 +msgid "Cannot receive nftables checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74 +msgid "Nftables checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75 +msgid "Nftables checks partially passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80 +msgid "Table exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85 +msgid "Rules mangle exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90 +msgid "Rules mangle counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95 +msgid "Rules mangle output exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100 +msgid "Rules mangle output counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105 +msgid "Rules proxy exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110 +msgid "Rules proxy counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116 +msgid "No other marking rules found" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117 +msgid "Additional marking rules found" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12 +msgid "Checking sing-box, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24 +msgid "Cannot receive Sing-box checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66 +msgid "Sing-box checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71 +msgid "Sing-box installed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76 +msgid "Sing-box version >= 1.12.4" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81 +msgid "Sing-box service exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86 +msgid "Sing-box autostart disabled" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91 +msgid "Sing-box process running" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96 +msgid "Sing-box listening ports" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49 +msgid "Restart podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59 +msgid "Stop podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69 +msgid "Start podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79 +msgid "Disable autostart" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89 +msgid "Enable autostart" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98 +msgid "Get global check" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189 +msgid "Not implement yet" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15 +msgid "Run Diagnostic" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:12 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:23 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:35 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:44 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:47 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:67 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:85 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:122 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:211 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:314 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:332 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:383 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:449 +msgid "Valid" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:14 +msgid "Invalid IP address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:27 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:33 +msgid "Invalid domain address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:41 +msgid "DNS server address cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:51 +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:64 +msgid "URL must use one of the following protocols:" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:69 +msgid "Invalid URL format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:78 +msgid "Path cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:102 +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:107 +msgid "IP address 0.0.0.0 is not allowed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:118 +msgid "CIDR must be between 0 and 32" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:139 +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:146 +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:154 +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:162 +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:181 +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:188 +msgid "Invalid Shadowsocks URL: missing server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:195 +msgid "Invalid Shadowsocks URL: missing port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:202 +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:208 +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:316 +msgid "Invalid VLESS URL: parsing failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:334 +msgid "Invalid JSON format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:344 +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:350 +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:381 +msgid "Invalid Trojan URL: parsing failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:392 +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:400 +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:411 +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:418 +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:425 +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:429 +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:435 +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:443 +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:447 +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:468 +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:692 +msgid "Fastest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:864 +msgid "HTTP error" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:875 +msgid "Unknown error" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:985 +msgid "DNS checks" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:990 +msgid "Sing-box checks" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:995 +msgid "Nftables checks" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1000 +msgid "FakeIP checks" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1048 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1056 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1064 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1072 +msgid "Not running" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1084 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1092 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1100 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1108 +msgid "Queued" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1578 +msgid "Dashboard currently unavailable" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1654 +msgid "Test latency" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1683 +msgid "Currently unavailable" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2024 +msgid "Traffic" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2026 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2051 +msgid "Uplink" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2027 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2055 +msgid "Downlink" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2048 +msgid "Traffic Total" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2078 +msgid "System info" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2081 +msgid "Active Connections" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2085 +msgid "Memory Usage" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2108 +msgid "Services info" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2111 +msgid "Podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112 +msgid "\\u2714 Enabled" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112 +msgid "\\u2718 Disabled" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2118 +msgid "Sing-box" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119 +msgid "\\u2714 Running" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119 +msgid "\\u2718 Stopped" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2365 +msgid "Checking dns, please wait" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2375 +msgid "Cannot receive DNS checks result" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2397 +msgid "DNS checks passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2405 +msgid "Bootsrap DNS" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2412 +msgid "Main DNS" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2417 +msgid "DNS on router" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2422 +msgid "DHCP has DNS server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2439 +msgid "Checking sing-box, please wait" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2449 +msgid "Cannot receive Sing-box checks result" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2471 +msgid "Sing-box checks passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2476 +msgid "Sing-box installed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2481 +msgid "Sing-box version >= 1.12.4" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2486 +msgid "Sing-box service exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2491 +msgid "Sing-box autostart disabled" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2496 +msgid "Sing-box process running" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2501 +msgid "Sing-box listening ports" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2518 +msgid "Checking nftables, please wait" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2530 +msgid "Cannot receive nftables checks result" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552 +msgid "Nftables checks passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552 +msgid "Nftables checks partially passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2557 +msgid "Table exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2562 +msgid "Rules mangle exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2567 +msgid "Rules mangle counters" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2572 +msgid "Rules mangle output exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2577 +msgid "Rules mangle output counters" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2582 +msgid "Rules proxy exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2587 +msgid "Rules proxy counters" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592 +msgid "No other marking rules found" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592 +msgid "Additional marking rules found" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2609 +msgid "Checking FakeIP, please wait" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2627 +msgid "FakeIP checks passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2633 +msgid "FakeIP checks partially passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2638 +msgid "FakeIP checks failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651 +msgid "Router DNS is routed through sing-box" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651 +msgid "Router DNS is not routed through sing-box" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656 +msgid "Browser is using FakeIP correctly" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656 +msgid "Browser is not using FakeIP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662 +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662 +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3288 +msgid "Successfully copied!" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3290 +msgid "Failed to copy!" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3306 +msgid "Download" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3311 +msgid "Copy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3318 +msgid "Close" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3350 +msgid "Restart podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3360 +msgid "Stop podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3370 +msgid "Start podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3380 +msgid "Disable autostart" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3390 +msgid "Enable autostart" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3399 +msgid "Get global check" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3408 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3840 +msgid "View logs" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3417 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3867 +msgid "Show sing-box config" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3577 +msgid "Not implement yet" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3587 +msgid "Run Diagnostic" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3651 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3652 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3653 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3654 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3655 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3656 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3950 +msgid "unknown" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3813 +msgid "Global check" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3969 +msgid "Outdated" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3978 +msgid "Latest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:4389 +msgid "Operation timed out" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26 +msgid "Podkop Settings" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27 +msgid "Configuration for Podkop service" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36 +msgid "Sections" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49 +msgid "Settings" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65 +msgid "Diagnostics" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80 +msgid "Dashboard" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12 +msgid "Connection Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13 +msgid "Select between VPN and Proxy connection methods for traffic routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22 +msgid "Configuration Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23 +msgid "Select how to configure the proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25 +msgid "Connection URL" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26 +msgid "Outbound Config" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27 +msgid "URLTest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34 +msgid "Proxy Configuration URL" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64 +msgid "Outbound Configuration" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65 +msgid "Enter complete outbound configuration in JSON format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87 +msgid "URLTest Proxy Links" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110 +msgid "UDP over TCP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111 +msgid "Applicable for SOCKS and Shadowsocks proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120 +msgid "Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121 +msgid "Select network interface for VPN connection" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166 +msgid "Domain Resolver" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167 +msgid "Enable built-in DNS resolver for domains handled by this section" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12 +msgid "DNS Protocol Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177 +msgid "Select the DNS protocol type for the domain resolver" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15 +msgid "DNS over HTTPS (DoH)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16 +msgid "DNS over TLS (DoT)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17 +msgid "UDP (Unprotected DNS)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24 +msgid "DNS Server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25 +msgid "Select or enter DNS server address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:193 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:217 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:28 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:51 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:254 +msgid "" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211 +msgid "Community Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212 +msgid "Select a predefined list for routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245 +msgid "Regional options cannot be used together" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247 +msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264 +msgid "Russia inside restrictions" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266 +msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299 +msgid "User Domain List Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300 +msgid "Select the list type for adding custom domains" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382 +msgid "Disabled" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383 +msgid "Dynamic List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304 +msgid "Text List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311 +msgid "User Domains" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312 +msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337 +msgid "User Domains List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338 +msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356 +msgid "At least one valid domain must be specified. Comments-only content is not allowed." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:447 +msgid "${validation.value}: ${validation.message}" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449 +msgid "Validation errors:" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379 +msgid "User Subnet List Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380 +msgid "Select the list type for adding custom subnets" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384 +msgid "Text List (comma/space/newline separated)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391 +msgid "User Subnets" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392 +msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417 +msgid "User Subnets List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:418 +msgid "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437 +msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458 +msgid "Local Domain Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482 +msgid "Specify the path to the list file located on the router filesystem" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481 +msgid "Local Subnet Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504 +msgid "Remote Domain Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505 +msgid "Specify remote URLs to download and use domain lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527 +msgid "Remote Subnet Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528 +msgid "Specify remote URLs to download and use subnet lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550 +msgid "Fully Routed IPs" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551 +msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575 +msgid "Enable Mixed Proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576 +msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586 +msgid "Mixed Proxy Port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:587 +msgid "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" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13 +msgid "Select DNS protocol to use" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45 +msgid "Bootstrap DNS server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 +msgid "The DNS server used to look up the IP address of an upstream DNS server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68 +msgid "DNS Rewrite TTL" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69 +msgid "Time in seconds for DNS record caching (default: 60)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75 +msgid "TTL value cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80 +msgid "TTL must be a positive number" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89 +msgid "Source Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90 +msgid "Select the network interface from which the traffic will originate" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126 +msgid "Enable Output Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:127 +msgid "You can select Output Network Interface, by default autodetect" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135 +msgid "Output Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136 +msgid "Select the network interface to which the traffic will originate" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182 +msgid "Interface Monitoring" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183 +msgid "Interface monitoring for Bad WAN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191 +msgid "Monitored Interfaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192 +msgid "Select the WAN interfaces to be monitored" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214 +msgid "Interface Monitoring Delay" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215 +msgid "Delay in milliseconds before reloading podkop after interface UP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222 +msgid "Delay value cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230 +msgid "Enable YACD" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239 +msgid "Disable QUIC" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240 +msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250 +msgid "List Update Frequency" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251 +msgid "Select how often the domain or subnet lists are updated automatically" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262 +msgid "Download Lists via Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263 +msgid "Downloading all lists via main Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271 +msgid "Download Lists via specific proxy section" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272 +msgid "Downloading all lists via specific Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300 +msgid "Dont Touch My DHCP!" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301 +msgid "Podkop will not modify your DHCP configuration" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309 +msgid "Config File Path" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310 +msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322 +msgid "Cache File Path" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323 +msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336 +msgid "Cache file path cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340 +msgid "Path must be absolute (start with /)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344 +msgid "Path must end with cache.db" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349 +msgid "Path must contain at least one directory (like /tmp/cache.db)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358 +msgid "Exclude NTP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359 +msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369 +msgid "Routing Excluded IPs" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370 +msgid "Specify a local IP address to be excluded from routing" +msgstr "" diff --git a/fe-app-podkop/locales/podkop.ru.po b/fe-app-podkop/locales/podkop.ru.po new file mode 100644 index 0000000..10dd5ae --- /dev/null +++ b/fe-app-podkop/locales/podkop.ru.po @@ -0,0 +1,1086 @@ +# RU translations for PODKOP package. +# Copyright (C) 2025 THE PODKOP'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PODKOP package. +# divocat, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PODKOP\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-21 21:31+0300\n" +"PO-Revision-Date: 2025-10-21 21:31+0300\n" +"Last-Translator: divocat\n" +"Language-Team: none\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +msgid "Successfully copied!" +msgstr "" + +msgid "Failed to copy!" +msgstr "" + +msgid "Operation timed out" +msgstr "Время ожидания истекло" + +msgid "HTTP error" +msgstr "Ошибка HTTP" + +msgid "Unknown error" +msgstr "Неизвестная ошибка" + +msgid "DNS server address cannot be empty" +msgstr "Адрес DNS-сервера не может быть пустым" + +msgid "Valid" +msgstr "Валидно" + +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" + +msgid "Invalid domain address" +msgstr "Неверный домен" + +msgid "Invalid IP address" +msgstr "Неверный IP-адрес" + +msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" +msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" + +msgid "Invalid JSON format" +msgstr "Неверный формат JSON" + +msgid "Path cannot be empty" +msgstr "Путь не может быть пустым" + +msgid "Invalid path format. Path must start with \"/\" and contain valid characters" +msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы" + +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" + +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" + +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" + +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" + +msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" +msgstr "Неверный URL Shadowsocks: отсутствует разделитель метода и пароля \":\"" + +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" + +msgid "Invalid Shadowsocks URL: missing server" +msgstr "Неверный URL Shadowsocks: отсутствует сервер" + +msgid "Invalid Shadowsocks URL: missing port" +msgstr "Неверный URL Shadowsocks: отсутствует порт" + +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "Неверный номер порта. Допустимо от 1 до 65535" + +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "Неверный URL Shadowsocks: ошибка разбора" + +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" + +msgid "IP address 0.0.0.0 is not allowed" +msgstr "IP-адрес 0.0.0.0 не допускается" + +msgid "CIDR must be between 0 and 32" +msgstr "CIDR должен быть между 0 и 32" + +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "Неверный URL Trojan: должен начинаться с trojan://" + +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "Неверный URL Trojan: не должен содержать пробелов" + +msgid "Invalid Trojan URL: parsing failed" +msgstr "Неверный URL Trojan: ошибка разбора" + +msgid "URL must use one of the following protocols:" +msgstr "URL должен использовать один из следующих протоколов:" + +msgid "Invalid URL format" +msgstr "Неверный формат URL" + +msgid "Invalid VLESS URL: parsing failed" +msgstr "Неверный URL VLESS: ошибка разбора" + +msgid "Download" +msgstr "" + +msgid "Copy" +msgstr "" + +msgid "Close" +msgstr "Закрыть" + +msgid "Fastest" +msgstr "Самый быстрый" + +msgid "Traffic" +msgstr "Трафик" + +msgid "Uplink" +msgstr "Исходящий" + +msgid "Downlink" +msgstr "Входящий" + +msgid "Traffic Total" +msgstr "Всего трафика" + +msgid "System info" +msgstr "Системная информация" + +msgid "Active Connections" +msgstr "Активные соединения" + +msgid "Memory Usage" +msgstr "Использование памяти" + +msgid "Services info" +msgstr "Информация о сервисах" + +msgid "Podkop" +msgstr "Podkop" + +msgid "✔ Enabled" +msgstr "✔ Включено" + +msgid "✘ Disabled" +msgstr "✘ Отключено" + +msgid "Sing-box" +msgstr "Sing-box" + +msgid "✔ Running" +msgstr "✔ Работает" + +msgid "✘ Stopped" +msgstr "✘ Остановлен" + +msgid "Not running" +msgstr "" + +msgid "Queued" +msgstr "" + +msgid "unknown" +msgstr "" + +msgid "Global check" +msgstr "Глобальная проверка" + +msgid "View logs" +msgstr "" + +msgid "Show sing-box config" +msgstr "" + +msgid "Outdated" +msgstr "" + +msgid "Latest" +msgstr "" + +msgid "Dashboard currently unavailable" +msgstr "Дашборд сейчас недоступен" + +msgid "Test latency" +msgstr "" + +msgid "Currently unavailable" +msgstr "Временно недоступно" + +msgid "DNS checks" +msgstr "" + +msgid "Sing-box checks" +msgstr "" + +msgid "Nftables checks" +msgstr "" + +msgid "FakeIP checks" +msgstr "" + +msgid "Checking dns, please wait" +msgstr "" + +msgid "Cannot receive DNS checks result" +msgstr "" + +msgid "DNS checks passed" +msgstr "" + +msgid "Bootsrap DNS" +msgstr "" + +msgid "Main DNS" +msgstr "" + +msgid "DNS on router" +msgstr "" + +msgid "DHCP has DNS server" +msgstr "" + +msgid "Checking FakeIP, please wait" +msgstr "" + +msgid "FakeIP checks passed" +msgstr "" + +msgid "FakeIP checks partially passed" +msgstr "" + +msgid "FakeIP checks failed" +msgstr "" + +msgid "Router DNS is routed through sing-box" +msgstr "" + +msgid "Router DNS is not routed through sing-box" +msgstr "" + +msgid "Browser is using FakeIP correctly" +msgstr "" + +msgid "Browser is not using FakeIP" +msgstr "" + +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +msgid "Checking nftables, please wait" +msgstr "" + +msgid "Cannot receive nftables checks result" +msgstr "" + +msgid "Nftables checks passed" +msgstr "" + +msgid "Nftables checks partially passed" +msgstr "" + +msgid "Table exist" +msgstr "" + +msgid "Rules mangle exist" +msgstr "" + +msgid "Rules mangle counters" +msgstr "" + +msgid "Rules mangle output exist" +msgstr "" + +msgid "Rules mangle output counters" +msgstr "" + +msgid "Rules proxy exist" +msgstr "" + +msgid "Rules proxy counters" +msgstr "" + +msgid "No other marking rules found" +msgstr "" + +msgid "Additional marking rules found" +msgstr "" + +msgid "Checking sing-box, please wait" +msgstr "" + +msgid "Cannot receive Sing-box checks result" +msgstr "" + +msgid "Sing-box checks passed" +msgstr "" + +msgid "Sing-box installed" +msgstr "" + +msgid "Sing-box version >= 1.12.4" +msgstr "" + +msgid "Sing-box service exist" +msgstr "" + +msgid "Sing-box autostart disabled" +msgstr "" + +msgid "Sing-box process running" +msgstr "" + +msgid "Sing-box listening ports" +msgstr "" + +msgid "Restart podkop" +msgstr "" + +msgid "Stop podkop" +msgstr "" + +msgid "Start podkop" +msgstr "" + +msgid "Disable autostart" +msgstr "" + +msgid "Enable autostart" +msgstr "" + +msgid "Get global check" +msgstr "" + +msgid "Not implement yet" +msgstr "" + +msgid "Run Diagnostic" +msgstr "" + +msgid "Valid" +msgstr "Валидно" + +msgid "Invalid IP address" +msgstr "Неверный IP-адрес" + +msgid "Invalid domain address" +msgstr "Неверный домен" + +msgid "DNS server address cannot be empty" +msgstr "Адрес DNS-сервера не может быть пустым" + +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" + +msgid "URL must use one of the following protocols:" +msgstr "URL должен использовать один из следующих протоколов:" + +msgid "Invalid URL format" +msgstr "Неверный формат URL" + +msgid "Path cannot be empty" +msgstr "Путь не может быть пустым" + +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" + +msgid "IP address 0.0.0.0 is not allowed" +msgstr "IP-адрес 0.0.0.0 не допускается" + +msgid "CIDR must be between 0 and 32" +msgstr "CIDR должен быть между 0 и 32" + +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" + +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" + +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" + +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" + +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" + +msgid "Invalid Shadowsocks URL: missing server" +msgstr "Неверный URL Shadowsocks: отсутствует сервер" + +msgid "Invalid Shadowsocks URL: missing port" +msgstr "Неверный URL Shadowsocks: отсутствует порт" + +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "Неверный номер порта. Допустимо от 1 до 65535" + +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "Неверный URL Shadowsocks: ошибка разбора" + +msgid "Invalid VLESS URL: parsing failed" +msgstr "Неверный URL VLESS: ошибка разбора" + +msgid "Invalid JSON format" +msgstr "Неверный формат JSON" + +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "Неверный URL Trojan: должен начинаться с trojan://" + +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "Неверный URL Trojan: не должен содержать пробелов" + +msgid "Invalid Trojan URL: parsing failed" +msgstr "Неверный URL Trojan: ошибка разбора" + +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +msgid "Fastest" +msgstr "Самый быстрый" + +msgid "HTTP error" +msgstr "Ошибка HTTP" + +msgid "Unknown error" +msgstr "Неизвестная ошибка" + +msgid "DNS checks" +msgstr "" + +msgid "Sing-box checks" +msgstr "" + +msgid "Nftables checks" +msgstr "" + +msgid "FakeIP checks" +msgstr "" + +msgid "Not running" +msgstr "" + +msgid "Queued" +msgstr "" + +msgid "Dashboard currently unavailable" +msgstr "Дашборд сейчас недоступен" + +msgid "Test latency" +msgstr "" + +msgid "Currently unavailable" +msgstr "Временно недоступно" + +msgid "Traffic" +msgstr "Трафик" + +msgid "Uplink" +msgstr "Исходящий" + +msgid "Downlink" +msgstr "Входящий" + +msgid "Traffic Total" +msgstr "Всего трафика" + +msgid "System info" +msgstr "Системная информация" + +msgid "Active Connections" +msgstr "Активные соединения" + +msgid "Memory Usage" +msgstr "Использование памяти" + +msgid "Services info" +msgstr "Информация о сервисах" + +msgid "Podkop" +msgstr "Podkop" + +msgid "\\u2714 Enabled" +msgstr "" + +msgid "\\u2718 Disabled" +msgstr "" + +msgid "Sing-box" +msgstr "Sing-box" + +msgid "\\u2714 Running" +msgstr "" + +msgid "\\u2718 Stopped" +msgstr "" + +msgid "Checking dns, please wait" +msgstr "" + +msgid "Cannot receive DNS checks result" +msgstr "" + +msgid "DNS checks passed" +msgstr "" + +msgid "Bootsrap DNS" +msgstr "" + +msgid "Main DNS" +msgstr "" + +msgid "DNS on router" +msgstr "" + +msgid "DHCP has DNS server" +msgstr "" + +msgid "Checking sing-box, please wait" +msgstr "" + +msgid "Cannot receive Sing-box checks result" +msgstr "" + +msgid "Sing-box checks passed" +msgstr "" + +msgid "Sing-box installed" +msgstr "" + +msgid "Sing-box version >= 1.12.4" +msgstr "" + +msgid "Sing-box service exist" +msgstr "" + +msgid "Sing-box autostart disabled" +msgstr "" + +msgid "Sing-box process running" +msgstr "" + +msgid "Sing-box listening ports" +msgstr "" + +msgid "Checking nftables, please wait" +msgstr "" + +msgid "Cannot receive nftables checks result" +msgstr "" + +msgid "Nftables checks passed" +msgstr "" + +msgid "Nftables checks partially passed" +msgstr "" + +msgid "Table exist" +msgstr "" + +msgid "Rules mangle exist" +msgstr "" + +msgid "Rules mangle counters" +msgstr "" + +msgid "Rules mangle output exist" +msgstr "" + +msgid "Rules mangle output counters" +msgstr "" + +msgid "Rules proxy exist" +msgstr "" + +msgid "Rules proxy counters" +msgstr "" + +msgid "No other marking rules found" +msgstr "" + +msgid "Additional marking rules found" +msgstr "" + +msgid "Checking FakeIP, please wait" +msgstr "" + +msgid "FakeIP checks passed" +msgstr "" + +msgid "FakeIP checks partially passed" +msgstr "" + +msgid "FakeIP checks failed" +msgstr "" + +msgid "Router DNS is routed through sing-box" +msgstr "" + +msgid "Router DNS is not routed through sing-box" +msgstr "" + +msgid "Browser is using FakeIP correctly" +msgstr "" + +msgid "Browser is not using FakeIP" +msgstr "" + +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +msgid "Successfully copied!" +msgstr "" + +msgid "Failed to copy!" +msgstr "" + +msgid "Download" +msgstr "" + +msgid "Copy" +msgstr "" + +msgid "Close" +msgstr "Закрыть" + +msgid "Restart podkop" +msgstr "" + +msgid "Stop podkop" +msgstr "" + +msgid "Start podkop" +msgstr "" + +msgid "Disable autostart" +msgstr "" + +msgid "Enable autostart" +msgstr "" + +msgid "Get global check" +msgstr "" + +msgid "View logs" +msgstr "" + +msgid "Show sing-box config" +msgstr "" + +msgid "Not implement yet" +msgstr "" + +msgid "Run Diagnostic" +msgstr "" + +msgid "unknown" +msgstr "" + +msgid "Global check" +msgstr "Глобальная проверка" + +msgid "Outdated" +msgstr "" + +msgid "Latest" +msgstr "" + +msgid "Operation timed out" +msgstr "Время ожидания истекло" + +msgid "Podkop Settings" +msgstr "" + +msgid "Configuration for Podkop service" +msgstr "" + +msgid "Sections" +msgstr "" + +msgid "Settings" +msgstr "" + +msgid "Diagnostics" +msgstr "Диагностика" + +msgid "Dashboard" +msgstr "Дашборд" + +msgid "Connection Type" +msgstr "Тип подключения" + +msgid "Select between VPN and Proxy connection methods for traffic routing" +msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" + +msgid "Configuration Type" +msgstr "Тип конфигурации" + +msgid "Select how to configure the proxy" +msgstr "Выберите способ настройки прокси" + +msgid "Connection URL" +msgstr "URL подключения" + +msgid "Outbound Config" +msgstr "Конфигурация Outbound" + +msgid "URLTest" +msgstr "URLTest" + +msgid "Proxy Configuration URL" +msgstr "URL конфигурации прокси" + +msgid "Outbound Configuration" +msgstr "Конфигурация исходящего соединения" + +msgid "Enter complete outbound configuration in JSON format" +msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" + +msgid "URLTest Proxy Links" +msgstr "Ссылки прокси для URLTest" + +msgid "UDP over TCP" +msgstr "" + +msgid "Applicable for SOCKS and Shadowsocks proxy" +msgstr "" + +msgid "Network Interface" +msgstr "Сетевой интерфейс" + +msgid "Select network interface for VPN connection" +msgstr "Выберите сетевой интерфейс для VPN подключения" + +msgid "Domain Resolver" +msgstr "Резолвер доменов" + +msgid "Enable built-in DNS resolver for domains handled by this section" +msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе" + +msgid "DNS Protocol Type" +msgstr "Тип протокола DNS" + +msgid "Select the DNS protocol type for the domain resolver" +msgstr "Выберите тип протокола DNS для резолвера доменов" + +msgid "DNS over HTTPS (DoH)" +msgstr "DNS через HTTPS (DoH)" + +msgid "DNS over TLS (DoT)" +msgstr "DNS через TLS (DoT)" + +msgid "UDP (Unprotected DNS)" +msgstr "UDP (Незащищённый DNS)" + +msgid "DNS Server" +msgstr "DNS-сервер" + +msgid "Select or enter DNS server address" +msgstr "Выберите или введите адрес DNS-сервера" + +msgid "" +msgstr "" + +msgid "Community Lists" +msgstr "Списки сообщества" + +msgid "Select a predefined list for routing" +msgstr "" + +msgid "Regional options cannot be used together" +msgstr "Нельзя использовать несколько региональных опций одновременно" + +msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." +msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." + +msgid "Russia inside restrictions" +msgstr "Ограничения Russia inside" + +msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." +msgstr "" + +msgid "User Domain List Type" +msgstr "Тип пользовательского списка доменов" + +msgid "Select the list type for adding custom domains" +msgstr "" + +msgid "Disabled" +msgstr "Отключено" + +msgid "Dynamic List" +msgstr "Динамический список" + +msgid "Text List" +msgstr "Текстовый список" + +msgid "User Domains" +msgstr "Пользовательские домены" + +msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" +msgstr "" + +msgid "User Domains List" +msgstr "Список пользовательских доменов" + +msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" +msgstr "" + +msgid "At least one valid domain must be specified. Comments-only content is not allowed." +msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается." + +msgid "${validation.value}: ${validation.message}" +msgstr "" + +msgid "Validation errors:" +msgstr "Ошибки валидации:" + +msgid "User Subnet List Type" +msgstr "Тип пользовательского списка подсетей" + +msgid "Select the list type for adding custom subnets" +msgstr "" + +msgid "Text List (comma/space/newline separated)" +msgstr "Текстовый список (через запятую, пробел или новую строку)" + +msgid "User Subnets" +msgstr "Пользовательские подсети" + +msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" +msgstr "" + +msgid "User Subnets List" +msgstr "Список пользовательских подсетей" + +msgid "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //" +msgstr "" + +msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." +msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы." + +msgid "Local Domain Lists" +msgstr "Локальные списки доменов" + +msgid "Specify the path to the list file located on the router filesystem" +msgstr "" + +msgid "Local Subnet Lists" +msgstr "Локальные списки подсетей" + +msgid "Remote Domain Lists" +msgstr "Удалённые списки доменов" + +msgid "Specify remote URLs to download and use domain lists" +msgstr "" + +msgid "Remote Subnet Lists" +msgstr "Удалённые списки подсетей" + +msgid "Specify remote URLs to download and use subnet lists" +msgstr "" + +msgid "Fully Routed IPs" +msgstr "" + +msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" +msgstr "" + +msgid "Enable Mixed Proxy" +msgstr "" + +msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" +msgstr "" + +msgid "Mixed Proxy Port" +msgstr "" + +msgid "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" +msgstr "" + +msgid "Select DNS protocol to use" +msgstr "Выберите протокол DNS" + +msgid "Bootstrap DNS server" +msgstr "Bootstrap DNS-сервер" + +msgid "The DNS server used to look up the IP address of an upstream DNS server" +msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" + +msgid "DNS Rewrite TTL" +msgstr "Перезапись TTL для DNS" + +msgid "Time in seconds for DNS record caching (default: 60)" +msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)" + +msgid "TTL value cannot be empty" +msgstr "Значение TTL не может быть пустым" + +msgid "TTL must be a positive number" +msgstr "TTL должно быть положительным числом" + +msgid "Source Network Interface" +msgstr "Сетевой интерфейс источника" + +msgid "Select the network interface from which the traffic will originate" +msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" + +msgid "Enable Output Network Interface" +msgstr "" + +msgid "You can select Output Network Interface, by default autodetect" +msgstr "" + +msgid "Output Network Interface" +msgstr "" + +msgid "Select the network interface to which the traffic will originate" +msgstr "" + +msgid "Interface Monitoring" +msgstr "" + +msgid "Interface monitoring for Bad WAN" +msgstr "" + +msgid "Monitored Interfaces" +msgstr "" + +msgid "Select the WAN interfaces to be monitored" +msgstr "Выберите WAN интерфейсы для мониторинга" + +msgid "Interface Monitoring Delay" +msgstr "Задержка при мониторинге интерфейсов" + +msgid "Delay in milliseconds before reloading podkop after interface UP" +msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса" + +msgid "Delay value cannot be empty" +msgstr "Значение задержки не может быть пустым" + +msgid "Enable YACD" +msgstr "" + +msgid "Disable QUIC" +msgstr "" + +msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" +msgstr "" + +msgid "List Update Frequency" +msgstr "Частота обновления списков" + +msgid "Select how often the domain or subnet lists are updated automatically" +msgstr "" + +msgid "Download Lists via Proxy/VPN" +msgstr "" + +msgid "Downloading all lists via main Proxy/VPN" +msgstr "Загрузка всех списков через основной прокси/VPN" + +msgid "Download Lists via specific proxy section" +msgstr "" + +msgid "Downloading all lists via specific Proxy/VPN" +msgstr "" + +msgid "Dont Touch My DHCP!" +msgstr "" + +msgid "Podkop will not modify your DHCP configuration" +msgstr "" + +msgid "Config File Path" +msgstr "Путь к файлу конфигурации" + +msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" +msgstr "Выберите путь к файлу конфигурации sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" + +msgid "Cache File Path" +msgstr "Путь к файлу кэша" + +msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" +msgstr "Выберите или введите путь к файлу кеша sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" + +msgid "Cache file path cannot be empty" +msgstr "Путь к файлу кэша не может быть пустым" + +msgid "Path must be absolute (start with /)" +msgstr "Путь должен быть абсолютным (начинаться с /)" + +msgid "Path must end with cache.db" +msgstr "Путь должен заканчиваться на cache.db" + +msgid "Path must contain at least one directory (like /tmp/cache.db)" +msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)" + +msgid "Exclude NTP" +msgstr "Исключить NTP" + +msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" +msgstr "" + +msgid "Routing Excluded IPs" +msgstr "" + +msgid "Specify a local IP address to be excluded from routing" +msgstr "" diff --git a/fe-app-podkop/package.json b/fe-app-podkop/package.json index 8eeb286..1b8a4bd 100644 --- a/fe-app-podkop/package.json +++ b/fe-app-podkop/package.json @@ -12,7 +12,12 @@ "dev": "tsup src/main.ts --watch", "test": "vitest", "ci": "yarn format && yarn lint --max-warnings=0 && yarn test --run && yarn build", - "watch:sftp": "node watch-upload.js" + "watch:sftp": "node watch-upload.js", + "locales:exctract-calls": "node extract-calls.js", + "locales:generate-pot": "node generate-pot.js", + "locales:generate-po:ru": "node generate-po.js ru", + "locales:distribute": "node distribute-locales.js", + "locales:actualize": "yarn locales:exctract-calls && yarn locales:generate-pot && yarn locales:generate-po:ru && yarn locales:distribute" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "8.45.0", @@ -21,6 +26,7 @@ "dotenv": "17.2.3", "eslint": "9.36.0", "eslint-config-prettier": "10.1.8", + "fast-glob": "3.3.3", "glob": "11.0.3", "prettier": "3.6.2", "ssh2-sftp-client": "12.0.1", diff --git a/fe-app-podkop/yarn.lock b/fe-app-podkop/yarn.lock index 93738ea..62d5fe2 100644 --- a/fe-app-podkop/yarn.lock +++ b/fe-app-podkop/yarn.lock @@ -996,6 +996,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + fast-glob@^3.3.2: version "3.3.3" resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 81d7460..9f60cd7 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -1,4 +1,4 @@ -// This file is autogenerated, please don't change manually +// This file is autogenerated, please don't change manually "use strict"; "require baseclass"; "require fs"; @@ -7,8 +7,7 @@ // src/validators/validateIp.ts function validateIPV4(ip) { - const ipRegex = - /^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/; + const ipRegex = /^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/; if (ipRegex.test(ip)) { return { valid: true, message: _("Valid") }; } @@ -17,8 +16,7 @@ function validateIPV4(ip) { // src/validators/validateDomain.ts function validateDomain(domain, allowDotTLD = false) { - const domainRegex = - /^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/; + const domainRegex = /^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/; if (allowDotTLD) { const dotTLD = /^\.[a-zA-Z]{2,}$/; if (dotTLD.test(domain)) { @@ -51,8 +49,8 @@ function validateDNS(value) { return { valid: false, message: _( - "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH", - ), + "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" + ) }; } @@ -63,7 +61,7 @@ function validateUrl(url, protocols = ["http:", "https:"]) { if (!protocols.includes(parsedUrl.protocol)) { return { valid: false, - message: `${_("URL must use one of the following protocols:")} ${protocols.join(", ")}`, + message: `${_("URL must use one of the following protocols:")} ${protocols.join(", ")}` }; } return { valid: true, message: _("Valid") }; @@ -77,21 +75,21 @@ function validatePath(value) { if (!value) { return { valid: false, - message: _("Path cannot be empty"), + message: _("Path cannot be empty") }; } const pathRegex = /^\/[a-zA-Z0-9_\-/.]+$/; if (pathRegex.test(value)) { return { valid: true, - message: _("Valid"), + message: _("Valid") }; } return { valid: false, message: _( - 'Invalid path format. Path must start with "/" and contain valid characters', - ), + 'Invalid path format. Path must start with "/" and contain valid characters' + ) }; } @@ -101,7 +99,7 @@ function validateSubnet(value) { if (!subnetRegex.test(value)) { return { valid: false, - message: _("Invalid format. Use X.X.X.X or X.X.X.X/Y"), + message: _("Invalid format. Use X.X.X.X or X.X.X.X/Y") }; } const [ip, cidr] = value.split("/"); @@ -117,7 +115,7 @@ function validateSubnet(value) { if (cidrNum < 0 || cidrNum > 32) { return { valid: false, - message: _("CIDR must be between 0 and 32"), + message: _("CIDR must be between 0 and 32") }; } } @@ -129,7 +127,7 @@ function bulkValidate(values, validate) { const results = values.map((value) => ({ ...validate(value), value })); return { valid: results.every((r) => r.valid), - results, + results }; } @@ -138,14 +136,14 @@ function validateShadowsocksUrl(url) { if (!url.startsWith("ss://")) { return { valid: false, - message: _("Invalid Shadowsocks URL: must start with ss://"), + message: _("Invalid Shadowsocks URL: must start with ss://") }; } try { if (!url || /\s/.test(url)) { return { valid: false, - message: _("Invalid Shadowsocks URL: must not contain spaces"), + message: _("Invalid Shadowsocks URL: must not contain spaces") }; } const mainPart = url.includes("?") ? url.split("?")[0] : url.split("#")[0]; @@ -153,7 +151,7 @@ function validateShadowsocksUrl(url) { if (!encryptedPart) { return { valid: false, - message: _("Invalid Shadowsocks URL: missing credentials"), + message: _("Invalid Shadowsocks URL: missing credentials") }; } try { @@ -162,8 +160,8 @@ function validateShadowsocksUrl(url) { return { valid: false, message: _( - "Invalid Shadowsocks URL: decoded credentials must contain method:password", - ), + "Invalid Shadowsocks URL: decoded credentials must contain method:password" + ) }; } } catch (_e) { @@ -171,8 +169,8 @@ function validateShadowsocksUrl(url) { return { valid: false, message: _( - 'Invalid Shadowsocks URL: missing method and password separator ":"', - ), + 'Invalid Shadowsocks URL: missing method and password separator ":"' + ) }; } } @@ -180,34 +178,34 @@ function validateShadowsocksUrl(url) { if (!serverPart) { return { valid: false, - message: _("Invalid Shadowsocks URL: missing server address"), + message: _("Invalid Shadowsocks URL: missing server address") }; } const [server, portAndRest] = serverPart.split(":"); if (!server) { return { valid: false, - message: _("Invalid Shadowsocks URL: missing server"), + message: _("Invalid Shadowsocks URL: missing server") }; } const port = portAndRest ? portAndRest.split(/[?#]/)[0] : null; if (!port) { return { valid: false, - message: _("Invalid Shadowsocks URL: missing port"), + message: _("Invalid Shadowsocks URL: missing port") }; } const portNum = parseInt(port, 10); if (isNaN(portNum) || portNum < 1 || portNum > 65535) { return { valid: false, - message: _("Invalid port number. Must be between 1 and 65535"), + message: _("Invalid port number. Must be between 1 and 65535") }; } } catch (_e) { return { valid: false, - message: _("Invalid Shadowsocks URL: parsing failed"), + message: _("Invalid Shadowsocks URL: parsing failed") }; } return { valid: true, message: _("Valid") }; @@ -216,10 +214,8 @@ function validateShadowsocksUrl(url) { // src/helpers/parseQueryString.ts function parseQueryString(query) { const clean = query.startsWith("?") ? query.slice(1) : query; - return clean - .split("&") - .filter(Boolean) - .reduce((acc, pair) => { + return clean.split("&").filter(Boolean).reduce( + (acc, pair) => { const [rawKey, rawValue = ""] = pair.split("="); if (!rawKey) { return acc; @@ -227,7 +223,9 @@ function parseQueryString(query) { const key = decodeURIComponent(rawKey); const value = decodeURIComponent(rawValue); return { ...acc, [key]: value }; - }, {}); + }, + {} + ); } // src/validators/validateVlessUrl.ts @@ -236,12 +234,12 @@ function validateVlessUrl(url) { if (!url.startsWith("vless://")) return { valid: false, - message: "Invalid VLESS URL: must start with vless://", + message: "Invalid VLESS URL: must start with vless://" }; if (/\s/.test(url)) return { valid: false, - message: "Invalid VLESS URL: must not contain spaces", + message: "Invalid VLESS URL: must not contain spaces" }; const body = url.slice("vless://".length); const [mainPart] = body.split("#"); @@ -249,7 +247,7 @@ function validateVlessUrl(url) { if (!userHostPort) return { valid: false, - message: "Invalid VLESS URL: missing host and UUID", + message: "Invalid VLESS URL: missing host and UUID" }; const [userPart, hostPortPart] = userHostPort.split("@"); if (!userPart) @@ -265,12 +263,12 @@ function validateVlessUrl(url) { if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) return { valid: false, - message: "Invalid VLESS URL: invalid port number", + message: "Invalid VLESS URL: invalid port number" }; if (!queryString) return { valid: false, - message: "Invalid VLESS URL: missing query parameters", + message: "Invalid VLESS URL: missing query parameters" }; const params = parseQueryString(queryString); const validTypes = [ @@ -282,36 +280,35 @@ function validateVlessUrl(url) { "httpupgrade", "xhttp", "ws", - "kcp", + "kcp" ]; const validSecurities = ["tls", "reality", "none"]; if (!params.type || !validTypes.includes(params.type)) return { valid: false, - message: "Invalid VLESS URL: unsupported or missing type", + message: "Invalid VLESS URL: unsupported or missing type" }; if (!params.security || !validSecurities.includes(params.security)) return { valid: false, - message: "Invalid VLESS URL: unsupported or missing security", + message: "Invalid VLESS URL: unsupported or missing security" }; if (params.security === "reality") { if (!params.pbk) return { valid: false, - message: "Invalid VLESS URL: missing pbk for reality", + message: "Invalid VLESS URL: missing pbk for reality" }; if (!params.fp) return { valid: false, - message: "Invalid VLESS URL: missing fp for reality", + message: "Invalid VLESS URL: missing fp for reality" }; } if (params.flow === "xtls-rprx-vision-udp443") { return { valid: false, - message: - "Invalid VLESS URL: flow xtls-rprx-vision-udp443 does not supported", + message: "Invalid VLESS URL: flow xtls-rprx-vision-udp443 does not supported" }; } return { valid: true, message: _("Valid") }; @@ -328,8 +325,8 @@ function validateOutboundJson(value) { return { valid: false, message: _( - 'Outbound JSON must contain at least "type", "server" and "server_port" fields', - ), + 'Outbound JSON must contain at least "type", "server" and "server_port" fields' + ) }; } return { valid: true, message: _("Valid") }; @@ -344,13 +341,13 @@ function validateTrojanUrl(url) { if (!url.startsWith("trojan://")) { return { valid: false, - message: _("Invalid Trojan URL: must start with trojan://"), + message: _("Invalid Trojan URL: must start with trojan://") }; } if (!url || /\s/.test(url)) { return { valid: false, - message: _("Invalid Trojan URL: must not contain spaces"), + message: _("Invalid Trojan URL: must not contain spaces") }; } const body = url.slice("trojan://".length); @@ -360,14 +357,14 @@ function validateTrojanUrl(url) { if (!userHostPort) return { valid: false, - message: "Invalid Trojan URL: missing credentials and host", + message: "Invalid Trojan URL: missing credentials and host" }; if (!userPart) return { valid: false, message: "Invalid Trojan URL: missing password" }; if (!hostPortPart) return { valid: false, - message: "Invalid Trojan URL: missing hostname and port", + message: "Invalid Trojan URL: missing hostname and port" }; const [host, port] = hostPortPart.split(":"); if (!host) @@ -378,7 +375,7 @@ function validateTrojanUrl(url) { if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) return { valid: false, - message: "Invalid Trojan URL: invalid port number", + message: "Invalid Trojan URL: invalid port number" }; } catch (_e) { return { valid: false, message: _("Invalid Trojan URL: parsing failed") }; @@ -393,41 +390,39 @@ function validateSocksUrl(url) { return { valid: false, message: _( - "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://", - ), + "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" + ) }; } if (!url || /\s/.test(url)) { return { valid: false, - message: _("Invalid SOCKS URL: must not contain spaces"), + message: _("Invalid SOCKS URL: must not contain spaces") }; } const body = url.replace(/^socks(4|4a|5):\/\//, ""); const [authAndHost] = body.split("#"); - const [credentials, hostPortPart] = authAndHost.includes("@") - ? authAndHost.split("@") - : [null, authAndHost]; + const [credentials, hostPortPart] = authAndHost.includes("@") ? authAndHost.split("@") : [null, authAndHost]; if (credentials) { const [username, _password] = credentials.split(":"); if (!username) { return { valid: false, - message: _("Invalid SOCKS URL: missing username"), + message: _("Invalid SOCKS URL: missing username") }; } } if (!hostPortPart) { return { valid: false, - message: _("Invalid SOCKS URL: missing host and port"), + message: _("Invalid SOCKS URL: missing host and port") }; } const [host, port] = hostPortPart.split(":"); if (!host) { return { valid: false, - message: _("Invalid SOCKS URL: missing hostname or IP"), + message: _("Invalid SOCKS URL: missing hostname or IP") }; } if (!port) { @@ -437,7 +432,7 @@ function validateSocksUrl(url) { if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) { return { valid: false, - message: _("Invalid SOCKS URL: invalid port number"), + message: _("Invalid SOCKS URL: invalid port number") }; } const ipv4Result = validateIPV4(host); @@ -445,7 +440,7 @@ function validateSocksUrl(url) { if (!ipv4Result.valid && !domainResult.valid) { return { valid: false, - message: _("Invalid SOCKS URL: invalid host format"), + message: _("Invalid SOCKS URL: invalid host format") }; } } catch (_e) { @@ -471,20 +466,14 @@ function validateProxyUrl(url) { return { valid: false, message: _( - "URL must start with vless://, ss://, trojan://, or socks4/5://", - ), + "URL must start with vless://, ss://, trojan://, or socks4/5://" + ) }; } // src/helpers/parseValueList.ts function parseValueList(value) { - return value - .split(/\n/) - .map((line) => line.split("//")[0]) - .join(" ") - .split(/[,\s]+/) - .map((s) => s.trim()) - .filter(Boolean); + return value.split(/\n/).map((line) => line.split("//")[0]).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean); } // src/podkop/methods/custom/getConfigSections.ts @@ -497,24 +486,24 @@ async function callBaseMethod(method, args = [], command = "/usr/bin/podkop") { const response = await executeShellCommand({ command, args: [method, ...args], - timeout: 1e4, + timeout: 1e4 }); if (response.stdout) { try { return { success: true, - data: JSON.parse(response.stdout), + data: JSON.parse(response.stdout) }; } catch (_e) { return { success: true, - data: response.stdout, + data: response.stdout }; } } return { success: false, - error: "", + error: "" }; } @@ -539,71 +528,81 @@ var Podkop; AvailableMethods2["SHOW_SING_BOX_CONFIG"] = "show_sing_box_config"; AvailableMethods2["CHECK_LOGS"] = "check_logs"; AvailableMethods2["GET_SYSTEM_INFO"] = "get_system_info"; - })( - (AvailableMethods = - Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})), - ); + })(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {})); let AvailableClashAPIMethods; ((AvailableClashAPIMethods2) => { AvailableClashAPIMethods2["GET_PROXIES"] = "get_proxies"; AvailableClashAPIMethods2["GET_PROXY_LATENCY"] = "get_proxy_latency"; AvailableClashAPIMethods2["GET_GROUP_LATENCY"] = "get_group_latency"; AvailableClashAPIMethods2["SET_GROUP_PROXY"] = "set_group_proxy"; - })( - (AvailableClashAPIMethods = - Podkop2.AvailableClashAPIMethods || - (Podkop2.AvailableClashAPIMethods = {})), - ); + })(AvailableClashAPIMethods = Podkop2.AvailableClashAPIMethods || (Podkop2.AvailableClashAPIMethods = {})); })(Podkop || (Podkop = {})); // src/podkop/methods/shell/index.ts var PodkopShellMethods = { - checkDNSAvailable: async () => - callBaseMethod(Podkop.AvailableMethods.CHECK_DNS_AVAILABLE), - checkFakeIP: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_FAKEIP), - checkNftRules: async () => - callBaseMethod(Podkop.AvailableMethods.CHECK_NFT_RULES), + checkDNSAvailable: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_DNS_AVAILABLE + ), + checkFakeIP: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_FAKEIP + ), + checkNftRules: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_NFT_RULES + ), getStatus: async () => callBaseMethod(Podkop.AvailableMethods.GET_STATUS), - checkSingBox: async () => - callBaseMethod(Podkop.AvailableMethods.CHECK_SING_BOX), - getSingBoxStatus: async () => - callBaseMethod(Podkop.AvailableMethods.GET_SING_BOX_STATUS), - getClashApiProxies: async () => - callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ - Podkop.AvailableClashAPIMethods.GET_PROXIES, - ]), - getClashApiProxyLatency: async (tag) => - callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ - Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, - tag, - ]), - getClashApiGroupLatency: async (tag) => - callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ - Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, - tag, - ]), - setClashApiGroupProxy: async (group, proxy) => - callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ - Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY, - group, - proxy, - ]), - restart: async () => - callBaseMethod(Podkop.AvailableMethods.RESTART, [], "/etc/init.d/podkop"), - start: async () => - callBaseMethod(Podkop.AvailableMethods.START, [], "/etc/init.d/podkop"), - stop: async () => - callBaseMethod(Podkop.AvailableMethods.STOP, [], "/etc/init.d/podkop"), - enable: async () => - callBaseMethod(Podkop.AvailableMethods.ENABLE, [], "/etc/init.d/podkop"), - disable: async () => - callBaseMethod(Podkop.AvailableMethods.DISABLE, [], "/etc/init.d/podkop"), + checkSingBox: async () => callBaseMethod( + Podkop.AvailableMethods.CHECK_SING_BOX + ), + getSingBoxStatus: async () => callBaseMethod( + Podkop.AvailableMethods.GET_SING_BOX_STATUS + ), + getClashApiProxies: async () => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_PROXIES + ]), + getClashApiProxyLatency: async (tag) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, + tag + ]), + getClashApiGroupLatency: async (tag) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, + tag + ]), + setClashApiGroupProxy: async (group, proxy) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [ + Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY, + group, + proxy + ]), + restart: async () => callBaseMethod( + Podkop.AvailableMethods.RESTART, + [], + "/etc/init.d/podkop" + ), + start: async () => callBaseMethod( + Podkop.AvailableMethods.START, + [], + "/etc/init.d/podkop" + ), + stop: async () => callBaseMethod( + Podkop.AvailableMethods.STOP, + [], + "/etc/init.d/podkop" + ), + enable: async () => callBaseMethod( + Podkop.AvailableMethods.ENABLE, + [], + "/etc/init.d/podkop" + ), + disable: async () => callBaseMethod( + Podkop.AvailableMethods.DISABLE, + [], + "/etc/init.d/podkop" + ), globalCheck: async () => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK), - showSingBoxConfig: async () => - callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), + showSingBoxConfig: async () => callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG), checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS), - getSystemInfo: async () => - callBaseMethod(Podkop.AvailableMethods.GET_SYSTEM_INFO), + getSystemInfo: async () => callBaseMethod( + Podkop.AvailableMethods.GET_SYSTEM_INFO + ) }; // src/podkop/methods/custom/getDashboardSections.ts @@ -613,108 +612,25 @@ async function getDashboardSections() { if (!clashProxies.success) { return { success: false, - data: [], + data: [] }; } const proxies = Object.entries(clashProxies.data.proxies).map( ([key, value]) => ({ code: key, - value, - }), + value + }) ); - const data = configSections - .filter( - (section) => - section.connection_type !== "block" && section[".type"] !== "settings", - ) - .map((section) => { - if (section.connection_type === "proxy") { - if (section.proxy_config_type === "url") { - const outbound = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-out`, - ); - const activeConfigs = splitProxyString(section.proxy_string); - const proxyDisplayName = - getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || ""; - return { - withTagSelect: false, - code: outbound?.code || section[".name"], - displayName: section[".name"], - outbounds: [ - { - code: outbound?.code || section[".name"], - displayName: proxyDisplayName, - latency: outbound?.value?.history?.[0]?.delay || 0, - type: outbound?.value?.type || "", - selected: true, - }, - ], - }; - } - if (section.proxy_config_type === "outbound") { - const outbound = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-out`, - ); - const parsedOutbound = JSON.parse(section.outbound_json); - const parsedTag = parsedOutbound?.tag - ? decodeURIComponent(parsedOutbound?.tag) - : void 0; - const proxyDisplayName = parsedTag || outbound?.value?.name || ""; - return { - withTagSelect: false, - code: outbound?.code || section[".name"], - displayName: section[".name"], - outbounds: [ - { - code: outbound?.code || section[".name"], - displayName: proxyDisplayName, - latency: outbound?.value?.history?.[0]?.delay || 0, - type: outbound?.value?.type || "", - selected: true, - }, - ], - }; - } - if (section.proxy_config_type === "urltest") { - const selector = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-out`, - ); - const outbound = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-urltest-out`, - ); - const outbounds = (outbound?.value?.all ?? []) - .map((code) => proxies.find((item) => item.code === code)) - .map((item, index) => ({ - code: item?.code || "", - displayName: - getProxyUrlName(section.urltest_proxy_links?.[index]) || - item?.value?.name || - "", - latency: item?.value?.history?.[0]?.delay || 0, - type: item?.value?.type || "", - selected: selector?.value?.now === item?.code, - })); - return { - withTagSelect: true, - code: selector?.code || section[".name"], - displayName: section[".name"], - outbounds: [ - { - code: outbound?.code || "", - displayName: _("Fastest"), - latency: outbound?.value?.history?.[0]?.delay || 0, - type: outbound?.value?.type || "", - selected: selector?.value?.now === outbound?.code, - }, - ...outbounds, - ], - }; - } - } - if (section.connection_type === "vpn") { + const data = configSections.filter( + (section) => section.connection_type !== "block" && section[".type"] !== "settings" + ).map((section) => { + if (section.connection_type === "proxy") { + if (section.proxy_config_type === "url") { const outbound = proxies.find( - (proxy) => proxy.code === `${section[".name"]}-out`, + (proxy) => proxy.code === `${section[".name"]}-out` ); + const activeConfigs = splitProxyString(section.proxy_string); + const proxyDisplayName = getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || ""; return { withTagSelect: false, code: outbound?.code || section[".name"], @@ -722,43 +638,119 @@ async function getDashboardSections() { outbounds: [ { code: outbound?.code || section[".name"], - displayName: section.interface || outbound?.value?.name || "", + displayName: proxyDisplayName, latency: outbound?.value?.history?.[0]?.delay || 0, type: outbound?.value?.type || "", - selected: true, - }, - ], + selected: true + } + ] }; } + if (section.proxy_config_type === "outbound") { + const outbound = proxies.find( + (proxy) => proxy.code === `${section[".name"]}-out` + ); + const parsedOutbound = JSON.parse(section.outbound_json); + const parsedTag = parsedOutbound?.tag ? decodeURIComponent(parsedOutbound?.tag) : void 0; + const proxyDisplayName = parsedTag || outbound?.value?.name || ""; + return { + withTagSelect: false, + code: outbound?.code || section[".name"], + displayName: section[".name"], + outbounds: [ + { + code: outbound?.code || section[".name"], + displayName: proxyDisplayName, + latency: outbound?.value?.history?.[0]?.delay || 0, + type: outbound?.value?.type || "", + selected: true + } + ] + }; + } + if (section.proxy_config_type === "urltest") { + const selector = proxies.find( + (proxy) => proxy.code === `${section[".name"]}-out` + ); + const outbound = proxies.find( + (proxy) => proxy.code === `${section[".name"]}-urltest-out` + ); + const outbounds = (outbound?.value?.all ?? []).map((code) => proxies.find((item) => item.code === code)).map((item, index) => ({ + code: item?.code || "", + displayName: getProxyUrlName(section.urltest_proxy_links?.[index]) || item?.value?.name || "", + latency: item?.value?.history?.[0]?.delay || 0, + type: item?.value?.type || "", + selected: selector?.value?.now === item?.code + })); + return { + withTagSelect: true, + code: selector?.code || section[".name"], + displayName: section[".name"], + outbounds: [ + { + code: outbound?.code || "", + displayName: _("Fastest"), + latency: outbound?.value?.history?.[0]?.delay || 0, + type: outbound?.value?.type || "", + selected: selector?.value?.now === outbound?.code + }, + ...outbounds + ] + }; + } + } + if (section.connection_type === "vpn") { + const outbound = proxies.find( + (proxy) => proxy.code === `${section[".name"]}-out` + ); return { withTagSelect: false, - code: section[".name"], + code: outbound?.code || section[".name"], displayName: section[".name"], - outbounds: [], + outbounds: [ + { + code: outbound?.code || section[".name"], + displayName: section.interface || outbound?.value?.name || "", + latency: outbound?.value?.history?.[0]?.delay || 0, + type: outbound?.value?.type || "", + selected: true + } + ] }; - }); + } + return { + withTagSelect: false, + code: section[".name"], + displayName: section[".name"], + outbounds: [] + }; + }); return { success: true, - data, + data }; } // src/podkop/methods/custom/index.ts var CustomPodkopMethods = { getConfigSections, - getDashboardSections, + getDashboardSections }; // src/constants.ts var STATUS_COLORS = { SUCCESS: "#4caf50", ERROR: "#f44336", - WARNING: "#ff9800", + WARNING: "#ff9800" }; var PODKOP_LUCI_APP_VERSION = "__COMPILED_VERSION_VARIABLE__"; var FAKEIP_CHECK_DOMAIN = "fakeip.podkop.fyi"; var IP_CHECK_DOMAIN = "ip.podkop.fyi"; -var REGIONAL_OPTIONS = ["russia_inside", "russia_outside", "ukraine_inside"]; +var REGIONAL_OPTIONS = [ + "russia_inside", + "russia_outside", + "ukraine_inside" +]; var ALLOWED_WITH_RUSSIA_INSIDE = [ "russia_inside", "meta", @@ -772,7 +764,7 @@ var ALLOWED_WITH_RUSSIA_INSIDE = [ "ovh", "hodca", "digitalocean", - "cloudfront", + "cloudfront" ]; var DOMAIN_LIST_OPTIONS = { russia_inside: "Russia inside", @@ -797,23 +789,22 @@ var DOMAIN_LIST_OPTIONS = { hetzner: "Hetzner ASN", ovh: "OVH ASN", digitalocean: "Digital Ocean ASN", - cloudfront: "CloudFront ASN", + cloudfront: "CloudFront ASN" }; var UPDATE_INTERVAL_OPTIONS = { "1h": "Every hour", "3h": "Every 3 hours", "12h": "Every 12 hours", "1d": "Every day", - "3d": "Every 3 days", + "3d": "Every 3 days" }; var DNS_SERVER_OPTIONS = { "1.1.1.1": "1.1.1.1 (Cloudflare)", "8.8.8.8": "8.8.8.8 (Google)", "9.9.9.9": "9.9.9.9 (Quad9)", "dns.adguard-dns.com": "dns.adguard-dns.com (AdGuard Default)", - "unfiltered.adguard-dns.com": - "unfiltered.adguard-dns.com (AdGuard Unfiltered)", - "family.adguard-dns.com": "family.adguard-dns.com (AdGuard Family)", + "unfiltered.adguard-dns.com": "unfiltered.adguard-dns.com (AdGuard Unfiltered)", + "family.adguard-dns.com": "family.adguard-dns.com (AdGuard Family)" }; var BOOTSTRAP_DNS_SERVER_OPTIONS = { "77.88.8.8": "77.88.8.8 (Yandex DNS)", @@ -823,7 +814,7 @@ var BOOTSTRAP_DNS_SERVER_OPTIONS = { "8.8.8.8": "8.8.8.8 (Google DNS)", "8.8.4.4": "8.8.4.4 (Google DNS)", "9.9.9.9": "9.9.9.9 (Quad9 DNS)", - "9.9.9.11": "9.9.9.11 (Quad9 DNS)", + "9.9.9.11": "9.9.9.11 (Quad9 DNS)" }; var DIAGNOSTICS_UPDATE_INTERVAL = 1e4; var CACHE_TIMEOUT = DIAGNOSTICS_UPDATE_INTERVAL - 1e3; @@ -853,38 +844,35 @@ var COMMAND_SCHEDULING = { // Background execution P9_PRIORITY: 1700, // Idle mode execution - P10_PRIORITY: 1900, + P10_PRIORITY: 1900 // Lowest priority }; // src/podkop/api.ts async function createBaseApiRequest(fetchFn, options) { - const wrappedFn = () => - options?.timeoutMs && options?.operationName - ? withTimeout( - fetchFn(), - options.timeoutMs, - options.operationName, - options.timeoutMessage, - ) - : fetchFn(); + const wrappedFn = () => options?.timeoutMs && options?.operationName ? withTimeout( + fetchFn(), + options.timeoutMs, + options.operationName, + options.timeoutMessage + ) : fetchFn(); try { const response = await wrappedFn(); if (!response.ok) { return { success: false, - message: `${_("HTTP error")} ${response.status}: ${response.statusText}`, + message: `${_("HTTP error")} ${response.status}: ${response.statusText}` }; } const data = await response.json(); return { success: true, - data, + data }; } catch (e) { return { success: false, - message: e instanceof Error ? e.message : _("Unknown error"), + message: e instanceof Error ? e.message : _("Unknown error") }; } } @@ -892,37 +880,35 @@ async function createBaseApiRequest(fetchFn, options) { // src/podkop/methods/fakeip/getFakeIpCheck.ts async function getFakeIpCheck() { return createBaseApiRequest( - () => - fetch(`https://${FAKEIP_CHECK_DOMAIN}/check`, { - method: "GET", - headers: { "Content-Type": "application/json" }, - }), + () => fetch(`https://${FAKEIP_CHECK_DOMAIN}/check`, { + method: "GET", + headers: { "Content-Type": "application/json" } + }), { operationName: "getFakeIpCheck", - timeoutMs: 5e3, - }, + timeoutMs: 5e3 + } ); } // src/podkop/methods/fakeip/getIpCheck.ts async function getIpCheck() { return createBaseApiRequest( - () => - fetch(`https://${IP_CHECK_DOMAIN}/check`, { - method: "GET", - headers: { "Content-Type": "application/json" }, - }), + () => fetch(`https://${IP_CHECK_DOMAIN}/check`, { + method: "GET", + headers: { "Content-Type": "application/json" } + }), { operationName: "getIpCheck", - timeoutMs: 5e3, - }, + timeoutMs: 5e3 + } ); } // src/podkop/methods/fakeip/index.ts var RemoteFakeIPMethods = { getFakeIpCheck, - getIpCheck, + getIpCheck }; // src/podkop/services/tab.service.ts @@ -944,7 +930,7 @@ var TabService = class _TabService { subtree: true, childList: true, attributes: true, - attributeFilter: ["class"], + attributeFilter: ["class"] }); this.notify(); } @@ -953,18 +939,18 @@ var TabService = class _TabService { } getTabsInfo() { const tabs = Array.from( - document.querySelectorAll(".cbi-tab, .cbi-tab-disabled"), + document.querySelectorAll(".cbi-tab, .cbi-tab-disabled") ); return tabs.map((el) => ({ el, id: el.dataset.tab || "", - active: - el.classList.contains("cbi-tab") && - !el.classList.contains("cbi-tab-disabled"), + active: el.classList.contains("cbi-tab") && !el.classList.contains("cbi-tab-disabled") })); } getActiveTabId() { - const active = document.querySelector(".cbi-tab:not(.cbi-tab-disabled)"); + const active = document.querySelector( + ".cbi-tab:not(.cbi-tab-disabled)" + ); return active?.dataset.tab || null; } notify() { @@ -997,23 +983,23 @@ var DIAGNOSTICS_CHECKS_MAP = { ["DNS" /* DNS */]: { order: 1, title: _("DNS checks"), - code: "DNS" /* DNS */, + code: "DNS" /* DNS */ }, ["SINGBOX" /* SINGBOX */]: { order: 2, title: _("Sing-box checks"), - code: "SINGBOX" /* SINGBOX */, + code: "SINGBOX" /* SINGBOX */ }, ["NFT" /* NFT */]: { order: 3, title: _("Nftables checks"), - code: "NFT" /* NFT */, + code: "NFT" /* NFT */ }, ["FAKEIP" /* FAKEIP */]: { order: 4, title: _("FakeIP checks"), - code: "FAKEIP" /* FAKEIP */, - }, + code: "FAKEIP" /* FAKEIP */ + } }; // src/podkop/tabs/diagnostic/diagnostic.store.ts @@ -1025,33 +1011,33 @@ var initialDiagnosticStore = { luci_app_version: "loading", sing_box_version: "loading", openwrt_version: "loading", - device_model: "loading", + device_model: "loading" }, diagnosticsActions: { restart: { - loading: false, + loading: false }, start: { - loading: false, + loading: false }, stop: { - loading: false, + loading: false }, enable: { - loading: false, + loading: false }, disable: { - loading: false, + loading: false }, globalCheck: { - loading: false, + loading: false }, viewLogs: { - loading: false, + loading: false }, showSingBoxConfig: { - loading: false, - }, + loading: false + } }, diagnosticsRunAction: { loading: false }, diagnosticsChecks: [ @@ -1061,7 +1047,7 @@ var initialDiagnosticStore = { order: DIAGNOSTICS_CHECKS_MAP.DNS.order, description: _("Not running"), items: [], - state: "skipped", + state: "skipped" }, { code: "SINGBOX" /* SINGBOX */, @@ -1069,7 +1055,7 @@ var initialDiagnosticStore = { order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, description: _("Not running"), items: [], - state: "skipped", + state: "skipped" }, { code: "NFT" /* NFT */, @@ -1077,7 +1063,7 @@ var initialDiagnosticStore = { order: DIAGNOSTICS_CHECKS_MAP.NFT.order, description: _("Not running"), items: [], - state: "skipped", + state: "skipped" }, { code: "FAKEIP" /* FAKEIP */, @@ -1085,9 +1071,9 @@ var initialDiagnosticStore = { order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, description: _("Not running"), items: [], - state: "skipped", - }, - ], + state: "skipped" + } + ] }; var loadingDiagnosticsChecksStore = { diagnosticsChecks: [ @@ -1097,7 +1083,7 @@ var loadingDiagnosticsChecksStore = { order: DIAGNOSTICS_CHECKS_MAP.DNS.order, description: _("Queued"), items: [], - state: "skipped", + state: "skipped" }, { code: "SINGBOX" /* SINGBOX */, @@ -1105,7 +1091,7 @@ var loadingDiagnosticsChecksStore = { order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, description: _("Queued"), items: [], - state: "skipped", + state: "skipped" }, { code: "NFT" /* NFT */, @@ -1113,7 +1099,7 @@ var loadingDiagnosticsChecksStore = { order: DIAGNOSTICS_CHECKS_MAP.NFT.order, description: _("Queued"), items: [], - state: "skipped", + state: "skipped" }, { code: "FAKEIP" /* FAKEIP */, @@ -1121,21 +1107,22 @@ var loadingDiagnosticsChecksStore = { order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, description: _("Queued"), items: [], - state: "skipped", - }, - ], + state: "skipped" + } + ] }; // src/podkop/services/store.service.ts function jsonStableStringify(obj) { return JSON.stringify(obj, (_2, value) => { if (value && typeof value === "object" && !Array.isArray(value)) { - return Object.keys(value) - .sort() - .reduce((acc, key) => { + return Object.keys(value).sort().reduce( + (acc, key) => { acc[key] = value[key]; return acc; - }, {}); + }, + {} + ); } return value; }); @@ -1218,35 +1205,35 @@ var StoreService = class { var initialStore = { tabService: { current: "", - all: [], + all: [] }, bandwidthWidget: { loading: true, failed: false, - data: { up: 0, down: 0 }, + data: { up: 0, down: 0 } }, trafficTotalWidget: { loading: true, failed: false, - data: { downloadTotal: 0, uploadTotal: 0 }, + data: { downloadTotal: 0, uploadTotal: 0 } }, systemInfoWidget: { loading: true, failed: false, - data: { connections: 0, memory: 0 }, + data: { connections: 0, memory: 0 } }, servicesInfoWidget: { loading: true, failed: false, - data: { singbox: 0, podkop: 0 }, + data: { singbox: 0, podkop: 0 } }, sectionsWidget: { loading: true, failed: false, latencyFetching: false, - data: [], + data: [] }, - ...initialDiagnosticStore, + ...initialDiagnosticStore }; var store = new StoreService(initialStore); @@ -1310,9 +1297,7 @@ var Logger = class { } download(filename = "logs.txt") { if (typeof document === "undefined") { - console.warn( - "Logger.download() \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435", - ); + console.warn("Logger.download() \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435"); return; } downloadAsTxt(this.getLogs(), filename); @@ -1346,7 +1331,7 @@ var PodkopLogWatcher = class _PodkopLogWatcher { this.intervalMs = options?.intervalMs ?? 5e3; logger.info( "[PodkopLogWatcher]", - `initialized (interval: ${this.intervalMs}ms)`, + `initialized (interval: ${this.intervalMs}ms)` ); } async checkOnce() { @@ -1355,10 +1340,7 @@ var PodkopLogWatcher = class _PodkopLogWatcher { return; } if (this.paused) { - logger.debug( - "[PodkopLogWatcher]", - "skipped check \u2014 tab not visible", - ); + logger.debug("[PodkopLogWatcher]", "skipped check \u2014 tab not visible"); return; } try { @@ -1388,7 +1370,7 @@ var PodkopLogWatcher = class _PodkopLogWatcher { this.timer = setInterval(() => this.checkOnce(), this.intervalMs); logger.info( "[PodkopLogWatcher]", - `started (interval: ${this.intervalMs}ms)`, + `started (interval: ${this.intervalMs}ms)` ); } stop() { @@ -1421,8 +1403,8 @@ function coreService() { store.set({ tabService: { current: activeId || "", - all: tabs.map((tab) => tab.id), - }, + all: tabs.map((tab) => tab.id) + } }); }); const watcher = PodkopLogWatcher.getInstance(); @@ -1437,14 +1419,11 @@ function coreService() { { intervalMs: 3e3, onNewLog: (line) => { - if ( - line.toLowerCase().includes("[error]") || - line.toLowerCase().includes("[fatal]") - ) { + if (line.toLowerCase().includes("[error]") || line.toLowerCase().includes("[fatal]")) { ui.addNotification("Podkop Error", E("div", {}, line), "error"); } - }, - }, + } + } ); watcher.start(); } @@ -1466,17 +1445,14 @@ var SocketManager = class _SocketManager { resetAll() { for (const [url, ws] of this.sockets.entries()) { try { - if ( - ws.readyState === WebSocket.OPEN || - ws.readyState === WebSocket.CONNECTING - ) { + if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) { ws.close(); } } catch (err) { logger.error( "[SOCKET]", `resetAll: failed to close socket ${url}`, - err, + err ); } } @@ -1495,7 +1471,7 @@ var SocketManager = class _SocketManager { logger.error( "[SOCKET]", `failed to construct WebSocket for ${url}:`, - err, + err ); this.triggerError(url, err instanceof Event ? err : String(err)); return; @@ -1597,23 +1573,23 @@ function renderFailedState() { "div", { class: "pdk_dashboard-page__outbound-section centered", - style: "height: 127px", + style: "height: 127px" }, - E("span", {}, [E("span", {}, _("Dashboard currently unavailable"))]), + E("span", {}, [E("span", {}, _("Dashboard currently unavailable"))]) ); } function renderLoadingState() { return E("div", { id: "dashboard-sections-grid-skeleton", class: "pdk_dashboard-page__outbound-section skeleton", - style: "height: 127px", + style: "height: 127px" }); } function renderDefaultState({ section, onChooseOutbound, onTestLatency, - latencyFetching, + latencyFetching }) { function testLatency() { if (section.withTagSelect) { @@ -1640,9 +1616,7 @@ function renderDefaultState({ "div", { class: `pdk_dashboard-page__outbound-grid__item ${outbound.selected ? "pdk_dashboard-page__outbound-grid__item--active" : ""} ${section.withTagSelect ? "pdk_dashboard-page__outbound-grid__item--selectable" : ""}`, - click: () => - section.withTagSelect && - onChooseOutbound(section.code, outbound.code), + click: () => section.withTagSelect && onChooseOutbound(section.code, outbound.code) }, [ E("b", {}, outbound.displayName), @@ -1650,15 +1624,15 @@ function renderDefaultState({ E( "div", { class: "pdk_dashboard-page__outbound-grid__item__type" }, - outbound.type, + outbound.type ), E( "div", { class: getLatencyClass() }, - outbound.latency ? `${outbound.latency}ms` : "N/A", - ), - ]), - ], + outbound.latency ? `${outbound.latency}ms` : "N/A" + ) + ]) + ] ); } return E("div", { class: "pdk_dashboard-page__outbound-section" }, [ @@ -1667,26 +1641,24 @@ function renderDefaultState({ E( "div", { - class: "pdk_dashboard-page__outbound-section__title-section__title", + class: "pdk_dashboard-page__outbound-section__title-section__title" }, - section.displayName, + section.displayName ), - latencyFetching - ? E("div", { class: "skeleton", style: "width: 99px; height: 28px" }) - : E( - "button", - { - class: "btn dashboard-sections-grid-item-test-latency", - click: () => testLatency(), - }, - _("Test latency"), - ), + latencyFetching ? E("div", { class: "skeleton", style: "width: 99px; height: 28px" }) : E( + "button", + { + class: "btn dashboard-sections-grid-item-test-latency", + click: () => testLatency() + }, + _("Test latency") + ) ]), E( "div", { class: "pdk_dashboard-page__outbound-grid" }, - section.outbounds.map((outbound) => renderOutbound(outbound)), - ), + section.outbounds.map((outbound) => renderOutbound(outbound)) + ) ]); } function renderSections(props) { @@ -1706,9 +1678,9 @@ function renderFailedState2() { { id: "", style: "height: 78px", - class: "pdk_dashboard-page__widgets-section__item centered", + class: "pdk_dashboard-page__widgets-section__item centered" }, - _("Currently unavailable"), + _("Currently unavailable") ); } function renderLoadingState2() { @@ -1717,9 +1689,9 @@ function renderLoadingState2() { { id: "", style: "height: 78px", - class: "pdk_dashboard-page__widgets-section__item skeleton", + class: "pdk_dashboard-page__widgets-section__item skeleton" }, - "", + "" ); } function renderDefaultState2({ title, items }) { @@ -1727,28 +1699,28 @@ function renderDefaultState2({ title, items }) { E( "b", { class: "pdk_dashboard-page__widgets-section__item__title" }, - title, + title ), - ...items.map((item) => - E( + ...items.map( + (item) => E( "div", { - class: `pdk_dashboard-page__widgets-section__item__row ${item?.attributes?.class || ""}`, + class: `pdk_dashboard-page__widgets-section__item__row ${item?.attributes?.class || ""}` }, [ E( "span", { class: "pdk_dashboard-page__widgets-section__item__row__key" }, - `${item.key}: `, + `${item.key}: ` ), E( "span", { class: "pdk_dashboard-page__widgets-section__item__row__value" }, - item.value, - ), - ], - ), - ), + item.value + ) + ] + ) + ) ]); } function renderWidget(props) { @@ -1767,7 +1739,7 @@ function render() { "div", { id: "dashboard-status", - class: "pdk_dashboard-page", + class: "pdk_dashboard-page" }, [ // Widgets section @@ -1775,23 +1747,23 @@ function render() { E( "div", { id: "dashboard-widget-traffic" }, - renderWidget({ loading: true, failed: false, title: "", items: [] }), + renderWidget({ loading: true, failed: false, title: "", items: [] }) ), E( "div", { id: "dashboard-widget-traffic-total" }, - renderWidget({ loading: true, failed: false, title: "", items: [] }), + renderWidget({ loading: true, failed: false, title: "", items: [] }) ), E( "div", { id: "dashboard-widget-system-info" }, - renderWidget({ loading: true, failed: false, title: "", items: [] }), + renderWidget({ loading: true, failed: false, title: "", items: [] }) ), E( "div", { id: "dashboard-widget-service-info" }, - renderWidget({ loading: true, failed: false, title: "", items: [] }), - ), + renderWidget({ loading: true, failed: false, title: "", items: [] }) + ) ]), // All outbounds E( @@ -1804,14 +1776,16 @@ function render() { code: "", displayName: "", outbounds: [], - withTagSelect: false, + withTagSelect: false }, - onTestLatency: () => {}, - onChooseOutbound: () => {}, - latencyFetching: false, - }), - ), - ], + onTestLatency: () => { + }, + onChooseOutbound: () => { + }, + latencyFetching: false + }) + ) + ] ); } @@ -1831,15 +1805,15 @@ function prettyBytes(n) { async function fetchServicesInfo() { const [podkop, singbox] = await Promise.all([ PodkopShellMethods.getStatus(), - PodkopShellMethods.getSingBoxStatus(), + PodkopShellMethods.getSingBoxStatus() ]); if (!podkop.success || !singbox.success) { store.set({ servicesInfoWidget: { loading: false, failed: true, - data: { singbox: 0, podkop: 0 }, - }, + data: { singbox: 0, podkop: 0 } + } }); } if (podkop.success && singbox.success) { @@ -1847,8 +1821,8 @@ async function fetchServicesInfo() { servicesInfoWidget: { loading: false, failed: false, - data: { singbox: singbox.data.running, podkop: podkop.data.enabled }, - }, + data: { singbox: singbox.data.running, podkop: podkop.data.enabled } + } }); } } @@ -1859,8 +1833,8 @@ async function fetchDashboardSections() { store.set({ sectionsWidget: { ...prev, - failed: false, - }, + failed: false + } }); const { data, success } = await CustomPodkopMethods.getDashboardSections(); if (!success) { @@ -1871,8 +1845,8 @@ async function fetchDashboardSections() { latencyFetching: false, loading: false, failed: !success, - data, - }, + data + } }); } async function connectToClashSockets() { @@ -1884,24 +1858,24 @@ async function connectToClashSockets() { bandwidthWidget: { loading: false, failed: false, - data: { up: parsedMsg.up, down: parsedMsg.down }, - }, + data: { up: parsedMsg.up, down: parsedMsg.down } + } }); }, (_err) => { logger.error( "[DASHBOARD]", "connectToClashSockets - traffic: failed to connect to", - getClashWsUrl(), + getClashWsUrl() ); store.set({ bandwidthWidget: { loading: false, failed: true, - data: { up: 0, down: 0 }, - }, + data: { up: 0, down: 0 } + } }); - }, + } ); socket.subscribe( `${getClashWsUrl()}/connections?token=`, @@ -1913,41 +1887,41 @@ async function connectToClashSockets() { failed: false, data: { downloadTotal: parsedMsg.downloadTotal, - uploadTotal: parsedMsg.uploadTotal, - }, + uploadTotal: parsedMsg.uploadTotal + } }, systemInfoWidget: { loading: false, failed: false, data: { connections: parsedMsg.connections?.length, - memory: parsedMsg.memory, - }, - }, + memory: parsedMsg.memory + } + } }); }, (_err) => { logger.error( "[DASHBOARD]", "connectToClashSockets - connections: failed to connect to", - getClashWsUrl(), + getClashWsUrl() ); store.set({ trafficTotalWidget: { loading: false, failed: true, - data: { downloadTotal: 0, uploadTotal: 0 }, + data: { downloadTotal: 0, uploadTotal: 0 } }, systemInfoWidget: { loading: false, failed: true, data: { connections: 0, - memory: 0, - }, - }, + memory: 0 + } + } }); - }, + } ); } async function handleChooseOutbound(selector, tag) { @@ -1958,32 +1932,32 @@ async function handleTestGroupLatency(tag) { store.set({ sectionsWidget: { ...store.get().sectionsWidget, - latencyFetching: true, - }, + latencyFetching: true + } }); await PodkopShellMethods.getClashApiGroupLatency(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { ...store.get().sectionsWidget, - latencyFetching: false, - }, + latencyFetching: false + } }); } async function handleTestProxyLatency(tag) { store.set({ sectionsWidget: { ...store.get().sectionsWidget, - latencyFetching: true, - }, + latencyFetching: true + } }); await PodkopShellMethods.getClashApiProxyLatency(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { ...store.get().sectionsWidget, - latencyFetching: false, - }, + latencyFetching: false + } }); } async function renderSectionsWidget() { @@ -1998,18 +1972,20 @@ async function renderSectionsWidget() { code: "", displayName: "", outbounds: [], - withTagSelect: false, + withTagSelect: false }, - onTestLatency: () => {}, - onChooseOutbound: () => {}, - latencyFetching: sectionsWidget.latencyFetching, + onTestLatency: () => { + }, + onChooseOutbound: () => { + }, + latencyFetching: sectionsWidget.latencyFetching }); return preserveScrollForPage(() => { container.replaceChildren(renderedWidget); }); } - const renderedWidgets = sectionsWidget.data.map((section) => - renderSections({ + const renderedWidgets = sectionsWidget.data.map( + (section) => renderSections({ loading: sectionsWidget.loading, failed: sectionsWidget.failed, section, @@ -2022,8 +1998,8 @@ async function renderSectionsWidget() { }, onChooseOutbound: (selector, tag) => { handleChooseOutbound(selector, tag); - }, - }), + } + }) ); return preserveScrollForPage(() => { container.replaceChildren(...renderedWidgets); @@ -2038,7 +2014,7 @@ async function renderBandwidthWidget() { loading: traffic.loading, failed: traffic.failed, title: "", - items: [], + items: [] }); return container.replaceChildren(renderedWidget2); } @@ -2048,8 +2024,8 @@ async function renderBandwidthWidget() { title: _("Traffic"), items: [ { key: _("Uplink"), value: `${prettyBytes(traffic.data.up)}/s` }, - { key: _("Downlink"), value: `${prettyBytes(traffic.data.down)}/s` }, - ], + { key: _("Downlink"), value: `${prettyBytes(traffic.data.down)}/s` } + ] }); container.replaceChildren(renderedWidget); } @@ -2062,7 +2038,7 @@ async function renderTrafficTotalWidget() { loading: trafficTotalWidget.loading, failed: trafficTotalWidget.failed, title: "", - items: [], + items: [] }); return container.replaceChildren(renderedWidget2); } @@ -2073,13 +2049,13 @@ async function renderTrafficTotalWidget() { items: [ { key: _("Uplink"), - value: String(prettyBytes(trafficTotalWidget.data.uploadTotal)), + value: String(prettyBytes(trafficTotalWidget.data.uploadTotal)) }, { key: _("Downlink"), - value: String(prettyBytes(trafficTotalWidget.data.downloadTotal)), - }, - ], + value: String(prettyBytes(trafficTotalWidget.data.downloadTotal)) + } + ] }); container.replaceChildren(renderedWidget); } @@ -2092,7 +2068,7 @@ async function renderSystemInfoWidget() { loading: systemInfoWidget.loading, failed: systemInfoWidget.failed, title: "", - items: [], + items: [] }); return container.replaceChildren(renderedWidget2); } @@ -2103,13 +2079,13 @@ async function renderSystemInfoWidget() { items: [ { key: _("Active Connections"), - value: String(systemInfoWidget.data.connections), + value: String(systemInfoWidget.data.connections) }, { key: _("Memory Usage"), - value: String(prettyBytes(systemInfoWidget.data.memory)), - }, - ], + value: String(prettyBytes(systemInfoWidget.data.memory)) + } + ] }); container.replaceChildren(renderedWidget); } @@ -2122,7 +2098,7 @@ async function renderServicesInfoWidget() { loading: servicesInfoWidget.loading, failed: servicesInfoWidget.failed, title: "", - items: [], + items: [] }); return container.replaceChildren(renderedWidget2); } @@ -2133,27 +2109,19 @@ async function renderServicesInfoWidget() { items: [ { key: _("Podkop"), - value: servicesInfoWidget.data.podkop - ? _("\u2714 Enabled") - : _("\u2718 Disabled"), + value: servicesInfoWidget.data.podkop ? _("\u2714 Enabled") : _("\u2718 Disabled"), attributes: { - class: servicesInfoWidget.data.podkop - ? "pdk_dashboard-page__widgets-section__item__row--success" - : "pdk_dashboard-page__widgets-section__item__row--error", - }, + class: servicesInfoWidget.data.podkop ? "pdk_dashboard-page__widgets-section__item__row--success" : "pdk_dashboard-page__widgets-section__item__row--error" + } }, { key: _("Sing-box"), - value: servicesInfoWidget.data.singbox - ? _("\u2714 Running") - : _("\u2718 Stopped"), + value: servicesInfoWidget.data.singbox ? _("\u2714 Running") : _("\u2718 Stopped"), attributes: { - class: servicesInfoWidget.data.singbox - ? "pdk_dashboard-page__widgets-section__item__row--success" - : "pdk_dashboard-page__widgets-section__item__row--error", - }, - }, - ], + class: servicesInfoWidget.data.singbox ? "pdk_dashboard-page__widgets-section__item__row--success" : "pdk_dashboard-page__widgets-section__item__row--error" + } + } + ] }); container.replaceChildren(renderedWidget); } @@ -2188,27 +2156,24 @@ function onPageUnmount() { "trafficTotalWidget", "systemInfoWidget", "servicesInfoWidget", - "sectionsWidget", + "sectionsWidget" ]); socket.resetAll(); } function registerLifecycleListeners() { store.subscribe((next, prev, diff) => { - if ( - diff.tabService && - next.tabService.current !== prev.tabService.current - ) { + if (diff.tabService && next.tabService.current !== prev.tabService.current) { logger.debug( "[DASHBOARD]", "active tab diff event, active tab:", - diff.tabService.current, + diff.tabService.current ); const isDashboardVisible = next.tabService.current === "dashboard"; if (isDashboardVisible) { logger.debug( "[DASHBOARD]", "registerLifecycleListeners", - "onPageMount", + "onPageMount" ); return onPageMount(); } @@ -2216,7 +2181,7 @@ function registerLifecycleListeners() { logger.debug( "[DASHBOARD]", "registerLifecycleListeners", - "onPageUnmount", + "onPageUnmount" ); return onPageUnmount(); } @@ -2356,7 +2321,7 @@ var styles = ` var DashboardTab = { render, initController, - styles, + styles }; // src/podkop/tabs/diagnostic/renderDiagnostic.ts @@ -2366,13 +2331,13 @@ function render2() { E("div", { id: "pdk_diagnostic-page-run-check" }), E("div", { class: "pdk_diagnostic-page__checks", - id: "pdk_diagnostic-page-checks", - }), + id: "pdk_diagnostic-page-checks" + }) ]), E("div", { class: "pdk_diagnostic-page__right-bar" }, [ E("div", { id: "pdk_diagnostic-page-actions" }), - E("div", { id: "pdk_diagnostic-page-system-info" }), - ]), + E("div", { id: "pdk_diagnostic-page-system-info" }) + ]) ]); } @@ -2382,11 +2347,11 @@ function updateCheckStore(check, minified) { const other = diagnosticsChecks.filter((item) => item.code !== check.code); const smallCheck = { ...check, - items: check.items.filter((item) => item.state !== "success"), + items: check.items.filter((item) => item.state !== "success") }; const targetCheck = minified ? smallCheck : check; store.set({ - diagnosticsChecks: [...other, targetCheck], + diagnosticsChecks: [...other, targetCheck] }); } @@ -2399,7 +2364,7 @@ async function runDnsCheck() { title, description: _("Checking dns, please wait"), state: "loading", - items: [], + items: [] }); const dnsChecks = await PodkopShellMethods.checkDNSAvailable(); if (!dnsChecks.success) { @@ -2409,21 +2374,13 @@ async function runDnsCheck() { title, description: _("Cannot receive DNS checks result"), state: "error", - items: [], + items: [] }); throw new Error("DNS checks failed"); } const data = dnsChecks.data; - const allGood = - Boolean(data.dns_on_router) && - Boolean(data.dhcp_config_status) && - Boolean(data.bootstrap_dns_status) && - Boolean(data.dns_status); - const atLeastOneGood = - Boolean(data.dns_on_router) || - Boolean(data.dhcp_config_status) || - Boolean(data.bootstrap_dns_status) || - Boolean(data.dns_status); + const allGood = Boolean(data.dns_on_router) && Boolean(data.dhcp_config_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); + const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.dhcp_config_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); function getStatus() { if (allGood) { return "success"; @@ -2440,29 +2397,32 @@ async function runDnsCheck() { description: _("DNS checks passed"), state: getStatus(), items: [ - ...insertIf(data.dns_type === "doh" || data.dns_type === "dot", [ - { - state: data.bootstrap_dns_status ? "success" : "error", - key: _("Bootsrap DNS"), - value: data.bootstrap_dns_server, - }, - ]), + ...insertIf( + data.dns_type === "doh" || data.dns_type === "dot", + [ + { + state: data.bootstrap_dns_status ? "success" : "error", + key: _("Bootsrap DNS"), + value: data.bootstrap_dns_server + } + ] + ), { state: data.dns_status ? "success" : "error", key: _("Main DNS"), - value: `${data.dns_server} [${data.dns_type}]`, + value: `${data.dns_server} [${data.dns_type}]` }, { state: data.dns_on_router ? "success" : "error", key: _("DNS on router"), - value: "", + value: "" }, { state: data.dhcp_config_status ? "success" : "error", key: _("DHCP has DNS server"), - value: "", - }, - ], + value: "" + } + ] }); if (!atLeastOneGood) { throw new Error("DNS checks failed"); @@ -2478,7 +2438,7 @@ async function runSingBoxCheck() { title, description: _("Checking sing-box, please wait"), state: "loading", - items: [], + items: [] }); const singBoxChecks = await PodkopShellMethods.checkSingBox(); if (!singBoxChecks.success) { @@ -2488,25 +2448,13 @@ async function runSingBoxCheck() { title, description: _("Cannot receive Sing-box checks result"), state: "error", - items: [], + items: [] }); throw new Error("Sing-box checks failed"); } const data = singBoxChecks.data; - const allGood = - Boolean(data.sing_box_installed) && - Boolean(data.sing_box_version_ok) && - Boolean(data.sing_box_service_exist) && - Boolean(data.sing_box_autostart_disabled) && - Boolean(data.sing_box_process_running) && - Boolean(data.sing_box_ports_listening); - const atLeastOneGood = - Boolean(data.sing_box_installed) || - Boolean(data.sing_box_version_ok) || - Boolean(data.sing_box_service_exist) || - Boolean(data.sing_box_autostart_disabled) || - Boolean(data.sing_box_process_running) || - Boolean(data.sing_box_ports_listening); + const allGood = Boolean(data.sing_box_installed) && Boolean(data.sing_box_version_ok) && Boolean(data.sing_box_service_exist) && Boolean(data.sing_box_autostart_disabled) && Boolean(data.sing_box_process_running) && Boolean(data.sing_box_ports_listening); + const atLeastOneGood = Boolean(data.sing_box_installed) || Boolean(data.sing_box_version_ok) || Boolean(data.sing_box_service_exist) || Boolean(data.sing_box_autostart_disabled) || Boolean(data.sing_box_process_running) || Boolean(data.sing_box_ports_listening); function getStatus() { if (allGood) { return "success"; @@ -2526,34 +2474,34 @@ async function runSingBoxCheck() { { state: data.sing_box_installed ? "success" : "error", key: _("Sing-box installed"), - value: "", + value: "" }, { state: data.sing_box_version_ok ? "success" : "error", key: _("Sing-box version >= 1.12.4"), - value: "", + value: "" }, { state: data.sing_box_service_exist ? "success" : "error", key: _("Sing-box service exist"), - value: "", + value: "" }, { state: data.sing_box_autostart_disabled ? "success" : "error", key: _("Sing-box autostart disabled"), - value: "", + value: "" }, { state: data.sing_box_process_running ? "success" : "error", key: _("Sing-box process running"), - value: "", + value: "" }, { state: data.sing_box_ports_listening ? "success" : "error", key: _("Sing-box listening ports"), - value: "", - }, - ], + value: "" + } + ] }); if (!atLeastOneGood || !data.sing_box_process_running) { throw new Error("Sing-box checks failed"); @@ -2569,7 +2517,7 @@ async function runNftCheck() { title, description: _("Checking nftables, please wait"), state: "loading", - items: [], + items: [] }); await RemoteFakeIPMethods.getFakeIpCheck(); await RemoteFakeIPMethods.getIpCheck(); @@ -2581,29 +2529,13 @@ async function runNftCheck() { title, description: _("Cannot receive nftables checks result"), state: "error", - items: [], + items: [] }); throw new Error("Nftables checks failed"); } const data = nftablesChecks.data; - const allGood = - Boolean(data.table_exist) && - Boolean(data.rules_mangle_exist) && - Boolean(data.rules_mangle_counters) && - Boolean(data.rules_mangle_output_exist) && - Boolean(data.rules_mangle_output_counters) && - Boolean(data.rules_proxy_exist) && - Boolean(data.rules_proxy_counters) && - Boolean(data.rules_other_mark_exist); - const atLeastOneGood = - Boolean(data.table_exist) || - Boolean(data.rules_mangle_exist) || - Boolean(data.rules_mangle_counters) || - Boolean(data.rules_mangle_output_exist) || - Boolean(data.rules_mangle_output_counters) || - Boolean(data.rules_proxy_exist) || - Boolean(data.rules_proxy_counters) || - Boolean(data.rules_other_mark_exist); + const allGood = Boolean(data.table_exist) && Boolean(data.rules_mangle_exist) && Boolean(data.rules_mangle_counters) && Boolean(data.rules_mangle_output_exist) && Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && Boolean(data.rules_other_mark_exist); + const atLeastOneGood = Boolean(data.table_exist) || Boolean(data.rules_mangle_exist) || Boolean(data.rules_mangle_counters) || Boolean(data.rules_mangle_output_exist) || Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || Boolean(data.rules_other_mark_exist); function getStatus() { if (allGood) { return "success"; @@ -2617,54 +2549,50 @@ async function runNftCheck() { order, code, title, - description: allGood - ? _("Nftables checks passed") - : _("Nftables checks partially passed"), + description: allGood ? _("Nftables checks passed") : _("Nftables checks partially passed"), state: getStatus(), items: [ { state: data.table_exist ? "success" : "error", key: _("Table exist"), - value: "", + value: "" }, { state: data.rules_mangle_exist ? "success" : "error", key: _("Rules mangle exist"), - value: "", + value: "" }, { state: data.rules_mangle_counters ? "success" : "error", key: _("Rules mangle counters"), - value: "", + value: "" }, { state: data.rules_mangle_output_exist ? "success" : "error", key: _("Rules mangle output exist"), - value: "", + value: "" }, { state: data.rules_mangle_output_counters ? "success" : "error", key: _("Rules mangle output counters"), - value: "", + value: "" }, { state: data.rules_proxy_exist ? "success" : "error", key: _("Rules proxy exist"), - value: "", + value: "" }, { state: data.rules_proxy_counters ? "success" : "error", key: _("Rules proxy counters"), - value: "", + value: "" }, { state: !data.rules_other_mark_exist ? "success" : "warning", - key: !data.rules_other_mark_exist - ? _("No other marking rules found") - : _("Additional marking rules found"), - value: "", - }, - ], + key: !data.rules_other_mark_exist ? _("No other marking rules found") : _("Additional marking rules found"), + value: "" + } + ] }); if (!atLeastOneGood) { throw new Error("Nftables checks failed"); @@ -2680,39 +2608,34 @@ async function runFakeIPCheck() { title, description: _("Checking FakeIP, please wait"), state: "loading", - items: [], + items: [] }); const routerFakeIPResponse = await PodkopShellMethods.checkFakeIP(); const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); const checks = { router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, - browserFakeIP: - checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, - differentIP: - checkFakeIPResponse.success && - checkIPResponse.success && - checkFakeIPResponse.data.IP !== checkIPResponse.data.IP, + browserFakeIP: checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, + differentIP: checkFakeIPResponse.success && checkIPResponse.success && checkFakeIPResponse.data.IP !== checkIPResponse.data.IP }; const allGood = checks.router || checks.browserFakeIP || checks.differentIP; - const atLeastOneGood = - checks.router && checks.browserFakeIP && checks.differentIP; + const atLeastOneGood = checks.router && checks.browserFakeIP && checks.differentIP; function getMeta() { if (allGood) { return { state: "success", - description: _("FakeIP checks passed"), + description: _("FakeIP checks passed") }; } if (atLeastOneGood) { return { state: "warning", - description: _("FakeIP checks partially passed"), + description: _("FakeIP checks partially passed") }; } return { state: "error", - description: _("FakeIP checks failed"), + description: _("FakeIP checks failed") }; } const { state, description } = getMeta(); @@ -2725,28 +2648,22 @@ async function runFakeIPCheck() { items: [ { state: checks.router ? "success" : "warning", - key: checks.router - ? _("Router DNS is routed through sing-box") - : _("Router DNS is not routed through sing-box"), - value: "", + key: checks.router ? _("Router DNS is routed through sing-box") : _("Router DNS is not routed through sing-box"), + value: "" }, { state: checks.browserFakeIP ? "success" : "error", - key: checks.browserFakeIP - ? _("Browser is using FakeIP correctly") - : _("Browser is not using FakeIP"), - value: "", + key: checks.browserFakeIP ? _("Browser is using FakeIP correctly") : _("Browser is not using FakeIP"), + value: "" }, ...insertIf(checks.browserFakeIP, [ { state: checks.differentIP ? "success" : "error", - key: checks.differentIP - ? _("Proxy traffic is routed via FakeIP") - : _("Proxy traffic is not routed via FakeIP"), - value: "", - }, - ]), - ], + key: checks.differentIP ? _("Proxy traffic is routed via FakeIP") : _("Proxy traffic is not routed via FakeIP"), + value: "" + } + ]) + ] }); } @@ -2818,11 +2735,11 @@ function renderLoaderCircleIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-loader-circle rotate", + class: "lucide lucide-loader-circle rotate" }, [ svgEl("path", { - d: "M21 12a9 9 0 1 1-6.219-8.56", + d: "M21 12a9 9 0 1 1-6.219-8.56" }), svgEl("animateTransform", { attributeName: "transform", @@ -2831,9 +2748,9 @@ function renderLoaderCircleIcon24() { from: "0 12 12", to: "360 12 12", dur: "1s", - repeatCount: "indefinite", - }), - ], + repeatCount: "indefinite" + }) + ] ); } @@ -2852,27 +2769,27 @@ function renderCircleAlertIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-alert-icon lucide-circle-alert", + class: "lucide lucide-circle-alert-icon lucide-circle-alert" }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10", + r: "10" }), svgEl("line", { x1: "12", y1: "8", x2: "12", - y2: "12", + y2: "12" }), svgEl("line", { x1: "12", y1: "16", x2: "12.01", - y2: "16", - }), - ], + y2: "16" + }) + ] ); } @@ -2891,18 +2808,18 @@ function renderCircleCheckIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-check-icon lucide-circle-check", + class: "lucide lucide-circle-check-icon lucide-circle-check" }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10", + r: "10" }), svgEl("path", { - d: "M9 12l2 2 4-4", - }), - ], + d: "M9 12l2 2 4-4" + }) + ] ); } @@ -2921,21 +2838,21 @@ function renderCircleSlashIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-slash-icon lucide-circle-slash", + class: "lucide lucide-circle-slash-icon lucide-circle-slash" }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10", + r: "10" }), svgEl("line", { x1: "9", y1: "15", x2: "15", - y2: "9", - }), - ], + y2: "9" + }) + ] ); } @@ -2954,21 +2871,21 @@ function renderCircleXIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-x-icon lucide-circle-x", + class: "lucide lucide-circle-x-icon lucide-circle-x" }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10", + r: "10" }), svgEl("path", { - d: "M15 9L9 15", + d: "M15 9L9 15" }), svgEl("path", { - d: "M9 9L15 15", - }), - ], + d: "M9 9L15 15" + }) + ] ); } @@ -2985,13 +2902,13 @@ function renderCheckIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-check-icon lucide-check", + class: "lucide lucide-check-icon lucide-check" }, [ svgEl("path", { - d: "M20 6 9 17l-5-5", - }), - ], + d: "M20 6 9 17l-5-5" + }) + ] ); } @@ -3008,9 +2925,9 @@ function renderXIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-x-icon lucide-x", + class: "lucide lucide-x-icon lucide-x" }, - [svgEl("path", { d: "M18 6 6 18" }), svgEl("path", { d: "m6 6 12 12" })], + [svgEl("path", { d: "M18 6 6 18" }), svgEl("path", { d: "m6 6 12 12" })] ); } @@ -3027,15 +2944,15 @@ function renderTriangleAlertIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-triangle-alert-icon lucide-triangle-alert", + class: "lucide lucide-triangle-alert-icon lucide-triangle-alert" }, [ svgEl("path", { - d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3", + d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }), svgEl("path", { d: "M12 9v4" }), - svgEl("path", { d: "M12 17h.01" }), - ], + svgEl("path", { d: "M12 17h.01" }) + ] ); } @@ -3052,7 +2969,7 @@ function renderPauseIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-pause-icon lucide-pause", + class: "lucide lucide-pause-icon lucide-pause" }, [ svgEl("rect", { @@ -3060,16 +2977,16 @@ function renderPauseIcon24() { y: "3", width: "5", height: "18", - rx: "1", + rx: "1" }), svgEl("rect", { x: "5", y: "3", width: "5", height: "18", - rx: "1", - }), - ], + rx: "1" + }) + ] ); } @@ -3086,13 +3003,13 @@ function renderPlayIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-play-icon lucide-play", + class: "lucide lucide-play-icon lucide-play" }, [ svgEl("path", { - d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z", - }), - ], + d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" + }) + ] ); } @@ -3109,16 +3026,16 @@ function renderRotateCcwIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-rotate-ccw-icon lucide-rotate-ccw", + class: "lucide lucide-rotate-ccw-icon lucide-rotate-ccw" }, [ svgEl("path", { - d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", + d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }), svgEl("path", { - d: "M3 3v5h5", - }), - ], + d: "M3 3v5h5" + }) + ] ); } @@ -3135,22 +3052,22 @@ function renderCircleStopIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-stop-icon lucide-circle-stop", + class: "lucide lucide-circle-stop-icon lucide-circle-stop" }, [ svgEl("circle", { cx: "12", cy: "12", - r: "10", + r: "10" }), svgEl("rect", { x: "9", y: "9", width: "6", height: "6", - rx: "1", - }), - ], + rx: "1" + }) + ] ); } @@ -3167,18 +3084,18 @@ function renderCirclePlayIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-play-icon lucide-circle-play", + class: "lucide lucide-circle-play-icon lucide-circle-play" }, [ svgEl("path", { - d: "M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z", + d: "M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z" }), svgEl("circle", { cx: "12", cy: "12", - r: "10", - }), - ], + r: "10" + }) + ] ); } @@ -3195,16 +3112,16 @@ function renderCircleCheckBigIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-circle-check-big-icon lucide-circle-check-big", + class: "lucide lucide-circle-check-big-icon lucide-circle-check-big" }, [ svgEl("path", { - d: "M21.801 10A10 10 0 1 1 17 3.335", + d: "M21.801 10A10 10 0 1 1 17 3.335" }), svgEl("path", { - d: "m9 11 3 3L22 4", - }), - ], + d: "m9 11 3 3L22 4" + }) + ] ); } @@ -3221,7 +3138,7 @@ function renderSquareChartGanttIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-square-chart-gantt-icon lucide-square-chart-gantt", + class: "lucide lucide-square-chart-gantt-icon lucide-square-chart-gantt" }, [ svgEl("rect", { @@ -3229,12 +3146,12 @@ function renderSquareChartGanttIcon24() { height: "18", x: "3", y: "3", - rx: "2", + rx: "2" }), svgEl("path", { d: "M9 8h7" }), svgEl("path", { d: "M8 12h6" }), - svgEl("path", { d: "M11 16h5" }), - ], + svgEl("path", { d: "M11 16h5" }) + ] ); } @@ -3251,7 +3168,7 @@ function renderCogIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-cog-icon lucide-cog", + class: "lucide lucide-cog-icon lucide-cog" }, [ svgEl("path", { d: "M11 10.27 7 3.34" }), @@ -3267,8 +3184,8 @@ function renderCogIcon24() { svgEl("path", { d: "m3.34 17 1.73-1" }), svgEl("path", { d: "m3.34 7 1.73 1" }), svgEl("circle", { cx: "12", cy: "12", r: "2" }), - svgEl("circle", { cx: "12", cy: "12", r: "8" }), - ], + svgEl("circle", { cx: "12", cy: "12", r: "8" }) + ] ); } @@ -3285,12 +3202,12 @@ function renderSearchIcon24() { "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", - class: "lucide lucide-search-icon lucide-search", + class: "lucide lucide-search-icon lucide-search" }, [ svgEl("path", { d: "m21 21-4.34-4.34" }), - svgEl("circle", { cx: "11", cy: "11", r: "8" }), - ], + svgEl("circle", { cx: "11", cy: "11", r: "8" }) + ] ); } @@ -3301,12 +3218,12 @@ function renderButton({ loading, onClick, text, - icon, + icon }) { const hasIcon = !!loading || !!icon; function getWrappedIcon() { const iconWrap = E("span", { - class: "pdk-partial-button__icon", + class: "pdk-partial-button__icon" }); if (loading) { iconWrap.appendChild(renderLoaderCircleIcon24()); @@ -3325,10 +3242,8 @@ function renderButton({ ...insertIf(Boolean(disabled), ["pdk-partial-button--disabled"]), ...insertIf(Boolean(loading), ["pdk-partial-button--loading"]), ...insertIf(Boolean(hasIcon), ["pdk-partial-button--with-icon"]), - ...classNames, - ] - .filter(Boolean) - .join(" "); + ...classNames + ].filter(Boolean).join(" "); } function getDisabled() { if (loading || disabled) { @@ -3339,7 +3254,7 @@ function renderButton({ return E( "button", { class: getClass(), disabled: getDisabled(), click: onClick }, - [...insertIf(hasIcon, [getWrappedIcon()]), E("span", {}, text)], + [...insertIf(hasIcon, [getWrappedIcon()]), E("span", {}, text)] ); } @@ -3389,23 +3304,22 @@ function renderModal(text, name) { renderButton({ classNames: ["cbi-button-apply"], text: _("Download"), - onClick: () => downloadAsTxt(text, name), + onClick: () => downloadAsTxt(text, name) }), renderButton({ classNames: ["cbi-button-apply"], text: _("Copy"), - onClick: () => - copyToClipboard(` \`\`\`${name} + onClick: () => copyToClipboard(` \`\`\`${name} ${text} - \`\`\``), + \`\`\``) }), renderButton({ classNames: ["cbi-button-remove"], text: _("Close"), - onClick: ui.hideModal, - }), - ]), - ]), + onClick: ui.hideModal + }) + ]) + ]) ); } @@ -3424,7 +3338,7 @@ function renderAvailableActions({ disable, globalCheck, viewLogs, - showSingBoxConfig, + showSingBoxConfig }) { return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [ E("b", {}, "Available actions"), @@ -3435,8 +3349,8 @@ function renderAvailableActions({ icon: renderRotateCcwIcon24, text: _("Restart podkop"), loading: restart.loading, - disabled: restart.disabled, - }), + disabled: restart.disabled + }) ]), ...insertIf(stop.visible, [ renderButton({ @@ -3445,8 +3359,8 @@ function renderAvailableActions({ icon: renderCircleStopIcon24, text: _("Stop podkop"), loading: stop.loading, - disabled: stop.disabled, - }), + disabled: stop.disabled + }) ]), ...insertIf(start.visible, [ renderButton({ @@ -3455,8 +3369,8 @@ function renderAvailableActions({ icon: renderCirclePlayIcon24, text: _("Start podkop"), loading: start.loading, - disabled: start.disabled, - }), + disabled: start.disabled + }) ]), ...insertIf(disable.visible, [ renderButton({ @@ -3465,8 +3379,8 @@ function renderAvailableActions({ icon: renderPauseIcon24, text: _("Disable autostart"), loading: disable.loading, - disabled: disable.disabled, - }), + disabled: disable.disabled + }) ]), ...insertIf(enable.visible, [ renderButton({ @@ -3475,8 +3389,8 @@ function renderAvailableActions({ icon: renderPlayIcon24, text: _("Enable autostart"), loading: enable.loading, - disabled: enable.disabled, - }), + disabled: enable.disabled + }) ]), ...insertIf(globalCheck.visible, [ renderButton({ @@ -3484,8 +3398,8 @@ function renderAvailableActions({ icon: renderCircleCheckBigIcon24, text: _("Get global check"), loading: globalCheck.loading, - disabled: globalCheck.disabled, - }), + disabled: globalCheck.disabled + }) ]), ...insertIf(viewLogs.visible, [ renderButton({ @@ -3493,8 +3407,8 @@ function renderAvailableActions({ icon: renderSquareChartGanttIcon24, text: _("View logs"), loading: viewLogs.loading, - disabled: viewLogs.disabled, - }), + disabled: viewLogs.disabled + }) ]), ...insertIf(showSingBoxConfig.visible, [ renderButton({ @@ -3502,9 +3416,9 @@ function renderAvailableActions({ icon: renderCogIcon24, text: _("Show sing-box config"), loading: showSingBoxConfig.loading, - disabled: showSingBoxConfig.disabled, - }), - ]), + disabled: showSingBoxConfig.disabled + }) + ]) ]); } @@ -3516,7 +3430,7 @@ function renderCheckSummary(items) { const renderedItems = items.map((item) => { function getIcon() { const iconWrap = E("span", { - class: "pdk_diagnostic_alert__summary__item__icon", + class: "pdk_diagnostic_alert__summary__item__icon" }); if (item.state === "success") { iconWrap.appendChild(renderCheckIcon24()); @@ -3532,9 +3446,9 @@ function renderCheckSummary(items) { return E( "div", { - class: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}`, + class: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}` }, - [getIcon(), E("b", {}, item.key), E("div", {}, item.value)], + [getIcon(), E("b", {}, item.key), E("div", {}, item.value)] ); }); return E("div", { class: "pdk_diagnostic_alert__summary" }, renderedItems); @@ -3552,12 +3466,12 @@ function renderLoadingState3(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description, - ), + props.description + ) ]), E("div", {}, ""), - renderCheckSummary(props.items), - ], + renderCheckSummary(props.items) + ] ); } function renderWarningState(props) { @@ -3573,12 +3487,12 @@ function renderWarningState(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description, - ), + props.description + ) ]), E("div", {}, ""), - renderCheckSummary(props.items), - ], + renderCheckSummary(props.items) + ] ); } function renderErrorState(props) { @@ -3594,12 +3508,12 @@ function renderErrorState(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description, - ), + props.description + ) ]), E("div", {}, ""), - renderCheckSummary(props.items), - ], + renderCheckSummary(props.items) + ] ); } function renderSuccessState(props) { @@ -3615,12 +3529,12 @@ function renderSuccessState(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description, - ), + props.description + ) ]), E("div", {}, ""), - renderCheckSummary(props.items), - ], + renderCheckSummary(props.items) + ] ); } function renderSkippedState(props) { @@ -3636,12 +3550,12 @@ function renderSkippedState(props) { E( "div", { class: "pdk_diagnostic_alert__description" }, - props.description, - ), + props.description + ) ]), E("div", {}, ""), - renderCheckSummary(props.items), - ], + renderCheckSummary(props.items) + ] ); } function renderCheckSection(props) { @@ -3664,15 +3578,18 @@ function renderCheckSection(props) { } // src/podkop/tabs/diagnostic/partials/renderRunAction.ts -function renderRunAction({ loading, click }) { +function renderRunAction({ + loading, + click +}) { return E("div", { class: "pdk_diagnostic-page__run_check_wrapper" }, [ renderButton({ text: _("Run Diagnostic"), onClick: click, icon: renderSearchIcon24, loading, - classNames: ["cbi-button-apply"], - }), + classNames: ["cbi-button-apply"] + }) ]); } @@ -3682,20 +3599,18 @@ function renderSystemInfo({ items }) { E( "b", { class: "pdk_diagnostic-page__right-bar__system-info__title" }, - "System information", + "System information" ), ...items.map((item) => { const tagClass = [ "pdk_diagnostic-page__right-bar__system-info__row__tag", ...insertIf(item.tag?.kind === "warning", [ - "pdk_diagnostic-page__right-bar__system-info__row__tag--warning", + "pdk_diagnostic-page__right-bar__system-info__row__tag--warning" ]), ...insertIf(item.tag?.kind === "success", [ - "pdk_diagnostic-page__right-bar__system-info__row__tag--success", - ]), - ] - .filter(Boolean) - .join(" "); + "pdk_diagnostic-page__right-bar__system-info__row__tag--success" + ]) + ].filter(Boolean).join(" "); return E( "div", { class: "pdk_diagnostic-page__right-bar__system-info__row" }, @@ -3703,11 +3618,11 @@ function renderSystemInfo({ items }) { E("b", {}, item.key), E("div", {}, [ E("span", {}, item.value), - E("span", { class: tagClass }, item?.tag?.label), - ]), - ], + E("span", { class: tagClass }, item?.tag?.label) + ]) + ] ); - }), + }) ]); } @@ -3726,8 +3641,8 @@ async function fetchSystemInfo() { store.set({ diagnosticsSystemInfo: { loading: false, - ...systemInfo.data, - }, + ...systemInfo.data + } }); } else { store.set({ @@ -3738,19 +3653,17 @@ async function fetchSystemInfo() { luci_app_version: _("unknown"), sing_box_version: _("unknown"), openwrt_version: _("unknown"), - device_model: _("unknown"), - }, + device_model: _("unknown") + } }); } } function renderDiagnosticsChecks() { logger.debug("[DIAGNOSTIC]", "renderDiagnosticsChecks"); - const diagnosticsChecks = store - .get() - .diagnosticsChecks.sort((a, b) => a.order - b.order); + const diagnosticsChecks = store.get().diagnosticsChecks.sort((a, b) => a.order - b.order); const container = document.getElementById("pdk_diagnostic-page-checks"); - const renderedDiagnosticsChecks = diagnosticsChecks.map((check) => - renderCheckSection(check), + const renderedDiagnosticsChecks = diagnosticsChecks.map( + (check) => renderCheckSection(check) ); return preserveScrollForPage(() => { container.replaceChildren(...renderedDiagnosticsChecks); @@ -3762,7 +3675,7 @@ function renderDiagnosticRunActionWidget() { const container = document.getElementById("pdk_diagnostic-page-run-check"); const renderedAction = renderRunAction({ loading, - click: () => runChecks(), + click: () => runChecks() }); return preserveScrollForPage(() => { container.replaceChildren(renderedAction); @@ -3773,8 +3686,8 @@ async function handleRestart() { store.set({ diagnosticsActions: { ...diagnosticsActions, - restart: { loading: true }, - }, + restart: { loading: true } + } }); try { await PodkopShellMethods.restart(); @@ -3786,8 +3699,8 @@ async function handleRestart() { store.set({ diagnosticsActions: { ...diagnosticsActions, - restart: { loading: false }, - }, + restart: { loading: false } + } }); store.reset(["diagnosticsChecks"]); }, 5e3); @@ -3798,8 +3711,8 @@ async function handleStop() { store.set({ diagnosticsActions: { ...diagnosticsActions, - stop: { loading: true }, - }, + stop: { loading: true } + } }); try { await PodkopShellMethods.stop(); @@ -3810,8 +3723,8 @@ async function handleStop() { store.set({ diagnosticsActions: { ...diagnosticsActions, - stop: { loading: false }, - }, + stop: { loading: false } + } }); store.reset(["diagnosticsChecks"]); } @@ -3821,8 +3734,8 @@ async function handleStart() { store.set({ diagnosticsActions: { ...diagnosticsActions, - start: { loading: true }, - }, + start: { loading: true } + } }); try { await PodkopShellMethods.start(); @@ -3834,8 +3747,8 @@ async function handleStart() { store.set({ diagnosticsActions: { ...diagnosticsActions, - start: { loading: false }, - }, + start: { loading: false } + } }); store.reset(["diagnosticsChecks"]); }, 5e3); @@ -3846,8 +3759,8 @@ async function handleEnable() { store.set({ diagnosticsActions: { ...diagnosticsActions, - enable: { loading: true }, - }, + enable: { loading: true } + } }); try { await PodkopShellMethods.enable(); @@ -3858,8 +3771,8 @@ async function handleEnable() { store.set({ diagnosticsActions: { ...diagnosticsActions, - enable: { loading: false }, - }, + enable: { loading: false } + } }); } } @@ -3868,8 +3781,8 @@ async function handleDisable() { store.set({ diagnosticsActions: { ...diagnosticsActions, - disable: { loading: true }, - }, + disable: { loading: true } + } }); try { await PodkopShellMethods.disable(); @@ -3880,8 +3793,8 @@ async function handleDisable() { store.set({ diagnosticsActions: { ...diagnosticsActions, - disable: { loading: false }, - }, + disable: { loading: false } + } }); } } @@ -3890,15 +3803,15 @@ async function handleShowGlobalCheck() { store.set({ diagnosticsActions: { ...diagnosticsActions, - globalCheck: { loading: true }, - }, + globalCheck: { loading: true } + } }); try { const globalCheck = await PodkopShellMethods.globalCheck(); if (globalCheck.success) { ui.showModal( _("Global check"), - renderModal(globalCheck.data, "global_check"), + renderModal(globalCheck.data, "global_check") ); } } catch (e) { @@ -3907,8 +3820,8 @@ async function handleShowGlobalCheck() { store.set({ diagnosticsActions: { ...diagnosticsActions, - globalCheck: { loading: false }, - }, + globalCheck: { loading: false } + } }); } } @@ -3917,13 +3830,16 @@ async function handleViewLogs() { store.set({ diagnosticsActions: { ...diagnosticsActions, - viewLogs: { loading: true }, - }, + viewLogs: { loading: true } + } }); try { const viewLogs = await PodkopShellMethods.checkLogs(); if (viewLogs.success) { - ui.showModal(_("View logs"), renderModal(viewLogs.data, "view_logs")); + ui.showModal( + _("View logs"), + renderModal(viewLogs.data, "view_logs") + ); } } catch (e) { logger.error("[DIAGNOSTIC]", "handleViewLogs - e", e); @@ -3931,8 +3847,8 @@ async function handleViewLogs() { store.set({ diagnosticsActions: { ...diagnosticsActions, - viewLogs: { loading: false }, - }, + viewLogs: { loading: false } + } }); } } @@ -3941,15 +3857,15 @@ async function handleShowSingBoxConfig() { store.set({ diagnosticsActions: { ...diagnosticsActions, - showSingBoxConfig: { loading: true }, - }, + showSingBoxConfig: { loading: true } + } }); try { const showSingBoxConfig = await PodkopShellMethods.showSingBoxConfig(); if (showSingBoxConfig.success) { ui.showModal( _("Show sing-box config"), - renderModal(showSingBoxConfig.data, "show_sing_box_config"), + renderModal(showSingBoxConfig.data, "show_sing_box_config") ); } } catch (e) { @@ -3958,8 +3874,8 @@ async function handleShowSingBoxConfig() { store.set({ diagnosticsActions: { ...diagnosticsActions, - showSingBoxConfig: { loading: false }, - }, + showSingBoxConfig: { loading: false } + } }); } } @@ -3969,61 +3885,57 @@ function renderDiagnosticAvailableActionsWidget() { logger.debug("[DIAGNOSTIC]", "renderDiagnosticAvailableActionsWidget"); const podkopEnabled = Boolean(servicesInfoWidget.data.podkop); const singBoxRunning = Boolean(servicesInfoWidget.data.singbox); - const atLeastOneServiceCommandLoading = - servicesInfoWidget.loading || - diagnosticsActions.restart.loading || - diagnosticsActions.start.loading || - diagnosticsActions.stop.loading; + const atLeastOneServiceCommandLoading = servicesInfoWidget.loading || diagnosticsActions.restart.loading || diagnosticsActions.start.loading || diagnosticsActions.stop.loading; const container = document.getElementById("pdk_diagnostic-page-actions"); const renderedActions = renderAvailableActions({ restart: { loading: diagnosticsActions.restart.loading, visible: true, onClick: handleRestart, - disabled: atLeastOneServiceCommandLoading, + disabled: atLeastOneServiceCommandLoading }, start: { loading: diagnosticsActions.start.loading, visible: !singBoxRunning, onClick: handleStart, - disabled: atLeastOneServiceCommandLoading, + disabled: atLeastOneServiceCommandLoading }, stop: { loading: diagnosticsActions.stop.loading, visible: singBoxRunning, onClick: handleStop, - disabled: atLeastOneServiceCommandLoading, + disabled: atLeastOneServiceCommandLoading }, enable: { loading: diagnosticsActions.enable.loading, visible: !podkopEnabled, onClick: handleEnable, - disabled: atLeastOneServiceCommandLoading, + disabled: atLeastOneServiceCommandLoading }, disable: { loading: diagnosticsActions.disable.loading, visible: podkopEnabled, onClick: handleDisable, - disabled: atLeastOneServiceCommandLoading, + disabled: atLeastOneServiceCommandLoading }, globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, onClick: handleShowGlobalCheck, - disabled: atLeastOneServiceCommandLoading, + disabled: atLeastOneServiceCommandLoading }, viewLogs: { loading: diagnosticsActions.viewLogs.loading, visible: true, onClick: handleViewLogs, - disabled: atLeastOneServiceCommandLoading, + disabled: atLeastOneServiceCommandLoading }, showSingBoxConfig: { loading: diagnosticsActions.showSingBoxConfig.loading, visible: true, onClick: handleShowSingBoxConfig, - disabled: atLeastOneServiceCommandLoading, - }, + disabled: atLeastOneServiceCommandLoading + } }); return preserveScrollForPage(() => { container.replaceChildren(renderedActions); @@ -4037,16 +3949,16 @@ function renderDiagnosticSystemInfoWidget() { const loading = diagnosticsSystemInfo.loading; const unknown = diagnosticsSystemInfo.podkop_version === _("unknown"); const hasActualVersion = Boolean( - diagnosticsSystemInfo.podkop_latest_version, + diagnosticsSystemInfo.podkop_latest_version ); const version = normalizeCompiledVersion( - diagnosticsSystemInfo.podkop_version, + diagnosticsSystemInfo.podkop_version ); const isDevVersion = version === "dev"; if (loading || unknown || !hasActualVersion || isDevVersion) { return { key: "Podkop", - value: version, + value: version }; } if (version !== diagnosticsSystemInfo.podkop_latest_version) { @@ -4055,8 +3967,8 @@ function renderDiagnosticSystemInfoWidget() { value: version, tag: { label: _("Outdated"), - kind: "warning", - }, + kind: "warning" + } }; } return { @@ -4064,8 +3976,8 @@ function renderDiagnosticSystemInfoWidget() { value: version, tag: { label: _("Latest"), - kind: "success", - }, + kind: "success" + } }; } const renderedSystemInfo = renderSystemInfo({ @@ -4073,21 +3985,21 @@ function renderDiagnosticSystemInfoWidget() { getPodkopVersionRow(), { key: "Luci App", - value: normalizeCompiledVersion(diagnosticsSystemInfo.luci_app_version), + value: normalizeCompiledVersion(diagnosticsSystemInfo.luci_app_version) }, { key: "Sing-box", - value: diagnosticsSystemInfo.sing_box_version, + value: diagnosticsSystemInfo.sing_box_version }, { key: "OS", - value: diagnosticsSystemInfo.openwrt_version, + value: diagnosticsSystemInfo.openwrt_version }, { key: "Device", - value: diagnosticsSystemInfo.device_model, - }, - ], + value: diagnosticsSystemInfo.device_model + } + ] }); return preserveScrollForPage(() => { container.replaceChildren(renderedSystemInfo); @@ -4111,7 +4023,7 @@ async function runChecks() { try { store.set({ diagnosticsRunAction: { loading: true }, - diagnosticsChecks: loadingDiagnosticsChecksStore.diagnosticsChecks, + diagnosticsChecks: loadingDiagnosticsChecksStore.diagnosticsChecks }); await runDnsCheck(); await runSingBoxCheck(); @@ -4139,26 +4051,23 @@ function onPageUnmount2() { "diagnosticsActions", "diagnosticsSystemInfo", "diagnosticsChecks", - "diagnosticsRunAction", + "diagnosticsRunAction" ]); } function registerLifecycleListeners2() { store.subscribe((next, prev, diff) => { - if ( - diff.tabService && - next.tabService.current !== prev.tabService.current - ) { + if (diff.tabService && next.tabService.current !== prev.tabService.current) { logger.debug( "[DIAGNOSTIC]", "active tab diff event, active tab:", - diff.tabService.current, + diff.tabService.current ); const isDIAGNOSTICVisible = next.tabService.current === "diagnostic"; if (isDIAGNOSTICVisible) { logger.debug( "[DIAGNOSTIC]", "registerLifecycleListeners", - "onPageMount", + "onPageMount" ); return onPageMount2(); } @@ -4166,7 +4075,7 @@ function registerLifecycleListeners2() { logger.debug( "[DIAGNOSTIC]", "registerLifecycleListeners", - "onPageUnmount", + "onPageUnmount" ); return onPageUnmount2(); } @@ -4351,7 +4260,7 @@ var styles4 = ` var DiagnosticTab = { render: render2, initController: initController2, - styles: styles4, + styles: styles4 }; // src/styles.ts @@ -4472,17 +4381,12 @@ function injectGlobalStyles() { - `, + ` ); } // src/helpers/withTimeout.ts -async function withTimeout( - promise, - timeoutMs, - operationName, - timeoutMessage = _("Operation timed out"), -) { +async function withTimeout(promise, timeoutMs, operationName, timeoutMessage = _("Operation timed out")) { let timeoutId; const start = performance.now(); const timeoutPromise = new Promise((_2, reject) => { @@ -4501,13 +4405,13 @@ async function withTimeout( async function executeShellCommand({ command, args, - timeout = COMMAND_TIMEOUT, + timeout = COMMAND_TIMEOUT }) { try { return withTimeout( fs.exec(command, args), timeout, - [command, ...args].join(" "), + [command, ...args].join(" ") ); } catch (err) { const error = err; @@ -4557,7 +4461,7 @@ async function onMount(id) { }); observer.observe(document.body, { childList: true, - subtree: true, + subtree: true }); }); } @@ -4574,11 +4478,7 @@ function getClashUIUrl() { // src/helpers/splitProxyString.ts function splitProxyString(str) { - return str - .split("\n") - .map((line) => line.trim()) - .filter((line) => !line.startsWith("//")) - .filter(Boolean); + return str.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("//")).filter(Boolean); } // src/helpers/preserveScrollForPage.ts @@ -4597,9 +4497,7 @@ function svgEl(tag, attrs = {}, children = []) { for (const [k, v] of Object.entries(attrs)) { if (v != null) el.setAttribute(k, String(v)); } - (Array.isArray(children) ? children : [children]) - .filter(Boolean) - .forEach((ch) => el.appendChild(ch)); + (Array.isArray(children) ? children : [children]).filter(Boolean).forEach((ch) => el.appendChild(ch)); return el; } @@ -4668,5 +4566,5 @@ return baseclass.extend({ validateTrojanUrl, validateUrl, validateVlessUrl, - withTimeout, + withTimeout }); diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index 7cfb6bc..10dd5ae 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -1,15 +1,15 @@ -# Russian translations for PODKOP package. +# RU translations for PODKOP package. # Copyright (C) 2025 THE PODKOP'S COPYRIGHT HOLDER # This file is distributed under the same license as the PODKOP package. -# Automatically generated, 2025. +# divocat, 2025. # msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-07 16:55+0300\n" -"PO-Revision-Date: 2025-10-07 23:45+0300\n" -"Last-Translator: Automatically generated\n" +"POT-Creation-Date: 2025-10-21 21:31+0300\n" +"PO-Revision-Date: 2025-10-21 21:31+0300\n" +"Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" "MIME-Version: 1.0\n" @@ -17,269 +17,41 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -msgid "Basic Settings" -msgstr "Основные настройки" - -msgid "Connection Type" -msgstr "Тип подключения" - -msgid "Select between VPN and Proxy connection methods for traffic routing" -msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" - -msgid "Configuration Type" -msgstr "Тип конфигурации" - -msgid "Select how to configure the proxy" -msgstr "Выберите способ настройки прокси" - -msgid "Connection URL" -msgstr "URL подключения" - -msgid "Outbound Config" -msgstr "Конфигурация Outbound" - -msgid "URLTest" -msgstr "URLTest" - -msgid "Proxy Configuration URL" -msgstr "URL конфигурации прокси" - -msgid "Current config: " -msgstr "Текущая конфигурация: " - -msgid "Config without description" -msgstr "Конфигурация без описания" - -msgid "" -"Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup " -"configs" +msgid "Successfully copied!" msgstr "" -"Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для " -"резервных конфигураций" -msgid "No active configuration found. One configuration is required." -msgstr "Активная конфигурация не найдена. Требуется хотя бы одна незакомментированная строка." - -msgid "Multiply active configurations found. Please leave one configuration." -msgstr "Найдено несколько активных конфигураций. Оставьте только одну." - -msgid "Invalid URL format:" -msgstr "Неверный формат URL:" - -msgid "Outbound Configuration" -msgstr "Конфигурация исходящего соединения" - -msgid "Enter complete outbound configuration in JSON format" -msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" - -msgid "URLTest Proxy Links" -msgstr "Ссылки прокси для URLTest" - -msgid "Shadowsocks UDP over TCP" -msgstr "Shadowsocks UDP через TCP" - -msgid "Apply for SS2022" -msgstr "Применить для SS2022" - -msgid "Network Interface" -msgstr "Сетевой интерфейс" - -msgid "Select network interface for VPN connection" -msgstr "Выберите сетевой интерфейс для VPN подключения" - -msgid "Domain Resolver" -msgstr "Резолвер доменов" - -msgid "Enable built-in DNS resolver for domains handled by this section" -msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе" - -msgid "DNS Protocol Type" -msgstr "Тип протокола DNS" - -msgid "Select the DNS protocol type for the domain resolver" -msgstr "Выберите тип протокола DNS для резолвера доменов" - -msgid "DNS over HTTPS (DoH)" -msgstr "DNS через HTTPS (DoH)" - -msgid "DNS over TLS (DoT)" -msgstr "DNS через TLS (DoT)" - -msgid "UDP (Unprotected DNS)" -msgstr "UDP (Незащищённый DNS)" - -msgid "DNS Server" -msgstr "DNS-сервер" - -msgid "Select or enter DNS server address" -msgstr "Выберите или введите адрес DNS-сервера" - -msgid "Community Lists" -msgstr "Списки сообщества" - -msgid "Service List" -msgstr "Список сервисов" - -msgid "Select predefined service for routing" -msgstr "Выберите предустановленные сервисы для маршрутизации" - -msgid "Regional options cannot be used together" -msgstr "Нельзя использовать несколько региональных опций одновременно" - -#, javascript-format -msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." -msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." - -msgid "Russia inside restrictions" -msgstr "Ограничения Russia inside" - -#, javascript-format -msgid "" -"Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." +msgid "Failed to copy!" msgstr "" -"Внимание: «Russia inside» может использоваться только с %s. %s уже находится в «Russia inside» и был удалён из выбора." -msgid "User Domain List Type" -msgstr "Тип пользовательского списка доменов" +msgid "Operation timed out" +msgstr "Время ожидания истекло" -msgid "Select how to add your custom domains" -msgstr "Выберите способ добавления пользовательских доменов" +msgid "HTTP error" +msgstr "Ошибка HTTP" -msgid "Disabled" -msgstr "Отключено" - -msgid "Dynamic List" -msgstr "Динамический список" - -msgid "Text List" -msgstr "Текстовый список" - -msgid "User Domains" -msgstr "Пользовательские домены" - -msgid "Enter domain names without protocols (example: sub.example.com or example.com)" -msgstr "Введите доменные имена без протоколов (например: sub.example.com или example.com)" - -msgid "User Domains List" -msgstr "Список пользовательских доменов" - -msgid "Enter domain names separated by comma, space or newline. You can add comments after //" -msgstr "Введите домены через запятую, пробел или с новой строки. Можно добавлять комментарии после //" - -msgid "At least one valid domain must be specified. Comments-only content is not allowed." -msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается." - -msgid "Validation errors:" -msgstr "Ошибки валидации:" - -msgid "Local Domain Lists" -msgstr "Локальные списки доменов" - -msgid "Use the list from the router filesystem" -msgstr "Использовать список из файловой системы роутера" - -msgid "Local Domain List Paths" -msgstr "Пути к локальным спискам доменов" - -msgid "Enter the list file path" -msgstr "Введите путь к файлу списка" - -msgid "Remote Domain Lists" -msgstr "Удалённые списки доменов" - -msgid "Download and use domain lists from remote URLs" -msgstr "Загружать и использовать списки доменов с удалённых URL" - -msgid "Remote Domain URLs" -msgstr "URL удалённых доменов" - -msgid "Enter full URLs starting with http:// or https://" -msgstr "Введите полные URL, начинающиеся с http:// или https://" - -msgid "Local Subnet Lists" -msgstr "Локальные списки подсетей" - -msgid "Local Subnet List Paths" -msgstr "Пути к локальным спискам подсетей" - -msgid "User Subnet List Type" -msgstr "Тип пользовательского списка подсетей" - -msgid "Select how to add your custom subnets" -msgstr "Выберите способ добавления пользовательских подсетей" - -msgid "Text List (comma/space/newline separated)" -msgstr "Текстовый список (через запятую, пробел или новую строку)" - -msgid "User Subnets" -msgstr "Пользовательские подсети" - -msgid "Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses" -msgstr "Введите подсети в нотации CIDR (например: 103.21.244.0/22) или отдельные IP-адреса" - -msgid "User Subnets List" -msgstr "Список пользовательских подсетей" - -msgid "" -"Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments " -"after //" -msgstr "" -"Введите подсети в нотации CIDR или IP-адреса через запятую, пробел или новую строку. Можно добавлять комментарии " -"после //" - -msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." -msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы." - -msgid "Remote Subnet Lists" -msgstr "Удалённые списки подсетей" - -msgid "Download and use subnet lists from remote URLs" -msgstr "Загружать и использовать списки подсетей с удалённых URL" - -msgid "Remote Subnet URLs" -msgstr "URL удалённых подсетей" - -msgid "IP for full redirection" -msgstr "IP для полного перенаправления" - -msgid "Specify local IP addresses whose traffic will always use the configured route" -msgstr "Укажите локальные IP-адреса, трафик которых всегда будет использовать настроенный маршрут" - -msgid "Local IPs" -msgstr "Локальные IP-адреса" - -msgid "Enter valid IPv4 addresses" -msgstr "Введите действительные IPv4-адреса" - -msgid "Extra configurations" -msgstr "Дополнительные конфигурации" - -msgid "Add Section" -msgstr "Добавить раздел" - -msgid "Dashboard" -msgstr "Дашборд" - -msgid "Valid" -msgstr "Валидно" - -msgid "Invalid IP address" -msgstr "Неверный IP-адрес" - -msgid "Invalid domain address" -msgstr "Неверный домен" +msgid "Unknown error" +msgstr "Неизвестная ошибка" msgid "DNS server address cannot be empty" msgstr "Адрес DNS-сервера не может быть пустым" +msgid "Valid" +msgstr "Валидно" + msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" -msgid "URL must use one of the following protocols:" -msgstr "URL должен использовать один из следующих протоколов:" +msgid "Invalid domain address" +msgstr "Неверный домен" -msgid "Invalid URL format" -msgstr "Неверный формат URL" +msgid "Invalid IP address" +msgstr "Неверный IP-адрес" + +msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" +msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" + +msgid "Invalid JSON format" +msgstr "Неверный формат JSON" msgid "Path cannot be empty" msgstr "Путь не может быть пустым" @@ -287,14 +59,8 @@ msgstr "Путь не может быть пустым" msgid "Invalid path format. Path must start with \"/\" and contain valid characters" msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы" -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" - -msgid "IP address 0.0.0.0 is not allowed" -msgstr "IP-адрес 0.0.0.0 не допускается" - -msgid "CIDR must be between 0 and 32" -msgstr "CIDR должен быть между 0 и 32" +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" msgid "Invalid Shadowsocks URL: must start with ss://" msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" @@ -326,47 +92,41 @@ msgstr "Неверный номер порта. Допустимо от 1 до 6 msgid "Invalid Shadowsocks URL: parsing failed" msgstr "Неверный URL Shadowsocks: ошибка разбора" -msgid "Invalid VLESS URL: must not contain spaces" -msgstr "Неверный URL VLESS: не должен содержать пробелов" +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" -msgid "Invalid VLESS URL: must start with vless://" -msgstr "Неверный URL VLESS: должен начинаться с vless://" +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" -msgid "Invalid VLESS URL: missing UUID" -msgstr "Неверный URL VLESS: отсутствует UUID" +msgid "Invalid SOCKS URL: missing username" +msgstr "" -msgid "Invalid VLESS URL: missing server" -msgstr "Неверный URL VLESS: отсутствует сервер" +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" -msgid "Invalid VLESS URL: missing port" -msgstr "Неверный URL VLESS: отсутствует порт" +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" -msgid "Invalid VLESS URL: invalid port number. Must be between 1 and 65535" -msgstr "Неверный URL VLESS: недопустимый порт (1–65535)" +msgid "Invalid SOCKS URL: missing port" +msgstr "" -msgid "Invalid VLESS URL: missing query parameters" -msgstr "Неверный URL VLESS: отсутствуют параметры запроса" +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" -msgid "Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws" -msgstr "Неверный URL VLESS: тип должен быть tcp, raw, udp, grpc, http или ws" +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" -msgid "Invalid VLESS URL: security must be one of tls, reality, none" -msgstr "Неверный URL VLESS: параметр security должен быть tls, reality или none" +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" -msgid "Invalid VLESS URL: missing pbk parameter for reality security" -msgstr "Неверный URL VLESS: отсутствует параметр pbk для security=reality" +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" -msgid "Invalid VLESS URL: missing fp parameter for reality security" -msgstr "Неверный URL VLESS: отсутствует параметр fp для security=reality" +msgid "IP address 0.0.0.0 is not allowed" +msgstr "IP-адрес 0.0.0.0 не допускается" -msgid "Invalid VLESS URL: parsing failed" -msgstr "Неверный URL VLESS: ошибка разбора" - -msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" -msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" - -msgid "Invalid JSON format" -msgstr "Неверный формат JSON" +msgid "CIDR must be between 0 and 32" +msgstr "CIDR должен быть между 0 и 32" msgid "Invalid Trojan URL: must start with trojan://" msgstr "Неверный URL Trojan: должен начинаться с trojan://" @@ -374,33 +134,30 @@ msgstr "Неверный URL Trojan: должен начинаться с trojan msgid "Invalid Trojan URL: must not contain spaces" msgstr "Неверный URL Trojan: не должен содержать пробелов" -msgid "Invalid Trojan URL: must contain username, hostname and port" -msgstr "Неверный URL Trojan: должен содержать имя пользователя, хост и порт" - msgid "Invalid Trojan URL: parsing failed" msgstr "Неверный URL Trojan: ошибка разбора" -msgid "URL must start with vless:// or ss:// or trojan://" -msgstr "URL должен начинаться с vless://, ss:// или trojan://" +msgid "URL must use one of the following protocols:" +msgstr "URL должен использовать один из следующих протоколов:" -msgid "Operation timed out" -msgstr "Время ожидания истекло" +msgid "Invalid URL format" +msgstr "Неверный формат URL" -msgid "HTTP error" -msgstr "Ошибка HTTP" +msgid "Invalid VLESS URL: parsing failed" +msgstr "Неверный URL VLESS: ошибка разбора" -msgid "Unknown error" -msgstr "Неизвестная ошибка" +msgid "Download" +msgstr "" + +msgid "Copy" +msgstr "" + +msgid "Close" +msgstr "Закрыть" msgid "Fastest" msgstr "Самый быстрый" -msgid "Dashboard currently unavailable" -msgstr "Дашборд сейчас недоступен" - -msgid "Currently unavailable" -msgstr "Временно недоступно" - msgid "Traffic" msgstr "Трафик" @@ -443,164 +200,761 @@ msgstr "✔ Работает" msgid "✘ Stopped" msgstr "✘ Остановлен" -msgid "Copied!" -msgstr "Скопировано!" +msgid "Not running" +msgstr "" -msgid "Failed to copy: " -msgstr "Не удалось скопировать: " +msgid "Queued" +msgstr "" -msgid "Loading..." -msgstr "Загрузка..." - -msgid "Copy to Clipboard" -msgstr "Копировать в буфер" - -msgid "Close" -msgstr "Закрыть" - -msgid "No output" -msgstr "Нет вывода" - -msgid "FakeIP is working in browser!" -msgstr "FakeIP работает в браузере!" - -msgid "FakeIP is not working in browser" -msgstr "FakeIP не работает в браузере" - -msgid "Check DNS server on current device (PC, phone)" -msgstr "Проверьте DNS-сервер на текущем устройстве (ПК, телефон)" - -msgid "Its must be router!" -msgstr "Это должен быть роутер!" - -msgid "Proxy working correctly" -msgstr "Прокси работает корректно" - -msgid "Direct IP: " -msgstr "Прямой IP: " - -msgid "Proxy IP: " -msgstr "Прокси IP: " - -msgid "Proxy is not working - same IP for both domains" -msgstr "Прокси не работает — одинаковый IP для обоих доменов" - -msgid "IP: " -msgstr "IP: " - -msgid "Proxy check failed" -msgstr "Проверка прокси не удалась" - -msgid "Check failed: " -msgstr "Проверка не удалась: " - -msgid "timeout" -msgstr "таймаут" - -msgid "Error: " -msgstr "Ошибка: " - -msgid "Podkop Status" -msgstr "Статус Podkop" +msgid "unknown" +msgstr "" msgid "Global check" msgstr "Глобальная проверка" -msgid "Click here for all the info" -msgstr "Нажмите для просмотра всей информации" +msgid "View logs" +msgstr "" -msgid "Update Lists" -msgstr "Обновить списки" +msgid "Show sing-box config" +msgstr "" -msgid "Lists Update Results" -msgstr "Результаты обновления списков" +msgid "Outdated" +msgstr "" -msgid "Sing-box Status" -msgstr "Статус Sing-box" +msgid "Latest" +msgstr "" -msgid "Check NFT Rules" -msgstr "Проверить правила NFT" +msgid "Dashboard currently unavailable" +msgstr "Дашборд сейчас недоступен" -msgid "NFT Rules" -msgstr "Правила NFT" +msgid "Test latency" +msgstr "" -msgid "Check DNSMasq" -msgstr "Проверить DNSMasq" +msgid "Currently unavailable" +msgstr "Временно недоступно" -msgid "DNSMasq Configuration" -msgstr "Конфигурация DNSMasq" +msgid "DNS checks" +msgstr "" -msgid "FakeIP Status" -msgstr "Статус FakeIP" +msgid "Sing-box checks" +msgstr "" -msgid "DNS Status" -msgstr "Статус DNS" +msgid "Nftables checks" +msgstr "" -msgid "Main config" -msgstr "Основная конфигурация" +msgid "FakeIP checks" +msgstr "" -msgid "Version Information" -msgstr "Информация о версии" +msgid "Checking dns, please wait" +msgstr "" -msgid "Podkop: " -msgstr "Podkop: " +msgid "Cannot receive DNS checks result" +msgstr "" -msgid "LuCI App: " -msgstr "LuCI App: " +msgid "DNS checks passed" +msgstr "" -msgid "Sing-box: " -msgstr "Sing-box: " +msgid "Bootsrap DNS" +msgstr "" -msgid "OpenWrt Version: " -msgstr "Версия OpenWrt: " +msgid "Main DNS" +msgstr "" -msgid "Device Model: " -msgstr "Модель устройства: " +msgid "DNS on router" +msgstr "" -msgid "Unknown" -msgstr "Неизвестно" +msgid "DHCP has DNS server" +msgstr "" -msgid "works in browser" -msgstr "работает в браузере" +msgid "Checking FakeIP, please wait" +msgstr "" -msgid "does not work in browser" -msgstr "не работает в браузере" +msgid "FakeIP checks passed" +msgstr "" -msgid "works on router" -msgstr "работает на роутере" +msgid "FakeIP checks partially passed" +msgstr "" -msgid "does not work on router" -msgstr "не работает на роутере" +msgid "FakeIP checks failed" +msgstr "" -msgid "Config: " -msgstr "Конфигурация: " +msgid "Router DNS is routed through sing-box" +msgstr "" + +msgid "Router DNS is not routed through sing-box" +msgstr "" + +msgid "Browser is using FakeIP correctly" +msgstr "" + +msgid "Browser is not using FakeIP" +msgstr "" + +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +msgid "Checking nftables, please wait" +msgstr "" + +msgid "Cannot receive nftables checks result" +msgstr "" + +msgid "Nftables checks passed" +msgstr "" + +msgid "Nftables checks partially passed" +msgstr "" + +msgid "Table exist" +msgstr "" + +msgid "Rules mangle exist" +msgstr "" + +msgid "Rules mangle counters" +msgstr "" + +msgid "Rules mangle output exist" +msgstr "" + +msgid "Rules mangle output counters" +msgstr "" + +msgid "Rules proxy exist" +msgstr "" + +msgid "Rules proxy counters" +msgstr "" + +msgid "No other marking rules found" +msgstr "" + +msgid "Additional marking rules found" +msgstr "" + +msgid "Checking sing-box, please wait" +msgstr "" + +msgid "Cannot receive Sing-box checks result" +msgstr "" + +msgid "Sing-box checks passed" +msgstr "" + +msgid "Sing-box installed" +msgstr "" + +msgid "Sing-box version >= 1.12.4" +msgstr "" + +msgid "Sing-box service exist" +msgstr "" + +msgid "Sing-box autostart disabled" +msgstr "" + +msgid "Sing-box process running" +msgstr "" + +msgid "Sing-box listening ports" +msgstr "" + +msgid "Restart podkop" +msgstr "" + +msgid "Stop podkop" +msgstr "" + +msgid "Start podkop" +msgstr "" + +msgid "Disable autostart" +msgstr "" + +msgid "Enable autostart" +msgstr "" + +msgid "Get global check" +msgstr "" + +msgid "Not implement yet" +msgstr "" + +msgid "Run Diagnostic" +msgstr "" + +msgid "Valid" +msgstr "Валидно" + +msgid "Invalid IP address" +msgstr "Неверный IP-адрес" + +msgid "Invalid domain address" +msgstr "Неверный домен" + +msgid "DNS server address cannot be empty" +msgstr "Адрес DNS-сервера не может быть пустым" + +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" + +msgid "URL must use one of the following protocols:" +msgstr "URL должен использовать один из следующих протоколов:" + +msgid "Invalid URL format" +msgstr "Неверный формат URL" + +msgid "Path cannot be empty" +msgstr "Путь не может быть пустым" + +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" + +msgid "IP address 0.0.0.0 is not allowed" +msgstr "IP-адрес 0.0.0.0 не допускается" + +msgid "CIDR must be between 0 and 32" +msgstr "CIDR должен быть между 0 и 32" + +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" + +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" + +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" + +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" + +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" + +msgid "Invalid Shadowsocks URL: missing server" +msgstr "Неверный URL Shadowsocks: отсутствует сервер" + +msgid "Invalid Shadowsocks URL: missing port" +msgstr "Неверный URL Shadowsocks: отсутствует порт" + +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "Неверный номер порта. Допустимо от 1 до 65535" + +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "Неверный URL Shadowsocks: ошибка разбора" + +msgid "Invalid VLESS URL: parsing failed" +msgstr "Неверный URL VLESS: ошибка разбора" + +msgid "Invalid JSON format" +msgstr "Неверный формат JSON" + +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "Неверный URL Trojan: должен начинаться с trojan://" + +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "Неверный URL Trojan: не должен содержать пробелов" + +msgid "Invalid Trojan URL: parsing failed" +msgstr "Неверный URL Trojan: ошибка разбора" + +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +msgid "Fastest" +msgstr "Самый быстрый" + +msgid "HTTP error" +msgstr "Ошибка HTTP" + +msgid "Unknown error" +msgstr "Неизвестная ошибка" + +msgid "DNS checks" +msgstr "" + +msgid "Sing-box checks" +msgstr "" + +msgid "Nftables checks" +msgstr "" + +msgid "FakeIP checks" +msgstr "" + +msgid "Not running" +msgstr "" + +msgid "Queued" +msgstr "" + +msgid "Dashboard currently unavailable" +msgstr "Дашборд сейчас недоступен" + +msgid "Test latency" +msgstr "" + +msgid "Currently unavailable" +msgstr "Временно недоступно" + +msgid "Traffic" +msgstr "Трафик" + +msgid "Uplink" +msgstr "Исходящий" + +msgid "Downlink" +msgstr "Входящий" + +msgid "Traffic Total" +msgstr "Всего трафика" + +msgid "System info" +msgstr "Системная информация" + +msgid "Active Connections" +msgstr "Активные соединения" + +msgid "Memory Usage" +msgstr "Использование памяти" + +msgid "Services info" +msgstr "Информация о сервисах" + +msgid "Podkop" +msgstr "Podkop" + +msgid "\\u2714 Enabled" +msgstr "" + +msgid "\\u2718 Disabled" +msgstr "" + +msgid "Sing-box" +msgstr "Sing-box" + +msgid "\\u2714 Running" +msgstr "" + +msgid "\\u2718 Stopped" +msgstr "" + +msgid "Checking dns, please wait" +msgstr "" + +msgid "Cannot receive DNS checks result" +msgstr "" + +msgid "DNS checks passed" +msgstr "" + +msgid "Bootsrap DNS" +msgstr "" + +msgid "Main DNS" +msgstr "" + +msgid "DNS on router" +msgstr "" + +msgid "DHCP has DNS server" +msgstr "" + +msgid "Checking sing-box, please wait" +msgstr "" + +msgid "Cannot receive Sing-box checks result" +msgstr "" + +msgid "Sing-box checks passed" +msgstr "" + +msgid "Sing-box installed" +msgstr "" + +msgid "Sing-box version >= 1.12.4" +msgstr "" + +msgid "Sing-box service exist" +msgstr "" + +msgid "Sing-box autostart disabled" +msgstr "" + +msgid "Sing-box process running" +msgstr "" + +msgid "Sing-box listening ports" +msgstr "" + +msgid "Checking nftables, please wait" +msgstr "" + +msgid "Cannot receive nftables checks result" +msgstr "" + +msgid "Nftables checks passed" +msgstr "" + +msgid "Nftables checks partially passed" +msgstr "" + +msgid "Table exist" +msgstr "" + +msgid "Rules mangle exist" +msgstr "" + +msgid "Rules mangle counters" +msgstr "" + +msgid "Rules mangle output exist" +msgstr "" + +msgid "Rules mangle output counters" +msgstr "" + +msgid "Rules proxy exist" +msgstr "" + +msgid "Rules proxy counters" +msgstr "" + +msgid "No other marking rules found" +msgstr "" + +msgid "Additional marking rules found" +msgstr "" + +msgid "Checking FakeIP, please wait" +msgstr "" + +msgid "FakeIP checks passed" +msgstr "" + +msgid "FakeIP checks partially passed" +msgstr "" + +msgid "FakeIP checks failed" +msgstr "" + +msgid "Router DNS is routed through sing-box" +msgstr "" + +msgid "Router DNS is not routed through sing-box" +msgstr "" + +msgid "Browser is using FakeIP correctly" +msgstr "" + +msgid "Browser is not using FakeIP" +msgstr "" + +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +msgid "Successfully copied!" +msgstr "" + +msgid "Failed to copy!" +msgstr "" + +msgid "Download" +msgstr "" + +msgid "Copy" +msgstr "" + +msgid "Close" +msgstr "Закрыть" + +msgid "Restart podkop" +msgstr "" + +msgid "Stop podkop" +msgstr "" + +msgid "Start podkop" +msgstr "" + +msgid "Disable autostart" +msgstr "" + +msgid "Enable autostart" +msgstr "" + +msgid "Get global check" +msgstr "" + +msgid "View logs" +msgstr "" + +msgid "Show sing-box config" +msgstr "" + +msgid "Not implement yet" +msgstr "" + +msgid "Run Diagnostic" +msgstr "" + +msgid "unknown" +msgstr "" + +msgid "Global check" +msgstr "Глобальная проверка" + +msgid "Outdated" +msgstr "" + +msgid "Latest" +msgstr "" + +msgid "Operation timed out" +msgstr "Время ожидания истекло" + +msgid "Podkop Settings" +msgstr "" + +msgid "Configuration for Podkop service" +msgstr "" + +msgid "Sections" +msgstr "" + +msgid "Settings" +msgstr "" msgid "Diagnostics" msgstr "Диагностика" -msgid "Additional Settings" -msgstr "Дополнительные настройки" +msgid "Dashboard" +msgstr "Дашборд" -msgid "Yacd enable" -msgstr "Включить YACD" +msgid "Connection Type" +msgstr "Тип подключения" -msgid "Exclude NTP" -msgstr "Исключить NTP" +msgid "Select between VPN and Proxy connection methods for traffic routing" +msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" -msgid "Allows you to exclude NTP protocol traffic from the tunnel" -msgstr "Позволяет исключить направление трафика NTP-протокола в туннель" +msgid "Configuration Type" +msgstr "Тип конфигурации" -msgid "QUIC disable" -msgstr "Отключить QUIC" +msgid "Select how to configure the proxy" +msgstr "Выберите способ настройки прокси" -msgid "For issues with the video stream" -msgstr "Для проблем с видеопотоком" +msgid "Connection URL" +msgstr "URL подключения" -msgid "List Update Frequency" -msgstr "Частота обновления списков" +msgid "Outbound Config" +msgstr "Конфигурация Outbound" -msgid "Select how often the lists will be updated" -msgstr "Выберите как часто будут обновляться списки" +msgid "URLTest" +msgstr "URLTest" + +msgid "Proxy Configuration URL" +msgstr "URL конфигурации прокси" + +msgid "Outbound Configuration" +msgstr "Конфигурация исходящего соединения" + +msgid "Enter complete outbound configuration in JSON format" +msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" + +msgid "URLTest Proxy Links" +msgstr "Ссылки прокси для URLTest" + +msgid "UDP over TCP" +msgstr "" + +msgid "Applicable for SOCKS and Shadowsocks proxy" +msgstr "" + +msgid "Network Interface" +msgstr "Сетевой интерфейс" + +msgid "Select network interface for VPN connection" +msgstr "Выберите сетевой интерфейс для VPN подключения" + +msgid "Domain Resolver" +msgstr "Резолвер доменов" + +msgid "Enable built-in DNS resolver for domains handled by this section" +msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе" + +msgid "DNS Protocol Type" +msgstr "Тип протокола DNS" + +msgid "Select the DNS protocol type for the domain resolver" +msgstr "Выберите тип протокола DNS для резолвера доменов" + +msgid "DNS over HTTPS (DoH)" +msgstr "DNS через HTTPS (DoH)" + +msgid "DNS over TLS (DoT)" +msgstr "DNS через TLS (DoT)" + +msgid "UDP (Unprotected DNS)" +msgstr "UDP (Незащищённый DNS)" + +msgid "DNS Server" +msgstr "DNS-сервер" + +msgid "Select or enter DNS server address" +msgstr "Выберите или введите адрес DNS-сервера" + +msgid "" +msgstr "" + +msgid "Community Lists" +msgstr "Списки сообщества" + +msgid "Select a predefined list for routing" +msgstr "" + +msgid "Regional options cannot be used together" +msgstr "Нельзя использовать несколько региональных опций одновременно" + +msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." +msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." + +msgid "Russia inside restrictions" +msgstr "Ограничения Russia inside" + +msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." +msgstr "" + +msgid "User Domain List Type" +msgstr "Тип пользовательского списка доменов" + +msgid "Select the list type for adding custom domains" +msgstr "" + +msgid "Disabled" +msgstr "Отключено" + +msgid "Dynamic List" +msgstr "Динамический список" + +msgid "Text List" +msgstr "Текстовый список" + +msgid "User Domains" +msgstr "Пользовательские домены" + +msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" +msgstr "" + +msgid "User Domains List" +msgstr "Список пользовательских доменов" + +msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" +msgstr "" + +msgid "At least one valid domain must be specified. Comments-only content is not allowed." +msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается." + +msgid "${validation.value}: ${validation.message}" +msgstr "" + +msgid "Validation errors:" +msgstr "Ошибки валидации:" + +msgid "User Subnet List Type" +msgstr "Тип пользовательского списка подсетей" + +msgid "Select the list type for adding custom subnets" +msgstr "" + +msgid "Text List (comma/space/newline separated)" +msgstr "Текстовый список (через запятую, пробел или новую строку)" + +msgid "User Subnets" +msgstr "Пользовательские подсети" + +msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" +msgstr "" + +msgid "User Subnets List" +msgstr "Список пользовательских подсетей" + +msgid "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //" +msgstr "" + +msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." +msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы." + +msgid "Local Domain Lists" +msgstr "Локальные списки доменов" + +msgid "Specify the path to the list file located on the router filesystem" +msgstr "" + +msgid "Local Subnet Lists" +msgstr "Локальные списки подсетей" + +msgid "Remote Domain Lists" +msgstr "Удалённые списки доменов" + +msgid "Specify remote URLs to download and use domain lists" +msgstr "" + +msgid "Remote Subnet Lists" +msgstr "Удалённые списки подсетей" + +msgid "Specify remote URLs to download and use subnet lists" +msgstr "" + +msgid "Fully Routed IPs" +msgstr "" + +msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" +msgstr "" + +msgid "Enable Mixed Proxy" +msgstr "" + +msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" +msgstr "" + +msgid "Mixed Proxy Port" +msgstr "" + +msgid "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" +msgstr "" msgid "Select DNS protocol to use" msgstr "Выберите протокол DNS" @@ -623,6 +977,78 @@ msgstr "Значение TTL не может быть пустым" msgid "TTL must be a positive number" msgstr "TTL должно быть положительным числом" +msgid "Source Network Interface" +msgstr "Сетевой интерфейс источника" + +msgid "Select the network interface from which the traffic will originate" +msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" + +msgid "Enable Output Network Interface" +msgstr "" + +msgid "You can select Output Network Interface, by default autodetect" +msgstr "" + +msgid "Output Network Interface" +msgstr "" + +msgid "Select the network interface to which the traffic will originate" +msgstr "" + +msgid "Interface Monitoring" +msgstr "" + +msgid "Interface monitoring for Bad WAN" +msgstr "" + +msgid "Monitored Interfaces" +msgstr "" + +msgid "Select the WAN interfaces to be monitored" +msgstr "Выберите WAN интерфейсы для мониторинга" + +msgid "Interface Monitoring Delay" +msgstr "Задержка при мониторинге интерфейсов" + +msgid "Delay in milliseconds before reloading podkop after interface UP" +msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса" + +msgid "Delay value cannot be empty" +msgstr "Значение задержки не может быть пустым" + +msgid "Enable YACD" +msgstr "" + +msgid "Disable QUIC" +msgstr "" + +msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" +msgstr "" + +msgid "List Update Frequency" +msgstr "Частота обновления списков" + +msgid "Select how often the domain or subnet lists are updated automatically" +msgstr "" + +msgid "Download Lists via Proxy/VPN" +msgstr "" + +msgid "Downloading all lists via main Proxy/VPN" +msgstr "Загрузка всех списков через основной прокси/VPN" + +msgid "Download Lists via specific proxy section" +msgstr "" + +msgid "Downloading all lists via specific Proxy/VPN" +msgstr "" + +msgid "Dont Touch My DHCP!" +msgstr "" + +msgid "Podkop will not modify your DHCP configuration" +msgstr "" + msgid "Config File Path" msgstr "Путь к файлу конфигурации" @@ -647,53 +1073,14 @@ msgstr "Путь должен заканчиваться на cache.db" msgid "Path must contain at least one directory (like /tmp/cache.db)" msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)" -msgid "Source Network Interface" -msgstr "Сетевой интерфейс источника" +msgid "Exclude NTP" +msgstr "Исключить NTP" -msgid "Select the network interface from which the traffic will originate" -msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" +msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" +msgstr "" -msgid "Interface monitoring" -msgstr "Мониторинг интерфейсов" +msgid "Routing Excluded IPs" +msgstr "" -msgid "Interface monitoring for bad WAN" -msgstr "Мониторинг интерфейсов для плохого WAN" - -msgid "Interface for monitoring" -msgstr "Интерфейс для мониторинга" - -msgid "Select the WAN interfaces to be monitored" -msgstr "Выберите WAN интерфейсы для мониторинга" - -msgid "Interface Monitoring Delay" -msgstr "Задержка при мониторинге интерфейсов" - -msgid "Delay in milliseconds before reloading podkop after interface UP" -msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса" - -msgid "Delay value cannot be empty" -msgstr "Значение задержки не может быть пустым" - -msgid "Dont touch my DHCP!" -msgstr "Не трогать мой DHCP!" - -msgid "Podkop will not change the DHCP config" -msgstr "Podkop не будет изменять конфигурацию DHCP" - -msgid "Proxy download of lists" -msgstr "Загрузка списков через прокси" - -msgid "Downloading all lists via main Proxy/VPN" -msgstr "Загрузка всех списков через основной прокси/VPN" - -msgid "IP for exclusion" -msgstr "IP для исключения" - -msgid "Specify local IP addresses that will never use the configured route" -msgstr "Укажите локальные IP-адреса, которые никогда не будут использовать настроенный маршрут" - -msgid "Mixed enable" -msgstr "Включить смешанный режим" - -msgid "Browser port: 2080" -msgstr "Порт браузера: 2080" +msgid "Specify a local IP address to be excluded from routing" +msgstr "" diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index 778c412..64f11d4 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -1,954 +1,1517 @@ # SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PODKOP package. -# FIRST AUTHOR , YEAR. -# +# divocat , 2025. #, fuzzy msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-07 16:55+0300\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" +"POT-Creation-Date: 2025-10-21 18:31+0300\n" +"PO-Revision-Date: 2025-10-21 18:31+0300\n" +"Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: htdocs/luci-static/resources/view/podkop/configSection.js:12 -msgid "Basic Settings" +#: src/helpers/copyToClipboard.ts:10 +msgid "Successfully copied!" msgstr "" -#: htdocs/luci-static/resources/view/podkop/configSection.js:18 -msgid "Connection Type" +#: src/helpers/copyToClipboard.ts:12 +msgid "Failed to copy!" msgstr "" -#: htdocs/luci-static/resources/view/podkop/configSection.js:19 -msgid "Select between VPN and Proxy connection methods for traffic routing" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:30 -msgid "Configuration Type" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:31 -msgid "Select how to configure the proxy" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:33 -msgid "Connection URL" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:34 -msgid "Outbound Config" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:35 -msgid "URLTest" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:44 -msgid "Proxy Configuration URL" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:81 -msgid "Current config: " -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:88 -#: htdocs/luci-static/resources/view/podkop/configSection.js:96 -#: htdocs/luci-static/resources/view/podkop/configSection.js:106 -msgid "Config without description" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:115 -msgid "" -"Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup " -"configs" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:139 -msgid "No active configuration found. One configuration is required." -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:145 -msgid "Multiply active configurations found. Please leave one configuration." -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:157 -msgid "Invalid URL format:" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:165 -msgid "Outbound Configuration" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:166 -msgid "Enter complete outbound configuration in JSON format" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:190 -msgid "URLTest Proxy Links" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:214 -msgid "Shadowsocks UDP over TCP" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:215 -msgid "Apply for SS2022" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:226 -msgid "Network Interface" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:227 -msgid "Select network interface for VPN connection" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:274 -msgid "Domain Resolver" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:275 -msgid "Enable built-in DNS resolver for domains handled by this section" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:286 -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:61 -msgid "DNS Protocol Type" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:287 -msgid "Select the DNS protocol type for the domain resolver" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:289 -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:64 -msgid "DNS over HTTPS (DoH)" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:290 -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:65 -msgid "DNS over TLS (DoT)" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:291 -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:66 -msgid "UDP (Unprotected DNS)" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:301 -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:75 -msgid "DNS Server" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:302 -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:76 -msgid "Select or enter DNS server address" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:325 -msgid "Community Lists" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:335 -msgid "Service List" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:336 -msgid "Select predefined service for routing" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:372 -msgid "Regional options cannot be used together" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:375 -#, javascript-format -msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:391 -msgid "Russia inside restrictions" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:394 -#, javascript-format -msgid "" -"Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:427 -msgid "User Domain List Type" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:428 -msgid "Select how to add your custom domains" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:430 -#: htdocs/luci-static/resources/view/podkop/configSection.js:625 -msgid "Disabled" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:431 -#: htdocs/luci-static/resources/view/podkop/configSection.js:626 -msgid "Dynamic List" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:432 -msgid "Text List" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:441 -msgid "User Domains" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:443 -msgid "Enter domain names without protocols (example: sub.example.com or example.com)" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:469 -msgid "User Domains List" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:471 -msgid "Enter domain names separated by comma, space or newline. You can add comments after //" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:490 -msgid "At least one valid domain must be specified. Comments-only content is not allowed." -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:501 -#: htdocs/luci-static/resources/view/podkop/configSection.js:696 -msgid "Validation errors:" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:511 -msgid "Local Domain Lists" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:512 -#: htdocs/luci-static/resources/view/podkop/configSection.js:586 -msgid "Use the list from the router filesystem" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:522 -msgid "Local Domain List Paths" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:523 -#: htdocs/luci-static/resources/view/podkop/configSection.js:597 -msgid "Enter the list file path" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:548 -msgid "Remote Domain Lists" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:549 -msgid "Download and use domain lists from remote URLs" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:559 -msgid "Remote Domain URLs" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:560 -#: htdocs/luci-static/resources/view/podkop/configSection.js:718 -msgid "Enter full URLs starting with http:// or https://" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:585 -msgid "Local Subnet Lists" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:596 -msgid "Local Subnet List Paths" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:622 -msgid "User Subnet List Type" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:623 -msgid "Select how to add your custom subnets" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:627 -msgid "Text List (comma/space/newline separated)" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:636 -msgid "User Subnets" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:638 -msgid "Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:664 -msgid "User Subnets List" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:666 -msgid "" -"Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments " -"after //" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:685 -msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:706 -msgid "Remote Subnet Lists" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:707 -msgid "Download and use subnet lists from remote URLs" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:717 -msgid "Remote Subnet URLs" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:743 -msgid "IP for full redirection" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:745 -msgid "Specify local IP addresses whose traffic will always use the configured route" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:756 -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:326 -msgid "Local IPs" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/configSection.js:757 -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:327 -msgid "Enter valid IPv4 addresses" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/podkop.js:64 -msgid "Extra configurations" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/podkop.js:68 -msgid "Add Section" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/dashboardTab.js:11 -msgid "Dashboard" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:11 htdocs/luci-static/resources/view/podkop/main.js:28 -#: htdocs/luci-static/resources/view/podkop/main.js:37 htdocs/luci-static/resources/view/podkop/main.js:40 -#: htdocs/luci-static/resources/view/podkop/main.js:60 htdocs/luci-static/resources/view/podkop/main.js:115 -#: htdocs/luci-static/resources/view/podkop/main.js:204 htdocs/luci-static/resources/view/podkop/main.js:295 -#: htdocs/luci-static/resources/view/podkop/main.js:313 htdocs/luci-static/resources/view/podkop/main.js:346 -msgid "Valid" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:13 -msgid "Invalid IP address" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:20 htdocs/luci-static/resources/view/podkop/main.js:26 -msgid "Invalid domain address" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:34 -msgid "DNS server address cannot be empty" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:45 -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:57 -msgid "URL must use one of the following protocols:" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:62 -msgid "Invalid URL format" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:71 -msgid "Path cannot be empty" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:84 -msgid "Invalid path format. Path must start with \"/\" and contain valid characters" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:95 -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:100 -msgid "IP address 0.0.0.0 is not allowed" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:111 -msgid "CIDR must be between 0 and 32" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:132 -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:139 -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:147 -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:156 -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:165 -msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:174 -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:181 -msgid "Invalid Shadowsocks URL: missing server" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:188 -msgid "Invalid Shadowsocks URL: missing port" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:195 -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:201 -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:214 -msgid "Invalid VLESS URL: must not contain spaces" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:220 -msgid "Invalid VLESS URL: must start with vless://" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:224 -msgid "Invalid VLESS URL: missing UUID" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:227 -msgid "Invalid VLESS URL: missing server" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:230 -msgid "Invalid VLESS URL: missing port" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:236 -msgid "Invalid VLESS URL: invalid port number. Must be between 1 and 65535" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:243 -msgid "Invalid VLESS URL: missing query parameters" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:263 -msgid "Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:273 -msgid "Invalid VLESS URL: security must be one of tls, reality, none" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:282 -msgid "Invalid VLESS URL: missing pbk parameter for reality security" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:290 -msgid "Invalid VLESS URL: missing fp parameter for reality security" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:297 -msgid "Invalid VLESS URL: parsing failed" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:309 -msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:315 -msgid "Invalid JSON format" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:324 -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:330 -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:339 -msgid "Invalid Trojan URL: must contain username, hostname and port" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:344 -msgid "Invalid Trojan URL: parsing failed" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:362 -msgid "URL must start with vless:// or ss:// or trojan://" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/main.js:568 +#: src/helpers/withTimeout.ts:7 msgid "Operation timed out" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:775 +#: src/podkop/api.ts:27 msgid "HTTP error" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:786 +#: src/podkop/api.ts:40 msgid "Unknown error" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:951 -msgid "Fastest" +#: src/validators/validateDns.ts:7 +msgid "DNS server address cannot be empty" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1222 -msgid "Dashboard currently unavailable" +#: src/validators/validateDns.ts:11 +#: src/validators/validateDns.ts:15 +#: src/validators/validateDomain.ts:13 +#: src/validators/validateDomain.ts:30 +#: src/validators/validateIp.ts:8 +#: src/validators/validateOutboundJson.ts:17 +#: src/validators/validatePath.ts:16 +#: src/validators/validateShadowsocksUrl.ts:95 +#: src/validators/validateSocksUrl.ts:80 +#: src/validators/validateSubnet.ts:38 +#: src/validators/validateTrojanUrl.ts:59 +#: src/validators/validateUrl.ts:16 +#: src/validators/validateVlessUrl.ts:107 +msgid "Valid" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1326 -msgid "Currently unavailable" +#: src/validators/validateDns.ts:20 +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1721 -msgid "Traffic" +#: src/validators/validateDomain.ts:18 +#: src/validators/validateDomain.ts:27 +msgid "Invalid domain address" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1723 htdocs/luci-static/resources/view/podkop/main.js:1748 -msgid "Uplink" +#: src/validators/validateIp.ts:11 +msgid "Invalid IP address" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1724 htdocs/luci-static/resources/view/podkop/main.js:1752 -msgid "Downlink" +#: src/validators/validateOutboundJson.ts:11 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:327 +msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1745 -msgid "Traffic Total" +#: src/validators/validateOutboundJson.ts:19 +msgid "Invalid JSON format" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1775 -msgid "System info" +#: src/validators/validatePath.ts:7 +msgid "Path cannot be empty" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1778 -msgid "Active Connections" +#: src/validators/validatePath.ts:22 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:90 +msgid "Invalid path format. Path must start with \"/\" and contain valid characters" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1782 -msgid "Memory Usage" +#: src/validators/validateProxyUrl.ts:27 +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1805 -msgid "Services info" +#: src/validators/validateShadowsocksUrl.ts:8 +msgid "Invalid Shadowsocks URL: must start with ss://" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1808 htdocs/luci-static/resources/view/podkop/diagnosticTab.js:1139 -msgid "Podkop" +#: src/validators/validateShadowsocksUrl.ts:16 +msgid "Invalid Shadowsocks URL: must not contain spaces" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1809 -msgid "✔ Enabled" +#: src/validators/validateShadowsocksUrl.ts:27 +msgid "Invalid Shadowsocks URL: missing credentials" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1809 -msgid "✘ Disabled" +#: src/validators/validateShadowsocksUrl.ts:37 +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1815 -msgid "Sing-box" +#: src/validators/validateShadowsocksUrl.ts:46 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:171 +msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1816 -msgid "✔ Running" +#: src/validators/validateShadowsocksUrl.ts:58 +msgid "Invalid Shadowsocks URL: missing server address" msgstr "" -#: htdocs/luci-static/resources/view/podkop/main.js:1816 -msgid "✘ Stopped" +#: src/validators/validateShadowsocksUrl.ts:67 +msgid "Invalid Shadowsocks URL: missing server" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:137 -msgid "Copied!" +#: src/validators/validateShadowsocksUrl.ts:76 +msgid "Invalid Shadowsocks URL: missing port" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:143 -msgid "Failed to copy: " +#: src/validators/validateShadowsocksUrl.ts:85 +msgid "Invalid port number. Must be between 1 and 65535" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:327 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:542 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:759 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:762 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:765 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:768 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:771 -msgid "Loading..." +#: src/validators/validateShadowsocksUrl.ts:91 +msgid "Invalid Shadowsocks URL: parsing failed" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:351 -msgid "Copy to Clipboard" +#: src/validators/validateSocksUrl.ts:10 +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:359 +#: src/validators/validateSocksUrl.ts:19 +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +#: src/validators/validateSocksUrl.ts:34 +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +#: src/validators/validateSocksUrl.ts:42 +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +#: src/validators/validateSocksUrl.ts:51 +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +#: src/validators/validateSocksUrl.ts:56 +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +#: src/validators/validateSocksUrl.ts:63 +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +#: src/validators/validateSocksUrl.ts:73 +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +#: src/validators/validateSocksUrl.ts:77 +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +#: src/validators/validateSubnet.ts:11 +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "" + +#: src/validators/validateSubnet.ts:18 +msgid "IP address 0.0.0.0 is not allowed" +msgstr "" + +#: src/validators/validateSubnet.ts:33 +msgid "CIDR must be between 0 and 32" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:8 +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:15 +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:56 +msgid "Invalid Trojan URL: parsing failed" +msgstr "" + +#: src/validators/validateUrl.ts:13 +msgid "URL must use one of the following protocols:" +msgstr "" + +#: src/validators/validateUrl.ts:18 +msgid "Invalid URL format" +msgstr "" + +#: src/validators/validateVlessUrl.ts:109 +msgid "Invalid VLESS URL: parsing failed" +msgstr "" + +#: src/partials/modal/renderModal.ts:15 +msgid "Download" +msgstr "" + +#: src/partials/modal/renderModal.ts:20 +msgid "Copy" +msgstr "" + +#: src/partials/modal/renderModal.ts:26 msgid "Close" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:380 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:483 -msgid "No output" +#: src/podkop/methods/custom/getDashboardSections.ts:117 +msgid "Fastest" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:398 -msgid "FakeIP is working in browser!" +#: src/podkop/tabs/dashboard/initController.ts:235 +msgid "Traffic" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:401 -msgid "FakeIP is not working in browser" +#: src/podkop/tabs/dashboard/initController.ts:237 +#: src/podkop/tabs/dashboard/initController.ts:268 +msgid "Uplink" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:403 -msgid "Check DNS server on current device (PC, phone)" +#: src/podkop/tabs/dashboard/initController.ts:238 +#: src/podkop/tabs/dashboard/initController.ts:272 +msgid "Downlink" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:404 -msgid "Its must be router!" +#: src/podkop/tabs/dashboard/initController.ts:265 +msgid "Traffic Total" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:426 -msgid "Proxy working correctly" +#: src/podkop/tabs/dashboard/initController.ts:301 +msgid "System info" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:428 -msgid "Direct IP: " +#: src/podkop/tabs/dashboard/initController.ts:304 +msgid "Active Connections" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:430 -msgid "Proxy IP: " +#: src/podkop/tabs/dashboard/initController.ts:308 +msgid "Memory Usage" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:434 -msgid "Proxy is not working - same IP for both domains" +#: src/podkop/tabs/dashboard/initController.ts:337 +msgid "Services info" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:437 -msgid "IP: " +#: src/podkop/tabs/dashboard/initController.ts:340 +msgid "Podkop" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:440 -msgid "Proxy check failed" +#: src/podkop/tabs/dashboard/initController.ts:342 +msgid "✔ Enabled" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:448 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:459 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:470 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:477 -msgid "Check failed: " +#: src/podkop/tabs/dashboard/initController.ts:343 +msgid "✘ Disabled" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:450 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:461 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:471 -msgid "timeout" +#: src/podkop/tabs/dashboard/initController.ts:351 +msgid "Sing-box" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:488 -msgid "Error: " +#: src/podkop/tabs/dashboard/initController.ts:353 +msgid "✔ Running" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:571 -msgid "Podkop Status" +#: src/podkop/tabs/dashboard/initController.ts:354 +msgid "✘ Stopped" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:604 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:55 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:63 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:71 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:79 +msgid "Not running" +msgstr "" + +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:95 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:103 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:111 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:119 +msgid "Queued" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:34 +#: src/podkop/tabs/diagnostic/initController.ts:35 +#: src/podkop/tabs/diagnostic/initController.ts:36 +#: src/podkop/tabs/diagnostic/initController.ts:37 +#: src/podkop/tabs/diagnostic/initController.ts:38 +#: src/podkop/tabs/diagnostic/initController.ts:39 +#: src/podkop/tabs/diagnostic/initController.ts:373 +msgid "unknown" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:218 msgid "Global check" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:606 -msgid "Click here for all the info" +#: src/podkop/tabs/diagnostic/initController.ts:248 +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:107 +msgid "View logs" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:614 -msgid "Update Lists" +#: src/podkop/tabs/diagnostic/initController.ts:278 +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116 +msgid "Show sing-box config" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:616 -msgid "Lists Update Results" +#: src/podkop/tabs/diagnostic/initController.ts:394 +msgid "Outdated" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:633 -msgid "Sing-box Status" +#: src/podkop/tabs/diagnostic/initController.ts:404 +msgid "Latest" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:660 -msgid "Check NFT Rules" +#: src/podkop/tabs/dashboard/partials/renderSections.ts:19 +msgid "Dashboard currently unavailable" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:662 -msgid "NFT Rules" +#: src/podkop/tabs/dashboard/partials/renderSections.ts:108 +msgid "Test latency" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:665 -msgid "Check DNSMasq" +#: src/podkop/tabs/dashboard/partials/renderWidget.ts:22 +msgid "Currently unavailable" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:667 -msgid "DNSMasq Configuration" +#: src/podkop/tabs/diagnostic/checks/contstants.ts:14 +msgid "DNS checks" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:684 -msgid "FakeIP Status" +#: src/podkop/tabs/diagnostic/checks/contstants.ts:19 +msgid "Sing-box checks" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:711 -msgid "DNS Status" +#: src/podkop/tabs/diagnostic/checks/contstants.ts:24 +msgid "Nftables checks" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:728 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:1096 -msgid "Main config" +#: src/podkop/tabs/diagnostic/checks/contstants.ts:29 +msgid "FakeIP checks" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:748 -msgid "Version Information" +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14 +msgid "Checking dns, please wait" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:758 -msgid "Podkop: " +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26 +msgid "Cannot receive DNS checks result" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:761 -msgid "LuCI App: " +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64 +msgid "DNS checks passed" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:764 -msgid "Sing-box: " +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72 +msgid "Bootsrap DNS" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:767 -msgid "OpenWrt Version: " +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79 +msgid "Main DNS" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:770 -msgid "Device Model: " +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84 +msgid "DNS on router" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:916 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:929 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:943 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:962 -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:964 -msgid "Unknown" +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89 +msgid "DHCP has DNS server" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:988 -msgid "works in browser" +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14 +msgid "Checking FakeIP, please wait" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:989 -msgid "does not work in browser" +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44 +msgid "FakeIP checks passed" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:1014 -msgid "works on router" +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51 +msgid "FakeIP checks partially passed" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:1015 -msgid "does not work on router" +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57 +msgid "FakeIP checks failed" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:1110 -msgid "Config: " +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73 +msgid "Router DNS is routed through sing-box" msgstr "" -#: htdocs/luci-static/resources/view/podkop/diagnosticTab.js:1127 +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74 +msgid "Router DNS is not routed through sing-box" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80 +msgid "Browser is using FakeIP correctly" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81 +msgid "Browser is not using FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88 +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89 +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12 +msgid "Checking nftables, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27 +msgid "Cannot receive nftables checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74 +msgid "Nftables checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75 +msgid "Nftables checks partially passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80 +msgid "Table exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85 +msgid "Rules mangle exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90 +msgid "Rules mangle counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95 +msgid "Rules mangle output exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100 +msgid "Rules mangle output counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105 +msgid "Rules proxy exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110 +msgid "Rules proxy counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116 +msgid "No other marking rules found" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117 +msgid "Additional marking rules found" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12 +msgid "Checking sing-box, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24 +msgid "Cannot receive Sing-box checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66 +msgid "Sing-box checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71 +msgid "Sing-box installed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76 +msgid "Sing-box version >= 1.12.4" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81 +msgid "Sing-box service exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86 +msgid "Sing-box autostart disabled" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91 +msgid "Sing-box process running" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96 +msgid "Sing-box listening ports" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49 +msgid "Restart podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59 +msgid "Stop podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69 +msgid "Start podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79 +msgid "Disable autostart" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89 +msgid "Enable autostart" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98 +msgid "Get global check" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189 +msgid "Not implement yet" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15 +msgid "Run Diagnostic" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:12 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:23 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:35 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:44 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:47 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:67 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:85 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:122 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:211 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:314 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:332 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:383 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:449 +msgid "Valid" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:14 +msgid "Invalid IP address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:27 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:33 +msgid "Invalid domain address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:41 +msgid "DNS server address cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:51 +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:64 +msgid "URL must use one of the following protocols:" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:69 +msgid "Invalid URL format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:78 +msgid "Path cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:102 +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:107 +msgid "IP address 0.0.0.0 is not allowed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:118 +msgid "CIDR must be between 0 and 32" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:139 +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:146 +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:154 +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:162 +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:181 +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:188 +msgid "Invalid Shadowsocks URL: missing server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:195 +msgid "Invalid Shadowsocks URL: missing port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:202 +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:208 +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:316 +msgid "Invalid VLESS URL: parsing failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:334 +msgid "Invalid JSON format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:344 +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:350 +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:381 +msgid "Invalid Trojan URL: parsing failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:392 +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:400 +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:411 +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:418 +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:425 +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:429 +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:435 +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:443 +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:447 +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:468 +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:692 +msgid "Fastest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:864 +msgid "HTTP error" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:875 +msgid "Unknown error" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:985 +msgid "DNS checks" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:990 +msgid "Sing-box checks" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:995 +msgid "Nftables checks" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1000 +msgid "FakeIP checks" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1048 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1056 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1064 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1072 +msgid "Not running" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1084 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1092 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1100 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1108 +msgid "Queued" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1578 +msgid "Dashboard currently unavailable" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1654 +msgid "Test latency" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1683 +msgid "Currently unavailable" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2024 +msgid "Traffic" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2026 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2051 +msgid "Uplink" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2027 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2055 +msgid "Downlink" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2048 +msgid "Traffic Total" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2078 +msgid "System info" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2081 +msgid "Active Connections" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2085 +msgid "Memory Usage" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2108 +msgid "Services info" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2111 +msgid "Podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112 +msgid "\\u2714 Enabled" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112 +msgid "\\u2718 Disabled" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2118 +msgid "Sing-box" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119 +msgid "\\u2714 Running" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119 +msgid "\\u2718 Stopped" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2365 +msgid "Checking dns, please wait" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2375 +msgid "Cannot receive DNS checks result" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2397 +msgid "DNS checks passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2405 +msgid "Bootsrap DNS" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2412 +msgid "Main DNS" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2417 +msgid "DNS on router" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2422 +msgid "DHCP has DNS server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2439 +msgid "Checking sing-box, please wait" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2449 +msgid "Cannot receive Sing-box checks result" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2471 +msgid "Sing-box checks passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2476 +msgid "Sing-box installed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2481 +msgid "Sing-box version >= 1.12.4" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2486 +msgid "Sing-box service exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2491 +msgid "Sing-box autostart disabled" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2496 +msgid "Sing-box process running" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2501 +msgid "Sing-box listening ports" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2518 +msgid "Checking nftables, please wait" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2530 +msgid "Cannot receive nftables checks result" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552 +msgid "Nftables checks passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552 +msgid "Nftables checks partially passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2557 +msgid "Table exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2562 +msgid "Rules mangle exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2567 +msgid "Rules mangle counters" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2572 +msgid "Rules mangle output exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2577 +msgid "Rules mangle output counters" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2582 +msgid "Rules proxy exist" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2587 +msgid "Rules proxy counters" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592 +msgid "No other marking rules found" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592 +msgid "Additional marking rules found" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2609 +msgid "Checking FakeIP, please wait" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2627 +msgid "FakeIP checks passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2633 +msgid "FakeIP checks partially passed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2638 +msgid "FakeIP checks failed" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651 +msgid "Router DNS is routed through sing-box" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651 +msgid "Router DNS is not routed through sing-box" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656 +msgid "Browser is using FakeIP correctly" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656 +msgid "Browser is not using FakeIP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662 +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662 +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3288 +msgid "Successfully copied!" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3290 +msgid "Failed to copy!" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3306 +msgid "Download" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3311 +msgid "Copy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3318 +msgid "Close" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3350 +msgid "Restart podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3360 +msgid "Stop podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3370 +msgid "Start podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3380 +msgid "Disable autostart" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3390 +msgid "Enable autostart" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3399 +msgid "Get global check" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3408 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3840 +msgid "View logs" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3417 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3867 +msgid "Show sing-box config" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3577 +msgid "Not implement yet" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3587 +msgid "Run Diagnostic" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3651 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3652 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3653 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3654 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3655 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3656 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3950 +msgid "unknown" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3813 +msgid "Global check" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3969 +msgid "Outdated" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3978 +msgid "Latest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:4389 +msgid "Operation timed out" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26 +msgid "Podkop Settings" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27 +msgid "Configuration for Podkop service" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36 +msgid "Sections" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49 +msgid "Settings" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65 msgid "Diagnostics" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:8 -msgid "Additional Settings" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80 +msgid "Dashboard" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:14 -msgid "Yacd enable" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12 +msgid "Connection Type" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:25 -msgid "Exclude NTP" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13 +msgid "Select between VPN and Proxy connection methods for traffic routing" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:26 -msgid "Allows you to exclude NTP protocol traffic from the tunnel" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22 +msgid "Configuration Type" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:36 -msgid "QUIC disable" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23 +msgid "Select how to configure the proxy" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:37 -msgid "For issues with the video stream" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25 +msgid "Connection URL" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:47 -msgid "List Update Frequency" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26 +msgid "Outbound Config" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:48 -msgid "Select how often the lists will be updated" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27 +msgid "URLTest" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:62 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34 +msgid "Proxy Configuration URL" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64 +msgid "Outbound Configuration" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65 +msgid "Enter complete outbound configuration in JSON format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87 +msgid "URLTest Proxy Links" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110 +msgid "UDP over TCP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111 +msgid "Applicable for SOCKS and Shadowsocks proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120 +msgid "Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121 +msgid "Select network interface for VPN connection" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166 +msgid "Domain Resolver" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167 +msgid "Enable built-in DNS resolver for domains handled by this section" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12 +msgid "DNS Protocol Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177 +msgid "Select the DNS protocol type for the domain resolver" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15 +msgid "DNS over HTTPS (DoH)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16 +msgid "DNS over TLS (DoT)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17 +msgid "UDP (Unprotected DNS)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24 +msgid "DNS Server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25 +msgid "Select or enter DNS server address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:193 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:217 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:28 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:51 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:254 +msgid "" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211 +msgid "Community Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212 +msgid "Select a predefined list for routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245 +msgid "Regional options cannot be used together" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247 +msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264 +msgid "Russia inside restrictions" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266 +msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299 +msgid "User Domain List Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300 +msgid "Select the list type for adding custom domains" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382 +msgid "Disabled" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383 +msgid "Dynamic List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304 +msgid "Text List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311 +msgid "User Domains" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312 +msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337 +msgid "User Domains List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338 +msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356 +msgid "At least one valid domain must be specified. Comments-only content is not allowed." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:447 +msgid "${validation.value}: ${validation.message}" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449 +msgid "Validation errors:" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379 +msgid "User Subnet List Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380 +msgid "Select the list type for adding custom subnets" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384 +msgid "Text List (comma/space/newline separated)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391 +msgid "User Subnets" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392 +msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417 +msgid "User Subnets List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:418 +msgid "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437 +msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458 +msgid "Local Domain Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482 +msgid "Specify the path to the list file located on the router filesystem" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481 +msgid "Local Subnet Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504 +msgid "Remote Domain Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505 +msgid "Specify remote URLs to download and use domain lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527 +msgid "Remote Subnet Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528 +msgid "Specify remote URLs to download and use subnet lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550 +msgid "Fully Routed IPs" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551 +msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575 +msgid "Enable Mixed Proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576 +msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586 +msgid "Mixed Proxy Port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:587 +msgid "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" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13 msgid "Select DNS protocol to use" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:98 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45 msgid "Bootstrap DNS server" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:100 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 msgid "The DNS server used to look up the IP address of an upstream DNS server" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:123 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68 msgid "DNS Rewrite TTL" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:124 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69 msgid "Time in seconds for DNS record caching (default: 60)" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:131 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75 msgid "TTL value cannot be empty" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:136 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80 msgid "TTL must be a positive number" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:146 -msgid "Config File Path" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:148 -msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:161 -msgid "Cache File Path" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:163 -msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:176 -msgid "Cache file path cannot be empty" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:180 -msgid "Path must be absolute (start with /)" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:184 -msgid "Path must end with cache.db" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:189 -msgid "Path must contain at least one directory (like /tmp/cache.db)" -msgstr "" - -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:199 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89 msgid "Source Network Interface" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:200 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90 msgid "Select the network interface from which the traffic will originate" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:238 -msgid "Interface monitoring" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126 +msgid "Enable Output Network Interface" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:239 -msgid "Interface monitoring for bad WAN" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:127 +msgid "You can select Output Network Interface, by default autodetect" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:249 -msgid "Interface for monitoring" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135 +msgid "Output Network Interface" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:250 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136 +msgid "Select the network interface to which the traffic will originate" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182 +msgid "Interface Monitoring" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183 +msgid "Interface monitoring for Bad WAN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191 +msgid "Monitored Interfaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192 msgid "Select the WAN interfaces to be monitored" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:274 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214 msgid "Interface Monitoring Delay" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:275 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215 msgid "Delay in milliseconds before reloading podkop after interface UP" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:283 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222 msgid "Delay value cannot be empty" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:292 -msgid "Dont touch my DHCP!" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230 +msgid "Enable YACD" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:293 -msgid "Podkop will not change the DHCP config" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239 +msgid "Disable QUIC" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:303 -msgid "Proxy download of lists" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240 +msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:304 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250 +msgid "List Update Frequency" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251 +msgid "Select how often the domain or subnet lists are updated automatically" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262 +msgid "Download Lists via Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263 msgid "Downloading all lists via main Proxy/VPN" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:315 -msgid "IP for exclusion" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271 +msgid "Download Lists via specific proxy section" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:316 -msgid "Specify local IP addresses that will never use the configured route" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272 +msgid "Downloading all lists via specific Proxy/VPN" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:352 -msgid "Mixed enable" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300 +msgid "Dont Touch My DHCP!" msgstr "" -#: htdocs/luci-static/resources/view/podkop/additionalTab.js:353 -msgid "Browser port: 2080" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301 +msgid "Podkop will not modify your DHCP configuration" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309 +msgid "Config File Path" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310 +msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322 +msgid "Cache File Path" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323 +msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336 +msgid "Cache file path cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340 +msgid "Path must be absolute (start with /)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344 +msgid "Path must end with cache.db" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349 +msgid "Path must contain at least one directory (like /tmp/cache.db)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358 +msgid "Exclude NTP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359 +msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369 +msgid "Routing Excluded IPs" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370 +msgid "Specify a local IP address to be excluded from routing" msgstr "" From 3379764adab901587d9710336cf13e8076aeac34 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 21 Oct 2025 21:57:24 +0300 Subject: [PATCH 106/121] feat: translate some keys --- .gitignore | 1 + fe-app-podkop/locales/calls.json | 8 - fe-app-podkop/locales/podkop.pot | 9 +- fe-app-podkop/locales/podkop.ru.po | 137 +++++++++--------- .../resources/view/podkop/section.js | 4 +- luci-app-podkop/po/ru/podkop.po | 137 +++++++++--------- luci-app-podkop/po/templates/podkop.pot | 9 +- 7 files changed, 141 insertions(+), 164 deletions(-) diff --git a/.gitignore b/.gitignore index ff06e12..21b50a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea fe-app-podkop/node_modules fe-app-podkop/.env +.DS_Store diff --git a/fe-app-podkop/locales/calls.json b/fe-app-podkop/locales/calls.json index 9f4c445..e972fd0 100644 --- a/fe-app-podkop/locales/calls.json +++ b/fe-app-podkop/locales/calls.json @@ -2102,14 +2102,6 @@ "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356" ] }, - { - "call": "_(`${validation.value}: ${validation.message}`)", - "key": "${validation.value}: ${validation.message}", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:447" - ] - }, { "call": "_(\"Validation errors:\")", "key": "Validation errors:", diff --git a/fe-app-podkop/locales/podkop.pot b/fe-app-podkop/locales/podkop.pot index 64f11d4..415585a 100644 --- a/fe-app-podkop/locales/podkop.pot +++ b/fe-app-podkop/locales/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 18:31+0300\n" -"PO-Revision-Date: 2025-10-21 18:31+0300\n" +"POT-Creation-Date: 2025-10-21 18:48+0300\n" +"PO-Revision-Date: 2025-10-21 18:48+0300\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -1249,11 +1249,6 @@ msgstr "" msgid "At least one valid domain must be specified. Comments-only content is not allowed." msgstr "" -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:447 -msgid "${validation.value}: ${validation.message}" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370 #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449 msgid "Validation errors:" diff --git a/fe-app-podkop/locales/podkop.ru.po b/fe-app-podkop/locales/podkop.ru.po index 10dd5ae..cb60b88 100644 --- a/fe-app-podkop/locales/podkop.ru.po +++ b/fe-app-podkop/locales/podkop.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 21:31+0300\n" -"PO-Revision-Date: 2025-10-21 21:31+0300\n" +"POT-Creation-Date: 2025-10-21 21:44+0300\n" +"PO-Revision-Date: 2025-10-21 21:44+0300\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -18,10 +18,10 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" msgid "Successfully copied!" -msgstr "" +msgstr "Успешно скопировано!" msgid "Failed to copy!" -msgstr "" +msgstr "Не удалось скопировать!" msgid "Operation timed out" msgstr "Время ожидания истекло" @@ -60,7 +60,7 @@ msgid "Invalid path format. Path must start with \"/\" and contain valid charact msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы" msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" +msgstr "Ссылка должна начинаться с vless://, ss://, trojan://, или socks4/5://" msgid "Invalid Shadowsocks URL: must start with ss://" msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" @@ -93,31 +93,31 @@ msgid "Invalid Shadowsocks URL: parsing failed" msgstr "Неверный URL Shadowsocks: ошибка разбора" msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" +msgstr "Невалидная SOCKS ссылка: должна начинаться с socks4://, socks4a://, или socks5://" msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" +msgstr "Невалидная SOCKS ссылка: не должна содержать пробелов" msgid "Invalid SOCKS URL: missing username" -msgstr "" +msgstr "Невалидная SOCKS ссылка: отсуствует имя пользователя" msgid "Invalid SOCKS URL: missing host and port" -msgstr "" +msgstr "Невалидная SOCKS ссылка: отсутствуют хост и порт" msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" +msgstr "Невалидная SOCKS ссылка: отсутствуют имя хоста или айпи" msgid "Invalid SOCKS URL: missing port" -msgstr "" +msgstr "Невалидная SOCKS ссылка: отсутствует порт" msgid "Invalid SOCKS URL: invalid port number" -msgstr "" +msgstr "Невалидная SOCKS ссылка: невалидный номер порта" msgid "Invalid SOCKS URL: invalid host format" -msgstr "" +msgstr "Невалидная SOCKS ссылка: невалидный формат хоста" msgid "Invalid SOCKS URL: parsing failed" -msgstr "" +msgstr "Невалидная SOCKS ссылка: не удалось распарсить" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" @@ -147,17 +147,14 @@ msgid "Invalid VLESS URL: parsing failed" msgstr "Неверный URL VLESS: ошибка разбора" msgid "Download" -msgstr "" +msgstr "Скачать" msgid "Copy" -msgstr "" +msgstr "Скопировать" msgid "Close" msgstr "Закрыть" -msgid "Fastest" -msgstr "Самый быстрый" - msgid "Traffic" msgstr "Трафик" @@ -201,82 +198,109 @@ msgid "✘ Stopped" msgstr "✘ Остановлен" msgid "Not running" -msgstr "" +msgstr "Не запущен" msgid "Queued" -msgstr "" +msgstr "В очереди" msgid "unknown" -msgstr "" +msgstr "неизвестно" msgid "Global check" msgstr "Глобальная проверка" msgid "View logs" -msgstr "" +msgstr "Посмотреть логи" msgid "Show sing-box config" -msgstr "" +msgstr "Показать sing-box конфиг" msgid "Outdated" -msgstr "" +msgstr "Устаревшая" msgid "Latest" -msgstr "" +msgstr "Последняя" + +msgid "Fastest" +msgstr "Самый быстрый" msgid "Dashboard currently unavailable" msgstr "Дашборд сейчас недоступен" msgid "Test latency" -msgstr "" +msgstr "Проверить задержку" msgid "Currently unavailable" msgstr "Временно недоступно" +msgid "Restart podkop" +msgstr "Перезапустить podkop" + +msgid "Stop podkop" +msgstr "Остановить podkop" + +msgid "Start podkop" +msgstr "Запустить podkop" + +msgid "Disable autostart" +msgstr "Отключить автостарт" + +msgid "Enable autostart" +msgstr "Включить автостарт" + +msgid "Get global check" +msgstr "Получить глобальную проверку" + +msgid "Not implement yet" +msgstr "Не реализовано" + +msgid "Run Diagnostic" +msgstr "Запустить диагностику" + msgid "DNS checks" -msgstr "" +msgstr "Проверка DNS" msgid "Sing-box checks" -msgstr "" +msgstr "Проверка Sing-box" msgid "Nftables checks" -msgstr "" +msgstr "Проверка Nftables" msgid "FakeIP checks" -msgstr "" +msgstr "Проверка FakeIP" msgid "Checking dns, please wait" -msgstr "" +msgstr "Проверяем dns, пожалуйста подождите" msgid "Cannot receive DNS checks result" -msgstr "" +msgstr "Не удалось получить результаты проверки DNS" msgid "DNS checks passed" -msgstr "" +msgstr "Проверка DNS прошла" msgid "Bootsrap DNS" -msgstr "" +msgstr "Загрузочный DNS" msgid "Main DNS" -msgstr "" +msgstr "Основной DNS" msgid "DNS on router" -msgstr "" +msgstr "DNS на роутере" msgid "DHCP has DNS server" -msgstr "" +msgstr "DHCP содежрит DNS сервер" msgid "Checking FakeIP, please wait" -msgstr "" +msgstr "Проверяем FakeIP, пожалуйста подождите" msgid "FakeIP checks passed" -msgstr "" +msgstr "Проверка FakeIP прошла" msgid "FakeIP checks partially passed" -msgstr "" +msgstr "Проверка FakeIP частично прошла" msgid "FakeIP checks failed" -msgstr "" +msgstr "Проверка FakeIP не удалась" msgid "Router DNS is routed through sing-box" msgstr "" @@ -362,30 +386,6 @@ msgstr "" msgid "Sing-box listening ports" msgstr "" -msgid "Restart podkop" -msgstr "" - -msgid "Stop podkop" -msgstr "" - -msgid "Start podkop" -msgstr "" - -msgid "Disable autostart" -msgstr "" - -msgid "Enable autostart" -msgstr "" - -msgid "Get global check" -msgstr "" - -msgid "Not implement yet" -msgstr "" - -msgid "Run Diagnostic" -msgstr "" - msgid "Valid" msgstr "Валидно" @@ -887,9 +887,6 @@ msgstr "" msgid "At least one valid domain must be specified. Comments-only content is not allowed." msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается." -msgid "${validation.value}: ${validation.message}" -msgstr "" - msgid "Validation errors:" msgstr "Ошибки валидации:" @@ -1041,7 +1038,7 @@ msgid "Download Lists via specific proxy section" msgstr "" msgid "Downloading all lists via specific Proxy/VPN" -msgstr "" +msgstr "Загрузка всех списков через указанный прокси/VPN" msgid "Dont Touch My DHCP!" msgstr "" 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 14b23ba..54f5bd3 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 @@ -365,7 +365,7 @@ function createSectionContent(section) { if (!valid) { const errors = results .filter((validation) => !validation.valid) // Leave only failed validations - .map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors + .map((validation) => `${validation.value}: ${validation.message}`); // Collect validation errors return [_("Validation errors:"), ...errors].join("\n"); } @@ -444,7 +444,7 @@ function createSectionContent(section) { if (!valid) { const errors = results .filter((validation) => !validation.valid) // Leave only failed validations - .map((validation) => _(`${validation.value}: ${validation.message}`)); // Collect validation errors + .map((validation) => `${validation.value}: ${validation.message}`); // Collect validation errors return [_("Validation errors:"), ...errors].join("\n"); } diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index 10dd5ae..cb60b88 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 21:31+0300\n" -"PO-Revision-Date: 2025-10-21 21:31+0300\n" +"POT-Creation-Date: 2025-10-21 21:44+0300\n" +"PO-Revision-Date: 2025-10-21 21:44+0300\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -18,10 +18,10 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" msgid "Successfully copied!" -msgstr "" +msgstr "Успешно скопировано!" msgid "Failed to copy!" -msgstr "" +msgstr "Не удалось скопировать!" msgid "Operation timed out" msgstr "Время ожидания истекло" @@ -60,7 +60,7 @@ msgid "Invalid path format. Path must start with \"/\" and contain valid charact msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы" msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" +msgstr "Ссылка должна начинаться с vless://, ss://, trojan://, или socks4/5://" msgid "Invalid Shadowsocks URL: must start with ss://" msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" @@ -93,31 +93,31 @@ msgid "Invalid Shadowsocks URL: parsing failed" msgstr "Неверный URL Shadowsocks: ошибка разбора" msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" +msgstr "Невалидная SOCKS ссылка: должна начинаться с socks4://, socks4a://, или socks5://" msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" +msgstr "Невалидная SOCKS ссылка: не должна содержать пробелов" msgid "Invalid SOCKS URL: missing username" -msgstr "" +msgstr "Невалидная SOCKS ссылка: отсуствует имя пользователя" msgid "Invalid SOCKS URL: missing host and port" -msgstr "" +msgstr "Невалидная SOCKS ссылка: отсутствуют хост и порт" msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" +msgstr "Невалидная SOCKS ссылка: отсутствуют имя хоста или айпи" msgid "Invalid SOCKS URL: missing port" -msgstr "" +msgstr "Невалидная SOCKS ссылка: отсутствует порт" msgid "Invalid SOCKS URL: invalid port number" -msgstr "" +msgstr "Невалидная SOCKS ссылка: невалидный номер порта" msgid "Invalid SOCKS URL: invalid host format" -msgstr "" +msgstr "Невалидная SOCKS ссылка: невалидный формат хоста" msgid "Invalid SOCKS URL: parsing failed" -msgstr "" +msgstr "Невалидная SOCKS ссылка: не удалось распарсить" msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" @@ -147,17 +147,14 @@ msgid "Invalid VLESS URL: parsing failed" msgstr "Неверный URL VLESS: ошибка разбора" msgid "Download" -msgstr "" +msgstr "Скачать" msgid "Copy" -msgstr "" +msgstr "Скопировать" msgid "Close" msgstr "Закрыть" -msgid "Fastest" -msgstr "Самый быстрый" - msgid "Traffic" msgstr "Трафик" @@ -201,82 +198,109 @@ msgid "✘ Stopped" msgstr "✘ Остановлен" msgid "Not running" -msgstr "" +msgstr "Не запущен" msgid "Queued" -msgstr "" +msgstr "В очереди" msgid "unknown" -msgstr "" +msgstr "неизвестно" msgid "Global check" msgstr "Глобальная проверка" msgid "View logs" -msgstr "" +msgstr "Посмотреть логи" msgid "Show sing-box config" -msgstr "" +msgstr "Показать sing-box конфиг" msgid "Outdated" -msgstr "" +msgstr "Устаревшая" msgid "Latest" -msgstr "" +msgstr "Последняя" + +msgid "Fastest" +msgstr "Самый быстрый" msgid "Dashboard currently unavailable" msgstr "Дашборд сейчас недоступен" msgid "Test latency" -msgstr "" +msgstr "Проверить задержку" msgid "Currently unavailable" msgstr "Временно недоступно" +msgid "Restart podkop" +msgstr "Перезапустить podkop" + +msgid "Stop podkop" +msgstr "Остановить podkop" + +msgid "Start podkop" +msgstr "Запустить podkop" + +msgid "Disable autostart" +msgstr "Отключить автостарт" + +msgid "Enable autostart" +msgstr "Включить автостарт" + +msgid "Get global check" +msgstr "Получить глобальную проверку" + +msgid "Not implement yet" +msgstr "Не реализовано" + +msgid "Run Diagnostic" +msgstr "Запустить диагностику" + msgid "DNS checks" -msgstr "" +msgstr "Проверка DNS" msgid "Sing-box checks" -msgstr "" +msgstr "Проверка Sing-box" msgid "Nftables checks" -msgstr "" +msgstr "Проверка Nftables" msgid "FakeIP checks" -msgstr "" +msgstr "Проверка FakeIP" msgid "Checking dns, please wait" -msgstr "" +msgstr "Проверяем dns, пожалуйста подождите" msgid "Cannot receive DNS checks result" -msgstr "" +msgstr "Не удалось получить результаты проверки DNS" msgid "DNS checks passed" -msgstr "" +msgstr "Проверка DNS прошла" msgid "Bootsrap DNS" -msgstr "" +msgstr "Загрузочный DNS" msgid "Main DNS" -msgstr "" +msgstr "Основной DNS" msgid "DNS on router" -msgstr "" +msgstr "DNS на роутере" msgid "DHCP has DNS server" -msgstr "" +msgstr "DHCP содежрит DNS сервер" msgid "Checking FakeIP, please wait" -msgstr "" +msgstr "Проверяем FakeIP, пожалуйста подождите" msgid "FakeIP checks passed" -msgstr "" +msgstr "Проверка FakeIP прошла" msgid "FakeIP checks partially passed" -msgstr "" +msgstr "Проверка FakeIP частично прошла" msgid "FakeIP checks failed" -msgstr "" +msgstr "Проверка FakeIP не удалась" msgid "Router DNS is routed through sing-box" msgstr "" @@ -362,30 +386,6 @@ msgstr "" msgid "Sing-box listening ports" msgstr "" -msgid "Restart podkop" -msgstr "" - -msgid "Stop podkop" -msgstr "" - -msgid "Start podkop" -msgstr "" - -msgid "Disable autostart" -msgstr "" - -msgid "Enable autostart" -msgstr "" - -msgid "Get global check" -msgstr "" - -msgid "Not implement yet" -msgstr "" - -msgid "Run Diagnostic" -msgstr "" - msgid "Valid" msgstr "Валидно" @@ -887,9 +887,6 @@ msgstr "" msgid "At least one valid domain must be specified. Comments-only content is not allowed." msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается." -msgid "${validation.value}: ${validation.message}" -msgstr "" - msgid "Validation errors:" msgstr "Ошибки валидации:" @@ -1041,7 +1038,7 @@ msgid "Download Lists via specific proxy section" msgstr "" msgid "Downloading all lists via specific Proxy/VPN" -msgstr "" +msgstr "Загрузка всех списков через указанный прокси/VPN" msgid "Dont Touch My DHCP!" msgstr "" diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index 64f11d4..415585a 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 18:31+0300\n" -"PO-Revision-Date: 2025-10-21 18:31+0300\n" +"POT-Creation-Date: 2025-10-21 18:48+0300\n" +"PO-Revision-Date: 2025-10-21 18:48+0300\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -1249,11 +1249,6 @@ msgstr "" msgid "At least one valid domain must be specified. Comments-only content is not allowed." msgstr "" -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:368 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:447 -msgid "${validation.value}: ${validation.message}" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370 #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449 msgid "Validation errors:" From 686841c2a17653c79f8d695d985a8eed859b7ded Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 21 Oct 2025 22:45:36 +0300 Subject: [PATCH 107/121] feat: translate some keys --- fe-app-podkop/extract-calls.js | 138 +- fe-app-podkop/locales/calls.json | 4085 +++++++++-------------- fe-app-podkop/locales/podkop.pot | 2360 +++++-------- fe-app-podkop/locales/podkop.ru.po | 1595 ++++----- fe-app-podkop/package.json | 2 + fe-app-podkop/yarn.lock | 99 +- luci-app-podkop/po/ru/podkop.po | 1573 ++++----- luci-app-podkop/po/templates/podkop.pot | 2360 +++++-------- 8 files changed, 4788 insertions(+), 7424 deletions(-) diff --git a/fe-app-podkop/extract-calls.js b/fe-app-podkop/extract-calls.js index 4bc88d1..6b7d765 100644 --- a/fe-app-podkop/extract-calls.js +++ b/fe-app-podkop/extract-calls.js @@ -1,93 +1,75 @@ -import fg from 'fast-glob'; import fs from 'fs/promises'; import path from 'path'; +import glob from 'fast-glob'; +import { parse } from '@babel/parser'; +import traverse from '@babel/traverse'; +import * as t from '@babel/types'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; -const outputFile = 'locales/calls.json'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); -const tsSearchGlob = 'src/**/*.ts'; -const jsSearchGlob = '../luci-app-podkop/htdocs/luci-static/resources/view/podkop/**/*.js'; +function stripIllegalReturn(code) { + return code.replace(/^\s*return\s+[^;]+;\s*$/gm, (match, offset, input) => { + const after = input.slice(offset + match.length).trim(); + return after === '' ? '' : match; + }); +} -function extractAllUnderscoreCallsFromContent(content) { - const results = []; - let index = 0; +const files = await glob([ + 'src/**/*.ts', + '../luci-app-podkop/htdocs/luci-static/resources/view/podkop/**/*.js', +], { + ignore: [ + '**/*.test.ts', + '**/main.js', + '../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js', + ], + absolute: true, +}); - while (index < content.length) { - const start = content.indexOf('_(', index); - if (start === -1) break; +const results = {}; - let i = start + 2; - let depth = 1; +for (const file of files) { + const contentRaw = await fs.readFile(file, 'utf8'); + const content = stripIllegalReturn(contentRaw); + const relativePath = path.relative(process.cwd(), file); - while (i < content.length && depth > 0) { - if (content[i] === '(') depth++; - else if (content[i] === ')') depth--; - i++; - } - - const raw = content.slice(start, i); - results.push({ raw, index: start }); - index = i; + let ast; + try { + ast = parse(content, { + sourceType: 'module', + plugins: file.endsWith('.ts') ? ['typescript'] : [], + }); + } catch (e) { + console.warn(`⚠️ Parse error in ${relativePath}, skipping`); + continue; } - return results; -} + traverse.default(ast, { + CallExpression(path) { + if (t.isIdentifier(path.node.callee, { name: '_' })) { + const arg = path.node.arguments[0]; + if (t.isStringLiteral(arg)) { + const key = arg.value.trim(); + if (!key) return; // ❌ пропустить пустые ключи + const location = `${relativePath}:${path.node.loc?.start.line ?? '?'}`; -function getLineNumber(content, charIndex) { - return content.slice(0, charIndex).split('\n').length; -} + if (!results[key]) { + results[key] = { call: key, key, places: [] }; + } -function extractKey(call) { - const match = call.match(/^_\(\s*(['"`])((?:\\\1|.)*?)\1\s*\)$/); - return match ? match[2].trim() : null; -} - -function normalizeCall(call) { - return call - .replace(/\s*\n\s*/g, ' ') - .replace(/\s+/g, ' ') - .replace(/\(\s+/g, '(') - .replace(/\s+\)/g, ')') - .replace(/,\s*\)$/, ')') - .trim(); -} - -async function extractAllUnderscoreCallsWithLocations() { - const files = [ - ...(await fg(tsSearchGlob, { ignore: ['**/*test.ts'], absolute: true })), - ...(await fg(jsSearchGlob, { ignore: ['**/main.js'], absolute: true })), - ]; - - const callMap = new Map(); - - for (const file of files) { - const content = await fs.readFile(file, 'utf8'); - const relativePath = path.relative(process.cwd(), file); - const extracted = extractAllUnderscoreCallsFromContent(content); - - for (const { raw, index } of extracted) { - const line = getLineNumber(content, index); - const location = `${relativePath}:${line}`; - - const normalized = normalizeCall(raw); - const key = extractKey(normalized); - - if (!callMap.has(normalized)) { - callMap.set(normalized, { - call: normalized, - key: key ?? '', - places: [], - }); + results[key].places.push(location); + } } - - callMap.get(normalized).places.push(location); - } - } - - const result = [...callMap.values()]; - await fs.mkdir(path.dirname(outputFile), { recursive: true }); - await fs.writeFile(outputFile, JSON.stringify(result, null, 2), 'utf8'); - - console.log(`✅ Найдено ${result.length} уникальных вызовов _(...). Сохранено в ${outputFile}`); + }, + }); } -extractAllUnderscoreCallsWithLocations().catch(console.error); +const outFile = 'locales/calls.json'; +const sorted = Object.values(results).sort((a, b) => a.key.localeCompare(b.key)); // 🔤 сортировка по ключу + +await fs.mkdir(path.dirname(outFile), { recursive: true }); +await fs.writeFile(outFile, JSON.stringify(sorted, null, 2), 'utf8'); +console.log(`✅ Extracted ${sorted.length} translations to ${outFile}`); diff --git a/fe-app-podkop/locales/calls.json b/fe-app-podkop/locales/calls.json index e972fd0..a5c991b 100644 --- a/fe-app-podkop/locales/calls.json +++ b/fe-app-podkop/locales/calls.json @@ -1,48 +1,1613 @@ [ { - "call": "_('Successfully copied!')", - "key": "Successfully copied!", + "call": "✔ Enabled", + "key": "✔ Enabled", "places": [ - "src/helpers/copyToClipboard.ts:10" + "src/podkop/tabs/dashboard/initController.ts:342" ] }, { - "call": "_('Failed to copy!')", - "key": "Failed to copy!", + "call": "✔ Running", + "key": "✔ Running", "places": [ - "src/helpers/copyToClipboard.ts:12" + "src/podkop/tabs/dashboard/initController.ts:353" ] }, { - "call": "_('Operation timed out')", - "key": "Operation timed out", + "call": "✘ Disabled", + "key": "✘ Disabled", "places": [ - "src/helpers/withTimeout.ts:7" + "src/podkop/tabs/dashboard/initController.ts:343" ] }, { - "call": "_('HTTP error')", - "key": "HTTP error", + "call": "✘ Stopped", + "key": "✘ Stopped", "places": [ - "src/podkop/api.ts:27" + "src/podkop/tabs/dashboard/initController.ts:354" ] }, { - "call": "_('Unknown error')", - "key": "Unknown error", + "call": "Active Connections", + "key": "Active Connections", "places": [ - "src/podkop/api.ts:40" + "src/podkop/tabs/dashboard/initController.ts:304" ] }, { - "call": "_('DNS server address cannot be empty')", + "call": "Additional marking rules found", + "key": "Additional marking rules found", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117" + ] + }, + { + "call": "Applicable for SOCKS and Shadowsocks proxy", + "key": "Applicable for SOCKS and Shadowsocks proxy", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111" + ] + }, + { + "call": "At least one valid domain must be specified. Comments-only content is not allowed.", + "key": "At least one valid domain must be specified. Comments-only content is not allowed.", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356" + ] + }, + { + "call": "At least one valid subnet or IP must be specified. Comments-only content is not allowed.", + "key": "At least one valid subnet or IP must be specified. Comments-only content is not allowed.", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437" + ] + }, + { + "call": "Bootsrap DNS", + "key": "Bootsrap DNS", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72" + ] + }, + { + "call": "Bootstrap DNS server", + "key": "Bootstrap DNS server", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45" + ] + }, + { + "call": "Browser is not using FakeIP", + "key": "Browser is not using FakeIP", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81" + ] + }, + { + "call": "Browser is using FakeIP correctly", + "key": "Browser is using FakeIP correctly", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80" + ] + }, + { + "call": "Cache File Path", + "key": "Cache File Path", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322" + ] + }, + { + "call": "Cache file path cannot be empty", + "key": "Cache file path cannot be empty", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336" + ] + }, + { + "call": "Cannot receive DNS checks result", + "key": "Cannot receive DNS checks result", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26" + ] + }, + { + "call": "Cannot receive nftables checks result", + "key": "Cannot receive nftables checks result", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27" + ] + }, + { + "call": "Cannot receive Sing-box checks result", + "key": "Cannot receive Sing-box checks result", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24" + ] + }, + { + "call": "Checking dns, please wait", + "key": "Checking dns, please wait", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14" + ] + }, + { + "call": "Checking FakeIP, please wait", + "key": "Checking FakeIP, please wait", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14" + ] + }, + { + "call": "Checking nftables, please wait", + "key": "Checking nftables, please wait", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12" + ] + }, + { + "call": "Checking sing-box, please wait", + "key": "Checking sing-box, please wait", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12" + ] + }, + { + "call": "CIDR must be between 0 and 32", + "key": "CIDR must be between 0 and 32", + "places": [ + "src/validators/validateSubnet.ts:33" + ] + }, + { + "call": "Close", + "key": "Close", + "places": [ + "src/partials/modal/renderModal.ts:26" + ] + }, + { + "call": "Community Lists", + "key": "Community Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211" + ] + }, + { + "call": "Config File Path", + "key": "Config File Path", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309" + ] + }, + { + "call": "Configuration for Podkop service", + "key": "Configuration for Podkop service", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27" + ] + }, + { + "call": "Configuration Type", + "key": "Configuration Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22" + ] + }, + { + "call": "Connection Type", + "key": "Connection Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12" + ] + }, + { + "call": "Connection URL", + "key": "Connection URL", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25" + ] + }, + { + "call": "Copy", + "key": "Copy", + "places": [ + "src/partials/modal/renderModal.ts:20" + ] + }, + { + "call": "Currently unavailable", + "key": "Currently unavailable", + "places": [ + "src/podkop/tabs/dashboard/partials/renderWidget.ts:22" + ] + }, + { + "call": "Dashboard", + "key": "Dashboard", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80" + ] + }, + { + "call": "Dashboard currently unavailable", + "key": "Dashboard currently unavailable", + "places": [ + "src/podkop/tabs/dashboard/partials/renderSections.ts:19" + ] + }, + { + "call": "Delay in milliseconds before reloading podkop after interface UP", + "key": "Delay in milliseconds before reloading podkop after interface UP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215" + ] + }, + { + "call": "Delay value cannot be empty", + "key": "Delay value cannot be empty", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222" + ] + }, + { + "call": "DHCP has DNS server", + "key": "DHCP has DNS server", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89" + ] + }, + { + "call": "Diagnostics", + "key": "Diagnostics", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65" + ] + }, + { + "call": "Disable autostart", + "key": "Disable autostart", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79" + ] + }, + { + "call": "Disable QUIC", + "key": "Disable QUIC", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239" + ] + }, + { + "call": "Disable the QUIC protocol to improve compatibility or fix issues with video streaming", + "key": "Disable the QUIC protocol to improve compatibility or fix issues with video streaming", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240" + ] + }, + { + "call": "Disabled", + "key": "Disabled", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382" + ] + }, + { + "call": "DNS checks", + "key": "DNS checks", + "places": [ + "src/podkop/tabs/diagnostic/checks/contstants.ts:14" + ] + }, + { + "call": "DNS checks passed", + "key": "DNS checks passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64" + ] + }, + { + "call": "DNS on router", + "key": "DNS on router", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84" + ] + }, + { + "call": "DNS over HTTPS (DoH)", + "key": "DNS over HTTPS (DoH)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15" + ] + }, + { + "call": "DNS over TLS (DoT)", + "key": "DNS over TLS (DoT)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16" + ] + }, + { + "call": "DNS Protocol Type", + "key": "DNS Protocol Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12" + ] + }, + { + "call": "DNS Rewrite TTL", + "key": "DNS Rewrite TTL", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68" + ] + }, + { + "call": "DNS Server", + "key": "DNS Server", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24" + ] + }, + { + "call": "DNS server address cannot be empty", "key": "DNS server address cannot be empty", "places": [ "src/validators/validateDns.ts:7" ] }, { - "call": "_('Valid')", + "call": "Domain Resolver", + "key": "Domain Resolver", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166" + ] + }, + { + "call": "Dont Touch My DHCP!", + "key": "Dont Touch My DHCP!", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300" + ] + }, + { + "call": "Downlink", + "key": "Downlink", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:238", + "src/podkop/tabs/dashboard/initController.ts:272" + ] + }, + { + "call": "Download", + "key": "Download", + "places": [ + "src/partials/modal/renderModal.ts:15" + ] + }, + { + "call": "Download Lists via Proxy/VPN", + "key": "Download Lists via Proxy/VPN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262" + ] + }, + { + "call": "Download Lists via specific proxy section", + "key": "Download Lists via specific proxy section", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271" + ] + }, + { + "call": "Downloading all lists via main Proxy/VPN", + "key": "Downloading all lists via main Proxy/VPN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263" + ] + }, + { + "call": "Downloading all lists via specific Proxy/VPN", + "key": "Downloading all lists via specific Proxy/VPN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272" + ] + }, + { + "call": "Dynamic List", + "key": "Dynamic List", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383" + ] + }, + { + "call": "Enable autostart", + "key": "Enable autostart", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89" + ] + }, + { + "call": "Enable built-in DNS resolver for domains handled by this section", + "key": "Enable built-in DNS resolver for domains handled by this section", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167" + ] + }, + { + "call": "Enable Mixed Proxy", + "key": "Enable Mixed Proxy", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575" + ] + }, + { + "call": "Enable Output Network Interface", + "key": "Enable Output Network Interface", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126" + ] + }, + { + "call": "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies", + "key": "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576" + ] + }, + { + "call": "Enable YACD", + "key": "Enable YACD", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230" + ] + }, + { + "call": "Enter complete outbound configuration in JSON format", + "key": "Enter complete outbound configuration in JSON format", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65" + ] + }, + { + "call": "Enter domain names separated by commas, spaces, or newlines. You can add comments using //", + "key": "Enter domain names separated by commas, spaces, or newlines. You can add comments using //", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338" + ] + }, + { + "call": "Enter domain names without protocols, e.g. example.com or sub.example.com", + "key": "Enter domain names without protocols, e.g. example.com or sub.example.com", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312" + ] + }, + { + "call": "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses", + "key": "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392" + ] + }, + { + "call": "Exclude NTP", + "key": "Exclude NTP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358" + ] + }, + { + "call": "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN", + "key": "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359" + ] + }, + { + "call": "Failed to copy!", + "key": "Failed to copy!", + "places": [ + "src/helpers/copyToClipboard.ts:12" + ] + }, + { + "call": "FakeIP checks", + "key": "FakeIP checks", + "places": [ + "src/podkop/tabs/diagnostic/checks/contstants.ts:29" + ] + }, + { + "call": "FakeIP checks failed", + "key": "FakeIP checks failed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57" + ] + }, + { + "call": "FakeIP checks partially passed", + "key": "FakeIP checks partially passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51" + ] + }, + { + "call": "FakeIP checks passed", + "key": "FakeIP checks passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44" + ] + }, + { + "call": "Fastest", + "key": "Fastest", + "places": [ + "src/podkop/methods/custom/getDashboardSections.ts:117" + ] + }, + { + "call": "Fully Routed IPs", + "key": "Fully Routed IPs", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550" + ] + }, + { + "call": "Get global check", + "key": "Get global check", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98" + ] + }, + { + "call": "Global check", + "key": "Global check", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:218" + ] + }, + { + "call": "HTTP error", + "key": "HTTP error", + "places": [ + "src/podkop/api.ts:27" + ] + }, + { + "call": "Interface Monitoring", + "key": "Interface Monitoring", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182" + ] + }, + { + "call": "Interface Monitoring Delay", + "key": "Interface Monitoring Delay", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214" + ] + }, + { + "call": "Interface monitoring for Bad WAN", + "key": "Interface monitoring for Bad WAN", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183" + ] + }, + { + "call": "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH", + "key": "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH", + "places": [ + "src/validators/validateDns.ts:20" + ] + }, + { + "call": "Invalid domain address", + "key": "Invalid domain address", + "places": [ + "src/validators/validateDomain.ts:18", + "src/validators/validateDomain.ts:27" + ] + }, + { + "call": "Invalid format. Use X.X.X.X or X.X.X.X/Y", + "key": "Invalid format. Use X.X.X.X or X.X.X.X/Y", + "places": [ + "src/validators/validateSubnet.ts:11" + ] + }, + { + "call": "Invalid IP address", + "key": "Invalid IP address", + "places": [ + "src/validators/validateIp.ts:11" + ] + }, + { + "call": "Invalid JSON format", + "key": "Invalid JSON format", + "places": [ + "src/validators/validateOutboundJson.ts:19" + ] + }, + { + "call": "Invalid path format. Path must start with \"/\" and contain valid characters", + "key": "Invalid path format. Path must start with \"/\" and contain valid characters", + "places": [ + "src/validators/validatePath.ts:22" + ] + }, + { + "call": "Invalid port number. Must be between 1 and 65535", + "key": "Invalid port number. Must be between 1 and 65535", + "places": [ + "src/validators/validateShadowsocksUrl.ts:85" + ] + }, + { + "call": "Invalid Shadowsocks URL: decoded credentials must contain method:password", + "key": "Invalid Shadowsocks URL: decoded credentials must contain method:password", + "places": [ + "src/validators/validateShadowsocksUrl.ts:37" + ] + }, + { + "call": "Invalid Shadowsocks URL: missing credentials", + "key": "Invalid Shadowsocks URL: missing credentials", + "places": [ + "src/validators/validateShadowsocksUrl.ts:27" + ] + }, + { + "call": "Invalid Shadowsocks URL: missing method and password separator \":\"", + "key": "Invalid Shadowsocks URL: missing method and password separator \":\"", + "places": [ + "src/validators/validateShadowsocksUrl.ts:46" + ] + }, + { + "call": "Invalid Shadowsocks URL: missing port", + "key": "Invalid Shadowsocks URL: missing port", + "places": [ + "src/validators/validateShadowsocksUrl.ts:76" + ] + }, + { + "call": "Invalid Shadowsocks URL: missing server", + "key": "Invalid Shadowsocks URL: missing server", + "places": [ + "src/validators/validateShadowsocksUrl.ts:67" + ] + }, + { + "call": "Invalid Shadowsocks URL: missing server address", + "key": "Invalid Shadowsocks URL: missing server address", + "places": [ + "src/validators/validateShadowsocksUrl.ts:58" + ] + }, + { + "call": "Invalid Shadowsocks URL: must not contain spaces", + "key": "Invalid Shadowsocks URL: must not contain spaces", + "places": [ + "src/validators/validateShadowsocksUrl.ts:16" + ] + }, + { + "call": "Invalid Shadowsocks URL: must start with ss://", + "key": "Invalid Shadowsocks URL: must start with ss://", + "places": [ + "src/validators/validateShadowsocksUrl.ts:8" + ] + }, + { + "call": "Invalid Shadowsocks URL: parsing failed", + "key": "Invalid Shadowsocks URL: parsing failed", + "places": [ + "src/validators/validateShadowsocksUrl.ts:91" + ] + }, + { + "call": "Invalid SOCKS URL: invalid host format", + "key": "Invalid SOCKS URL: invalid host format", + "places": [ + "src/validators/validateSocksUrl.ts:73" + ] + }, + { + "call": "Invalid SOCKS URL: invalid port number", + "key": "Invalid SOCKS URL: invalid port number", + "places": [ + "src/validators/validateSocksUrl.ts:63" + ] + }, + { + "call": "Invalid SOCKS URL: missing host and port", + "key": "Invalid SOCKS URL: missing host and port", + "places": [ + "src/validators/validateSocksUrl.ts:42" + ] + }, + { + "call": "Invalid SOCKS URL: missing hostname or IP", + "key": "Invalid SOCKS URL: missing hostname or IP", + "places": [ + "src/validators/validateSocksUrl.ts:51" + ] + }, + { + "call": "Invalid SOCKS URL: missing port", + "key": "Invalid SOCKS URL: missing port", + "places": [ + "src/validators/validateSocksUrl.ts:56" + ] + }, + { + "call": "Invalid SOCKS URL: missing username", + "key": "Invalid SOCKS URL: missing username", + "places": [ + "src/validators/validateSocksUrl.ts:34" + ] + }, + { + "call": "Invalid SOCKS URL: must not contain spaces", + "key": "Invalid SOCKS URL: must not contain spaces", + "places": [ + "src/validators/validateSocksUrl.ts:19" + ] + }, + { + "call": "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://", + "key": "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://", + "places": [ + "src/validators/validateSocksUrl.ts:10" + ] + }, + { + "call": "Invalid SOCKS URL: parsing failed", + "key": "Invalid SOCKS URL: parsing failed", + "places": [ + "src/validators/validateSocksUrl.ts:77" + ] + }, + { + "call": "Invalid Trojan URL: must not contain spaces", + "key": "Invalid Trojan URL: must not contain spaces", + "places": [ + "src/validators/validateTrojanUrl.ts:15" + ] + }, + { + "call": "Invalid Trojan URL: must start with trojan://", + "key": "Invalid Trojan URL: must start with trojan://", + "places": [ + "src/validators/validateTrojanUrl.ts:8" + ] + }, + { + "call": "Invalid Trojan URL: parsing failed", + "key": "Invalid Trojan URL: parsing failed", + "places": [ + "src/validators/validateTrojanUrl.ts:56" + ] + }, + { + "call": "Invalid URL format", + "key": "Invalid URL format", + "places": [ + "src/validators/validateUrl.ts:18" + ] + }, + { + "call": "Invalid VLESS URL: parsing failed", + "key": "Invalid VLESS URL: parsing failed", + "places": [ + "src/validators/validateVlessUrl.ts:109" + ] + }, + { + "call": "IP address 0.0.0.0 is not allowed", + "key": "IP address 0.0.0.0 is not allowed", + "places": [ + "src/validators/validateSubnet.ts:18" + ] + }, + { + "call": "Latest", + "key": "Latest", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:404" + ] + }, + { + "call": "List Update Frequency", + "key": "List Update Frequency", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250" + ] + }, + { + "call": "Local Domain Lists", + "key": "Local Domain Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458" + ] + }, + { + "call": "Local Subnet Lists", + "key": "Local Subnet Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481" + ] + }, + { + "call": "Main DNS", + "key": "Main DNS", + "places": [ + "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79" + ] + }, + { + "call": "Memory Usage", + "key": "Memory Usage", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:308" + ] + }, + { + "call": "Mixed Proxy Port", + "key": "Mixed Proxy Port", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586" + ] + }, + { + "call": "Monitored Interfaces", + "key": "Monitored Interfaces", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191" + ] + }, + { + "call": "Network Interface", + "key": "Network Interface", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120" + ] + }, + { + "call": "Nftables checks", + "key": "Nftables checks", + "places": [ + "src/podkop/tabs/diagnostic/checks/contstants.ts:24" + ] + }, + { + "call": "Nftables checks partially passed", + "key": "Nftables checks partially passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75" + ] + }, + { + "call": "Nftables checks passed", + "key": "Nftables checks passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74" + ] + }, + { + "call": "No other marking rules found", + "key": "No other marking rules found", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116" + ] + }, + { + "call": "Not implement yet", + "key": "Not implement yet", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189" + ] + }, + { + "call": "Not running", + "key": "Not running", + "places": [ + "src/podkop/tabs/diagnostic/diagnostic.store.ts:55", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:63", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:71", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:79" + ] + }, + { + "call": "Operation timed out", + "key": "Operation timed out", + "places": [ + "src/helpers/withTimeout.ts:7" + ] + }, + { + "call": "Outbound Config", + "key": "Outbound Config", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26" + ] + }, + { + "call": "Outbound Configuration", + "key": "Outbound Configuration", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64" + ] + }, + { + "call": "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields", + "key": "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields", + "places": [ + "src/validators/validateOutboundJson.ts:11" + ] + }, + { + "call": "Outdated", + "key": "Outdated", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:394" + ] + }, + { + "call": "Output Network Interface", + "key": "Output Network Interface", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135" + ] + }, + { + "call": "Path cannot be empty", + "key": "Path cannot be empty", + "places": [ + "src/validators/validatePath.ts:7" + ] + }, + { + "call": "Path must be absolute (start with /)", + "key": "Path must be absolute (start with /)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340" + ] + }, + { + "call": "Path must contain at least one directory (like /tmp/cache.db)", + "key": "Path must contain at least one directory (like /tmp/cache.db)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349" + ] + }, + { + "call": "Path must end with cache.db", + "key": "Path must end with cache.db", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344" + ] + }, + { + "call": "Podkop", + "key": "Podkop", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:340" + ] + }, + { + "call": "Podkop Settings", + "key": "Podkop Settings", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26" + ] + }, + { + "call": "Podkop will not modify your DHCP configuration", + "key": "Podkop will not modify your DHCP configuration", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301" + ] + }, + { + "call": "Proxy Configuration URL", + "key": "Proxy Configuration URL", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34" + ] + }, + { + "call": "Proxy traffic is not routed via FakeIP", + "key": "Proxy traffic is not routed via FakeIP", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89" + ] + }, + { + "call": "Proxy traffic is routed via FakeIP", + "key": "Proxy traffic is routed via FakeIP", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88" + ] + }, + { + "call": "Queued", + "key": "Queued", + "places": [ + "src/podkop/tabs/diagnostic/diagnostic.store.ts:95", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:103", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:111", + "src/podkop/tabs/diagnostic/diagnostic.store.ts:119" + ] + }, + { + "call": "Regional options cannot be used together", + "key": "Regional options cannot be used together", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245" + ] + }, + { + "call": "Remote Domain Lists", + "key": "Remote Domain Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504" + ] + }, + { + "call": "Remote Subnet Lists", + "key": "Remote Subnet Lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527" + ] + }, + { + "call": "Restart podkop", + "key": "Restart podkop", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49" + ] + }, + { + "call": "Router DNS is not routed through sing-box", + "key": "Router DNS is not routed through sing-box", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74" + ] + }, + { + "call": "Router DNS is routed through sing-box", + "key": "Router DNS is routed through sing-box", + "places": [ + "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73" + ] + }, + { + "call": "Routing Excluded IPs", + "key": "Routing Excluded IPs", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369" + ] + }, + { + "call": "Rules mangle counters", + "key": "Rules mangle counters", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90" + ] + }, + { + "call": "Rules mangle exist", + "key": "Rules mangle exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85" + ] + }, + { + "call": "Rules mangle output counters", + "key": "Rules mangle output counters", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100" + ] + }, + { + "call": "Rules mangle output exist", + "key": "Rules mangle output exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95" + ] + }, + { + "call": "Rules proxy counters", + "key": "Rules proxy counters", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110" + ] + }, + { + "call": "Rules proxy exist", + "key": "Rules proxy exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105" + ] + }, + { + "call": "Run Diagnostic", + "key": "Run Diagnostic", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15" + ] + }, + { + "call": "Russia inside restrictions", + "key": "Russia inside restrictions", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264" + ] + }, + { + "call": "Sections", + "key": "Sections", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36" + ] + }, + { + "call": "Select a predefined list for routing", + "key": "Select a predefined list for routing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212" + ] + }, + { + "call": "Select between VPN and Proxy connection methods for traffic routing", + "key": "Select between VPN and Proxy connection methods for traffic routing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13" + ] + }, + { + "call": "Select DNS protocol to use", + "key": "Select DNS protocol to use", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13" + ] + }, + { + "call": "Select how often the domain or subnet lists are updated automatically", + "key": "Select how often the domain or subnet lists are updated automatically", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251" + ] + }, + { + "call": "Select how to configure the proxy", + "key": "Select how to configure the proxy", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23" + ] + }, + { + "call": "Select network interface for VPN connection", + "key": "Select network interface for VPN connection", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121" + ] + }, + { + "call": "Select or enter DNS server address", + "key": "Select or enter DNS server address", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25" + ] + }, + { + "call": "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing", + "key": "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323" + ] + }, + { + "call": "Select path for sing-box config file. Change this ONLY if you know what you are doing", + "key": "Select path for sing-box config file. Change this ONLY if you know what you are doing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310" + ] + }, + { + "call": "Select the DNS protocol type for the domain resolver", + "key": "Select the DNS protocol type for the domain resolver", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177" + ] + }, + { + "call": "Select the list type for adding custom domains", + "key": "Select the list type for adding custom domains", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300" + ] + }, + { + "call": "Select the list type for adding custom subnets", + "key": "Select the list type for adding custom subnets", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380" + ] + }, + { + "call": "Select the network interface from which the traffic will originate", + "key": "Select the network interface from which the traffic will originate", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90" + ] + }, + { + "call": "Select the network interface to which the traffic will originate", + "key": "Select the network interface to which the traffic will originate", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136" + ] + }, + { + "call": "Select the WAN interfaces to be monitored", + "key": "Select the WAN interfaces to be monitored", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192" + ] + }, + { + "call": "Services info", + "key": "Services info", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:337" + ] + }, + { + "call": "Settings", + "key": "Settings", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49" + ] + }, + { + "call": "Show sing-box config", + "key": "Show sing-box config", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:278", + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116" + ] + }, + { + "call": "Sing-box", + "key": "Sing-box", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:351" + ] + }, + { + "call": "Sing-box autostart disabled", + "key": "Sing-box autostart disabled", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86" + ] + }, + { + "call": "Sing-box checks", + "key": "Sing-box checks", + "places": [ + "src/podkop/tabs/diagnostic/checks/contstants.ts:19" + ] + }, + { + "call": "Sing-box checks passed", + "key": "Sing-box checks passed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66" + ] + }, + { + "call": "Sing-box installed", + "key": "Sing-box installed", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71" + ] + }, + { + "call": "Sing-box listening ports", + "key": "Sing-box listening ports", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96" + ] + }, + { + "call": "Sing-box process running", + "key": "Sing-box process running", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91" + ] + }, + { + "call": "Sing-box service exist", + "key": "Sing-box service exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81" + ] + }, + { + "call": "Sing-box version >= 1.12.4", + "key": "Sing-box version >= 1.12.4", + "places": [ + "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76" + ] + }, + { + "call": "Source Network Interface", + "key": "Source Network Interface", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89" + ] + }, + { + "call": "Specify a local IP address to be excluded from routing", + "key": "Specify a local IP address to be excluded from routing", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370" + ] + }, + { + "call": "Specify local IP addresses or subnets whose traffic will always be routed through the configured route", + "key": "Specify local IP addresses or subnets whose traffic will always be routed through the configured route", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551" + ] + }, + { + "call": "Specify remote URLs to download and use domain lists", + "key": "Specify remote URLs to download and use domain lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505" + ] + }, + { + "call": "Specify remote URLs to download and use subnet lists", + "key": "Specify remote URLs to download and use subnet lists", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528" + ] + }, + { + "call": "Specify the path to the list file located on the router filesystem", + "key": "Specify the path to the list file located on the router filesystem", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482" + ] + }, + { + "call": "Start podkop", + "key": "Start podkop", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69" + ] + }, + { + "call": "Stop podkop", + "key": "Stop podkop", + "places": [ + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59" + ] + }, + { + "call": "Successfully copied!", + "key": "Successfully copied!", + "places": [ + "src/helpers/copyToClipboard.ts:10" + ] + }, + { + "call": "System info", + "key": "System info", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:301" + ] + }, + { + "call": "Table exist", + "key": "Table exist", + "places": [ + "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80" + ] + }, + { + "call": "Test latency", + "key": "Test latency", + "places": [ + "src/podkop/tabs/dashboard/partials/renderSections.ts:108" + ] + }, + { + "call": "Text List", + "key": "Text List", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304" + ] + }, + { + "call": "Text List (comma/space/newline separated)", + "key": "Text List (comma/space/newline separated)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384" + ] + }, + { + "call": "The DNS server used to look up the IP address of an upstream DNS server", + "key": "The DNS server used to look up the IP address of an upstream DNS server", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46" + ] + }, + { + "call": "Time in seconds for DNS record caching (default: 60)", + "key": "Time in seconds for DNS record caching (default: 60)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69" + ] + }, + { + "call": "Traffic", + "key": "Traffic", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:235" + ] + }, + { + "call": "Traffic Total", + "key": "Traffic Total", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:265" + ] + }, + { + "call": "TTL must be a positive number", + "key": "TTL must be a positive number", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80" + ] + }, + { + "call": "TTL value cannot be empty", + "key": "TTL value cannot be empty", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75" + ] + }, + { + "call": "UDP (Unprotected DNS)", + "key": "UDP (Unprotected DNS)", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181", + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17" + ] + }, + { + "call": "UDP over TCP", + "key": "UDP over TCP", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110" + ] + }, + { + "call": "unknown", + "key": "unknown", + "places": [ + "src/podkop/tabs/diagnostic/initController.ts:34", + "src/podkop/tabs/diagnostic/initController.ts:35", + "src/podkop/tabs/diagnostic/initController.ts:36", + "src/podkop/tabs/diagnostic/initController.ts:37", + "src/podkop/tabs/diagnostic/initController.ts:38", + "src/podkop/tabs/diagnostic/initController.ts:39", + "src/podkop/tabs/diagnostic/initController.ts:373" + ] + }, + { + "call": "Unknown error", + "key": "Unknown error", + "places": [ + "src/podkop/api.ts:40" + ] + }, + { + "call": "Uplink", + "key": "Uplink", + "places": [ + "src/podkop/tabs/dashboard/initController.ts:237", + "src/podkop/tabs/dashboard/initController.ts:268" + ] + }, + { + "call": "URL must start with vless://, ss://, trojan://, or socks4/5://", + "key": "URL must start with vless://, ss://, trojan://, or socks4/5://", + "places": [ + "src/validators/validateProxyUrl.ts:27" + ] + }, + { + "call": "URL must use one of the following protocols:", + "key": "URL must use one of the following protocols:", + "places": [ + "src/validators/validateUrl.ts:13" + ] + }, + { + "call": "URLTest", + "key": "URLTest", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27" + ] + }, + { + "call": "URLTest Proxy Links", + "key": "URLTest Proxy Links", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87" + ] + }, + { + "call": "User Domain List Type", + "key": "User Domain List Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299" + ] + }, + { + "call": "User Domains", + "key": "User Domains", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311" + ] + }, + { + "call": "User Domains List", + "key": "User Domains List", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337" + ] + }, + { + "call": "User Subnet List Type", + "key": "User Subnet List Type", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379" + ] + }, + { + "call": "User Subnets", + "key": "User Subnets", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391" + ] + }, + { + "call": "User Subnets List", + "key": "User Subnets List", + "places": [ + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417" + ] + }, + { + "call": "Valid", "key": "Valid", "places": [ "src/validators/validateDns.ts:11", @@ -61,2049 +1626,7 @@ ] }, { - "call": "_('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH')", - "key": "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH", - "places": [ - "src/validators/validateDns.ts:20" - ] - }, - { - "call": "_('Invalid domain address')", - "key": "Invalid domain address", - "places": [ - "src/validators/validateDomain.ts:18", - "src/validators/validateDomain.ts:27" - ] - }, - { - "call": "_('Invalid IP address')", - "key": "Invalid IP address", - "places": [ - "src/validators/validateIp.ts:11" - ] - }, - { - "call": "_('Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields')", - "key": "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields", - "places": [ - "src/validators/validateOutboundJson.ts:11", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:327" - ] - }, - { - "call": "_('Invalid JSON format')", - "key": "Invalid JSON format", - "places": [ - "src/validators/validateOutboundJson.ts:19" - ] - }, - { - "call": "_('Path cannot be empty')", - "key": "Path cannot be empty", - "places": [ - "src/validators/validatePath.ts:7" - ] - }, - { - "call": "_('Invalid path format. Path must start with \"/\" and contain valid characters')", - "key": "Invalid path format. Path must start with \"/\" and contain valid characters", - "places": [ - "src/validators/validatePath.ts:22", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:90" - ] - }, - { - "call": "_('URL must start with vless://, ss://, trojan://, or socks4/5://')", - "key": "URL must start with vless://, ss://, trojan://, or socks4/5://", - "places": [ - "src/validators/validateProxyUrl.ts:27" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: must start with ss://')", - "key": "Invalid Shadowsocks URL: must start with ss://", - "places": [ - "src/validators/validateShadowsocksUrl.ts:8" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: must not contain spaces')", - "key": "Invalid Shadowsocks URL: must not contain spaces", - "places": [ - "src/validators/validateShadowsocksUrl.ts:16" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: missing credentials')", - "key": "Invalid Shadowsocks URL: missing credentials", - "places": [ - "src/validators/validateShadowsocksUrl.ts:27" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: decoded credentials must contain method:password')", - "key": "Invalid Shadowsocks URL: decoded credentials must contain method:password", - "places": [ - "src/validators/validateShadowsocksUrl.ts:37" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: missing method and password separator \":\"')", - "key": "Invalid Shadowsocks URL: missing method and password separator \":\"", - "places": [ - "src/validators/validateShadowsocksUrl.ts:46", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:171" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: missing server address')", - "key": "Invalid Shadowsocks URL: missing server address", - "places": [ - "src/validators/validateShadowsocksUrl.ts:58" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: missing server')", - "key": "Invalid Shadowsocks URL: missing server", - "places": [ - "src/validators/validateShadowsocksUrl.ts:67" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: missing port')", - "key": "Invalid Shadowsocks URL: missing port", - "places": [ - "src/validators/validateShadowsocksUrl.ts:76" - ] - }, - { - "call": "_('Invalid port number. Must be between 1 and 65535')", - "key": "Invalid port number. Must be between 1 and 65535", - "places": [ - "src/validators/validateShadowsocksUrl.ts:85" - ] - }, - { - "call": "_('Invalid Shadowsocks URL: parsing failed')", - "key": "Invalid Shadowsocks URL: parsing failed", - "places": [ - "src/validators/validateShadowsocksUrl.ts:91" - ] - }, - { - "call": "_('Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://')", - "key": "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://", - "places": [ - "src/validators/validateSocksUrl.ts:10" - ] - }, - { - "call": "_('Invalid SOCKS URL: must not contain spaces')", - "key": "Invalid SOCKS URL: must not contain spaces", - "places": [ - "src/validators/validateSocksUrl.ts:19" - ] - }, - { - "call": "_('Invalid SOCKS URL: missing username')", - "key": "Invalid SOCKS URL: missing username", - "places": [ - "src/validators/validateSocksUrl.ts:34" - ] - }, - { - "call": "_('Invalid SOCKS URL: missing host and port')", - "key": "Invalid SOCKS URL: missing host and port", - "places": [ - "src/validators/validateSocksUrl.ts:42" - ] - }, - { - "call": "_('Invalid SOCKS URL: missing hostname or IP')", - "key": "Invalid SOCKS URL: missing hostname or IP", - "places": [ - "src/validators/validateSocksUrl.ts:51" - ] - }, - { - "call": "_('Invalid SOCKS URL: missing port')", - "key": "Invalid SOCKS URL: missing port", - "places": [ - "src/validators/validateSocksUrl.ts:56" - ] - }, - { - "call": "_('Invalid SOCKS URL: invalid port number')", - "key": "Invalid SOCKS URL: invalid port number", - "places": [ - "src/validators/validateSocksUrl.ts:63" - ] - }, - { - "call": "_('Invalid SOCKS URL: invalid host format')", - "key": "Invalid SOCKS URL: invalid host format", - "places": [ - "src/validators/validateSocksUrl.ts:73" - ] - }, - { - "call": "_('Invalid SOCKS URL: parsing failed')", - "key": "Invalid SOCKS URL: parsing failed", - "places": [ - "src/validators/validateSocksUrl.ts:77" - ] - }, - { - "call": "_('Invalid format. Use X.X.X.X or X.X.X.X/Y')", - "key": "Invalid format. Use X.X.X.X or X.X.X.X/Y", - "places": [ - "src/validators/validateSubnet.ts:11" - ] - }, - { - "call": "_('IP address 0.0.0.0 is not allowed')", - "key": "IP address 0.0.0.0 is not allowed", - "places": [ - "src/validators/validateSubnet.ts:18" - ] - }, - { - "call": "_('CIDR must be between 0 and 32')", - "key": "CIDR must be between 0 and 32", - "places": [ - "src/validators/validateSubnet.ts:33" - ] - }, - { - "call": "_('Invalid Trojan URL: must start with trojan://')", - "key": "Invalid Trojan URL: must start with trojan://", - "places": [ - "src/validators/validateTrojanUrl.ts:8" - ] - }, - { - "call": "_('Invalid Trojan URL: must not contain spaces')", - "key": "Invalid Trojan URL: must not contain spaces", - "places": [ - "src/validators/validateTrojanUrl.ts:15" - ] - }, - { - "call": "_('Invalid Trojan URL: parsing failed')", - "key": "Invalid Trojan URL: parsing failed", - "places": [ - "src/validators/validateTrojanUrl.ts:56" - ] - }, - { - "call": "_('URL must use one of the following protocols:')", - "key": "URL must use one of the following protocols:", - "places": [ - "src/validators/validateUrl.ts:13" - ] - }, - { - "call": "_('Invalid URL format')", - "key": "Invalid URL format", - "places": [ - "src/validators/validateUrl.ts:18" - ] - }, - { - "call": "_('Invalid VLESS URL: parsing failed')", - "key": "Invalid VLESS URL: parsing failed", - "places": [ - "src/validators/validateVlessUrl.ts:109" - ] - }, - { - "call": "_('Download')", - "key": "Download", - "places": [ - "src/partials/modal/renderModal.ts:15" - ] - }, - { - "call": "_('Copy')", - "key": "Copy", - "places": [ - "src/partials/modal/renderModal.ts:20" - ] - }, - { - "call": "_('Close')", - "key": "Close", - "places": [ - "src/partials/modal/renderModal.ts:26" - ] - }, - { - "call": "_('Fastest')", - "key": "Fastest", - "places": [ - "src/podkop/methods/custom/getDashboardSections.ts:117" - ] - }, - { - "call": "_('Traffic')", - "key": "Traffic", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:235" - ] - }, - { - "call": "_('Uplink')", - "key": "Uplink", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:237", - "src/podkop/tabs/dashboard/initController.ts:268" - ] - }, - { - "call": "_('Downlink')", - "key": "Downlink", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:238", - "src/podkop/tabs/dashboard/initController.ts:272" - ] - }, - { - "call": "_('Traffic Total')", - "key": "Traffic Total", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:265" - ] - }, - { - "call": "_('System info')", - "key": "System info", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:301" - ] - }, - { - "call": "_('Active Connections')", - "key": "Active Connections", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:304" - ] - }, - { - "call": "_('Memory Usage')", - "key": "Memory Usage", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:308" - ] - }, - { - "call": "_('Services info')", - "key": "Services info", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:337" - ] - }, - { - "call": "_('Podkop')", - "key": "Podkop", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:340" - ] - }, - { - "call": "_('✔ Enabled')", - "key": "✔ Enabled", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:342" - ] - }, - { - "call": "_('✘ Disabled')", - "key": "✘ Disabled", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:343" - ] - }, - { - "call": "_('Sing-box')", - "key": "Sing-box", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:351" - ] - }, - { - "call": "_('✔ Running')", - "key": "✔ Running", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:353" - ] - }, - { - "call": "_('✘ Stopped')", - "key": "✘ Stopped", - "places": [ - "src/podkop/tabs/dashboard/initController.ts:354" - ] - }, - { - "call": "_('Not running')", - "key": "Not running", - "places": [ - "src/podkop/tabs/diagnostic/diagnostic.store.ts:55", - "src/podkop/tabs/diagnostic/diagnostic.store.ts:63", - "src/podkop/tabs/diagnostic/diagnostic.store.ts:71", - "src/podkop/tabs/diagnostic/diagnostic.store.ts:79" - ] - }, - { - "call": "_('Queued')", - "key": "Queued", - "places": [ - "src/podkop/tabs/diagnostic/diagnostic.store.ts:95", - "src/podkop/tabs/diagnostic/diagnostic.store.ts:103", - "src/podkop/tabs/diagnostic/diagnostic.store.ts:111", - "src/podkop/tabs/diagnostic/diagnostic.store.ts:119" - ] - }, - { - "call": "_('unknown')", - "key": "unknown", - "places": [ - "src/podkop/tabs/diagnostic/initController.ts:34", - "src/podkop/tabs/diagnostic/initController.ts:35", - "src/podkop/tabs/diagnostic/initController.ts:36", - "src/podkop/tabs/diagnostic/initController.ts:37", - "src/podkop/tabs/diagnostic/initController.ts:38", - "src/podkop/tabs/diagnostic/initController.ts:39", - "src/podkop/tabs/diagnostic/initController.ts:373" - ] - }, - { - "call": "_('Global check')", - "key": "Global check", - "places": [ - "src/podkop/tabs/diagnostic/initController.ts:218" - ] - }, - { - "call": "_('View logs')", - "key": "View logs", - "places": [ - "src/podkop/tabs/diagnostic/initController.ts:248", - "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:107" - ] - }, - { - "call": "_('Show sing-box config')", - "key": "Show sing-box config", - "places": [ - "src/podkop/tabs/diagnostic/initController.ts:278", - "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116" - ] - }, - { - "call": "_('Outdated')", - "key": "Outdated", - "places": [ - "src/podkop/tabs/diagnostic/initController.ts:394" - ] - }, - { - "call": "_('Latest')", - "key": "Latest", - "places": [ - "src/podkop/tabs/diagnostic/initController.ts:404" - ] - }, - { - "call": "_('Dashboard currently unavailable')", - "key": "Dashboard currently unavailable", - "places": [ - "src/podkop/tabs/dashboard/partials/renderSections.ts:19" - ] - }, - { - "call": "_('Test latency')", - "key": "Test latency", - "places": [ - "src/podkop/tabs/dashboard/partials/renderSections.ts:108" - ] - }, - { - "call": "_('Currently unavailable')", - "key": "Currently unavailable", - "places": [ - "src/podkop/tabs/dashboard/partials/renderWidget.ts:22" - ] - }, - { - "call": "_('DNS checks')", - "key": "DNS checks", - "places": [ - "src/podkop/tabs/diagnostic/checks/contstants.ts:14" - ] - }, - { - "call": "_('Sing-box checks')", - "key": "Sing-box checks", - "places": [ - "src/podkop/tabs/diagnostic/checks/contstants.ts:19" - ] - }, - { - "call": "_('Nftables checks')", - "key": "Nftables checks", - "places": [ - "src/podkop/tabs/diagnostic/checks/contstants.ts:24" - ] - }, - { - "call": "_('FakeIP checks')", - "key": "FakeIP checks", - "places": [ - "src/podkop/tabs/diagnostic/checks/contstants.ts:29" - ] - }, - { - "call": "_('Checking dns, please wait')", - "key": "Checking dns, please wait", - "places": [ - "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14" - ] - }, - { - "call": "_('Cannot receive DNS checks result')", - "key": "Cannot receive DNS checks result", - "places": [ - "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26" - ] - }, - { - "call": "_('DNS checks passed')", - "key": "DNS checks passed", - "places": [ - "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64" - ] - }, - { - "call": "_('Bootsrap DNS')", - "key": "Bootsrap DNS", - "places": [ - "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72" - ] - }, - { - "call": "_('Main DNS')", - "key": "Main DNS", - "places": [ - "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79" - ] - }, - { - "call": "_('DNS on router')", - "key": "DNS on router", - "places": [ - "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84" - ] - }, - { - "call": "_('DHCP has DNS server')", - "key": "DHCP has DNS server", - "places": [ - "src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89" - ] - }, - { - "call": "_('Checking FakeIP, please wait')", - "key": "Checking FakeIP, please wait", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14" - ] - }, - { - "call": "_('FakeIP checks passed')", - "key": "FakeIP checks passed", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44" - ] - }, - { - "call": "_('FakeIP checks partially passed')", - "key": "FakeIP checks partially passed", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51" - ] - }, - { - "call": "_('FakeIP checks failed')", - "key": "FakeIP checks failed", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57" - ] - }, - { - "call": "_('Router DNS is routed through sing-box')", - "key": "Router DNS is routed through sing-box", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73" - ] - }, - { - "call": "_('Router DNS is not routed through sing-box')", - "key": "Router DNS is not routed through sing-box", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74" - ] - }, - { - "call": "_('Browser is using FakeIP correctly')", - "key": "Browser is using FakeIP correctly", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80" - ] - }, - { - "call": "_('Browser is not using FakeIP')", - "key": "Browser is not using FakeIP", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81" - ] - }, - { - "call": "_('Proxy traffic is routed via FakeIP')", - "key": "Proxy traffic is routed via FakeIP", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88" - ] - }, - { - "call": "_('Proxy traffic is not routed via FakeIP')", - "key": "Proxy traffic is not routed via FakeIP", - "places": [ - "src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89" - ] - }, - { - "call": "_('Checking nftables, please wait')", - "key": "Checking nftables, please wait", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12" - ] - }, - { - "call": "_('Cannot receive nftables checks result')", - "key": "Cannot receive nftables checks result", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27" - ] - }, - { - "call": "_('Nftables checks passed')", - "key": "Nftables checks passed", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74" - ] - }, - { - "call": "_('Nftables checks partially passed')", - "key": "Nftables checks partially passed", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75" - ] - }, - { - "call": "_('Table exist')", - "key": "Table exist", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80" - ] - }, - { - "call": "_('Rules mangle exist')", - "key": "Rules mangle exist", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85" - ] - }, - { - "call": "_('Rules mangle counters')", - "key": "Rules mangle counters", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90" - ] - }, - { - "call": "_('Rules mangle output exist')", - "key": "Rules mangle output exist", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95" - ] - }, - { - "call": "_('Rules mangle output counters')", - "key": "Rules mangle output counters", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100" - ] - }, - { - "call": "_('Rules proxy exist')", - "key": "Rules proxy exist", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105" - ] - }, - { - "call": "_('Rules proxy counters')", - "key": "Rules proxy counters", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110" - ] - }, - { - "call": "_('No other marking rules found')", - "key": "No other marking rules found", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116" - ] - }, - { - "call": "_('Additional marking rules found')", - "key": "Additional marking rules found", - "places": [ - "src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117" - ] - }, - { - "call": "_('Checking sing-box, please wait')", - "key": "Checking sing-box, please wait", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12" - ] - }, - { - "call": "_('Cannot receive Sing-box checks result')", - "key": "Cannot receive Sing-box checks result", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24" - ] - }, - { - "call": "_('Sing-box checks passed')", - "key": "Sing-box checks passed", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66" - ] - }, - { - "call": "_('Sing-box installed')", - "key": "Sing-box installed", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71" - ] - }, - { - "call": "_('Sing-box version >= 1.12.4')", - "key": "Sing-box version >= 1.12.4", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76" - ] - }, - { - "call": "_('Sing-box service exist')", - "key": "Sing-box service exist", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81" - ] - }, - { - "call": "_('Sing-box autostart disabled')", - "key": "Sing-box autostart disabled", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86" - ] - }, - { - "call": "_('Sing-box process running')", - "key": "Sing-box process running", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91" - ] - }, - { - "call": "_('Sing-box listening ports')", - "key": "Sing-box listening ports", - "places": [ - "src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96" - ] - }, - { - "call": "_('Restart podkop')", - "key": "Restart podkop", - "places": [ - "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49" - ] - }, - { - "call": "_('Stop podkop')", - "key": "Stop podkop", - "places": [ - "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59" - ] - }, - { - "call": "_('Start podkop')", - "key": "Start podkop", - "places": [ - "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69" - ] - }, - { - "call": "_('Disable autostart')", - "key": "Disable autostart", - "places": [ - "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79" - ] - }, - { - "call": "_('Enable autostart')", - "key": "Enable autostart", - "places": [ - "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89" - ] - }, - { - "call": "_('Get global check')", - "key": "Get global check", - "places": [ - "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98" - ] - }, - { - "call": "_('Not implement yet')", - "key": "Not implement yet", - "places": [ - "src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189" - ] - }, - { - "call": "_('Run Diagnostic')", - "key": "Run Diagnostic", - "places": [ - "src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15" - ] - }, - { - "call": "_(\"Valid\")", - "key": "Valid", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:12", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:23", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:35", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:44", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:47", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:67", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:85", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:122", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:211", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:314", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:332", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:383", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:449" - ] - }, - { - "call": "_(\"Invalid IP address\")", - "key": "Invalid IP address", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:14" - ] - }, - { - "call": "_(\"Invalid domain address\")", - "key": "Invalid domain address", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:27", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:33" - ] - }, - { - "call": "_(\"DNS server address cannot be empty\")", - "key": "DNS server address cannot be empty", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:41" - ] - }, - { - "call": "_(\"Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH\")", - "key": "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:51" - ] - }, - { - "call": "_(\"URL must use one of the following protocols:\")", - "key": "URL must use one of the following protocols:", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:64" - ] - }, - { - "call": "_(\"Invalid URL format\")", - "key": "Invalid URL format", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:69" - ] - }, - { - "call": "_(\"Path cannot be empty\")", - "key": "Path cannot be empty", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:78" - ] - }, - { - "call": "_(\"Invalid format. Use X.X.X.X or X.X.X.X/Y\")", - "key": "Invalid format. Use X.X.X.X or X.X.X.X/Y", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:102" - ] - }, - { - "call": "_(\"IP address 0.0.0.0 is not allowed\")", - "key": "IP address 0.0.0.0 is not allowed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:107" - ] - }, - { - "call": "_(\"CIDR must be between 0 and 32\")", - "key": "CIDR must be between 0 and 32", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:118" - ] - }, - { - "call": "_(\"Invalid Shadowsocks URL: must start with ss://\")", - "key": "Invalid Shadowsocks URL: must start with ss://", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:139" - ] - }, - { - "call": "_(\"Invalid Shadowsocks URL: must not contain spaces\")", - "key": "Invalid Shadowsocks URL: must not contain spaces", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:146" - ] - }, - { - "call": "_(\"Invalid Shadowsocks URL: missing credentials\")", - "key": "Invalid Shadowsocks URL: missing credentials", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:154" - ] - }, - { - "call": "_(\"Invalid Shadowsocks URL: decoded credentials must contain method:password\")", - "key": "Invalid Shadowsocks URL: decoded credentials must contain method:password", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:162" - ] - }, - { - "call": "_(\"Invalid Shadowsocks URL: missing server address\")", - "key": "Invalid Shadowsocks URL: missing server address", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:181" - ] - }, - { - "call": "_(\"Invalid Shadowsocks URL: missing server\")", - "key": "Invalid Shadowsocks URL: missing server", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:188" - ] - }, - { - "call": "_(\"Invalid Shadowsocks URL: missing port\")", - "key": "Invalid Shadowsocks URL: missing port", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:195" - ] - }, - { - "call": "_(\"Invalid port number. Must be between 1 and 65535\")", - "key": "Invalid port number. Must be between 1 and 65535", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:202" - ] - }, - { - "call": "_(\"Invalid Shadowsocks URL: parsing failed\")", - "key": "Invalid Shadowsocks URL: parsing failed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:208" - ] - }, - { - "call": "_(\"Invalid VLESS URL: parsing failed\")", - "key": "Invalid VLESS URL: parsing failed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:316" - ] - }, - { - "call": "_(\"Invalid JSON format\")", - "key": "Invalid JSON format", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:334" - ] - }, - { - "call": "_(\"Invalid Trojan URL: must start with trojan://\")", - "key": "Invalid Trojan URL: must start with trojan://", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:344" - ] - }, - { - "call": "_(\"Invalid Trojan URL: must not contain spaces\")", - "key": "Invalid Trojan URL: must not contain spaces", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:350" - ] - }, - { - "call": "_(\"Invalid Trojan URL: parsing failed\")", - "key": "Invalid Trojan URL: parsing failed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:381" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://\")", - "key": "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:392" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: must not contain spaces\")", - "key": "Invalid SOCKS URL: must not contain spaces", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:400" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: missing username\")", - "key": "Invalid SOCKS URL: missing username", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:411" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: missing host and port\")", - "key": "Invalid SOCKS URL: missing host and port", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:418" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: missing hostname or IP\")", - "key": "Invalid SOCKS URL: missing hostname or IP", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:425" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: missing port\")", - "key": "Invalid SOCKS URL: missing port", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:429" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: invalid port number\")", - "key": "Invalid SOCKS URL: invalid port number", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:435" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: invalid host format\")", - "key": "Invalid SOCKS URL: invalid host format", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:443" - ] - }, - { - "call": "_(\"Invalid SOCKS URL: parsing failed\")", - "key": "Invalid SOCKS URL: parsing failed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:447" - ] - }, - { - "call": "_(\"URL must start with vless://, ss://, trojan://, or socks4/5://\")", - "key": "URL must start with vless://, ss://, trojan://, or socks4/5://", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:468" - ] - }, - { - "call": "_(\"Fastest\")", - "key": "Fastest", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:692" - ] - }, - { - "call": "_(\"HTTP error\")", - "key": "HTTP error", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:864" - ] - }, - { - "call": "_(\"Unknown error\")", - "key": "Unknown error", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:875" - ] - }, - { - "call": "_(\"DNS checks\")", - "key": "DNS checks", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:985" - ] - }, - { - "call": "_(\"Sing-box checks\")", - "key": "Sing-box checks", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:990" - ] - }, - { - "call": "_(\"Nftables checks\")", - "key": "Nftables checks", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:995" - ] - }, - { - "call": "_(\"FakeIP checks\")", - "key": "FakeIP checks", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1000" - ] - }, - { - "call": "_(\"Not running\")", - "key": "Not running", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1048", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1056", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1064", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1072" - ] - }, - { - "call": "_(\"Queued\")", - "key": "Queued", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1084", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1092", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1100", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1108" - ] - }, - { - "call": "_(\"Dashboard currently unavailable\")", - "key": "Dashboard currently unavailable", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1578" - ] - }, - { - "call": "_(\"Test latency\")", - "key": "Test latency", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1654" - ] - }, - { - "call": "_(\"Currently unavailable\")", - "key": "Currently unavailable", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1683" - ] - }, - { - "call": "_(\"Traffic\")", - "key": "Traffic", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2024" - ] - }, - { - "call": "_(\"Uplink\")", - "key": "Uplink", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2026", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2051" - ] - }, - { - "call": "_(\"Downlink\")", - "key": "Downlink", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2027", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2055" - ] - }, - { - "call": "_(\"Traffic Total\")", - "key": "Traffic Total", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2048" - ] - }, - { - "call": "_(\"System info\")", - "key": "System info", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2078" - ] - }, - { - "call": "_(\"Active Connections\")", - "key": "Active Connections", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2081" - ] - }, - { - "call": "_(\"Memory Usage\")", - "key": "Memory Usage", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2085" - ] - }, - { - "call": "_(\"Services info\")", - "key": "Services info", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2108" - ] - }, - { - "call": "_(\"Podkop\")", - "key": "Podkop", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2111" - ] - }, - { - "call": "_(\"\\u2714 Enabled\")", - "key": "\\u2714 Enabled", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112" - ] - }, - { - "call": "_(\"\\u2718 Disabled\")", - "key": "\\u2718 Disabled", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112" - ] - }, - { - "call": "_(\"Sing-box\")", - "key": "Sing-box", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2118" - ] - }, - { - "call": "_(\"\\u2714 Running\")", - "key": "\\u2714 Running", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119" - ] - }, - { - "call": "_(\"\\u2718 Stopped\")", - "key": "\\u2718 Stopped", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119" - ] - }, - { - "call": "_(\"Checking dns, please wait\")", - "key": "Checking dns, please wait", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2365" - ] - }, - { - "call": "_(\"Cannot receive DNS checks result\")", - "key": "Cannot receive DNS checks result", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2375" - ] - }, - { - "call": "_(\"DNS checks passed\")", - "key": "DNS checks passed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2397" - ] - }, - { - "call": "_(\"Bootsrap DNS\")", - "key": "Bootsrap DNS", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2405" - ] - }, - { - "call": "_(\"Main DNS\")", - "key": "Main DNS", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2412" - ] - }, - { - "call": "_(\"DNS on router\")", - "key": "DNS on router", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2417" - ] - }, - { - "call": "_(\"DHCP has DNS server\")", - "key": "DHCP has DNS server", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2422" - ] - }, - { - "call": "_(\"Checking sing-box, please wait\")", - "key": "Checking sing-box, please wait", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2439" - ] - }, - { - "call": "_(\"Cannot receive Sing-box checks result\")", - "key": "Cannot receive Sing-box checks result", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2449" - ] - }, - { - "call": "_(\"Sing-box checks passed\")", - "key": "Sing-box checks passed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2471" - ] - }, - { - "call": "_(\"Sing-box installed\")", - "key": "Sing-box installed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2476" - ] - }, - { - "call": "_(\"Sing-box version >= 1.12.4\")", - "key": "Sing-box version >= 1.12.4", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2481" - ] - }, - { - "call": "_(\"Sing-box service exist\")", - "key": "Sing-box service exist", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2486" - ] - }, - { - "call": "_(\"Sing-box autostart disabled\")", - "key": "Sing-box autostart disabled", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2491" - ] - }, - { - "call": "_(\"Sing-box process running\")", - "key": "Sing-box process running", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2496" - ] - }, - { - "call": "_(\"Sing-box listening ports\")", - "key": "Sing-box listening ports", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2501" - ] - }, - { - "call": "_(\"Checking nftables, please wait\")", - "key": "Checking nftables, please wait", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2518" - ] - }, - { - "call": "_(\"Cannot receive nftables checks result\")", - "key": "Cannot receive nftables checks result", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2530" - ] - }, - { - "call": "_(\"Nftables checks passed\")", - "key": "Nftables checks passed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552" - ] - }, - { - "call": "_(\"Nftables checks partially passed\")", - "key": "Nftables checks partially passed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552" - ] - }, - { - "call": "_(\"Table exist\")", - "key": "Table exist", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2557" - ] - }, - { - "call": "_(\"Rules mangle exist\")", - "key": "Rules mangle exist", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2562" - ] - }, - { - "call": "_(\"Rules mangle counters\")", - "key": "Rules mangle counters", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2567" - ] - }, - { - "call": "_(\"Rules mangle output exist\")", - "key": "Rules mangle output exist", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2572" - ] - }, - { - "call": "_(\"Rules mangle output counters\")", - "key": "Rules mangle output counters", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2577" - ] - }, - { - "call": "_(\"Rules proxy exist\")", - "key": "Rules proxy exist", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2582" - ] - }, - { - "call": "_(\"Rules proxy counters\")", - "key": "Rules proxy counters", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2587" - ] - }, - { - "call": "_(\"No other marking rules found\")", - "key": "No other marking rules found", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592" - ] - }, - { - "call": "_(\"Additional marking rules found\")", - "key": "Additional marking rules found", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592" - ] - }, - { - "call": "_(\"Checking FakeIP, please wait\")", - "key": "Checking FakeIP, please wait", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2609" - ] - }, - { - "call": "_(\"FakeIP checks passed\")", - "key": "FakeIP checks passed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2627" - ] - }, - { - "call": "_(\"FakeIP checks partially passed\")", - "key": "FakeIP checks partially passed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2633" - ] - }, - { - "call": "_(\"FakeIP checks failed\")", - "key": "FakeIP checks failed", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2638" - ] - }, - { - "call": "_(\"Router DNS is routed through sing-box\")", - "key": "Router DNS is routed through sing-box", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651" - ] - }, - { - "call": "_(\"Router DNS is not routed through sing-box\")", - "key": "Router DNS is not routed through sing-box", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651" - ] - }, - { - "call": "_(\"Browser is using FakeIP correctly\")", - "key": "Browser is using FakeIP correctly", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656" - ] - }, - { - "call": "_(\"Browser is not using FakeIP\")", - "key": "Browser is not using FakeIP", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656" - ] - }, - { - "call": "_(\"Proxy traffic is routed via FakeIP\")", - "key": "Proxy traffic is routed via FakeIP", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662" - ] - }, - { - "call": "_(\"Proxy traffic is not routed via FakeIP\")", - "key": "Proxy traffic is not routed via FakeIP", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662" - ] - }, - { - "call": "_(\"Successfully copied!\")", - "key": "Successfully copied!", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3288" - ] - }, - { - "call": "_(\"Failed to copy!\")", - "key": "Failed to copy!", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3290" - ] - }, - { - "call": "_(\"Download\")", - "key": "Download", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3306" - ] - }, - { - "call": "_(\"Copy\")", - "key": "Copy", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3311" - ] - }, - { - "call": "_(\"Close\")", - "key": "Close", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3318" - ] - }, - { - "call": "_(\"Restart podkop\")", - "key": "Restart podkop", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3350" - ] - }, - { - "call": "_(\"Stop podkop\")", - "key": "Stop podkop", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3360" - ] - }, - { - "call": "_(\"Start podkop\")", - "key": "Start podkop", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3370" - ] - }, - { - "call": "_(\"Disable autostart\")", - "key": "Disable autostart", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3380" - ] - }, - { - "call": "_(\"Enable autostart\")", - "key": "Enable autostart", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3390" - ] - }, - { - "call": "_(\"Get global check\")", - "key": "Get global check", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3399" - ] - }, - { - "call": "_(\"View logs\")", - "key": "View logs", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3408", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3840" - ] - }, - { - "call": "_(\"Show sing-box config\")", - "key": "Show sing-box config", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3417", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3867" - ] - }, - { - "call": "_(\"Not implement yet\")", - "key": "Not implement yet", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3577" - ] - }, - { - "call": "_(\"Run Diagnostic\")", - "key": "Run Diagnostic", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3587" - ] - }, - { - "call": "_(\"unknown\")", - "key": "unknown", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3651", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3652", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3653", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3654", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3655", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3656", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3950" - ] - }, - { - "call": "_(\"Global check\")", - "key": "Global check", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3813" - ] - }, - { - "call": "_(\"Outdated\")", - "key": "Outdated", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3969" - ] - }, - { - "call": "_(\"Latest\")", - "key": "Latest", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3978" - ] - }, - { - "call": "_(\"Operation timed out\")", - "key": "Operation timed out", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:4389" - ] - }, - { - "call": "_(\"Podkop Settings\")", - "key": "Podkop Settings", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26" - ] - }, - { - "call": "_(\"Configuration for Podkop service\")", - "key": "Configuration for Podkop service", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27" - ] - }, - { - "call": "_(\"Sections\")", - "key": "Sections", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36" - ] - }, - { - "call": "_(\"Settings\")", - "key": "Settings", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49" - ] - }, - { - "call": "_(\"Diagnostics\")", - "key": "Diagnostics", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65" - ] - }, - { - "call": "_(\"Dashboard\")", - "key": "Dashboard", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80" - ] - }, - { - "call": "_(\"Connection Type\")", - "key": "Connection Type", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12" - ] - }, - { - "call": "_(\"Select between VPN and Proxy connection methods for traffic routing\")", - "key": "Select between VPN and Proxy connection methods for traffic routing", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13" - ] - }, - { - "call": "_(\"Configuration Type\")", - "key": "Configuration Type", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22" - ] - }, - { - "call": "_(\"Select how to configure the proxy\")", - "key": "Select how to configure the proxy", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23" - ] - }, - { - "call": "_(\"Connection URL\")", - "key": "Connection URL", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25" - ] - }, - { - "call": "_(\"Outbound Config\")", - "key": "Outbound Config", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26" - ] - }, - { - "call": "_(\"URLTest\")", - "key": "URLTest", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27" - ] - }, - { - "call": "_(\"Proxy Configuration URL\")", - "key": "Proxy Configuration URL", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34" - ] - }, - { - "call": "_(\"Outbound Configuration\")", - "key": "Outbound Configuration", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64" - ] - }, - { - "call": "_(\"Enter complete outbound configuration in JSON format\")", - "key": "Enter complete outbound configuration in JSON format", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65" - ] - }, - { - "call": "_(\"URLTest Proxy Links\")", - "key": "URLTest Proxy Links", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87" - ] - }, - { - "call": "_(\"UDP over TCP\")", - "key": "UDP over TCP", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110" - ] - }, - { - "call": "_(\"Applicable for SOCKS and Shadowsocks proxy\")", - "key": "Applicable for SOCKS and Shadowsocks proxy", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111" - ] - }, - { - "call": "_(\"Network Interface\")", - "key": "Network Interface", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120" - ] - }, - { - "call": "_(\"Select network interface for VPN connection\")", - "key": "Select network interface for VPN connection", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121" - ] - }, - { - "call": "_(\"Domain Resolver\")", - "key": "Domain Resolver", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166" - ] - }, - { - "call": "_(\"Enable built-in DNS resolver for domains handled by this section\")", - "key": "Enable built-in DNS resolver for domains handled by this section", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167" - ] - }, - { - "call": "_(\"DNS Protocol Type\")", - "key": "DNS Protocol Type", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12" - ] - }, - { - "call": "_(\"Select the DNS protocol type for the domain resolver\")", - "key": "Select the DNS protocol type for the domain resolver", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177" - ] - }, - { - "call": "_(\"DNS over HTTPS (DoH)\")", - "key": "DNS over HTTPS (DoH)", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15" - ] - }, - { - "call": "_(\"DNS over TLS (DoT)\")", - "key": "DNS over TLS (DoT)", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16" - ] - }, - { - "call": "_(\"UDP (Unprotected DNS)\")", - "key": "UDP (Unprotected DNS)", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17" - ] - }, - { - "call": "_(\"DNS Server\")", - "key": "DNS Server", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24" - ] - }, - { - "call": "_(\"Select or enter DNS server address\")", - "key": "Select or enter DNS server address", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25" - ] - }, - { - "call": "_(label)", - "key": "", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:193", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:217", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:28", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:51", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:254" - ] - }, - { - "call": "_(\"Community Lists\")", - "key": "Community Lists", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211" - ] - }, - { - "call": "_(\"Select a predefined list for routing\")", - "key": "Select a predefined list for routing", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212" - ] - }, - { - "call": "_(\"Regional options cannot be used together\")", - "key": "Regional options cannot be used together", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245" - ] - }, - { - "call": "_(\"Warning: %s cannot be used together with %s. Previous selections have been removed.\")", - "key": "Warning: %s cannot be used together with %s. Previous selections have been removed.", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247" - ] - }, - { - "call": "_(\"Russia inside restrictions\")", - "key": "Russia inside restrictions", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264" - ] - }, - { - "call": "_(\"Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.\")", - "key": "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266" - ] - }, - { - "call": "_(\"User Domain List Type\")", - "key": "User Domain List Type", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299" - ] - }, - { - "call": "_(\"Select the list type for adding custom domains\")", - "key": "Select the list type for adding custom domains", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300" - ] - }, - { - "call": "_(\"Disabled\")", - "key": "Disabled", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382" - ] - }, - { - "call": "_(\"Dynamic List\")", - "key": "Dynamic List", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383" - ] - }, - { - "call": "_(\"Text List\")", - "key": "Text List", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304" - ] - }, - { - "call": "_(\"User Domains\")", - "key": "User Domains", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311" - ] - }, - { - "call": "_(\"Enter domain names without protocols, e.g. example.com or sub.example.com\")", - "key": "Enter domain names without protocols, e.g. example.com or sub.example.com", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312" - ] - }, - { - "call": "_(\"User Domains List\")", - "key": "User Domains List", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337" - ] - }, - { - "call": "_(\"Enter domain names separated by commas, spaces, or newlines. You can add comments using //\")", - "key": "Enter domain names separated by commas, spaces, or newlines. You can add comments using //", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338" - ] - }, - { - "call": "_(\"At least one valid domain must be specified. Comments-only content is not allowed.\")", - "key": "At least one valid domain must be specified. Comments-only content is not allowed.", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356" - ] - }, - { - "call": "_(\"Validation errors:\")", + "call": "Validation errors:", "key": "Validation errors:", "places": [ "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370", @@ -2111,452 +1634,32 @@ ] }, { - "call": "_(\"User Subnet List Type\")", - "key": "User Subnet List Type", + "call": "View logs", + "key": "View logs", "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379" + "src/podkop/tabs/diagnostic/initController.ts:248", + "src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:107" ] }, { - "call": "_(\"Select the list type for adding custom subnets\")", - "key": "Select the list type for adding custom subnets", + "call": "Warning: %s cannot be used together with %s. Previous selections have been removed.", + "key": "Warning: %s cannot be used together with %s. Previous selections have been removed.", "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380" + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247" ] }, { - "call": "_(\"Text List (comma/space/newline separated)\")", - "key": "Text List (comma/space/newline separated)", + "call": "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.", + "key": "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.", "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384" + "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266" ] }, { - "call": "_(\"User Subnets\")", - "key": "User Subnets", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391" - ] - }, - { - "call": "_(\"Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses\")", - "key": "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392" - ] - }, - { - "call": "_(\"User Subnets List\")", - "key": "User Subnets List", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417" - ] - }, - { - "call": "_(\"Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //\")", - "key": "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:418" - ] - }, - { - "call": "_(\"At least one valid subnet or IP must be specified. Comments-only content is not allowed.\")", - "key": "At least one valid subnet or IP must be specified. Comments-only content is not allowed.", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437" - ] - }, - { - "call": "_(\"Local Domain Lists\")", - "key": "Local Domain Lists", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458" - ] - }, - { - "call": "_(\"Specify the path to the list file located on the router filesystem\")", - "key": "Specify the path to the list file located on the router filesystem", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459", - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482" - ] - }, - { - "call": "_(\"Local Subnet Lists\")", - "key": "Local Subnet Lists", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481" - ] - }, - { - "call": "_(\"Remote Domain Lists\")", - "key": "Remote Domain Lists", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504" - ] - }, - { - "call": "_(\"Specify remote URLs to download and use domain lists\")", - "key": "Specify remote URLs to download and use domain lists", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505" - ] - }, - { - "call": "_(\"Remote Subnet Lists\")", - "key": "Remote Subnet Lists", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527" - ] - }, - { - "call": "_(\"Specify remote URLs to download and use subnet lists\")", - "key": "Specify remote URLs to download and use subnet lists", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528" - ] - }, - { - "call": "_(\"Fully Routed IPs\")", - "key": "Fully Routed IPs", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550" - ] - }, - { - "call": "_(\"Specify local IP addresses or subnets whose traffic will always be routed through the configured route\")", - "key": "Specify local IP addresses or subnets whose traffic will always be routed through the configured route", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551" - ] - }, - { - "call": "_(\"Enable Mixed Proxy\")", - "key": "Enable Mixed Proxy", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575" - ] - }, - { - "call": "_(\"Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies\")", - "key": "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576" - ] - }, - { - "call": "_(\"Mixed Proxy Port\")", - "key": "Mixed Proxy Port", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586" - ] - }, - { - "call": "_(\"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\")", - "key": "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", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:587" - ] - }, - { - "call": "_(\"Select DNS protocol to use\")", - "key": "Select DNS protocol to use", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13" - ] - }, - { - "call": "_(\"Bootstrap DNS server\")", - "key": "Bootstrap DNS server", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45" - ] - }, - { - "call": "_(\"The DNS server used to look up the IP address of an upstream DNS server\")", - "key": "The DNS server used to look up the IP address of an upstream DNS server", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46" - ] - }, - { - "call": "_(\"DNS Rewrite TTL\")", - "key": "DNS Rewrite TTL", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68" - ] - }, - { - "call": "_(\"Time in seconds for DNS record caching (default: 60)\")", - "key": "Time in seconds for DNS record caching (default: 60)", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69" - ] - }, - { - "call": "_(\"TTL value cannot be empty\")", - "key": "TTL value cannot be empty", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75" - ] - }, - { - "call": "_(\"TTL must be a positive number\")", - "key": "TTL must be a positive number", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80" - ] - }, - { - "call": "_(\"Source Network Interface\")", - "key": "Source Network Interface", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89" - ] - }, - { - "call": "_(\"Select the network interface from which the traffic will originate\")", - "key": "Select the network interface from which the traffic will originate", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90" - ] - }, - { - "call": "_(\"Enable Output Network Interface\")", - "key": "Enable Output Network Interface", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126" - ] - }, - { - "call": "_(\"You can select Output Network Interface, by default autodetect\")", + "call": "You can select Output Network Interface, by default autodetect", "key": "You can select Output Network Interface, by default autodetect", "places": [ "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:127" ] - }, - { - "call": "_(\"Output Network Interface\")", - "key": "Output Network Interface", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135" - ] - }, - { - "call": "_(\"Select the network interface to which the traffic will originate\")", - "key": "Select the network interface to which the traffic will originate", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136" - ] - }, - { - "call": "_(\"Interface Monitoring\")", - "key": "Interface Monitoring", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182" - ] - }, - { - "call": "_(\"Interface monitoring for Bad WAN\")", - "key": "Interface monitoring for Bad WAN", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183" - ] - }, - { - "call": "_(\"Monitored Interfaces\")", - "key": "Monitored Interfaces", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191" - ] - }, - { - "call": "_(\"Select the WAN interfaces to be monitored\")", - "key": "Select the WAN interfaces to be monitored", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192" - ] - }, - { - "call": "_(\"Interface Monitoring Delay\")", - "key": "Interface Monitoring Delay", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214" - ] - }, - { - "call": "_(\"Delay in milliseconds before reloading podkop after interface UP\")", - "key": "Delay in milliseconds before reloading podkop after interface UP", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215" - ] - }, - { - "call": "_(\"Delay value cannot be empty\")", - "key": "Delay value cannot be empty", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222" - ] - }, - { - "call": "_(\"Enable YACD\")", - "key": "Enable YACD", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230" - ] - }, - { - "call": "_(\"Disable QUIC\")", - "key": "Disable QUIC", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239" - ] - }, - { - "call": "_(\"Disable the QUIC protocol to improve compatibility or fix issues with video streaming\")", - "key": "Disable the QUIC protocol to improve compatibility or fix issues with video streaming", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240" - ] - }, - { - "call": "_(\"List Update Frequency\")", - "key": "List Update Frequency", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250" - ] - }, - { - "call": "_(\"Select how often the domain or subnet lists are updated automatically\")", - "key": "Select how often the domain or subnet lists are updated automatically", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251" - ] - }, - { - "call": "_(\"Download Lists via Proxy/VPN\")", - "key": "Download Lists via Proxy/VPN", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262" - ] - }, - { - "call": "_(\"Downloading all lists via main Proxy/VPN\")", - "key": "Downloading all lists via main Proxy/VPN", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263" - ] - }, - { - "call": "_(\"Download Lists via specific proxy section\")", - "key": "Download Lists via specific proxy section", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271" - ] - }, - { - "call": "_(\"Downloading all lists via specific Proxy/VPN\")", - "key": "Downloading all lists via specific Proxy/VPN", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272" - ] - }, - { - "call": "_(\"Dont Touch My DHCP!\")", - "key": "Dont Touch My DHCP!", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300" - ] - }, - { - "call": "_(\"Podkop will not modify your DHCP configuration\")", - "key": "Podkop will not modify your DHCP configuration", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301" - ] - }, - { - "call": "_(\"Config File Path\")", - "key": "Config File Path", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309" - ] - }, - { - "call": "_(\"Select path for sing-box config file. Change this ONLY if you know what you are doing\")", - "key": "Select path for sing-box config file. Change this ONLY if you know what you are doing", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310" - ] - }, - { - "call": "_(\"Cache File Path\")", - "key": "Cache File Path", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322" - ] - }, - { - "call": "_(\"Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing\")", - "key": "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323" - ] - }, - { - "call": "_(\"Cache file path cannot be empty\")", - "key": "Cache file path cannot be empty", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336" - ] - }, - { - "call": "_(\"Path must be absolute (start with /)\")", - "key": "Path must be absolute (start with /)", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340" - ] - }, - { - "call": "_(\"Path must end with cache.db\")", - "key": "Path must end with cache.db", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344" - ] - }, - { - "call": "_(\"Path must contain at least one directory (like /tmp/cache.db)\")", - "key": "Path must contain at least one directory (like /tmp/cache.db)", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349" - ] - }, - { - "call": "_(\"Exclude NTP\")", - "key": "Exclude NTP", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358" - ] - }, - { - "call": "_(\"Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN\")", - "key": "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359" - ] - }, - { - "call": "_(\"Routing Excluded IPs\")", - "key": "Routing Excluded IPs", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369" - ] - }, - { - "call": "_(\"Specify a local IP address to be excluded from routing\")", - "key": "Specify a local IP address to be excluded from routing", - "places": [ - "../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370" - ] } ] \ No newline at end of file diff --git a/fe-app-podkop/locales/podkop.pot b/fe-app-podkop/locales/podkop.pot index 415585a..f8c7d1c 100644 --- a/fe-app-podkop/locales/podkop.pot +++ b/fe-app-podkop/locales/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 18:48+0300\n" -"PO-Revision-Date: 2025-10-21 18:48+0300\n" +"POT-Creation-Date: 2025-10-21 19:33+0300\n" +"PO-Revision-Date: 2025-10-21 19:33+0300\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -16,28 +16,933 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/helpers/copyToClipboard.ts:10 -msgid "Successfully copied!" +#: src/podkop/tabs/dashboard/initController.ts:342 +msgid "✔ Enabled" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:353 +msgid "✔ Running" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:343 +msgid "✘ Disabled" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:354 +msgid "✘ Stopped" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:304 +msgid "Active Connections" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117 +msgid "Additional marking rules found" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111 +msgid "Applicable for SOCKS and Shadowsocks proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356 +msgid "At least one valid domain must be specified. Comments-only content is not allowed." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437 +msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72 +msgid "Bootsrap DNS" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45 +msgid "Bootstrap DNS server" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81 +msgid "Browser is not using FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80 +msgid "Browser is using FakeIP correctly" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322 +msgid "Cache File Path" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336 +msgid "Cache file path cannot be empty" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26 +msgid "Cannot receive DNS checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27 +msgid "Cannot receive nftables checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24 +msgid "Cannot receive Sing-box checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14 +msgid "Checking dns, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14 +msgid "Checking FakeIP, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12 +msgid "Checking nftables, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12 +msgid "Checking sing-box, please wait" +msgstr "" + +#: src/validators/validateSubnet.ts:33 +msgid "CIDR must be between 0 and 32" +msgstr "" + +#: src/partials/modal/renderModal.ts:26 +msgid "Close" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211 +msgid "Community Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309 +msgid "Config File Path" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27 +msgid "Configuration for Podkop service" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22 +msgid "Configuration Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12 +msgid "Connection Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25 +msgid "Connection URL" +msgstr "" + +#: src/partials/modal/renderModal.ts:20 +msgid "Copy" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderWidget.ts:22 +msgid "Currently unavailable" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80 +msgid "Dashboard" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderSections.ts:19 +msgid "Dashboard currently unavailable" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215 +msgid "Delay in milliseconds before reloading podkop after interface UP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222 +msgid "Delay value cannot be empty" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89 +msgid "DHCP has DNS server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65 +msgid "Diagnostics" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79 +msgid "Disable autostart" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239 +msgid "Disable QUIC" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240 +msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382 +msgid "Disabled" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:14 +msgid "DNS checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64 +msgid "DNS checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84 +msgid "DNS on router" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15 +msgid "DNS over HTTPS (DoH)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16 +msgid "DNS over TLS (DoT)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12 +msgid "DNS Protocol Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68 +msgid "DNS Rewrite TTL" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24 +msgid "DNS Server" +msgstr "" + +#: src/validators/validateDns.ts:7 +msgid "DNS server address cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166 +msgid "Domain Resolver" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300 +msgid "Dont Touch My DHCP!" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:238 +#: src/podkop/tabs/dashboard/initController.ts:272 +msgid "Downlink" +msgstr "" + +#: src/partials/modal/renderModal.ts:15 +msgid "Download" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262 +msgid "Download Lists via Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271 +msgid "Download Lists via specific proxy section" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263 +msgid "Downloading all lists via main Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272 +msgid "Downloading all lists via specific Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383 +msgid "Dynamic List" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89 +msgid "Enable autostart" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167 +msgid "Enable built-in DNS resolver for domains handled by this section" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575 +msgid "Enable Mixed Proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126 +msgid "Enable Output Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576 +msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230 +msgid "Enable YACD" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65 +msgid "Enter complete outbound configuration in JSON format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338 +msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312 +msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392 +msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358 +msgid "Exclude NTP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359 +msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" msgstr "" #: src/helpers/copyToClipboard.ts:12 msgid "Failed to copy!" msgstr "" -#: src/helpers/withTimeout.ts:7 -msgid "Operation timed out" +#: src/podkop/tabs/diagnostic/checks/contstants.ts:29 +msgid "FakeIP checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57 +msgid "FakeIP checks failed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51 +msgid "FakeIP checks partially passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44 +msgid "FakeIP checks passed" +msgstr "" + +#: src/podkop/methods/custom/getDashboardSections.ts:117 +msgid "Fastest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550 +msgid "Fully Routed IPs" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98 +msgid "Get global check" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:218 +msgid "Global check" msgstr "" #: src/podkop/api.ts:27 msgid "HTTP error" msgstr "" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182 +msgid "Interface Monitoring" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214 +msgid "Interface Monitoring Delay" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183 +msgid "Interface monitoring for Bad WAN" +msgstr "" + +#: src/validators/validateDns.ts:20 +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "" + +#: src/validators/validateDomain.ts:18 +#: src/validators/validateDomain.ts:27 +msgid "Invalid domain address" +msgstr "" + +#: src/validators/validateSubnet.ts:11 +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "" + +#: src/validators/validateIp.ts:11 +msgid "Invalid IP address" +msgstr "" + +#: src/validators/validateOutboundJson.ts:19 +msgid "Invalid JSON format" +msgstr "" + +#: src/validators/validatePath.ts:22 +msgid "Invalid path format. Path must start with \"/\" and contain valid characters" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:85 +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:37 +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:27 +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:46 +msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:76 +msgid "Invalid Shadowsocks URL: missing port" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:67 +msgid "Invalid Shadowsocks URL: missing server" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:58 +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:16 +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:8 +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:91 +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "" + +#: src/validators/validateSocksUrl.ts:73 +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +#: src/validators/validateSocksUrl.ts:63 +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +#: src/validators/validateSocksUrl.ts:42 +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +#: src/validators/validateSocksUrl.ts:51 +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +#: src/validators/validateSocksUrl.ts:56 +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +#: src/validators/validateSocksUrl.ts:34 +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +#: src/validators/validateSocksUrl.ts:19 +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +#: src/validators/validateSocksUrl.ts:10 +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +#: src/validators/validateSocksUrl.ts:77 +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:15 +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:8 +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:56 +msgid "Invalid Trojan URL: parsing failed" +msgstr "" + +#: src/validators/validateUrl.ts:18 +msgid "Invalid URL format" +msgstr "" + +#: src/validators/validateVlessUrl.ts:109 +msgid "Invalid VLESS URL: parsing failed" +msgstr "" + +#: src/validators/validateSubnet.ts:18 +msgid "IP address 0.0.0.0 is not allowed" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:404 +msgid "Latest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250 +msgid "List Update Frequency" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458 +msgid "Local Domain Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481 +msgid "Local Subnet Lists" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79 +msgid "Main DNS" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:308 +msgid "Memory Usage" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586 +msgid "Mixed Proxy Port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191 +msgid "Monitored Interfaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120 +msgid "Network Interface" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:24 +msgid "Nftables checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75 +msgid "Nftables checks partially passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74 +msgid "Nftables checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116 +msgid "No other marking rules found" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189 +msgid "Not implement yet" +msgstr "" + +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:55 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:63 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:71 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:79 +msgid "Not running" +msgstr "" + +#: src/helpers/withTimeout.ts:7 +msgid "Operation timed out" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26 +msgid "Outbound Config" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64 +msgid "Outbound Configuration" +msgstr "" + +#: src/validators/validateOutboundJson.ts:11 +msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:394 +msgid "Outdated" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135 +msgid "Output Network Interface" +msgstr "" + +#: src/validators/validatePath.ts:7 +msgid "Path cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340 +msgid "Path must be absolute (start with /)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349 +msgid "Path must contain at least one directory (like /tmp/cache.db)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344 +msgid "Path must end with cache.db" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:340 +msgid "Podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26 +msgid "Podkop Settings" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301 +msgid "Podkop will not modify your DHCP configuration" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34 +msgid "Proxy Configuration URL" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89 +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88 +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:95 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:103 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:111 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:119 +msgid "Queued" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245 +msgid "Regional options cannot be used together" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504 +msgid "Remote Domain Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527 +msgid "Remote Subnet Lists" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49 +msgid "Restart podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74 +msgid "Router DNS is not routed through sing-box" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73 +msgid "Router DNS is routed through sing-box" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369 +msgid "Routing Excluded IPs" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90 +msgid "Rules mangle counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85 +msgid "Rules mangle exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100 +msgid "Rules mangle output counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95 +msgid "Rules mangle output exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110 +msgid "Rules proxy counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105 +msgid "Rules proxy exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15 +msgid "Run Diagnostic" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264 +msgid "Russia inside restrictions" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36 +msgid "Sections" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212 +msgid "Select a predefined list for routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13 +msgid "Select between VPN and Proxy connection methods for traffic routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13 +msgid "Select DNS protocol to use" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251 +msgid "Select how often the domain or subnet lists are updated automatically" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23 +msgid "Select how to configure the proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121 +msgid "Select network interface for VPN connection" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25 +msgid "Select or enter DNS server address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323 +msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310 +msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177 +msgid "Select the DNS protocol type for the domain resolver" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300 +msgid "Select the list type for adding custom domains" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380 +msgid "Select the list type for adding custom subnets" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90 +msgid "Select the network interface from which the traffic will originate" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136 +msgid "Select the network interface to which the traffic will originate" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192 +msgid "Select the WAN interfaces to be monitored" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:337 +msgid "Services info" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49 +msgid "Settings" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:278 +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116 +msgid "Show sing-box config" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:351 +msgid "Sing-box" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86 +msgid "Sing-box autostart disabled" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:19 +msgid "Sing-box checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66 +msgid "Sing-box checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71 +msgid "Sing-box installed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96 +msgid "Sing-box listening ports" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91 +msgid "Sing-box process running" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81 +msgid "Sing-box service exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76 +msgid "Sing-box version >= 1.12.4" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89 +msgid "Source Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370 +msgid "Specify a local IP address to be excluded from routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551 +msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505 +msgid "Specify remote URLs to download and use domain lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528 +msgid "Specify remote URLs to download and use subnet lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482 +msgid "Specify the path to the list file located on the router filesystem" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69 +msgid "Start podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59 +msgid "Stop podkop" +msgstr "" + +#: src/helpers/copyToClipboard.ts:10 +msgid "Successfully copied!" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:301 +msgid "System info" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80 +msgid "Table exist" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderSections.ts:108 +msgid "Test latency" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304 +msgid "Text List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384 +msgid "Text List (comma/space/newline separated)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 +msgid "The DNS server used to look up the IP address of an upstream DNS server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69 +msgid "Time in seconds for DNS record caching (default: 60)" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:235 +msgid "Traffic" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:265 +msgid "Traffic Total" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80 +msgid "TTL must be a positive number" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75 +msgid "TTL value cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17 +msgid "UDP (Unprotected DNS)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110 +msgid "UDP over TCP" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:34 +#: src/podkop/tabs/diagnostic/initController.ts:35 +#: src/podkop/tabs/diagnostic/initController.ts:36 +#: src/podkop/tabs/diagnostic/initController.ts:37 +#: src/podkop/tabs/diagnostic/initController.ts:38 +#: src/podkop/tabs/diagnostic/initController.ts:39 +#: src/podkop/tabs/diagnostic/initController.ts:373 +msgid "unknown" +msgstr "" + #: src/podkop/api.ts:40 msgid "Unknown error" msgstr "" -#: src/validators/validateDns.ts:7 -msgid "DNS server address cannot be empty" +#: src/podkop/tabs/dashboard/initController.ts:237 +#: src/podkop/tabs/dashboard/initController.ts:268 +msgid "Uplink" +msgstr "" + +#: src/validators/validateProxyUrl.ts:27 +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +#: src/validators/validateUrl.ts:13 +msgid "URL must use one of the following protocols:" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27 +msgid "URLTest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87 +msgid "URLTest Proxy Links" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299 +msgid "User Domain List Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311 +msgid "User Domains" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337 +msgid "User Domains List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379 +msgid "User Subnet List Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391 +msgid "User Subnets" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417 +msgid "User Subnets List" msgstr "" #: src/validators/validateDns.ts:11 @@ -56,254 +961,9 @@ msgstr "" msgid "Valid" msgstr "" -#: src/validators/validateDns.ts:20 -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "" - -#: src/validators/validateDomain.ts:18 -#: src/validators/validateDomain.ts:27 -msgid "Invalid domain address" -msgstr "" - -#: src/validators/validateIp.ts:11 -msgid "Invalid IP address" -msgstr "" - -#: src/validators/validateOutboundJson.ts:11 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:327 -msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" -msgstr "" - -#: src/validators/validateOutboundJson.ts:19 -msgid "Invalid JSON format" -msgstr "" - -#: src/validators/validatePath.ts:7 -msgid "Path cannot be empty" -msgstr "" - -#: src/validators/validatePath.ts:22 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:90 -msgid "Invalid path format. Path must start with \"/\" and contain valid characters" -msgstr "" - -#: src/validators/validateProxyUrl.ts:27 -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:8 -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:16 -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:27 -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:37 -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:46 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:171 -msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:58 -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:67 -msgid "Invalid Shadowsocks URL: missing server" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:76 -msgid "Invalid Shadowsocks URL: missing port" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:85 -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:91 -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "" - -#: src/validators/validateSocksUrl.ts:10 -msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" - -#: src/validators/validateSocksUrl.ts:19 -msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" - -#: src/validators/validateSocksUrl.ts:34 -msgid "Invalid SOCKS URL: missing username" -msgstr "" - -#: src/validators/validateSocksUrl.ts:42 -msgid "Invalid SOCKS URL: missing host and port" -msgstr "" - -#: src/validators/validateSocksUrl.ts:51 -msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" - -#: src/validators/validateSocksUrl.ts:56 -msgid "Invalid SOCKS URL: missing port" -msgstr "" - -#: src/validators/validateSocksUrl.ts:63 -msgid "Invalid SOCKS URL: invalid port number" -msgstr "" - -#: src/validators/validateSocksUrl.ts:73 -msgid "Invalid SOCKS URL: invalid host format" -msgstr "" - -#: src/validators/validateSocksUrl.ts:77 -msgid "Invalid SOCKS URL: parsing failed" -msgstr "" - -#: src/validators/validateSubnet.ts:11 -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "" - -#: src/validators/validateSubnet.ts:18 -msgid "IP address 0.0.0.0 is not allowed" -msgstr "" - -#: src/validators/validateSubnet.ts:33 -msgid "CIDR must be between 0 and 32" -msgstr "" - -#: src/validators/validateTrojanUrl.ts:8 -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "" - -#: src/validators/validateTrojanUrl.ts:15 -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "" - -#: src/validators/validateTrojanUrl.ts:56 -msgid "Invalid Trojan URL: parsing failed" -msgstr "" - -#: src/validators/validateUrl.ts:13 -msgid "URL must use one of the following protocols:" -msgstr "" - -#: src/validators/validateUrl.ts:18 -msgid "Invalid URL format" -msgstr "" - -#: src/validators/validateVlessUrl.ts:109 -msgid "Invalid VLESS URL: parsing failed" -msgstr "" - -#: src/partials/modal/renderModal.ts:15 -msgid "Download" -msgstr "" - -#: src/partials/modal/renderModal.ts:20 -msgid "Copy" -msgstr "" - -#: src/partials/modal/renderModal.ts:26 -msgid "Close" -msgstr "" - -#: src/podkop/methods/custom/getDashboardSections.ts:117 -msgid "Fastest" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:235 -msgid "Traffic" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:237 -#: src/podkop/tabs/dashboard/initController.ts:268 -msgid "Uplink" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:238 -#: src/podkop/tabs/dashboard/initController.ts:272 -msgid "Downlink" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:265 -msgid "Traffic Total" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:301 -msgid "System info" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:304 -msgid "Active Connections" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:308 -msgid "Memory Usage" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:337 -msgid "Services info" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:340 -msgid "Podkop" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:342 -msgid "✔ Enabled" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:343 -msgid "✘ Disabled" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:351 -msgid "Sing-box" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:353 -msgid "✔ Running" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:354 -msgid "✘ Stopped" -msgstr "" - -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:55 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:63 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:71 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:79 -msgid "Not running" -msgstr "" - -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:95 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:103 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:111 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:119 -msgid "Queued" -msgstr "" - -#: src/podkop/tabs/diagnostic/initController.ts:34 -#: src/podkop/tabs/diagnostic/initController.ts:35 -#: src/podkop/tabs/diagnostic/initController.ts:36 -#: src/podkop/tabs/diagnostic/initController.ts:37 -#: src/podkop/tabs/diagnostic/initController.ts:38 -#: src/podkop/tabs/diagnostic/initController.ts:39 -#: src/podkop/tabs/diagnostic/initController.ts:373 -msgid "unknown" -msgstr "" - -#: src/podkop/tabs/diagnostic/initController.ts:218 -msgid "Global check" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449 +msgid "Validation errors:" msgstr "" #: src/podkop/tabs/diagnostic/initController.ts:248 @@ -311,1202 +971,14 @@ msgstr "" msgid "View logs" msgstr "" -#: src/podkop/tabs/diagnostic/initController.ts:278 -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116 -msgid "Show sing-box config" -msgstr "" - -#: src/podkop/tabs/diagnostic/initController.ts:394 -msgid "Outdated" -msgstr "" - -#: src/podkop/tabs/diagnostic/initController.ts:404 -msgid "Latest" -msgstr "" - -#: src/podkop/tabs/dashboard/partials/renderSections.ts:19 -msgid "Dashboard currently unavailable" -msgstr "" - -#: src/podkop/tabs/dashboard/partials/renderSections.ts:108 -msgid "Test latency" -msgstr "" - -#: src/podkop/tabs/dashboard/partials/renderWidget.ts:22 -msgid "Currently unavailable" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/contstants.ts:14 -msgid "DNS checks" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/contstants.ts:19 -msgid "Sing-box checks" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/contstants.ts:24 -msgid "Nftables checks" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/contstants.ts:29 -msgid "FakeIP checks" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14 -msgid "Checking dns, please wait" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26 -msgid "Cannot receive DNS checks result" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64 -msgid "DNS checks passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72 -msgid "Bootsrap DNS" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79 -msgid "Main DNS" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84 -msgid "DNS on router" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89 -msgid "DHCP has DNS server" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14 -msgid "Checking FakeIP, please wait" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44 -msgid "FakeIP checks passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51 -msgid "FakeIP checks partially passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57 -msgid "FakeIP checks failed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73 -msgid "Router DNS is routed through sing-box" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74 -msgid "Router DNS is not routed through sing-box" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80 -msgid "Browser is using FakeIP correctly" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81 -msgid "Browser is not using FakeIP" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88 -msgid "Proxy traffic is routed via FakeIP" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89 -msgid "Proxy traffic is not routed via FakeIP" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12 -msgid "Checking nftables, please wait" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27 -msgid "Cannot receive nftables checks result" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74 -msgid "Nftables checks passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75 -msgid "Nftables checks partially passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80 -msgid "Table exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85 -msgid "Rules mangle exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90 -msgid "Rules mangle counters" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95 -msgid "Rules mangle output exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100 -msgid "Rules mangle output counters" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105 -msgid "Rules proxy exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110 -msgid "Rules proxy counters" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116 -msgid "No other marking rules found" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117 -msgid "Additional marking rules found" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12 -msgid "Checking sing-box, please wait" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24 -msgid "Cannot receive Sing-box checks result" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66 -msgid "Sing-box checks passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71 -msgid "Sing-box installed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76 -msgid "Sing-box version >= 1.12.4" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81 -msgid "Sing-box service exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86 -msgid "Sing-box autostart disabled" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91 -msgid "Sing-box process running" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96 -msgid "Sing-box listening ports" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49 -msgid "Restart podkop" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59 -msgid "Stop podkop" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69 -msgid "Start podkop" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79 -msgid "Disable autostart" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89 -msgid "Enable autostart" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98 -msgid "Get global check" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189 -msgid "Not implement yet" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15 -msgid "Run Diagnostic" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:12 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:23 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:35 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:44 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:47 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:67 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:85 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:122 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:211 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:314 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:332 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:383 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:449 -msgid "Valid" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:14 -msgid "Invalid IP address" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:27 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:33 -msgid "Invalid domain address" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:41 -msgid "DNS server address cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:51 -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:64 -msgid "URL must use one of the following protocols:" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:69 -msgid "Invalid URL format" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:78 -msgid "Path cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:102 -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:107 -msgid "IP address 0.0.0.0 is not allowed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:118 -msgid "CIDR must be between 0 and 32" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:139 -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:146 -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:154 -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:162 -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:181 -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:188 -msgid "Invalid Shadowsocks URL: missing server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:195 -msgid "Invalid Shadowsocks URL: missing port" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:202 -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:208 -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:316 -msgid "Invalid VLESS URL: parsing failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:334 -msgid "Invalid JSON format" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:344 -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:350 -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:381 -msgid "Invalid Trojan URL: parsing failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:392 -msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:400 -msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:411 -msgid "Invalid SOCKS URL: missing username" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:418 -msgid "Invalid SOCKS URL: missing host and port" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:425 -msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:429 -msgid "Invalid SOCKS URL: missing port" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:435 -msgid "Invalid SOCKS URL: invalid port number" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:443 -msgid "Invalid SOCKS URL: invalid host format" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:447 -msgid "Invalid SOCKS URL: parsing failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:468 -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:692 -msgid "Fastest" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:864 -msgid "HTTP error" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:875 -msgid "Unknown error" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:985 -msgid "DNS checks" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:990 -msgid "Sing-box checks" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:995 -msgid "Nftables checks" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1000 -msgid "FakeIP checks" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1048 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1056 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1064 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1072 -msgid "Not running" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1084 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1092 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1100 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1108 -msgid "Queued" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1578 -msgid "Dashboard currently unavailable" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1654 -msgid "Test latency" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1683 -msgid "Currently unavailable" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2024 -msgid "Traffic" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2026 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2051 -msgid "Uplink" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2027 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2055 -msgid "Downlink" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2048 -msgid "Traffic Total" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2078 -msgid "System info" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2081 -msgid "Active Connections" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2085 -msgid "Memory Usage" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2108 -msgid "Services info" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2111 -msgid "Podkop" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112 -msgid "\\u2714 Enabled" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112 -msgid "\\u2718 Disabled" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2118 -msgid "Sing-box" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119 -msgid "\\u2714 Running" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119 -msgid "\\u2718 Stopped" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2365 -msgid "Checking dns, please wait" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2375 -msgid "Cannot receive DNS checks result" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2397 -msgid "DNS checks passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2405 -msgid "Bootsrap DNS" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2412 -msgid "Main DNS" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2417 -msgid "DNS on router" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2422 -msgid "DHCP has DNS server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2439 -msgid "Checking sing-box, please wait" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2449 -msgid "Cannot receive Sing-box checks result" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2471 -msgid "Sing-box checks passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2476 -msgid "Sing-box installed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2481 -msgid "Sing-box version >= 1.12.4" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2486 -msgid "Sing-box service exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2491 -msgid "Sing-box autostart disabled" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2496 -msgid "Sing-box process running" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2501 -msgid "Sing-box listening ports" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2518 -msgid "Checking nftables, please wait" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2530 -msgid "Cannot receive nftables checks result" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552 -msgid "Nftables checks passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552 -msgid "Nftables checks partially passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2557 -msgid "Table exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2562 -msgid "Rules mangle exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2567 -msgid "Rules mangle counters" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2572 -msgid "Rules mangle output exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2577 -msgid "Rules mangle output counters" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2582 -msgid "Rules proxy exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2587 -msgid "Rules proxy counters" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592 -msgid "No other marking rules found" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592 -msgid "Additional marking rules found" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2609 -msgid "Checking FakeIP, please wait" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2627 -msgid "FakeIP checks passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2633 -msgid "FakeIP checks partially passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2638 -msgid "FakeIP checks failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651 -msgid "Router DNS is routed through sing-box" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651 -msgid "Router DNS is not routed through sing-box" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656 -msgid "Browser is using FakeIP correctly" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656 -msgid "Browser is not using FakeIP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662 -msgid "Proxy traffic is routed via FakeIP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662 -msgid "Proxy traffic is not routed via FakeIP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3288 -msgid "Successfully copied!" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3290 -msgid "Failed to copy!" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3306 -msgid "Download" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3311 -msgid "Copy" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3318 -msgid "Close" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3350 -msgid "Restart podkop" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3360 -msgid "Stop podkop" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3370 -msgid "Start podkop" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3380 -msgid "Disable autostart" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3390 -msgid "Enable autostart" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3399 -msgid "Get global check" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3408 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3840 -msgid "View logs" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3417 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3867 -msgid "Show sing-box config" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3577 -msgid "Not implement yet" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3587 -msgid "Run Diagnostic" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3651 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3652 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3653 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3654 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3655 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3656 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3950 -msgid "unknown" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3813 -msgid "Global check" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3969 -msgid "Outdated" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3978 -msgid "Latest" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:4389 -msgid "Operation timed out" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26 -msgid "Podkop Settings" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27 -msgid "Configuration for Podkop service" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36 -msgid "Sections" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49 -msgid "Settings" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65 -msgid "Diagnostics" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80 -msgid "Dashboard" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12 -msgid "Connection Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13 -msgid "Select between VPN and Proxy connection methods for traffic routing" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22 -msgid "Configuration Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23 -msgid "Select how to configure the proxy" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25 -msgid "Connection URL" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26 -msgid "Outbound Config" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27 -msgid "URLTest" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34 -msgid "Proxy Configuration URL" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64 -msgid "Outbound Configuration" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65 -msgid "Enter complete outbound configuration in JSON format" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87 -msgid "URLTest Proxy Links" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110 -msgid "UDP over TCP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111 -msgid "Applicable for SOCKS and Shadowsocks proxy" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120 -msgid "Network Interface" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121 -msgid "Select network interface for VPN connection" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166 -msgid "Domain Resolver" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167 -msgid "Enable built-in DNS resolver for domains handled by this section" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12 -msgid "DNS Protocol Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177 -msgid "Select the DNS protocol type for the domain resolver" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15 -msgid "DNS over HTTPS (DoH)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16 -msgid "DNS over TLS (DoT)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17 -msgid "UDP (Unprotected DNS)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24 -msgid "DNS Server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25 -msgid "Select or enter DNS server address" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:193 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:217 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:28 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:51 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:254 -msgid "" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211 -msgid "Community Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212 -msgid "Select a predefined list for routing" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245 -msgid "Regional options cannot be used together" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247 msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." msgstr "" -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264 -msgid "Russia inside restrictions" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266 msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." msgstr "" -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299 -msgid "User Domain List Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300 -msgid "Select the list type for adding custom domains" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382 -msgid "Disabled" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383 -msgid "Dynamic List" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304 -msgid "Text List" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311 -msgid "User Domains" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312 -msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337 -msgid "User Domains List" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338 -msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356 -msgid "At least one valid domain must be specified. Comments-only content is not allowed." -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449 -msgid "Validation errors:" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379 -msgid "User Subnet List Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380 -msgid "Select the list type for adding custom subnets" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384 -msgid "Text List (comma/space/newline separated)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391 -msgid "User Subnets" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392 -msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417 -msgid "User Subnets List" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:418 -msgid "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437 -msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458 -msgid "Local Domain Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482 -msgid "Specify the path to the list file located on the router filesystem" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481 -msgid "Local Subnet Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504 -msgid "Remote Domain Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505 -msgid "Specify remote URLs to download and use domain lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527 -msgid "Remote Subnet Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528 -msgid "Specify remote URLs to download and use subnet lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550 -msgid "Fully Routed IPs" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551 -msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575 -msgid "Enable Mixed Proxy" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576 -msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586 -msgid "Mixed Proxy Port" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:587 -msgid "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" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13 -msgid "Select DNS protocol to use" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45 -msgid "Bootstrap DNS server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 -msgid "The DNS server used to look up the IP address of an upstream DNS server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68 -msgid "DNS Rewrite TTL" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69 -msgid "Time in seconds for DNS record caching (default: 60)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75 -msgid "TTL value cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80 -msgid "TTL must be a positive number" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89 -msgid "Source Network Interface" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90 -msgid "Select the network interface from which the traffic will originate" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126 -msgid "Enable Output Network Interface" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:127 msgid "You can select Output Network Interface, by default autodetect" msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135 -msgid "Output Network Interface" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136 -msgid "Select the network interface to which the traffic will originate" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182 -msgid "Interface Monitoring" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183 -msgid "Interface monitoring for Bad WAN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191 -msgid "Monitored Interfaces" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192 -msgid "Select the WAN interfaces to be monitored" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214 -msgid "Interface Monitoring Delay" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215 -msgid "Delay in milliseconds before reloading podkop after interface UP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222 -msgid "Delay value cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230 -msgid "Enable YACD" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239 -msgid "Disable QUIC" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240 -msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250 -msgid "List Update Frequency" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251 -msgid "Select how often the domain or subnet lists are updated automatically" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262 -msgid "Download Lists via Proxy/VPN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263 -msgid "Downloading all lists via main Proxy/VPN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271 -msgid "Download Lists via specific proxy section" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272 -msgid "Downloading all lists via specific Proxy/VPN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300 -msgid "Dont Touch My DHCP!" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301 -msgid "Podkop will not modify your DHCP configuration" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309 -msgid "Config File Path" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310 -msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322 -msgid "Cache File Path" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323 -msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336 -msgid "Cache file path cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340 -msgid "Path must be absolute (start with /)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344 -msgid "Path must end with cache.db" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349 -msgid "Path must contain at least one directory (like /tmp/cache.db)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358 -msgid "Exclude NTP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359 -msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369 -msgid "Routing Excluded IPs" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370 -msgid "Specify a local IP address to be excluded from routing" -msgstr "" diff --git a/fe-app-podkop/locales/podkop.ru.po b/fe-app-podkop/locales/podkop.ru.po index cb60b88..ea6eee7 100644 --- a/fe-app-podkop/locales/podkop.ru.po +++ b/fe-app-podkop/locales/podkop.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 21:44+0300\n" -"PO-Revision-Date: 2025-10-21 21:44+0300\n" +"POT-Creation-Date: 2025-10-21 22:33+0300\n" +"PO-Revision-Date: 2025-10-21 22:33+0300\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -17,995 +17,107 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -msgid "Successfully copied!" -msgstr "Успешно скопировано!" - -msgid "Failed to copy!" -msgstr "Не удалось скопировать!" - -msgid "Operation timed out" -msgstr "Время ожидания истекло" - -msgid "HTTP error" -msgstr "Ошибка HTTP" - -msgid "Unknown error" -msgstr "Неизвестная ошибка" - -msgid "DNS server address cannot be empty" -msgstr "Адрес DNS-сервера не может быть пустым" - -msgid "Valid" -msgstr "Валидно" - -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" - -msgid "Invalid domain address" -msgstr "Неверный домен" - -msgid "Invalid IP address" -msgstr "Неверный IP-адрес" - -msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" -msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" - -msgid "Invalid JSON format" -msgstr "Неверный формат JSON" - -msgid "Path cannot be empty" -msgstr "Путь не может быть пустым" - -msgid "Invalid path format. Path must start with \"/\" and contain valid characters" -msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы" - -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "Ссылка должна начинаться с vless://, ss://, trojan://, или socks4/5://" - -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" - -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" - -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" - -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" - -msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" -msgstr "Неверный URL Shadowsocks: отсутствует разделитель метода и пароля \":\"" - -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" - -msgid "Invalid Shadowsocks URL: missing server" -msgstr "Неверный URL Shadowsocks: отсутствует сервер" - -msgid "Invalid Shadowsocks URL: missing port" -msgstr "Неверный URL Shadowsocks: отсутствует порт" - -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "Неверный номер порта. Допустимо от 1 до 65535" - -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "Неверный URL Shadowsocks: ошибка разбора" - -msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "Невалидная SOCKS ссылка: должна начинаться с socks4://, socks4a://, или socks5://" - -msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "Невалидная SOCKS ссылка: не должна содержать пробелов" - -msgid "Invalid SOCKS URL: missing username" -msgstr "Невалидная SOCKS ссылка: отсуствует имя пользователя" - -msgid "Invalid SOCKS URL: missing host and port" -msgstr "Невалидная SOCKS ссылка: отсутствуют хост и порт" - -msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "Невалидная SOCKS ссылка: отсутствуют имя хоста или айпи" - -msgid "Invalid SOCKS URL: missing port" -msgstr "Невалидная SOCKS ссылка: отсутствует порт" - -msgid "Invalid SOCKS URL: invalid port number" -msgstr "Невалидная SOCKS ссылка: невалидный номер порта" - -msgid "Invalid SOCKS URL: invalid host format" -msgstr "Невалидная SOCKS ссылка: невалидный формат хоста" - -msgid "Invalid SOCKS URL: parsing failed" -msgstr "Невалидная SOCKS ссылка: не удалось распарсить" - -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" - -msgid "IP address 0.0.0.0 is not allowed" -msgstr "IP-адрес 0.0.0.0 не допускается" - -msgid "CIDR must be between 0 and 32" -msgstr "CIDR должен быть между 0 и 32" - -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "Неверный URL Trojan: должен начинаться с trojan://" - -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "Неверный URL Trojan: не должен содержать пробелов" - -msgid "Invalid Trojan URL: parsing failed" -msgstr "Неверный URL Trojan: ошибка разбора" - -msgid "URL must use one of the following protocols:" -msgstr "URL должен использовать один из следующих протоколов:" - -msgid "Invalid URL format" -msgstr "Неверный формат URL" - -msgid "Invalid VLESS URL: parsing failed" -msgstr "Неверный URL VLESS: ошибка разбора" - -msgid "Download" -msgstr "Скачать" - -msgid "Copy" -msgstr "Скопировать" - -msgid "Close" -msgstr "Закрыть" - -msgid "Traffic" -msgstr "Трафик" - -msgid "Uplink" -msgstr "Исходящий" - -msgid "Downlink" -msgstr "Входящий" - -msgid "Traffic Total" -msgstr "Всего трафика" - -msgid "System info" -msgstr "Системная информация" - -msgid "Active Connections" -msgstr "Активные соединения" - -msgid "Memory Usage" -msgstr "Использование памяти" - -msgid "Services info" -msgstr "Информация о сервисах" - -msgid "Podkop" -msgstr "Podkop" - msgid "✔ Enabled" msgstr "✔ Включено" -msgid "✘ Disabled" -msgstr "✘ Отключено" - -msgid "Sing-box" -msgstr "Sing-box" - msgid "✔ Running" msgstr "✔ Работает" +msgid "✘ Disabled" +msgstr "✘ Отключено" + msgid "✘ Stopped" msgstr "✘ Остановлен" -msgid "Not running" -msgstr "Не запущен" - -msgid "Queued" -msgstr "В очереди" - -msgid "unknown" -msgstr "неизвестно" - -msgid "Global check" -msgstr "Глобальная проверка" - -msgid "View logs" -msgstr "Посмотреть логи" - -msgid "Show sing-box config" -msgstr "Показать sing-box конфиг" - -msgid "Outdated" -msgstr "Устаревшая" - -msgid "Latest" -msgstr "Последняя" - -msgid "Fastest" -msgstr "Самый быстрый" - -msgid "Dashboard currently unavailable" -msgstr "Дашборд сейчас недоступен" - -msgid "Test latency" -msgstr "Проверить задержку" - -msgid "Currently unavailable" -msgstr "Временно недоступно" - -msgid "Restart podkop" -msgstr "Перезапустить podkop" - -msgid "Stop podkop" -msgstr "Остановить podkop" - -msgid "Start podkop" -msgstr "Запустить podkop" - -msgid "Disable autostart" -msgstr "Отключить автостарт" - -msgid "Enable autostart" -msgstr "Включить автостарт" - -msgid "Get global check" -msgstr "Получить глобальную проверку" - -msgid "Not implement yet" -msgstr "Не реализовано" - -msgid "Run Diagnostic" -msgstr "Запустить диагностику" - -msgid "DNS checks" -msgstr "Проверка DNS" - -msgid "Sing-box checks" -msgstr "Проверка Sing-box" - -msgid "Nftables checks" -msgstr "Проверка Nftables" - -msgid "FakeIP checks" -msgstr "Проверка FakeIP" - -msgid "Checking dns, please wait" -msgstr "Проверяем dns, пожалуйста подождите" - -msgid "Cannot receive DNS checks result" -msgstr "Не удалось получить результаты проверки DNS" - -msgid "DNS checks passed" -msgstr "Проверка DNS прошла" - -msgid "Bootsrap DNS" -msgstr "Загрузочный DNS" - -msgid "Main DNS" -msgstr "Основной DNS" - -msgid "DNS on router" -msgstr "DNS на роутере" - -msgid "DHCP has DNS server" -msgstr "DHCP содежрит DNS сервер" - -msgid "Checking FakeIP, please wait" -msgstr "Проверяем FakeIP, пожалуйста подождите" - -msgid "FakeIP checks passed" -msgstr "Проверка FakeIP прошла" - -msgid "FakeIP checks partially passed" -msgstr "Проверка FakeIP частично прошла" - -msgid "FakeIP checks failed" -msgstr "Проверка FakeIP не удалась" - -msgid "Router DNS is routed through sing-box" -msgstr "" - -msgid "Router DNS is not routed through sing-box" -msgstr "" - -msgid "Browser is using FakeIP correctly" -msgstr "" - -msgid "Browser is not using FakeIP" -msgstr "" - -msgid "Proxy traffic is routed via FakeIP" -msgstr "" - -msgid "Proxy traffic is not routed via FakeIP" -msgstr "" - -msgid "Checking nftables, please wait" -msgstr "" - -msgid "Cannot receive nftables checks result" -msgstr "" - -msgid "Nftables checks passed" -msgstr "" - -msgid "Nftables checks partially passed" -msgstr "" - -msgid "Table exist" -msgstr "" - -msgid "Rules mangle exist" -msgstr "" - -msgid "Rules mangle counters" -msgstr "" - -msgid "Rules mangle output exist" -msgstr "" - -msgid "Rules mangle output counters" -msgstr "" - -msgid "Rules proxy exist" -msgstr "" - -msgid "Rules proxy counters" -msgstr "" - -msgid "No other marking rules found" -msgstr "" - -msgid "Additional marking rules found" -msgstr "" - -msgid "Checking sing-box, please wait" -msgstr "" - -msgid "Cannot receive Sing-box checks result" -msgstr "" - -msgid "Sing-box checks passed" -msgstr "" - -msgid "Sing-box installed" -msgstr "" - -msgid "Sing-box version >= 1.12.4" -msgstr "" - -msgid "Sing-box service exist" -msgstr "" - -msgid "Sing-box autostart disabled" -msgstr "" - -msgid "Sing-box process running" -msgstr "" - -msgid "Sing-box listening ports" -msgstr "" - -msgid "Valid" -msgstr "Валидно" - -msgid "Invalid IP address" -msgstr "Неверный IP-адрес" - -msgid "Invalid domain address" -msgstr "Неверный домен" - -msgid "DNS server address cannot be empty" -msgstr "Адрес DNS-сервера не может быть пустым" - -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" - -msgid "URL must use one of the following protocols:" -msgstr "URL должен использовать один из следующих протоколов:" - -msgid "Invalid URL format" -msgstr "Неверный формат URL" - -msgid "Path cannot be empty" -msgstr "Путь не может быть пустым" - -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" - -msgid "IP address 0.0.0.0 is not allowed" -msgstr "IP-адрес 0.0.0.0 не допускается" - -msgid "CIDR must be between 0 and 32" -msgstr "CIDR должен быть между 0 и 32" - -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" - -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" - -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" - -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" - -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" - -msgid "Invalid Shadowsocks URL: missing server" -msgstr "Неверный URL Shadowsocks: отсутствует сервер" - -msgid "Invalid Shadowsocks URL: missing port" -msgstr "Неверный URL Shadowsocks: отсутствует порт" - -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "Неверный номер порта. Допустимо от 1 до 65535" - -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "Неверный URL Shadowsocks: ошибка разбора" - -msgid "Invalid VLESS URL: parsing failed" -msgstr "Неверный URL VLESS: ошибка разбора" - -msgid "Invalid JSON format" -msgstr "Неверный формат JSON" - -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "Неверный URL Trojan: должен начинаться с trojan://" - -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "Неверный URL Trojan: не должен содержать пробелов" - -msgid "Invalid Trojan URL: parsing failed" -msgstr "Неверный URL Trojan: ошибка разбора" - -msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" - -msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" - -msgid "Invalid SOCKS URL: missing username" -msgstr "" - -msgid "Invalid SOCKS URL: missing host and port" -msgstr "" - -msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" - -msgid "Invalid SOCKS URL: missing port" -msgstr "" - -msgid "Invalid SOCKS URL: invalid port number" -msgstr "" - -msgid "Invalid SOCKS URL: invalid host format" -msgstr "" - -msgid "Invalid SOCKS URL: parsing failed" -msgstr "" - -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" - -msgid "Fastest" -msgstr "Самый быстрый" - -msgid "HTTP error" -msgstr "Ошибка HTTP" - -msgid "Unknown error" -msgstr "Неизвестная ошибка" - -msgid "DNS checks" -msgstr "" - -msgid "Sing-box checks" -msgstr "" - -msgid "Nftables checks" -msgstr "" - -msgid "FakeIP checks" -msgstr "" - -msgid "Not running" -msgstr "" - -msgid "Queued" -msgstr "" - -msgid "Dashboard currently unavailable" -msgstr "Дашборд сейчас недоступен" - -msgid "Test latency" -msgstr "" - -msgid "Currently unavailable" -msgstr "Временно недоступно" - -msgid "Traffic" -msgstr "Трафик" - -msgid "Uplink" -msgstr "Исходящий" - -msgid "Downlink" -msgstr "Входящий" - -msgid "Traffic Total" -msgstr "Всего трафика" - -msgid "System info" -msgstr "Системная информация" - msgid "Active Connections" msgstr "Активные соединения" -msgid "Memory Usage" -msgstr "Использование памяти" - -msgid "Services info" -msgstr "Информация о сервисах" - -msgid "Podkop" -msgstr "Podkop" - -msgid "\\u2714 Enabled" -msgstr "" - -msgid "\\u2718 Disabled" -msgstr "" - -msgid "Sing-box" -msgstr "Sing-box" - -msgid "\\u2714 Running" -msgstr "" - -msgid "\\u2718 Stopped" -msgstr "" - -msgid "Checking dns, please wait" -msgstr "" - -msgid "Cannot receive DNS checks result" -msgstr "" - -msgid "DNS checks passed" -msgstr "" - -msgid "Bootsrap DNS" -msgstr "" - -msgid "Main DNS" -msgstr "" - -msgid "DNS on router" -msgstr "" - -msgid "DHCP has DNS server" -msgstr "" - -msgid "Checking sing-box, please wait" -msgstr "" - -msgid "Cannot receive Sing-box checks result" -msgstr "" - -msgid "Sing-box checks passed" -msgstr "" - -msgid "Sing-box installed" -msgstr "" - -msgid "Sing-box version >= 1.12.4" -msgstr "" - -msgid "Sing-box service exist" -msgstr "" - -msgid "Sing-box autostart disabled" -msgstr "" - -msgid "Sing-box process running" -msgstr "" - -msgid "Sing-box listening ports" -msgstr "" - -msgid "Checking nftables, please wait" -msgstr "" - -msgid "Cannot receive nftables checks result" -msgstr "" - -msgid "Nftables checks passed" -msgstr "" - -msgid "Nftables checks partially passed" -msgstr "" - -msgid "Table exist" -msgstr "" - -msgid "Rules mangle exist" -msgstr "" - -msgid "Rules mangle counters" -msgstr "" - -msgid "Rules mangle output exist" -msgstr "" - -msgid "Rules mangle output counters" -msgstr "" - -msgid "Rules proxy exist" -msgstr "" - -msgid "Rules proxy counters" -msgstr "" - -msgid "No other marking rules found" -msgstr "" - msgid "Additional marking rules found" -msgstr "" - -msgid "Checking FakeIP, please wait" -msgstr "" - -msgid "FakeIP checks passed" -msgstr "" - -msgid "FakeIP checks partially passed" -msgstr "" - -msgid "FakeIP checks failed" -msgstr "" - -msgid "Router DNS is routed through sing-box" -msgstr "" - -msgid "Router DNS is not routed through sing-box" -msgstr "" - -msgid "Browser is using FakeIP correctly" -msgstr "" - -msgid "Browser is not using FakeIP" -msgstr "" - -msgid "Proxy traffic is routed via FakeIP" -msgstr "" - -msgid "Proxy traffic is not routed via FakeIP" -msgstr "" - -msgid "Successfully copied!" -msgstr "" - -msgid "Failed to copy!" -msgstr "" - -msgid "Download" -msgstr "" - -msgid "Copy" -msgstr "" - -msgid "Close" -msgstr "Закрыть" - -msgid "Restart podkop" -msgstr "" - -msgid "Stop podkop" -msgstr "" - -msgid "Start podkop" -msgstr "" - -msgid "Disable autostart" -msgstr "" - -msgid "Enable autostart" -msgstr "" - -msgid "Get global check" -msgstr "" - -msgid "View logs" -msgstr "" - -msgid "Show sing-box config" -msgstr "" - -msgid "Not implement yet" -msgstr "" - -msgid "Run Diagnostic" -msgstr "" - -msgid "unknown" -msgstr "" - -msgid "Global check" -msgstr "Глобальная проверка" - -msgid "Outdated" -msgstr "" - -msgid "Latest" -msgstr "" - -msgid "Operation timed out" -msgstr "Время ожидания истекло" - -msgid "Podkop Settings" -msgstr "" - -msgid "Configuration for Podkop service" -msgstr "" - -msgid "Sections" -msgstr "" - -msgid "Settings" -msgstr "" - -msgid "Diagnostics" -msgstr "Диагностика" - -msgid "Dashboard" -msgstr "Дашборд" - -msgid "Connection Type" -msgstr "Тип подключения" - -msgid "Select between VPN and Proxy connection methods for traffic routing" -msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" - -msgid "Configuration Type" -msgstr "Тип конфигурации" - -msgid "Select how to configure the proxy" -msgstr "Выберите способ настройки прокси" - -msgid "Connection URL" -msgstr "URL подключения" - -msgid "Outbound Config" -msgstr "Конфигурация Outbound" - -msgid "URLTest" -msgstr "URLTest" - -msgid "Proxy Configuration URL" -msgstr "URL конфигурации прокси" - -msgid "Outbound Configuration" -msgstr "Конфигурация исходящего соединения" - -msgid "Enter complete outbound configuration in JSON format" -msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" - -msgid "URLTest Proxy Links" -msgstr "Ссылки прокси для URLTest" - -msgid "UDP over TCP" -msgstr "" +msgstr "Найдены дополнительные правила маркировки" msgid "Applicable for SOCKS and Shadowsocks proxy" -msgstr "" - -msgid "Network Interface" -msgstr "Сетевой интерфейс" - -msgid "Select network interface for VPN connection" -msgstr "Выберите сетевой интерфейс для VPN подключения" - -msgid "Domain Resolver" -msgstr "Резолвер доменов" - -msgid "Enable built-in DNS resolver for domains handled by this section" -msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе" - -msgid "DNS Protocol Type" -msgstr "Тип протокола DNS" - -msgid "Select the DNS protocol type for the domain resolver" -msgstr "Выберите тип протокола DNS для резолвера доменов" - -msgid "DNS over HTTPS (DoH)" -msgstr "DNS через HTTPS (DoH)" - -msgid "DNS over TLS (DoT)" -msgstr "DNS через TLS (DoT)" - -msgid "UDP (Unprotected DNS)" -msgstr "UDP (Незащищённый DNS)" - -msgid "DNS Server" -msgstr "DNS-сервер" - -msgid "Select or enter DNS server address" -msgstr "Выберите или введите адрес DNS-сервера" - -msgid "" -msgstr "" - -msgid "Community Lists" -msgstr "Списки сообщества" - -msgid "Select a predefined list for routing" -msgstr "" - -msgid "Regional options cannot be used together" -msgstr "Нельзя использовать несколько региональных опций одновременно" - -msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." -msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." - -msgid "Russia inside restrictions" -msgstr "Ограничения Russia inside" - -msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." -msgstr "" - -msgid "User Domain List Type" -msgstr "Тип пользовательского списка доменов" - -msgid "Select the list type for adding custom domains" -msgstr "" - -msgid "Disabled" -msgstr "Отключено" - -msgid "Dynamic List" -msgstr "Динамический список" - -msgid "Text List" -msgstr "Текстовый список" - -msgid "User Domains" -msgstr "Пользовательские домены" - -msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" -msgstr "" - -msgid "User Domains List" -msgstr "Список пользовательских доменов" - -msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" -msgstr "" +msgstr "Применимо для SOCKS и Shadowsocks прокси" msgid "At least one valid domain must be specified. Comments-only content is not allowed." msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается." -msgid "Validation errors:" -msgstr "Ошибки валидации:" - -msgid "User Subnet List Type" -msgstr "Тип пользовательского списка подсетей" - -msgid "Select the list type for adding custom subnets" -msgstr "" - -msgid "Text List (comma/space/newline separated)" -msgstr "Текстовый список (через запятую, пробел или новую строку)" - -msgid "User Subnets" -msgstr "Пользовательские подсети" - -msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" -msgstr "" - -msgid "User Subnets List" -msgstr "Список пользовательских подсетей" - -msgid "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //" -msgstr "" - msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы." -msgid "Local Domain Lists" -msgstr "Локальные списки доменов" - -msgid "Specify the path to the list file located on the router filesystem" -msgstr "" - -msgid "Local Subnet Lists" -msgstr "Локальные списки подсетей" - -msgid "Remote Domain Lists" -msgstr "Удалённые списки доменов" - -msgid "Specify remote URLs to download and use domain lists" -msgstr "" - -msgid "Remote Subnet Lists" -msgstr "Удалённые списки подсетей" - -msgid "Specify remote URLs to download and use subnet lists" -msgstr "" - -msgid "Fully Routed IPs" -msgstr "" - -msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" -msgstr "" - -msgid "Enable Mixed Proxy" -msgstr "" - -msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" -msgstr "" - -msgid "Mixed Proxy Port" -msgstr "" - -msgid "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" -msgstr "" - -msgid "Select DNS protocol to use" -msgstr "Выберите протокол DNS" +msgid "Bootsrap DNS" +msgstr "Bootstrap DNS" msgid "Bootstrap DNS server" msgstr "Bootstrap DNS-сервер" -msgid "The DNS server used to look up the IP address of an upstream DNS server" -msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" +msgid "Browser is not using FakeIP" +msgstr "Браузер не использует FakeIP" -msgid "DNS Rewrite TTL" -msgstr "Перезапись TTL для DNS" +msgid "Browser is using FakeIP correctly" +msgstr "Браузер использует FakeIP" -msgid "Time in seconds for DNS record caching (default: 60)" -msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)" +msgid "Cache File Path" +msgstr "Путь к файлу кэша" -msgid "TTL value cannot be empty" -msgstr "Значение TTL не может быть пустым" +msgid "Cache file path cannot be empty" +msgstr "Путь к файлу кэша не может быть пустым" -msgid "TTL must be a positive number" -msgstr "TTL должно быть положительным числом" +msgid "Cannot receive DNS checks result" +msgstr "Не удалось получить результаты проверки DNS" -msgid "Source Network Interface" -msgstr "Сетевой интерфейс источника" +msgid "Cannot receive nftables checks result" +msgstr "Не удалось получить результаты проверки nftables" -msgid "Select the network interface from which the traffic will originate" -msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" +msgid "Cannot receive Sing-box checks result" +msgstr "Не удалось получить результаты проверки Sing-box" -msgid "Enable Output Network Interface" -msgstr "" +msgid "Checking dns, please wait" +msgstr "Проверка dns, пожалуйста подождите" -msgid "You can select Output Network Interface, by default autodetect" -msgstr "" +msgid "Checking FakeIP, please wait" +msgstr "Проверка FakeIP, пожалуйста подождите" -msgid "Output Network Interface" -msgstr "" +msgid "Checking nftables, please wait" +msgstr "Проверка nftables, пожалуйста подождите" -msgid "Select the network interface to which the traffic will originate" -msgstr "" +msgid "Checking sing-box, please wait" +msgstr "Проверка sing-box, пожалуйста подождите" -msgid "Interface Monitoring" -msgstr "" +msgid "CIDR must be between 0 and 32" +msgstr "CIDR должен быть между 0 и 32" -msgid "Interface monitoring for Bad WAN" -msgstr "" +msgid "Close" +msgstr "Закрыть" -msgid "Monitored Interfaces" -msgstr "" +msgid "Community Lists" +msgstr "Списки сообщества" -msgid "Select the WAN interfaces to be monitored" -msgstr "Выберите WAN интерфейсы для мониторинга" +msgid "Config File Path" +msgstr "Путь к файлу конфигурации" -msgid "Interface Monitoring Delay" -msgstr "Задержка при мониторинге интерфейсов" +msgid "Configuration for Podkop service" +msgstr "Настройки сервиса Podkop" + +msgid "Configuration Type" +msgstr "Тип конфигурации" + +msgid "Connection Type" +msgstr "Тип подключения" + +msgid "Connection URL" +msgstr "URL подключения" + +msgid "Copy" +msgstr "Копировать" + +msgid "Currently unavailable" +msgstr "Временно недоступно" + +msgid "Dashboard" +msgstr "Дашборд" + +msgid "Dashboard currently unavailable" +msgstr "Дашборд сейчас недоступен" msgid "Delay in milliseconds before reloading podkop after interface UP" msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса" @@ -1013,71 +125,590 @@ msgstr "Задержка в миллисекундах перед перезаг msgid "Delay value cannot be empty" msgstr "Значение задержки не может быть пустым" -msgid "Enable YACD" -msgstr "" +msgid "DHCP has DNS server" +msgstr "DHCP содержит DNS сервер" + +msgid "Diagnostics" +msgstr "Диагностика" + +msgid "Disable autostart" +msgstr "Отключить автостарт" msgid "Disable QUIC" -msgstr "" +msgstr "Отключить QUIC" msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" -msgstr "" +msgstr "Отключить QUIC протокол для улучшения совместимости или исправления видео стриминга" -msgid "List Update Frequency" -msgstr "Частота обновления списков" +msgid "Disabled" +msgstr "Отключено" -msgid "Select how often the domain or subnet lists are updated automatically" -msgstr "" +msgid "DNS checks" +msgstr "DNS проверки" + +msgid "DNS checks passed" +msgstr "DNS проверки успшено завершены" + +msgid "DNS on router" +msgstr "DNS на роутере" + +msgid "DNS over HTTPS (DoH)" +msgstr "DNS через HTTPS (DoH)" + +msgid "DNS over TLS (DoT)" +msgstr "DNS через TLS (DoT)" + +msgid "DNS Protocol Type" +msgstr "Тип протокола DNS" + +msgid "DNS Rewrite TTL" +msgstr "Перезапись TTL для DNS" + +msgid "DNS Server" +msgstr "DNS-сервер" + +msgid "DNS server address cannot be empty" +msgstr "Адрес DNS-сервера не может быть пустым" + +msgid "Domain Resolver" +msgstr "Резолвер доменов" + +msgid "Dont Touch My DHCP!" +msgstr "Dont Touch My DHCP!" + +msgid "Downlink" +msgstr "Входящий" + +msgid "Download" +msgstr "Скачать" msgid "Download Lists via Proxy/VPN" -msgstr "" +msgstr "Скачивать списки через Proxy/VPN" + +msgid "Download Lists via specific proxy section" +msgstr "Скачивать списки через выбранную секцию" msgid "Downloading all lists via main Proxy/VPN" msgstr "Загрузка всех списков через основной прокси/VPN" -msgid "Download Lists via specific proxy section" -msgstr "" - msgid "Downloading all lists via specific Proxy/VPN" msgstr "Загрузка всех списков через указанный прокси/VPN" -msgid "Dont Touch My DHCP!" -msgstr "" +msgid "Dynamic List" +msgstr "Динамический список" -msgid "Podkop will not modify your DHCP configuration" -msgstr "" +msgid "Enable autostart" +msgstr "Включить автостарт" -msgid "Config File Path" -msgstr "Путь к файлу конфигурации" +msgid "Enable built-in DNS resolver for domains handled by this section" +msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе" -msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" -msgstr "Выберите путь к файлу конфигурации sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" +msgid "Enable Mixed Proxy" +msgstr "Включить смешанный прокси" -msgid "Cache File Path" -msgstr "Путь к файлу кэша" +msgid "Enable Output Network Interface" +msgstr "Включить выходной сетевой интерфейс" -msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" -msgstr "Выберите или введите путь к файлу кеша sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" +msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" +msgstr "Включить смешанный прокси-сервер, разрешив этому разделу маршрутизировать трафик как через HTTP, так и через SOCKS-прокси." -msgid "Cache file path cannot be empty" -msgstr "Путь к файлу кэша не может быть пустым" +msgid "Enable YACD" +msgstr "Включить YACD" -msgid "Path must be absolute (start with /)" -msgstr "Путь должен быть абсолютным (начинаться с /)" +msgid "Enter complete outbound configuration in JSON format" +msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" -msgid "Path must end with cache.db" -msgstr "Путь должен заканчиваться на cache.db" +msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" +msgstr "Введите доменные имена, разделяя их запятыми, пробелами или переносами строк. Вы можете добавлять комментарии, используя //" -msgid "Path must contain at least one directory (like /tmp/cache.db)" -msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)" +msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" +msgstr "Введите доменные имена без протоколов, например example.com или sub.example.com" + +msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" +msgstr "Введите подсети в нотации CIDR (например, 103.21.244.0/22) или отдельные IP-адреса" msgid "Exclude NTP" msgstr "Исключить NTP" msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" +msgstr "Исключите трафик протокола NTP из туннеля, чтобы предотвратить его маршрутизацию через прокси-сервер или VPN." + +msgid "Failed to copy!" +msgstr "Не удалось скопировать!" + +msgid "FakeIP checks" +msgstr "Проверка FakeIP" + +msgid "FakeIP checks failed" +msgstr "Проверки FakeIP не пройдены" + +msgid "FakeIP checks partially passed" +msgstr "Проверка FakeIP частично пройдена" + +msgid "FakeIP checks passed" +msgstr "Проверки FakeIP пройдены" + +msgid "Fastest" +msgstr "Самый быстрый" + +msgid "Fully Routed IPs" +msgstr "Полностью маршрутизированные IP-адреса" + +msgid "Get global check" +msgstr "Получить глобальную проверку" + +msgid "Global check" +msgstr "Глобальная проверка" + +msgid "HTTP error" +msgstr "Ошибка HTTP" + +msgid "Interface Monitoring" +msgstr "Мониторинг интерфейса" + +msgid "Interface Monitoring Delay" +msgstr "Задержка при мониторинге интерфейсов" + +msgid "Interface monitoring for Bad WAN" +msgstr "Мониторинг интерфейса для Bad WAN" + +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" + +msgid "Invalid domain address" +msgstr "Неверный домен" + +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" + +msgid "Invalid IP address" +msgstr "Неверный IP-адрес" + +msgid "Invalid JSON format" +msgstr "Неверный формат JSON" + +msgid "Invalid path format. Path must start with \"/\" and contain valid characters" +msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы" + +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "Неверный номер порта. Допустимо от 1 до 65535" + +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" + +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" + +msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" +msgstr "Неверный URL Shadowsocks: отсутствует разделитель метода и пароля \":\"" + +msgid "Invalid Shadowsocks URL: missing port" +msgstr "Неверный URL Shadowsocks: отсутствует порт" + +msgid "Invalid Shadowsocks URL: missing server" +msgstr "Неверный URL Shadowsocks: отсутствует сервер" + +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" + +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" + +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" + +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "Неверный URL Shadowsocks: ошибка разбора" + +msgid "Invalid SOCKS URL: invalid host format" +msgstr "Неверный URL SOCKS: неверный формат хоста" + +msgid "Invalid SOCKS URL: invalid port number" +msgstr "Неверный URL SOCKS: неверный номер порта" + +msgid "Invalid SOCKS URL: missing host and port" +msgstr "Неверный URL SOCKS: отсутствует хост и порт" + +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "Неверный URL SOCKS: отсутствует имя хоста или IP-адрес" + +msgid "Invalid SOCKS URL: missing port" +msgstr "Неверный URL SOCKS: отсутствует порт" + +msgid "Invalid SOCKS URL: missing username" +msgstr "Неверный URL SOCKS: отсутствует имя пользователя" + +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "Неверный URL SOCKS: не должен содержать пробелов" + +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "Неверный URL-адрес SOCKS: должен начинаться с socks4://, socks4a:// или socks5://" + +msgid "Invalid SOCKS URL: parsing failed" +msgstr "Неверный URL SOCKS: парсинг не удался" + +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "Неверный URL Trojan: не должен содержать пробелов" + +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "Неверный URL Trojan: должен начинаться с trojan://" + +msgid "Invalid Trojan URL: parsing failed" +msgstr "Неверный URL Trojan: ошибка разбора" + +msgid "Invalid URL format" +msgstr "Неверный формат URL" + +msgid "Invalid VLESS URL: parsing failed" +msgstr "Неверный URL VLESS: ошибка разбора" + +msgid "IP address 0.0.0.0 is not allowed" +msgstr "IP-адрес 0.0.0.0 не допускается" + +msgid "Latest" +msgstr "Последняя" + +msgid "List Update Frequency" +msgstr "Частота обновления списков" + +msgid "Local Domain Lists" +msgstr "Локальные списки доменов" + +msgid "Local Subnet Lists" +msgstr "Локальные списки подсетей" + +msgid "Main DNS" +msgstr "Основной DNS" + +msgid "Memory Usage" +msgstr "Использование памяти" + +msgid "Mixed Proxy Port" +msgstr "Порт смешанного прокси" + +msgid "Monitored Interfaces" +msgstr "Наблюдаемые интерфейсы" + +msgid "Network Interface" +msgstr "Сетевой интерфейс" + +msgid "Nftables checks" +msgstr "" + +msgid "Nftables checks partially passed" +msgstr "" + +msgid "Nftables checks passed" +msgstr "" + +msgid "No other marking rules found" +msgstr "" + +msgid "Not implement yet" +msgstr "" + +msgid "Not running" +msgstr "" + +msgid "Operation timed out" +msgstr "Время ожидания истекло" + +msgid "Outbound Config" +msgstr "Конфигурация Outbound" + +msgid "Outbound Configuration" +msgstr "Конфигурация исходящего соединения" + +msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" +msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" + +msgid "Outdated" +msgstr "" + +msgid "Output Network Interface" +msgstr "" + +msgid "Path cannot be empty" +msgstr "Путь не может быть пустым" + +msgid "Path must be absolute (start with /)" +msgstr "Путь должен быть абсолютным (начинаться с /)" + +msgid "Path must contain at least one directory (like /tmp/cache.db)" +msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)" + +msgid "Path must end with cache.db" +msgstr "Путь должен заканчиваться на cache.db" + +msgid "Podkop" +msgstr "Podkop" + +msgid "Podkop Settings" +msgstr "" + +msgid "Podkop will not modify your DHCP configuration" +msgstr "" + +msgid "Proxy Configuration URL" +msgstr "URL конфигурации прокси" + +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +msgid "Queued" +msgstr "" + +msgid "Regional options cannot be used together" +msgstr "Нельзя использовать несколько региональных опций одновременно" + +msgid "Remote Domain Lists" +msgstr "Удалённые списки доменов" + +msgid "Remote Subnet Lists" +msgstr "Удалённые списки подсетей" + +msgid "Restart podkop" +msgstr "" + +msgid "Router DNS is not routed through sing-box" +msgstr "" + +msgid "Router DNS is routed through sing-box" msgstr "" msgid "Routing Excluded IPs" msgstr "" +msgid "Rules mangle counters" +msgstr "" + +msgid "Rules mangle exist" +msgstr "" + +msgid "Rules mangle output counters" +msgstr "" + +msgid "Rules mangle output exist" +msgstr "" + +msgid "Rules proxy counters" +msgstr "" + +msgid "Rules proxy exist" +msgstr "" + +msgid "Run Diagnostic" +msgstr "" + +msgid "Russia inside restrictions" +msgstr "Ограничения Russia inside" + +msgid "Sections" +msgstr "" + +msgid "Select a predefined list for routing" +msgstr "" + +msgid "Select between VPN and Proxy connection methods for traffic routing" +msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" + +msgid "Select DNS protocol to use" +msgstr "Выберите протокол DNS" + +msgid "Select how often the domain or subnet lists are updated automatically" +msgstr "" + +msgid "Select how to configure the proxy" +msgstr "Выберите способ настройки прокси" + +msgid "Select network interface for VPN connection" +msgstr "Выберите сетевой интерфейс для VPN подключения" + +msgid "Select or enter DNS server address" +msgstr "Выберите или введите адрес DNS-сервера" + +msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" +msgstr "Выберите или введите путь к файлу кеша sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" + +msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" +msgstr "Выберите путь к файлу конфигурации sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" + +msgid "Select the DNS protocol type for the domain resolver" +msgstr "Выберите тип протокола DNS для резолвера доменов" + +msgid "Select the list type for adding custom domains" +msgstr "" + +msgid "Select the list type for adding custom subnets" +msgstr "" + +msgid "Select the network interface from which the traffic will originate" +msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" + +msgid "Select the network interface to which the traffic will originate" +msgstr "" + +msgid "Select the WAN interfaces to be monitored" +msgstr "Выберите WAN интерфейсы для мониторинга" + +msgid "Services info" +msgstr "Информация о сервисах" + +msgid "Settings" +msgstr "" + +msgid "Show sing-box config" +msgstr "" + +msgid "Sing-box" +msgstr "Sing-box" + +msgid "Sing-box autostart disabled" +msgstr "" + +msgid "Sing-box checks" +msgstr "" + +msgid "Sing-box checks passed" +msgstr "" + +msgid "Sing-box installed" +msgstr "" + +msgid "Sing-box listening ports" +msgstr "" + +msgid "Sing-box process running" +msgstr "" + +msgid "Sing-box service exist" +msgstr "" + +msgid "Sing-box version >= 1.12.4" +msgstr "" + +msgid "Source Network Interface" +msgstr "Сетевой интерфейс источника" + msgid "Specify a local IP address to be excluded from routing" msgstr "" + +msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" +msgstr "" + +msgid "Specify remote URLs to download and use domain lists" +msgstr "" + +msgid "Specify remote URLs to download and use subnet lists" +msgstr "" + +msgid "Specify the path to the list file located on the router filesystem" +msgstr "" + +msgid "Start podkop" +msgstr "" + +msgid "Stop podkop" +msgstr "" + +msgid "Successfully copied!" +msgstr "" + +msgid "System info" +msgstr "Системная информация" + +msgid "Table exist" +msgstr "" + +msgid "Test latency" +msgstr "" + +msgid "Text List" +msgstr "Текстовый список" + +msgid "Text List (comma/space/newline separated)" +msgstr "Текстовый список (через запятую, пробел или новую строку)" + +msgid "The DNS server used to look up the IP address of an upstream DNS server" +msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" + +msgid "Time in seconds for DNS record caching (default: 60)" +msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)" + +msgid "Traffic" +msgstr "Трафик" + +msgid "Traffic Total" +msgstr "Всего трафика" + +msgid "TTL must be a positive number" +msgstr "TTL должно быть положительным числом" + +msgid "TTL value cannot be empty" +msgstr "Значение TTL не может быть пустым" + +msgid "UDP (Unprotected DNS)" +msgstr "UDP (Незащищённый DNS)" + +msgid "UDP over TCP" +msgstr "" + +msgid "unknown" +msgstr "" + +msgid "Unknown error" +msgstr "Неизвестная ошибка" + +msgid "Uplink" +msgstr "Исходящий" + +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +msgid "URL must use one of the following protocols:" +msgstr "URL должен использовать один из следующих протоколов:" + +msgid "URLTest" +msgstr "URLTest" + +msgid "URLTest Proxy Links" +msgstr "Ссылки прокси для URLTest" + +msgid "User Domain List Type" +msgstr "Тип пользовательского списка доменов" + +msgid "User Domains" +msgstr "Пользовательские домены" + +msgid "User Domains List" +msgstr "Список пользовательских доменов" + +msgid "User Subnet List Type" +msgstr "Тип пользовательского списка подсетей" + +msgid "User Subnets" +msgstr "Пользовательские подсети" + +msgid "User Subnets List" +msgstr "Список пользовательских подсетей" + +msgid "Valid" +msgstr "Валидно" + +msgid "Validation errors:" +msgstr "Ошибки валидации:" + +msgid "View logs" +msgstr "" + +msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." +msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." + +msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." +msgstr "" + +msgid "You can select Output Network Interface, by default autodetect" +msgstr "" diff --git a/fe-app-podkop/package.json b/fe-app-podkop/package.json index 1b8a4bd..ac1c001 100644 --- a/fe-app-podkop/package.json +++ b/fe-app-podkop/package.json @@ -20,6 +20,8 @@ "locales:actualize": "yarn locales:exctract-calls && yarn locales:generate-pot && yarn locales:generate-po:ru && yarn locales:distribute" }, "devDependencies": { + "@babel/parser": "7.28.4", + "@babel/traverse": "7.28.4", "@typescript-eslint/eslint-plugin": "8.45.0", "@typescript-eslint/parser": "8.45.0", "chokidar": "4.0.3", diff --git a/fe-app-podkop/yarn.lock b/fe-app-podkop/yarn.lock index 62d5fe2..a87787d 100644 --- a/fe-app-podkop/yarn.lock +++ b/fe-app-podkop/yarn.lock @@ -2,6 +2,78 @@ # yarn lockfile v1 +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/parser@7.28.4", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + +"@babel/types@^7.27.1", "@babel/types@^7.28.2", "@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@esbuild/aix-ppc64@0.25.10": version "0.25.10" resolved "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz#ee6b7163a13528e099ecf562b972f2bcebe0aa97" @@ -245,7 +317,7 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" -"@jridgewell/gen-mapping@^0.3.2": +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.2": version "0.3.13" resolved "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== @@ -263,7 +335,7 @@ resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/trace-mapping@^0.3.24": +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": version "0.3.31" resolved "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== @@ -996,18 +1068,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - -fast-glob@^3.3.2: +fast-glob@3.3.3, fast-glob@^3.3.2: version "3.3.3" resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== @@ -1226,6 +1287,11 @@ joycon@^3.1.1: resolved "https://registry.npmmirror.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + js-tokens@^9.0.1: version "9.0.1" resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" @@ -1238,6 +1304,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index cb60b88..967c3ef 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 21:44+0300\n" -"PO-Revision-Date: 2025-10-21 21:44+0300\n" +"POT-Creation-Date: 2025-10-21 22:33+0300\n" +"PO-Revision-Date: 2025-10-21 22:33+0300\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -17,995 +17,107 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -msgid "Successfully copied!" -msgstr "Успешно скопировано!" - -msgid "Failed to copy!" -msgstr "Не удалось скопировать!" - -msgid "Operation timed out" -msgstr "Время ожидания истекло" - -msgid "HTTP error" -msgstr "Ошибка HTTP" - -msgid "Unknown error" -msgstr "Неизвестная ошибка" - -msgid "DNS server address cannot be empty" -msgstr "Адрес DNS-сервера не может быть пустым" - -msgid "Valid" -msgstr "Валидно" - -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" - -msgid "Invalid domain address" -msgstr "Неверный домен" - -msgid "Invalid IP address" -msgstr "Неверный IP-адрес" - -msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" -msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" - -msgid "Invalid JSON format" -msgstr "Неверный формат JSON" - -msgid "Path cannot be empty" -msgstr "Путь не может быть пустым" - -msgid "Invalid path format. Path must start with \"/\" and contain valid characters" -msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы" - -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "Ссылка должна начинаться с vless://, ss://, trojan://, или socks4/5://" - -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" - -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" - -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" - -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" - -msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" -msgstr "Неверный URL Shadowsocks: отсутствует разделитель метода и пароля \":\"" - -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" - -msgid "Invalid Shadowsocks URL: missing server" -msgstr "Неверный URL Shadowsocks: отсутствует сервер" - -msgid "Invalid Shadowsocks URL: missing port" -msgstr "Неверный URL Shadowsocks: отсутствует порт" - -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "Неверный номер порта. Допустимо от 1 до 65535" - -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "Неверный URL Shadowsocks: ошибка разбора" - -msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "Невалидная SOCKS ссылка: должна начинаться с socks4://, socks4a://, или socks5://" - -msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "Невалидная SOCKS ссылка: не должна содержать пробелов" - -msgid "Invalid SOCKS URL: missing username" -msgstr "Невалидная SOCKS ссылка: отсуствует имя пользователя" - -msgid "Invalid SOCKS URL: missing host and port" -msgstr "Невалидная SOCKS ссылка: отсутствуют хост и порт" - -msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "Невалидная SOCKS ссылка: отсутствуют имя хоста или айпи" - -msgid "Invalid SOCKS URL: missing port" -msgstr "Невалидная SOCKS ссылка: отсутствует порт" - -msgid "Invalid SOCKS URL: invalid port number" -msgstr "Невалидная SOCKS ссылка: невалидный номер порта" - -msgid "Invalid SOCKS URL: invalid host format" -msgstr "Невалидная SOCKS ссылка: невалидный формат хоста" - -msgid "Invalid SOCKS URL: parsing failed" -msgstr "Невалидная SOCKS ссылка: не удалось распарсить" - -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" - -msgid "IP address 0.0.0.0 is not allowed" -msgstr "IP-адрес 0.0.0.0 не допускается" - -msgid "CIDR must be between 0 and 32" -msgstr "CIDR должен быть между 0 и 32" - -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "Неверный URL Trojan: должен начинаться с trojan://" - -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "Неверный URL Trojan: не должен содержать пробелов" - -msgid "Invalid Trojan URL: parsing failed" -msgstr "Неверный URL Trojan: ошибка разбора" - -msgid "URL must use one of the following protocols:" -msgstr "URL должен использовать один из следующих протоколов:" - -msgid "Invalid URL format" -msgstr "Неверный формат URL" - -msgid "Invalid VLESS URL: parsing failed" -msgstr "Неверный URL VLESS: ошибка разбора" - -msgid "Download" -msgstr "Скачать" - -msgid "Copy" -msgstr "Скопировать" - -msgid "Close" -msgstr "Закрыть" - -msgid "Traffic" -msgstr "Трафик" - -msgid "Uplink" -msgstr "Исходящий" - -msgid "Downlink" -msgstr "Входящий" - -msgid "Traffic Total" -msgstr "Всего трафика" - -msgid "System info" -msgstr "Системная информация" - -msgid "Active Connections" -msgstr "Активные соединения" - -msgid "Memory Usage" -msgstr "Использование памяти" - -msgid "Services info" -msgstr "Информация о сервисах" - -msgid "Podkop" -msgstr "Podkop" - msgid "✔ Enabled" msgstr "✔ Включено" -msgid "✘ Disabled" -msgstr "✘ Отключено" - -msgid "Sing-box" -msgstr "Sing-box" - msgid "✔ Running" msgstr "✔ Работает" +msgid "✘ Disabled" +msgstr "✘ Отключено" + msgid "✘ Stopped" msgstr "✘ Остановлен" -msgid "Not running" -msgstr "Не запущен" - -msgid "Queued" -msgstr "В очереди" - -msgid "unknown" -msgstr "неизвестно" - -msgid "Global check" -msgstr "Глобальная проверка" - -msgid "View logs" -msgstr "Посмотреть логи" - -msgid "Show sing-box config" -msgstr "Показать sing-box конфиг" - -msgid "Outdated" -msgstr "Устаревшая" - -msgid "Latest" -msgstr "Последняя" - -msgid "Fastest" -msgstr "Самый быстрый" - -msgid "Dashboard currently unavailable" -msgstr "Дашборд сейчас недоступен" - -msgid "Test latency" -msgstr "Проверить задержку" - -msgid "Currently unavailable" -msgstr "Временно недоступно" - -msgid "Restart podkop" -msgstr "Перезапустить podkop" - -msgid "Stop podkop" -msgstr "Остановить podkop" - -msgid "Start podkop" -msgstr "Запустить podkop" - -msgid "Disable autostart" -msgstr "Отключить автостарт" - -msgid "Enable autostart" -msgstr "Включить автостарт" - -msgid "Get global check" -msgstr "Получить глобальную проверку" - -msgid "Not implement yet" -msgstr "Не реализовано" - -msgid "Run Diagnostic" -msgstr "Запустить диагностику" - -msgid "DNS checks" -msgstr "Проверка DNS" - -msgid "Sing-box checks" -msgstr "Проверка Sing-box" - -msgid "Nftables checks" -msgstr "Проверка Nftables" - -msgid "FakeIP checks" -msgstr "Проверка FakeIP" - -msgid "Checking dns, please wait" -msgstr "Проверяем dns, пожалуйста подождите" - -msgid "Cannot receive DNS checks result" -msgstr "Не удалось получить результаты проверки DNS" - -msgid "DNS checks passed" -msgstr "Проверка DNS прошла" - -msgid "Bootsrap DNS" -msgstr "Загрузочный DNS" - -msgid "Main DNS" -msgstr "Основной DNS" - -msgid "DNS on router" -msgstr "DNS на роутере" - -msgid "DHCP has DNS server" -msgstr "DHCP содежрит DNS сервер" - -msgid "Checking FakeIP, please wait" -msgstr "Проверяем FakeIP, пожалуйста подождите" - -msgid "FakeIP checks passed" -msgstr "Проверка FakeIP прошла" - -msgid "FakeIP checks partially passed" -msgstr "Проверка FakeIP частично прошла" - -msgid "FakeIP checks failed" -msgstr "Проверка FakeIP не удалась" - -msgid "Router DNS is routed through sing-box" -msgstr "" - -msgid "Router DNS is not routed through sing-box" -msgstr "" - -msgid "Browser is using FakeIP correctly" -msgstr "" - -msgid "Browser is not using FakeIP" -msgstr "" - -msgid "Proxy traffic is routed via FakeIP" -msgstr "" - -msgid "Proxy traffic is not routed via FakeIP" -msgstr "" - -msgid "Checking nftables, please wait" -msgstr "" - -msgid "Cannot receive nftables checks result" -msgstr "" - -msgid "Nftables checks passed" -msgstr "" - -msgid "Nftables checks partially passed" -msgstr "" - -msgid "Table exist" -msgstr "" - -msgid "Rules mangle exist" -msgstr "" - -msgid "Rules mangle counters" -msgstr "" - -msgid "Rules mangle output exist" -msgstr "" - -msgid "Rules mangle output counters" -msgstr "" - -msgid "Rules proxy exist" -msgstr "" - -msgid "Rules proxy counters" -msgstr "" - -msgid "No other marking rules found" -msgstr "" - -msgid "Additional marking rules found" -msgstr "" - -msgid "Checking sing-box, please wait" -msgstr "" - -msgid "Cannot receive Sing-box checks result" -msgstr "" - -msgid "Sing-box checks passed" -msgstr "" - -msgid "Sing-box installed" -msgstr "" - -msgid "Sing-box version >= 1.12.4" -msgstr "" - -msgid "Sing-box service exist" -msgstr "" - -msgid "Sing-box autostart disabled" -msgstr "" - -msgid "Sing-box process running" -msgstr "" - -msgid "Sing-box listening ports" -msgstr "" - -msgid "Valid" -msgstr "Валидно" - -msgid "Invalid IP address" -msgstr "Неверный IP-адрес" - -msgid "Invalid domain address" -msgstr "Неверный домен" - -msgid "DNS server address cannot be empty" -msgstr "Адрес DNS-сервера не может быть пустым" - -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" - -msgid "URL must use one of the following protocols:" -msgstr "URL должен использовать один из следующих протоколов:" - -msgid "Invalid URL format" -msgstr "Неверный формат URL" - -msgid "Path cannot be empty" -msgstr "Путь не может быть пустым" - -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" - -msgid "IP address 0.0.0.0 is not allowed" -msgstr "IP-адрес 0.0.0.0 не допускается" - -msgid "CIDR must be between 0 and 32" -msgstr "CIDR должен быть между 0 и 32" - -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" - -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" - -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" - -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" - -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" - -msgid "Invalid Shadowsocks URL: missing server" -msgstr "Неверный URL Shadowsocks: отсутствует сервер" - -msgid "Invalid Shadowsocks URL: missing port" -msgstr "Неверный URL Shadowsocks: отсутствует порт" - -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "Неверный номер порта. Допустимо от 1 до 65535" - -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "Неверный URL Shadowsocks: ошибка разбора" - -msgid "Invalid VLESS URL: parsing failed" -msgstr "Неверный URL VLESS: ошибка разбора" - -msgid "Invalid JSON format" -msgstr "Неверный формат JSON" - -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "Неверный URL Trojan: должен начинаться с trojan://" - -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "Неверный URL Trojan: не должен содержать пробелов" - -msgid "Invalid Trojan URL: parsing failed" -msgstr "Неверный URL Trojan: ошибка разбора" - -msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" - -msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" - -msgid "Invalid SOCKS URL: missing username" -msgstr "" - -msgid "Invalid SOCKS URL: missing host and port" -msgstr "" - -msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" - -msgid "Invalid SOCKS URL: missing port" -msgstr "" - -msgid "Invalid SOCKS URL: invalid port number" -msgstr "" - -msgid "Invalid SOCKS URL: invalid host format" -msgstr "" - -msgid "Invalid SOCKS URL: parsing failed" -msgstr "" - -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" - -msgid "Fastest" -msgstr "Самый быстрый" - -msgid "HTTP error" -msgstr "Ошибка HTTP" - -msgid "Unknown error" -msgstr "Неизвестная ошибка" - -msgid "DNS checks" -msgstr "" - -msgid "Sing-box checks" -msgstr "" - -msgid "Nftables checks" -msgstr "" - -msgid "FakeIP checks" -msgstr "" - -msgid "Not running" -msgstr "" - -msgid "Queued" -msgstr "" - -msgid "Dashboard currently unavailable" -msgstr "Дашборд сейчас недоступен" - -msgid "Test latency" -msgstr "" - -msgid "Currently unavailable" -msgstr "Временно недоступно" - -msgid "Traffic" -msgstr "Трафик" - -msgid "Uplink" -msgstr "Исходящий" - -msgid "Downlink" -msgstr "Входящий" - -msgid "Traffic Total" -msgstr "Всего трафика" - -msgid "System info" -msgstr "Системная информация" - msgid "Active Connections" msgstr "Активные соединения" -msgid "Memory Usage" -msgstr "Использование памяти" - -msgid "Services info" -msgstr "Информация о сервисах" - -msgid "Podkop" -msgstr "Podkop" - -msgid "\\u2714 Enabled" -msgstr "" - -msgid "\\u2718 Disabled" -msgstr "" - -msgid "Sing-box" -msgstr "Sing-box" - -msgid "\\u2714 Running" -msgstr "" - -msgid "\\u2718 Stopped" -msgstr "" - -msgid "Checking dns, please wait" -msgstr "" - -msgid "Cannot receive DNS checks result" -msgstr "" - -msgid "DNS checks passed" -msgstr "" - -msgid "Bootsrap DNS" -msgstr "" - -msgid "Main DNS" -msgstr "" - -msgid "DNS on router" -msgstr "" - -msgid "DHCP has DNS server" -msgstr "" - -msgid "Checking sing-box, please wait" -msgstr "" - -msgid "Cannot receive Sing-box checks result" -msgstr "" - -msgid "Sing-box checks passed" -msgstr "" - -msgid "Sing-box installed" -msgstr "" - -msgid "Sing-box version >= 1.12.4" -msgstr "" - -msgid "Sing-box service exist" -msgstr "" - -msgid "Sing-box autostart disabled" -msgstr "" - -msgid "Sing-box process running" -msgstr "" - -msgid "Sing-box listening ports" -msgstr "" - -msgid "Checking nftables, please wait" -msgstr "" - -msgid "Cannot receive nftables checks result" -msgstr "" - -msgid "Nftables checks passed" -msgstr "" - -msgid "Nftables checks partially passed" -msgstr "" - -msgid "Table exist" -msgstr "" - -msgid "Rules mangle exist" -msgstr "" - -msgid "Rules mangle counters" -msgstr "" - -msgid "Rules mangle output exist" -msgstr "" - -msgid "Rules mangle output counters" -msgstr "" - -msgid "Rules proxy exist" -msgstr "" - -msgid "Rules proxy counters" -msgstr "" - -msgid "No other marking rules found" -msgstr "" - msgid "Additional marking rules found" -msgstr "" - -msgid "Checking FakeIP, please wait" -msgstr "" - -msgid "FakeIP checks passed" -msgstr "" - -msgid "FakeIP checks partially passed" -msgstr "" - -msgid "FakeIP checks failed" -msgstr "" - -msgid "Router DNS is routed through sing-box" -msgstr "" - -msgid "Router DNS is not routed through sing-box" -msgstr "" - -msgid "Browser is using FakeIP correctly" -msgstr "" - -msgid "Browser is not using FakeIP" -msgstr "" - -msgid "Proxy traffic is routed via FakeIP" -msgstr "" - -msgid "Proxy traffic is not routed via FakeIP" -msgstr "" - -msgid "Successfully copied!" -msgstr "" - -msgid "Failed to copy!" -msgstr "" - -msgid "Download" -msgstr "" - -msgid "Copy" -msgstr "" - -msgid "Close" -msgstr "Закрыть" - -msgid "Restart podkop" -msgstr "" - -msgid "Stop podkop" -msgstr "" - -msgid "Start podkop" -msgstr "" - -msgid "Disable autostart" -msgstr "" - -msgid "Enable autostart" -msgstr "" - -msgid "Get global check" -msgstr "" - -msgid "View logs" -msgstr "" - -msgid "Show sing-box config" -msgstr "" - -msgid "Not implement yet" -msgstr "" - -msgid "Run Diagnostic" -msgstr "" - -msgid "unknown" -msgstr "" - -msgid "Global check" -msgstr "Глобальная проверка" - -msgid "Outdated" -msgstr "" - -msgid "Latest" -msgstr "" - -msgid "Operation timed out" -msgstr "Время ожидания истекло" - -msgid "Podkop Settings" -msgstr "" - -msgid "Configuration for Podkop service" -msgstr "" - -msgid "Sections" -msgstr "" - -msgid "Settings" -msgstr "" - -msgid "Diagnostics" -msgstr "Диагностика" - -msgid "Dashboard" -msgstr "Дашборд" - -msgid "Connection Type" -msgstr "Тип подключения" - -msgid "Select between VPN and Proxy connection methods for traffic routing" -msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" - -msgid "Configuration Type" -msgstr "Тип конфигурации" - -msgid "Select how to configure the proxy" -msgstr "Выберите способ настройки прокси" - -msgid "Connection URL" -msgstr "URL подключения" - -msgid "Outbound Config" -msgstr "Конфигурация Outbound" - -msgid "URLTest" -msgstr "URLTest" - -msgid "Proxy Configuration URL" -msgstr "URL конфигурации прокси" - -msgid "Outbound Configuration" -msgstr "Конфигурация исходящего соединения" - -msgid "Enter complete outbound configuration in JSON format" -msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" - -msgid "URLTest Proxy Links" -msgstr "Ссылки прокси для URLTest" - -msgid "UDP over TCP" -msgstr "" +msgstr "Найдены дополнительные правила маркировки" msgid "Applicable for SOCKS and Shadowsocks proxy" msgstr "" -msgid "Network Interface" -msgstr "Сетевой интерфейс" - -msgid "Select network interface for VPN connection" -msgstr "Выберите сетевой интерфейс для VPN подключения" - -msgid "Domain Resolver" -msgstr "Резолвер доменов" - -msgid "Enable built-in DNS resolver for domains handled by this section" -msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе" - -msgid "DNS Protocol Type" -msgstr "Тип протокола DNS" - -msgid "Select the DNS protocol type for the domain resolver" -msgstr "Выберите тип протокола DNS для резолвера доменов" - -msgid "DNS over HTTPS (DoH)" -msgstr "DNS через HTTPS (DoH)" - -msgid "DNS over TLS (DoT)" -msgstr "DNS через TLS (DoT)" - -msgid "UDP (Unprotected DNS)" -msgstr "UDP (Незащищённый DNS)" - -msgid "DNS Server" -msgstr "DNS-сервер" - -msgid "Select or enter DNS server address" -msgstr "Выберите или введите адрес DNS-сервера" - -msgid "" -msgstr "" - -msgid "Community Lists" -msgstr "Списки сообщества" - -msgid "Select a predefined list for routing" -msgstr "" - -msgid "Regional options cannot be used together" -msgstr "Нельзя использовать несколько региональных опций одновременно" - -msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." -msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." - -msgid "Russia inside restrictions" -msgstr "Ограничения Russia inside" - -msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." -msgstr "" - -msgid "User Domain List Type" -msgstr "Тип пользовательского списка доменов" - -msgid "Select the list type for adding custom domains" -msgstr "" - -msgid "Disabled" -msgstr "Отключено" - -msgid "Dynamic List" -msgstr "Динамический список" - -msgid "Text List" -msgstr "Текстовый список" - -msgid "User Domains" -msgstr "Пользовательские домены" - -msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" -msgstr "" - -msgid "User Domains List" -msgstr "Список пользовательских доменов" - -msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" -msgstr "" - msgid "At least one valid domain must be specified. Comments-only content is not allowed." msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается." -msgid "Validation errors:" -msgstr "Ошибки валидации:" - -msgid "User Subnet List Type" -msgstr "Тип пользовательского списка подсетей" - -msgid "Select the list type for adding custom subnets" -msgstr "" - -msgid "Text List (comma/space/newline separated)" -msgstr "Текстовый список (через запятую, пробел или новую строку)" - -msgid "User Subnets" -msgstr "Пользовательские подсети" - -msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" -msgstr "" - -msgid "User Subnets List" -msgstr "Список пользовательских подсетей" - -msgid "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //" -msgstr "" - msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы." -msgid "Local Domain Lists" -msgstr "Локальные списки доменов" - -msgid "Specify the path to the list file located on the router filesystem" -msgstr "" - -msgid "Local Subnet Lists" -msgstr "Локальные списки подсетей" - -msgid "Remote Domain Lists" -msgstr "Удалённые списки доменов" - -msgid "Specify remote URLs to download and use domain lists" -msgstr "" - -msgid "Remote Subnet Lists" -msgstr "Удалённые списки подсетей" - -msgid "Specify remote URLs to download and use subnet lists" -msgstr "" - -msgid "Fully Routed IPs" -msgstr "" - -msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" -msgstr "" - -msgid "Enable Mixed Proxy" -msgstr "" - -msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" -msgstr "" - -msgid "Mixed Proxy Port" -msgstr "" - -msgid "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" -msgstr "" - -msgid "Select DNS protocol to use" -msgstr "Выберите протокол DNS" +msgid "Bootsrap DNS" +msgstr "Bootstrap DNS" msgid "Bootstrap DNS server" msgstr "Bootstrap DNS-сервер" -msgid "The DNS server used to look up the IP address of an upstream DNS server" -msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" +msgid "Browser is not using FakeIP" +msgstr "Браузер не использует FakeIP" -msgid "DNS Rewrite TTL" -msgstr "Перезапись TTL для DNS" +msgid "Browser is using FakeIP correctly" +msgstr "Браузер использует FakeIP" -msgid "Time in seconds for DNS record caching (default: 60)" -msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)" +msgid "Cache File Path" +msgstr "Путь к файлу кэша" -msgid "TTL value cannot be empty" -msgstr "Значение TTL не может быть пустым" +msgid "Cache file path cannot be empty" +msgstr "Путь к файлу кэша не может быть пустым" -msgid "TTL must be a positive number" -msgstr "TTL должно быть положительным числом" - -msgid "Source Network Interface" -msgstr "Сетевой интерфейс источника" - -msgid "Select the network interface from which the traffic will originate" -msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" - -msgid "Enable Output Network Interface" +msgid "Cannot receive DNS checks result" msgstr "" -msgid "You can select Output Network Interface, by default autodetect" +msgid "Cannot receive nftables checks result" msgstr "" -msgid "Output Network Interface" +msgid "Cannot receive Sing-box checks result" msgstr "" -msgid "Select the network interface to which the traffic will originate" +msgid "Checking dns, please wait" msgstr "" -msgid "Interface Monitoring" +msgid "Checking FakeIP, please wait" msgstr "" -msgid "Interface monitoring for Bad WAN" +msgid "Checking nftables, please wait" msgstr "" -msgid "Monitored Interfaces" +msgid "Checking sing-box, please wait" msgstr "" -msgid "Select the WAN interfaces to be monitored" -msgstr "Выберите WAN интерфейсы для мониторинга" +msgid "CIDR must be between 0 and 32" +msgstr "CIDR должен быть между 0 и 32" -msgid "Interface Monitoring Delay" -msgstr "Задержка при мониторинге интерфейсов" +msgid "Close" +msgstr "Закрыть" + +msgid "Community Lists" +msgstr "Списки сообщества" + +msgid "Config File Path" +msgstr "Путь к файлу конфигурации" + +msgid "Configuration for Podkop service" +msgstr "" + +msgid "Configuration Type" +msgstr "Тип конфигурации" + +msgid "Connection Type" +msgstr "Тип подключения" + +msgid "Connection URL" +msgstr "URL подключения" + +msgid "Copy" +msgstr "" + +msgid "Currently unavailable" +msgstr "Временно недоступно" + +msgid "Dashboard" +msgstr "Дашборд" + +msgid "Dashboard currently unavailable" +msgstr "Дашборд сейчас недоступен" msgid "Delay in milliseconds before reloading podkop after interface UP" msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса" @@ -1013,7 +125,13 @@ msgstr "Задержка в миллисекундах перед перезаг msgid "Delay value cannot be empty" msgstr "Значение задержки не может быть пустым" -msgid "Enable YACD" +msgid "DHCP has DNS server" +msgstr "" + +msgid "Diagnostics" +msgstr "Диагностика" + +msgid "Disable autostart" msgstr "" msgid "Disable QUIC" @@ -1022,53 +140,92 @@ msgstr "" msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" msgstr "" -msgid "List Update Frequency" -msgstr "Частота обновления списков" +msgid "Disabled" +msgstr "Отключено" -msgid "Select how often the domain or subnet lists are updated automatically" +msgid "DNS checks" +msgstr "" + +msgid "DNS checks passed" +msgstr "" + +msgid "DNS on router" +msgstr "" + +msgid "DNS over HTTPS (DoH)" +msgstr "DNS через HTTPS (DoH)" + +msgid "DNS over TLS (DoT)" +msgstr "DNS через TLS (DoT)" + +msgid "DNS Protocol Type" +msgstr "Тип протокола DNS" + +msgid "DNS Rewrite TTL" +msgstr "Перезапись TTL для DNS" + +msgid "DNS Server" +msgstr "DNS-сервер" + +msgid "DNS server address cannot be empty" +msgstr "Адрес DNS-сервера не может быть пустым" + +msgid "Domain Resolver" +msgstr "Резолвер доменов" + +msgid "Dont Touch My DHCP!" +msgstr "" + +msgid "Downlink" +msgstr "Входящий" + +msgid "Download" msgstr "" msgid "Download Lists via Proxy/VPN" msgstr "" -msgid "Downloading all lists via main Proxy/VPN" -msgstr "Загрузка всех списков через основной прокси/VPN" - msgid "Download Lists via specific proxy section" msgstr "" +msgid "Downloading all lists via main Proxy/VPN" +msgstr "Загрузка всех списков через основной прокси/VPN" + msgid "Downloading all lists via specific Proxy/VPN" msgstr "Загрузка всех списков через указанный прокси/VPN" -msgid "Dont Touch My DHCP!" +msgid "Dynamic List" +msgstr "Динамический список" + +msgid "Enable autostart" msgstr "" -msgid "Podkop will not modify your DHCP configuration" +msgid "Enable built-in DNS resolver for domains handled by this section" +msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе" + +msgid "Enable Mixed Proxy" msgstr "" -msgid "Config File Path" -msgstr "Путь к файлу конфигурации" +msgid "Enable Output Network Interface" +msgstr "" -msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" -msgstr "Выберите путь к файлу конфигурации sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" +msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" +msgstr "" -msgid "Cache File Path" -msgstr "Путь к файлу кэша" +msgid "Enable YACD" +msgstr "" -msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" -msgstr "Выберите или введите путь к файлу кеша sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" +msgid "Enter complete outbound configuration in JSON format" +msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" -msgid "Cache file path cannot be empty" -msgstr "Путь к файлу кэша не может быть пустым" +msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" +msgstr "" -msgid "Path must be absolute (start with /)" -msgstr "Путь должен быть абсолютным (начинаться с /)" +msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" +msgstr "" -msgid "Path must end with cache.db" -msgstr "Путь должен заканчиваться на cache.db" - -msgid "Path must contain at least one directory (like /tmp/cache.db)" -msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)" +msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" +msgstr "" msgid "Exclude NTP" msgstr "Исключить NTP" @@ -1076,8 +233,482 @@ msgstr "Исключить NTP" msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" msgstr "" +msgid "Failed to copy!" +msgstr "" + +msgid "FakeIP checks" +msgstr "" + +msgid "FakeIP checks failed" +msgstr "" + +msgid "FakeIP checks partially passed" +msgstr "" + +msgid "FakeIP checks passed" +msgstr "" + +msgid "Fastest" +msgstr "Самый быстрый" + +msgid "Fully Routed IPs" +msgstr "" + +msgid "Get global check" +msgstr "" + +msgid "Global check" +msgstr "Глобальная проверка" + +msgid "HTTP error" +msgstr "Ошибка HTTP" + +msgid "Interface Monitoring" +msgstr "" + +msgid "Interface Monitoring Delay" +msgstr "Задержка при мониторинге интерфейсов" + +msgid "Interface monitoring for Bad WAN" +msgstr "" + +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" + +msgid "Invalid domain address" +msgstr "Неверный домен" + +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y" + +msgid "Invalid IP address" +msgstr "Неверный IP-адрес" + +msgid "Invalid JSON format" +msgstr "Неверный формат JSON" + +msgid "Invalid path format. Path must start with \"/\" and contain valid characters" +msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы" + +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "Неверный номер порта. Допустимо от 1 до 65535" + +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password" + +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные" + +msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" +msgstr "Неверный URL Shadowsocks: отсутствует разделитель метода и пароля \":\"" + +msgid "Invalid Shadowsocks URL: missing port" +msgstr "Неверный URL Shadowsocks: отсутствует порт" + +msgid "Invalid Shadowsocks URL: missing server" +msgstr "Неверный URL Shadowsocks: отсутствует сервер" + +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера" + +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "Неверный URL Shadowsocks: не должен содержать пробелов" + +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "Неверный URL Shadowsocks: должен начинаться с ss://" + +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "Неверный URL Shadowsocks: ошибка разбора" + +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "Неверный URL Trojan: не должен содержать пробелов" + +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "Неверный URL Trojan: должен начинаться с trojan://" + +msgid "Invalid Trojan URL: parsing failed" +msgstr "Неверный URL Trojan: ошибка разбора" + +msgid "Invalid URL format" +msgstr "Неверный формат URL" + +msgid "Invalid VLESS URL: parsing failed" +msgstr "Неверный URL VLESS: ошибка разбора" + +msgid "IP address 0.0.0.0 is not allowed" +msgstr "IP-адрес 0.0.0.0 не допускается" + +msgid "Latest" +msgstr "" + +msgid "List Update Frequency" +msgstr "Частота обновления списков" + +msgid "Local Domain Lists" +msgstr "Локальные списки доменов" + +msgid "Local Subnet Lists" +msgstr "Локальные списки подсетей" + +msgid "Main DNS" +msgstr "" + +msgid "Memory Usage" +msgstr "Использование памяти" + +msgid "Mixed Proxy Port" +msgstr "" + +msgid "Monitored Interfaces" +msgstr "" + +msgid "Network Interface" +msgstr "Сетевой интерфейс" + +msgid "Nftables checks" +msgstr "" + +msgid "Nftables checks partially passed" +msgstr "" + +msgid "Nftables checks passed" +msgstr "" + +msgid "No other marking rules found" +msgstr "" + +msgid "Not implement yet" +msgstr "" + +msgid "Not running" +msgstr "" + +msgid "Operation timed out" +msgstr "Время ожидания истекло" + +msgid "Outbound Config" +msgstr "Конфигурация Outbound" + +msgid "Outbound Configuration" +msgstr "Конфигурация исходящего соединения" + +msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" +msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" + +msgid "Outdated" +msgstr "" + +msgid "Output Network Interface" +msgstr "" + +msgid "Path cannot be empty" +msgstr "Путь не может быть пустым" + +msgid "Path must be absolute (start with /)" +msgstr "Путь должен быть абсолютным (начинаться с /)" + +msgid "Path must contain at least one directory (like /tmp/cache.db)" +msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)" + +msgid "Path must end with cache.db" +msgstr "Путь должен заканчиваться на cache.db" + +msgid "Podkop" +msgstr "Podkop" + +msgid "Podkop Settings" +msgstr "" + +msgid "Podkop will not modify your DHCP configuration" +msgstr "" + +msgid "Proxy Configuration URL" +msgstr "URL конфигурации прокси" + +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +msgid "Queued" +msgstr "" + +msgid "Regional options cannot be used together" +msgstr "Нельзя использовать несколько региональных опций одновременно" + +msgid "Remote Domain Lists" +msgstr "Удалённые списки доменов" + +msgid "Remote Subnet Lists" +msgstr "Удалённые списки подсетей" + +msgid "Restart podkop" +msgstr "" + +msgid "Router DNS is not routed through sing-box" +msgstr "" + +msgid "Router DNS is routed through sing-box" +msgstr "" + msgid "Routing Excluded IPs" msgstr "" +msgid "Rules mangle counters" +msgstr "" + +msgid "Rules mangle exist" +msgstr "" + +msgid "Rules mangle output counters" +msgstr "" + +msgid "Rules mangle output exist" +msgstr "" + +msgid "Rules proxy counters" +msgstr "" + +msgid "Rules proxy exist" +msgstr "" + +msgid "Run Diagnostic" +msgstr "" + +msgid "Russia inside restrictions" +msgstr "Ограничения Russia inside" + +msgid "Sections" +msgstr "" + +msgid "Select a predefined list for routing" +msgstr "" + +msgid "Select between VPN and Proxy connection methods for traffic routing" +msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" + +msgid "Select DNS protocol to use" +msgstr "Выберите протокол DNS" + +msgid "Select how often the domain or subnet lists are updated automatically" +msgstr "" + +msgid "Select how to configure the proxy" +msgstr "Выберите способ настройки прокси" + +msgid "Select network interface for VPN connection" +msgstr "Выберите сетевой интерфейс для VPN подключения" + +msgid "Select or enter DNS server address" +msgstr "Выберите или введите адрес DNS-сервера" + +msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" +msgstr "Выберите или введите путь к файлу кеша sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" + +msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" +msgstr "Выберите путь к файлу конфигурации sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете" + +msgid "Select the DNS protocol type for the domain resolver" +msgstr "Выберите тип протокола DNS для резолвера доменов" + +msgid "Select the list type for adding custom domains" +msgstr "" + +msgid "Select the list type for adding custom subnets" +msgstr "" + +msgid "Select the network interface from which the traffic will originate" +msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" + +msgid "Select the network interface to which the traffic will originate" +msgstr "" + +msgid "Select the WAN interfaces to be monitored" +msgstr "Выберите WAN интерфейсы для мониторинга" + +msgid "Services info" +msgstr "Информация о сервисах" + +msgid "Settings" +msgstr "" + +msgid "Show sing-box config" +msgstr "" + +msgid "Sing-box" +msgstr "Sing-box" + +msgid "Sing-box autostart disabled" +msgstr "" + +msgid "Sing-box checks" +msgstr "" + +msgid "Sing-box checks passed" +msgstr "" + +msgid "Sing-box installed" +msgstr "" + +msgid "Sing-box listening ports" +msgstr "" + +msgid "Sing-box process running" +msgstr "" + +msgid "Sing-box service exist" +msgstr "" + +msgid "Sing-box version >= 1.12.4" +msgstr "" + +msgid "Source Network Interface" +msgstr "Сетевой интерфейс источника" + msgid "Specify a local IP address to be excluded from routing" msgstr "" + +msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" +msgstr "" + +msgid "Specify remote URLs to download and use domain lists" +msgstr "" + +msgid "Specify remote URLs to download and use subnet lists" +msgstr "" + +msgid "Specify the path to the list file located on the router filesystem" +msgstr "" + +msgid "Start podkop" +msgstr "" + +msgid "Stop podkop" +msgstr "" + +msgid "Successfully copied!" +msgstr "" + +msgid "System info" +msgstr "Системная информация" + +msgid "Table exist" +msgstr "" + +msgid "Test latency" +msgstr "" + +msgid "Text List" +msgstr "Текстовый список" + +msgid "Text List (comma/space/newline separated)" +msgstr "Текстовый список (через запятую, пробел или новую строку)" + +msgid "The DNS server used to look up the IP address of an upstream DNS server" +msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера" + +msgid "Time in seconds for DNS record caching (default: 60)" +msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)" + +msgid "Traffic" +msgstr "Трафик" + +msgid "Traffic Total" +msgstr "Всего трафика" + +msgid "TTL must be a positive number" +msgstr "TTL должно быть положительным числом" + +msgid "TTL value cannot be empty" +msgstr "Значение TTL не может быть пустым" + +msgid "UDP (Unprotected DNS)" +msgstr "UDP (Незащищённый DNS)" + +msgid "UDP over TCP" +msgstr "" + +msgid "unknown" +msgstr "" + +msgid "Unknown error" +msgstr "Неизвестная ошибка" + +msgid "Uplink" +msgstr "Исходящий" + +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +msgid "URL must use one of the following protocols:" +msgstr "URL должен использовать один из следующих протоколов:" + +msgid "URLTest" +msgstr "URLTest" + +msgid "URLTest Proxy Links" +msgstr "Ссылки прокси для URLTest" + +msgid "User Domain List Type" +msgstr "Тип пользовательского списка доменов" + +msgid "User Domains" +msgstr "Пользовательские домены" + +msgid "User Domains List" +msgstr "Список пользовательских доменов" + +msgid "User Subnet List Type" +msgstr "Тип пользовательского списка подсетей" + +msgid "User Subnets" +msgstr "Пользовательские подсети" + +msgid "User Subnets List" +msgstr "Список пользовательских подсетей" + +msgid "Valid" +msgstr "Валидно" + +msgid "Validation errors:" +msgstr "Ошибки валидации:" + +msgid "View logs" +msgstr "" + +msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." +msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." + +msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." +msgstr "" + +msgid "You can select Output Network Interface, by default autodetect" +msgstr "" diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index 415585a..f8c7d1c 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 18:48+0300\n" -"PO-Revision-Date: 2025-10-21 18:48+0300\n" +"POT-Creation-Date: 2025-10-21 19:33+0300\n" +"PO-Revision-Date: 2025-10-21 19:33+0300\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -16,28 +16,933 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/helpers/copyToClipboard.ts:10 -msgid "Successfully copied!" +#: src/podkop/tabs/dashboard/initController.ts:342 +msgid "✔ Enabled" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:353 +msgid "✔ Running" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:343 +msgid "✘ Disabled" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:354 +msgid "✘ Stopped" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:304 +msgid "Active Connections" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117 +msgid "Additional marking rules found" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111 +msgid "Applicable for SOCKS and Shadowsocks proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356 +msgid "At least one valid domain must be specified. Comments-only content is not allowed." +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437 +msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72 +msgid "Bootsrap DNS" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45 +msgid "Bootstrap DNS server" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81 +msgid "Browser is not using FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80 +msgid "Browser is using FakeIP correctly" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322 +msgid "Cache File Path" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336 +msgid "Cache file path cannot be empty" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26 +msgid "Cannot receive DNS checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27 +msgid "Cannot receive nftables checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24 +msgid "Cannot receive Sing-box checks result" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14 +msgid "Checking dns, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14 +msgid "Checking FakeIP, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12 +msgid "Checking nftables, please wait" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12 +msgid "Checking sing-box, please wait" +msgstr "" + +#: src/validators/validateSubnet.ts:33 +msgid "CIDR must be between 0 and 32" +msgstr "" + +#: src/partials/modal/renderModal.ts:26 +msgid "Close" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211 +msgid "Community Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309 +msgid "Config File Path" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27 +msgid "Configuration for Podkop service" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22 +msgid "Configuration Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12 +msgid "Connection Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25 +msgid "Connection URL" +msgstr "" + +#: src/partials/modal/renderModal.ts:20 +msgid "Copy" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderWidget.ts:22 +msgid "Currently unavailable" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80 +msgid "Dashboard" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderSections.ts:19 +msgid "Dashboard currently unavailable" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215 +msgid "Delay in milliseconds before reloading podkop after interface UP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222 +msgid "Delay value cannot be empty" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89 +msgid "DHCP has DNS server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65 +msgid "Diagnostics" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79 +msgid "Disable autostart" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239 +msgid "Disable QUIC" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240 +msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382 +msgid "Disabled" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:14 +msgid "DNS checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64 +msgid "DNS checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84 +msgid "DNS on router" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15 +msgid "DNS over HTTPS (DoH)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16 +msgid "DNS over TLS (DoT)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12 +msgid "DNS Protocol Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68 +msgid "DNS Rewrite TTL" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24 +msgid "DNS Server" +msgstr "" + +#: src/validators/validateDns.ts:7 +msgid "DNS server address cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166 +msgid "Domain Resolver" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300 +msgid "Dont Touch My DHCP!" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:238 +#: src/podkop/tabs/dashboard/initController.ts:272 +msgid "Downlink" +msgstr "" + +#: src/partials/modal/renderModal.ts:15 +msgid "Download" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262 +msgid "Download Lists via Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271 +msgid "Download Lists via specific proxy section" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263 +msgid "Downloading all lists via main Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272 +msgid "Downloading all lists via specific Proxy/VPN" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383 +msgid "Dynamic List" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89 +msgid "Enable autostart" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167 +msgid "Enable built-in DNS resolver for domains handled by this section" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575 +msgid "Enable Mixed Proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126 +msgid "Enable Output Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576 +msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230 +msgid "Enable YACD" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65 +msgid "Enter complete outbound configuration in JSON format" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338 +msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312 +msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392 +msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358 +msgid "Exclude NTP" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359 +msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" msgstr "" #: src/helpers/copyToClipboard.ts:12 msgid "Failed to copy!" msgstr "" -#: src/helpers/withTimeout.ts:7 -msgid "Operation timed out" +#: src/podkop/tabs/diagnostic/checks/contstants.ts:29 +msgid "FakeIP checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57 +msgid "FakeIP checks failed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51 +msgid "FakeIP checks partially passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44 +msgid "FakeIP checks passed" +msgstr "" + +#: src/podkop/methods/custom/getDashboardSections.ts:117 +msgid "Fastest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550 +msgid "Fully Routed IPs" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98 +msgid "Get global check" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:218 +msgid "Global check" msgstr "" #: src/podkop/api.ts:27 msgid "HTTP error" msgstr "" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182 +msgid "Interface Monitoring" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214 +msgid "Interface Monitoring Delay" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183 +msgid "Interface monitoring for Bad WAN" +msgstr "" + +#: src/validators/validateDns.ts:20 +msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" +msgstr "" + +#: src/validators/validateDomain.ts:18 +#: src/validators/validateDomain.ts:27 +msgid "Invalid domain address" +msgstr "" + +#: src/validators/validateSubnet.ts:11 +msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" +msgstr "" + +#: src/validators/validateIp.ts:11 +msgid "Invalid IP address" +msgstr "" + +#: src/validators/validateOutboundJson.ts:19 +msgid "Invalid JSON format" +msgstr "" + +#: src/validators/validatePath.ts:22 +msgid "Invalid path format. Path must start with \"/\" and contain valid characters" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:85 +msgid "Invalid port number. Must be between 1 and 65535" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:37 +msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:27 +msgid "Invalid Shadowsocks URL: missing credentials" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:46 +msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:76 +msgid "Invalid Shadowsocks URL: missing port" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:67 +msgid "Invalid Shadowsocks URL: missing server" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:58 +msgid "Invalid Shadowsocks URL: missing server address" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:16 +msgid "Invalid Shadowsocks URL: must not contain spaces" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:8 +msgid "Invalid Shadowsocks URL: must start with ss://" +msgstr "" + +#: src/validators/validateShadowsocksUrl.ts:91 +msgid "Invalid Shadowsocks URL: parsing failed" +msgstr "" + +#: src/validators/validateSocksUrl.ts:73 +msgid "Invalid SOCKS URL: invalid host format" +msgstr "" + +#: src/validators/validateSocksUrl.ts:63 +msgid "Invalid SOCKS URL: invalid port number" +msgstr "" + +#: src/validators/validateSocksUrl.ts:42 +msgid "Invalid SOCKS URL: missing host and port" +msgstr "" + +#: src/validators/validateSocksUrl.ts:51 +msgid "Invalid SOCKS URL: missing hostname or IP" +msgstr "" + +#: src/validators/validateSocksUrl.ts:56 +msgid "Invalid SOCKS URL: missing port" +msgstr "" + +#: src/validators/validateSocksUrl.ts:34 +msgid "Invalid SOCKS URL: missing username" +msgstr "" + +#: src/validators/validateSocksUrl.ts:19 +msgid "Invalid SOCKS URL: must not contain spaces" +msgstr "" + +#: src/validators/validateSocksUrl.ts:10 +msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" +msgstr "" + +#: src/validators/validateSocksUrl.ts:77 +msgid "Invalid SOCKS URL: parsing failed" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:15 +msgid "Invalid Trojan URL: must not contain spaces" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:8 +msgid "Invalid Trojan URL: must start with trojan://" +msgstr "" + +#: src/validators/validateTrojanUrl.ts:56 +msgid "Invalid Trojan URL: parsing failed" +msgstr "" + +#: src/validators/validateUrl.ts:18 +msgid "Invalid URL format" +msgstr "" + +#: src/validators/validateVlessUrl.ts:109 +msgid "Invalid VLESS URL: parsing failed" +msgstr "" + +#: src/validators/validateSubnet.ts:18 +msgid "IP address 0.0.0.0 is not allowed" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:404 +msgid "Latest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250 +msgid "List Update Frequency" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458 +msgid "Local Domain Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481 +msgid "Local Subnet Lists" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79 +msgid "Main DNS" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:308 +msgid "Memory Usage" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586 +msgid "Mixed Proxy Port" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191 +msgid "Monitored Interfaces" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120 +msgid "Network Interface" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:24 +msgid "Nftables checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75 +msgid "Nftables checks partially passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74 +msgid "Nftables checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116 +msgid "No other marking rules found" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189 +msgid "Not implement yet" +msgstr "" + +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:55 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:63 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:71 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:79 +msgid "Not running" +msgstr "" + +#: src/helpers/withTimeout.ts:7 +msgid "Operation timed out" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26 +msgid "Outbound Config" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64 +msgid "Outbound Configuration" +msgstr "" + +#: src/validators/validateOutboundJson.ts:11 +msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:394 +msgid "Outdated" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135 +msgid "Output Network Interface" +msgstr "" + +#: src/validators/validatePath.ts:7 +msgid "Path cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340 +msgid "Path must be absolute (start with /)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349 +msgid "Path must contain at least one directory (like /tmp/cache.db)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344 +msgid "Path must end with cache.db" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:340 +msgid "Podkop" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26 +msgid "Podkop Settings" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301 +msgid "Podkop will not modify your DHCP configuration" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34 +msgid "Proxy Configuration URL" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89 +msgid "Proxy traffic is not routed via FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88 +msgid "Proxy traffic is routed via FakeIP" +msgstr "" + +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:95 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:103 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:111 +#: src/podkop/tabs/diagnostic/diagnostic.store.ts:119 +msgid "Queued" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245 +msgid "Regional options cannot be used together" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504 +msgid "Remote Domain Lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527 +msgid "Remote Subnet Lists" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49 +msgid "Restart podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74 +msgid "Router DNS is not routed through sing-box" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73 +msgid "Router DNS is routed through sing-box" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369 +msgid "Routing Excluded IPs" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90 +msgid "Rules mangle counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85 +msgid "Rules mangle exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100 +msgid "Rules mangle output counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95 +msgid "Rules mangle output exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110 +msgid "Rules proxy counters" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105 +msgid "Rules proxy exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15 +msgid "Run Diagnostic" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264 +msgid "Russia inside restrictions" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36 +msgid "Sections" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212 +msgid "Select a predefined list for routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13 +msgid "Select between VPN and Proxy connection methods for traffic routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13 +msgid "Select DNS protocol to use" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251 +msgid "Select how often the domain or subnet lists are updated automatically" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23 +msgid "Select how to configure the proxy" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121 +msgid "Select network interface for VPN connection" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25 +msgid "Select or enter DNS server address" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323 +msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310 +msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177 +msgid "Select the DNS protocol type for the domain resolver" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300 +msgid "Select the list type for adding custom domains" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380 +msgid "Select the list type for adding custom subnets" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90 +msgid "Select the network interface from which the traffic will originate" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136 +msgid "Select the network interface to which the traffic will originate" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192 +msgid "Select the WAN interfaces to be monitored" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:337 +msgid "Services info" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49 +msgid "Settings" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:278 +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116 +msgid "Show sing-box config" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:351 +msgid "Sing-box" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86 +msgid "Sing-box autostart disabled" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/contstants.ts:19 +msgid "Sing-box checks" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66 +msgid "Sing-box checks passed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71 +msgid "Sing-box installed" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96 +msgid "Sing-box listening ports" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91 +msgid "Sing-box process running" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81 +msgid "Sing-box service exist" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76 +msgid "Sing-box version >= 1.12.4" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89 +msgid "Source Network Interface" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370 +msgid "Specify a local IP address to be excluded from routing" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551 +msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505 +msgid "Specify remote URLs to download and use domain lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528 +msgid "Specify remote URLs to download and use subnet lists" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482 +msgid "Specify the path to the list file located on the router filesystem" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69 +msgid "Start podkop" +msgstr "" + +#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59 +msgid "Stop podkop" +msgstr "" + +#: src/helpers/copyToClipboard.ts:10 +msgid "Successfully copied!" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:301 +msgid "System info" +msgstr "" + +#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80 +msgid "Table exist" +msgstr "" + +#: src/podkop/tabs/dashboard/partials/renderSections.ts:108 +msgid "Test latency" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304 +msgid "Text List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384 +msgid "Text List (comma/space/newline separated)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 +msgid "The DNS server used to look up the IP address of an upstream DNS server" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69 +msgid "Time in seconds for DNS record caching (default: 60)" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:235 +msgid "Traffic" +msgstr "" + +#: src/podkop/tabs/dashboard/initController.ts:265 +msgid "Traffic Total" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80 +msgid "TTL must be a positive number" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75 +msgid "TTL value cannot be empty" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17 +msgid "UDP (Unprotected DNS)" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110 +msgid "UDP over TCP" +msgstr "" + +#: src/podkop/tabs/diagnostic/initController.ts:34 +#: src/podkop/tabs/diagnostic/initController.ts:35 +#: src/podkop/tabs/diagnostic/initController.ts:36 +#: src/podkop/tabs/diagnostic/initController.ts:37 +#: src/podkop/tabs/diagnostic/initController.ts:38 +#: src/podkop/tabs/diagnostic/initController.ts:39 +#: src/podkop/tabs/diagnostic/initController.ts:373 +msgid "unknown" +msgstr "" + #: src/podkop/api.ts:40 msgid "Unknown error" msgstr "" -#: src/validators/validateDns.ts:7 -msgid "DNS server address cannot be empty" +#: src/podkop/tabs/dashboard/initController.ts:237 +#: src/podkop/tabs/dashboard/initController.ts:268 +msgid "Uplink" +msgstr "" + +#: src/validators/validateProxyUrl.ts:27 +msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" +msgstr "" + +#: src/validators/validateUrl.ts:13 +msgid "URL must use one of the following protocols:" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27 +msgid "URLTest" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87 +msgid "URLTest Proxy Links" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299 +msgid "User Domain List Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311 +msgid "User Domains" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337 +msgid "User Domains List" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379 +msgid "User Subnet List Type" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391 +msgid "User Subnets" +msgstr "" + +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417 +msgid "User Subnets List" msgstr "" #: src/validators/validateDns.ts:11 @@ -56,254 +961,9 @@ msgstr "" msgid "Valid" msgstr "" -#: src/validators/validateDns.ts:20 -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "" - -#: src/validators/validateDomain.ts:18 -#: src/validators/validateDomain.ts:27 -msgid "Invalid domain address" -msgstr "" - -#: src/validators/validateIp.ts:11 -msgid "Invalid IP address" -msgstr "" - -#: src/validators/validateOutboundJson.ts:11 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:327 -msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields" -msgstr "" - -#: src/validators/validateOutboundJson.ts:19 -msgid "Invalid JSON format" -msgstr "" - -#: src/validators/validatePath.ts:7 -msgid "Path cannot be empty" -msgstr "" - -#: src/validators/validatePath.ts:22 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:90 -msgid "Invalid path format. Path must start with \"/\" and contain valid characters" -msgstr "" - -#: src/validators/validateProxyUrl.ts:27 -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:8 -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:16 -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:27 -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:37 -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:46 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:171 -msgid "Invalid Shadowsocks URL: missing method and password separator \":\"" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:58 -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:67 -msgid "Invalid Shadowsocks URL: missing server" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:76 -msgid "Invalid Shadowsocks URL: missing port" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:85 -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "" - -#: src/validators/validateShadowsocksUrl.ts:91 -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "" - -#: src/validators/validateSocksUrl.ts:10 -msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" - -#: src/validators/validateSocksUrl.ts:19 -msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" - -#: src/validators/validateSocksUrl.ts:34 -msgid "Invalid SOCKS URL: missing username" -msgstr "" - -#: src/validators/validateSocksUrl.ts:42 -msgid "Invalid SOCKS URL: missing host and port" -msgstr "" - -#: src/validators/validateSocksUrl.ts:51 -msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" - -#: src/validators/validateSocksUrl.ts:56 -msgid "Invalid SOCKS URL: missing port" -msgstr "" - -#: src/validators/validateSocksUrl.ts:63 -msgid "Invalid SOCKS URL: invalid port number" -msgstr "" - -#: src/validators/validateSocksUrl.ts:73 -msgid "Invalid SOCKS URL: invalid host format" -msgstr "" - -#: src/validators/validateSocksUrl.ts:77 -msgid "Invalid SOCKS URL: parsing failed" -msgstr "" - -#: src/validators/validateSubnet.ts:11 -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "" - -#: src/validators/validateSubnet.ts:18 -msgid "IP address 0.0.0.0 is not allowed" -msgstr "" - -#: src/validators/validateSubnet.ts:33 -msgid "CIDR must be between 0 and 32" -msgstr "" - -#: src/validators/validateTrojanUrl.ts:8 -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "" - -#: src/validators/validateTrojanUrl.ts:15 -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "" - -#: src/validators/validateTrojanUrl.ts:56 -msgid "Invalid Trojan URL: parsing failed" -msgstr "" - -#: src/validators/validateUrl.ts:13 -msgid "URL must use one of the following protocols:" -msgstr "" - -#: src/validators/validateUrl.ts:18 -msgid "Invalid URL format" -msgstr "" - -#: src/validators/validateVlessUrl.ts:109 -msgid "Invalid VLESS URL: parsing failed" -msgstr "" - -#: src/partials/modal/renderModal.ts:15 -msgid "Download" -msgstr "" - -#: src/partials/modal/renderModal.ts:20 -msgid "Copy" -msgstr "" - -#: src/partials/modal/renderModal.ts:26 -msgid "Close" -msgstr "" - -#: src/podkop/methods/custom/getDashboardSections.ts:117 -msgid "Fastest" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:235 -msgid "Traffic" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:237 -#: src/podkop/tabs/dashboard/initController.ts:268 -msgid "Uplink" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:238 -#: src/podkop/tabs/dashboard/initController.ts:272 -msgid "Downlink" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:265 -msgid "Traffic Total" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:301 -msgid "System info" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:304 -msgid "Active Connections" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:308 -msgid "Memory Usage" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:337 -msgid "Services info" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:340 -msgid "Podkop" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:342 -msgid "✔ Enabled" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:343 -msgid "✘ Disabled" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:351 -msgid "Sing-box" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:353 -msgid "✔ Running" -msgstr "" - -#: src/podkop/tabs/dashboard/initController.ts:354 -msgid "✘ Stopped" -msgstr "" - -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:55 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:63 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:71 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:79 -msgid "Not running" -msgstr "" - -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:95 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:103 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:111 -#: src/podkop/tabs/diagnostic/diagnostic.store.ts:119 -msgid "Queued" -msgstr "" - -#: src/podkop/tabs/diagnostic/initController.ts:34 -#: src/podkop/tabs/diagnostic/initController.ts:35 -#: src/podkop/tabs/diagnostic/initController.ts:36 -#: src/podkop/tabs/diagnostic/initController.ts:37 -#: src/podkop/tabs/diagnostic/initController.ts:38 -#: src/podkop/tabs/diagnostic/initController.ts:39 -#: src/podkop/tabs/diagnostic/initController.ts:373 -msgid "unknown" -msgstr "" - -#: src/podkop/tabs/diagnostic/initController.ts:218 -msgid "Global check" +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370 +#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449 +msgid "Validation errors:" msgstr "" #: src/podkop/tabs/diagnostic/initController.ts:248 @@ -311,1202 +971,14 @@ msgstr "" msgid "View logs" msgstr "" -#: src/podkop/tabs/diagnostic/initController.ts:278 -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116 -msgid "Show sing-box config" -msgstr "" - -#: src/podkop/tabs/diagnostic/initController.ts:394 -msgid "Outdated" -msgstr "" - -#: src/podkop/tabs/diagnostic/initController.ts:404 -msgid "Latest" -msgstr "" - -#: src/podkop/tabs/dashboard/partials/renderSections.ts:19 -msgid "Dashboard currently unavailable" -msgstr "" - -#: src/podkop/tabs/dashboard/partials/renderSections.ts:108 -msgid "Test latency" -msgstr "" - -#: src/podkop/tabs/dashboard/partials/renderWidget.ts:22 -msgid "Currently unavailable" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/contstants.ts:14 -msgid "DNS checks" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/contstants.ts:19 -msgid "Sing-box checks" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/contstants.ts:24 -msgid "Nftables checks" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/contstants.ts:29 -msgid "FakeIP checks" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:14 -msgid "Checking dns, please wait" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:26 -msgid "Cannot receive DNS checks result" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:64 -msgid "DNS checks passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72 -msgid "Bootsrap DNS" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:79 -msgid "Main DNS" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:84 -msgid "DNS on router" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:89 -msgid "DHCP has DNS server" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:14 -msgid "Checking FakeIP, please wait" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:44 -msgid "FakeIP checks passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:51 -msgid "FakeIP checks partially passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:57 -msgid "FakeIP checks failed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:73 -msgid "Router DNS is routed through sing-box" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:74 -msgid "Router DNS is not routed through sing-box" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:80 -msgid "Browser is using FakeIP correctly" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:81 -msgid "Browser is not using FakeIP" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:88 -msgid "Proxy traffic is routed via FakeIP" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:89 -msgid "Proxy traffic is not routed via FakeIP" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:12 -msgid "Checking nftables, please wait" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:27 -msgid "Cannot receive nftables checks result" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:74 -msgid "Nftables checks passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:75 -msgid "Nftables checks partially passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:80 -msgid "Table exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:85 -msgid "Rules mangle exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:90 -msgid "Rules mangle counters" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:95 -msgid "Rules mangle output exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:100 -msgid "Rules mangle output counters" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:105 -msgid "Rules proxy exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:110 -msgid "Rules proxy counters" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:116 -msgid "No other marking rules found" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:117 -msgid "Additional marking rules found" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:12 -msgid "Checking sing-box, please wait" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:24 -msgid "Cannot receive Sing-box checks result" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:66 -msgid "Sing-box checks passed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:71 -msgid "Sing-box installed" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:76 -msgid "Sing-box version >= 1.12.4" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:81 -msgid "Sing-box service exist" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:86 -msgid "Sing-box autostart disabled" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:91 -msgid "Sing-box process running" -msgstr "" - -#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:96 -msgid "Sing-box listening ports" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49 -msgid "Restart podkop" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:59 -msgid "Stop podkop" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:69 -msgid "Start podkop" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:79 -msgid "Disable autostart" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:89 -msgid "Enable autostart" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:98 -msgid "Get global check" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderCheckSection.ts:189 -msgid "Not implement yet" -msgstr "" - -#: src/podkop/tabs/diagnostic/partials/renderRunAction.ts:15 -msgid "Run Diagnostic" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:12 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:23 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:35 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:44 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:47 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:67 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:85 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:122 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:211 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:314 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:332 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:383 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:449 -msgid "Valid" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:14 -msgid "Invalid IP address" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:27 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:33 -msgid "Invalid domain address" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:41 -msgid "DNS server address cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:51 -msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:64 -msgid "URL must use one of the following protocols:" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:69 -msgid "Invalid URL format" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:78 -msgid "Path cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:102 -msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:107 -msgid "IP address 0.0.0.0 is not allowed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:118 -msgid "CIDR must be between 0 and 32" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:139 -msgid "Invalid Shadowsocks URL: must start with ss://" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:146 -msgid "Invalid Shadowsocks URL: must not contain spaces" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:154 -msgid "Invalid Shadowsocks URL: missing credentials" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:162 -msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:181 -msgid "Invalid Shadowsocks URL: missing server address" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:188 -msgid "Invalid Shadowsocks URL: missing server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:195 -msgid "Invalid Shadowsocks URL: missing port" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:202 -msgid "Invalid port number. Must be between 1 and 65535" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:208 -msgid "Invalid Shadowsocks URL: parsing failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:316 -msgid "Invalid VLESS URL: parsing failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:334 -msgid "Invalid JSON format" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:344 -msgid "Invalid Trojan URL: must start with trojan://" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:350 -msgid "Invalid Trojan URL: must not contain spaces" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:381 -msgid "Invalid Trojan URL: parsing failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:392 -msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:400 -msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:411 -msgid "Invalid SOCKS URL: missing username" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:418 -msgid "Invalid SOCKS URL: missing host and port" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:425 -msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:429 -msgid "Invalid SOCKS URL: missing port" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:435 -msgid "Invalid SOCKS URL: invalid port number" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:443 -msgid "Invalid SOCKS URL: invalid host format" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:447 -msgid "Invalid SOCKS URL: parsing failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:468 -msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:692 -msgid "Fastest" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:864 -msgid "HTTP error" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:875 -msgid "Unknown error" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:985 -msgid "DNS checks" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:990 -msgid "Sing-box checks" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:995 -msgid "Nftables checks" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1000 -msgid "FakeIP checks" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1048 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1056 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1064 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1072 -msgid "Not running" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1084 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1092 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1100 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1108 -msgid "Queued" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1578 -msgid "Dashboard currently unavailable" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1654 -msgid "Test latency" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:1683 -msgid "Currently unavailable" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2024 -msgid "Traffic" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2026 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2051 -msgid "Uplink" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2027 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2055 -msgid "Downlink" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2048 -msgid "Traffic Total" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2078 -msgid "System info" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2081 -msgid "Active Connections" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2085 -msgid "Memory Usage" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2108 -msgid "Services info" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2111 -msgid "Podkop" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112 -msgid "\\u2714 Enabled" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2112 -msgid "\\u2718 Disabled" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2118 -msgid "Sing-box" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119 -msgid "\\u2714 Running" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2119 -msgid "\\u2718 Stopped" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2365 -msgid "Checking dns, please wait" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2375 -msgid "Cannot receive DNS checks result" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2397 -msgid "DNS checks passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2405 -msgid "Bootsrap DNS" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2412 -msgid "Main DNS" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2417 -msgid "DNS on router" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2422 -msgid "DHCP has DNS server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2439 -msgid "Checking sing-box, please wait" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2449 -msgid "Cannot receive Sing-box checks result" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2471 -msgid "Sing-box checks passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2476 -msgid "Sing-box installed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2481 -msgid "Sing-box version >= 1.12.4" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2486 -msgid "Sing-box service exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2491 -msgid "Sing-box autostart disabled" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2496 -msgid "Sing-box process running" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2501 -msgid "Sing-box listening ports" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2518 -msgid "Checking nftables, please wait" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2530 -msgid "Cannot receive nftables checks result" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552 -msgid "Nftables checks passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2552 -msgid "Nftables checks partially passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2557 -msgid "Table exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2562 -msgid "Rules mangle exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2567 -msgid "Rules mangle counters" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2572 -msgid "Rules mangle output exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2577 -msgid "Rules mangle output counters" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2582 -msgid "Rules proxy exist" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2587 -msgid "Rules proxy counters" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592 -msgid "No other marking rules found" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2592 -msgid "Additional marking rules found" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2609 -msgid "Checking FakeIP, please wait" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2627 -msgid "FakeIP checks passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2633 -msgid "FakeIP checks partially passed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2638 -msgid "FakeIP checks failed" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651 -msgid "Router DNS is routed through sing-box" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2651 -msgid "Router DNS is not routed through sing-box" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656 -msgid "Browser is using FakeIP correctly" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2656 -msgid "Browser is not using FakeIP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662 -msgid "Proxy traffic is routed via FakeIP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:2662 -msgid "Proxy traffic is not routed via FakeIP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3288 -msgid "Successfully copied!" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3290 -msgid "Failed to copy!" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3306 -msgid "Download" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3311 -msgid "Copy" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3318 -msgid "Close" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3350 -msgid "Restart podkop" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3360 -msgid "Stop podkop" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3370 -msgid "Start podkop" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3380 -msgid "Disable autostart" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3390 -msgid "Enable autostart" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3399 -msgid "Get global check" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3408 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3840 -msgid "View logs" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3417 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3867 -msgid "Show sing-box config" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3577 -msgid "Not implement yet" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3587 -msgid "Run Diagnostic" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3651 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3652 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3653 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3654 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3655 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3656 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3950 -msgid "unknown" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3813 -msgid "Global check" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3969 -msgid "Outdated" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:3978 -msgid "Latest" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js:4389 -msgid "Operation timed out" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:26 -msgid "Podkop Settings" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:27 -msgid "Configuration for Podkop service" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36 -msgid "Sections" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:49 -msgid "Settings" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:65 -msgid "Diagnostics" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:80 -msgid "Dashboard" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:12 -msgid "Connection Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:13 -msgid "Select between VPN and Proxy connection methods for traffic routing" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22 -msgid "Configuration Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23 -msgid "Select how to configure the proxy" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25 -msgid "Connection URL" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26 -msgid "Outbound Config" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27 -msgid "URLTest" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34 -msgid "Proxy Configuration URL" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64 -msgid "Outbound Configuration" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65 -msgid "Enter complete outbound configuration in JSON format" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87 -msgid "URLTest Proxy Links" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110 -msgid "UDP over TCP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111 -msgid "Applicable for SOCKS and Shadowsocks proxy" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120 -msgid "Network Interface" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121 -msgid "Select network interface for VPN connection" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166 -msgid "Domain Resolver" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167 -msgid "Enable built-in DNS resolver for domains handled by this section" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12 -msgid "DNS Protocol Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177 -msgid "Select the DNS protocol type for the domain resolver" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15 -msgid "DNS over HTTPS (DoH)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16 -msgid "DNS over TLS (DoT)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17 -msgid "UDP (Unprotected DNS)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24 -msgid "DNS Server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25 -msgid "Select or enter DNS server address" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:193 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:217 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:28 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:51 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:254 -msgid "" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211 -msgid "Community Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212 -msgid "Select a predefined list for routing" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245 -msgid "Regional options cannot be used together" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247 msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." msgstr "" -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264 -msgid "Russia inside restrictions" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266 msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." msgstr "" -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299 -msgid "User Domain List Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300 -msgid "Select the list type for adding custom domains" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382 -msgid "Disabled" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383 -msgid "Dynamic List" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304 -msgid "Text List" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311 -msgid "User Domains" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312 -msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337 -msgid "User Domains List" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338 -msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356 -msgid "At least one valid domain must be specified. Comments-only content is not allowed." -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449 -msgid "Validation errors:" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379 -msgid "User Subnet List Type" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380 -msgid "Select the list type for adding custom subnets" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384 -msgid "Text List (comma/space/newline separated)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391 -msgid "User Subnets" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392 -msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417 -msgid "User Subnets List" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:418 -msgid "Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. \" + \"You can add comments using //" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437 -msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed." -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458 -msgid "Local Domain Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459 -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482 -msgid "Specify the path to the list file located on the router filesystem" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481 -msgid "Local Subnet Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504 -msgid "Remote Domain Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505 -msgid "Specify remote URLs to download and use domain lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527 -msgid "Remote Subnet Lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528 -msgid "Specify remote URLs to download and use subnet lists" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550 -msgid "Fully Routed IPs" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551 -msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575 -msgid "Enable Mixed Proxy" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576 -msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586 -msgid "Mixed Proxy Port" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:587 -msgid "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" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:13 -msgid "Select DNS protocol to use" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:45 -msgid "Bootstrap DNS server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46 -msgid "The DNS server used to look up the IP address of an upstream DNS server" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:68 -msgid "DNS Rewrite TTL" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69 -msgid "Time in seconds for DNS record caching (default: 60)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:75 -msgid "TTL value cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:80 -msgid "TTL must be a positive number" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89 -msgid "Source Network Interface" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90 -msgid "Select the network interface from which the traffic will originate" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:126 -msgid "Enable Output Network Interface" -msgstr "" - #: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:127 msgid "You can select Output Network Interface, by default autodetect" msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:135 -msgid "Output Network Interface" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:136 -msgid "Select the network interface to which the traffic will originate" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:182 -msgid "Interface Monitoring" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:183 -msgid "Interface monitoring for Bad WAN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:191 -msgid "Monitored Interfaces" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:192 -msgid "Select the WAN interfaces to be monitored" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:214 -msgid "Interface Monitoring Delay" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:215 -msgid "Delay in milliseconds before reloading podkop after interface UP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:222 -msgid "Delay value cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:230 -msgid "Enable YACD" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:239 -msgid "Disable QUIC" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:240 -msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:250 -msgid "List Update Frequency" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:251 -msgid "Select how often the domain or subnet lists are updated automatically" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:262 -msgid "Download Lists via Proxy/VPN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:263 -msgid "Downloading all lists via main Proxy/VPN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:271 -msgid "Download Lists via specific proxy section" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:272 -msgid "Downloading all lists via specific Proxy/VPN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:300 -msgid "Dont Touch My DHCP!" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:301 -msgid "Podkop will not modify your DHCP configuration" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:309 -msgid "Config File Path" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:310 -msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:322 -msgid "Cache File Path" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:323 -msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336 -msgid "Cache file path cannot be empty" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:340 -msgid "Path must be absolute (start with /)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:344 -msgid "Path must end with cache.db" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349 -msgid "Path must contain at least one directory (like /tmp/cache.db)" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:358 -msgid "Exclude NTP" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:359 -msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:369 -msgid "Routing Excluded IPs" -msgstr "" - -#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370 -msgid "Specify a local IP address to be excluded from routing" -msgstr "" From e8a372594819be0f66207ba494a6ce99dbf2f470 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 21 Oct 2025 23:02:31 +0300 Subject: [PATCH 108/121] feat: translate all keys --- fe-app-podkop/locales/podkop.pot | 4 +- fe-app-podkop/locales/podkop.ru.po | 118 ++++++++++++------------ luci-app-podkop/po/ru/podkop.po | 108 +++++++++++----------- luci-app-podkop/po/templates/podkop.pot | 4 +- 4 files changed, 117 insertions(+), 117 deletions(-) diff --git a/fe-app-podkop/locales/podkop.pot b/fe-app-podkop/locales/podkop.pot index f8c7d1c..bfe7be9 100644 --- a/fe-app-podkop/locales/podkop.pot +++ b/fe-app-podkop/locales/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 19:33+0300\n" -"PO-Revision-Date: 2025-10-21 19:33+0300\n" +"POT-Creation-Date: 2025-10-21 19:45+0300\n" +"PO-Revision-Date: 2025-10-21 19:45+0300\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" diff --git a/fe-app-podkop/locales/podkop.ru.po b/fe-app-podkop/locales/podkop.ru.po index ea6eee7..fef9d11 100644 --- a/fe-app-podkop/locales/podkop.ru.po +++ b/fe-app-podkop/locales/podkop.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 22:33+0300\n" -"PO-Revision-Date: 2025-10-21 22:33+0300\n" +"POT-Creation-Date: 2025-10-21 22:45+0300\n" +"PO-Revision-Date: 2025-10-21 22:45+0300\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -147,7 +147,7 @@ msgid "DNS checks" msgstr "DNS проверки" msgid "DNS checks passed" -msgstr "DNS проверки успшено завершены" +msgstr "DNS проверки успешно завершены" msgid "DNS on router" msgstr "DNS на роутере" @@ -393,22 +393,22 @@ msgid "Network Interface" msgstr "Сетевой интерфейс" msgid "Nftables checks" -msgstr "" +msgstr "Проверки Nftables" msgid "Nftables checks partially passed" -msgstr "" +msgstr "Проверки Nftables частично пройдена" msgid "Nftables checks passed" -msgstr "" +msgstr "Nftables проверки успешно завершены" msgid "No other marking rules found" -msgstr "" +msgstr "Другие правила маркировки не найдены" msgid "Not implement yet" -msgstr "" +msgstr "Ещё не реализовано" msgid "Not running" -msgstr "" +msgstr "Не запущено" msgid "Operation timed out" msgstr "Время ожидания истекло" @@ -423,10 +423,10 @@ msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_por msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" msgid "Outdated" -msgstr "" +msgstr "Устаревшая" msgid "Output Network Interface" -msgstr "" +msgstr "Выходной сетевой интерфейс" msgid "Path cannot be empty" msgstr "Путь не может быть пустым" @@ -444,22 +444,22 @@ msgid "Podkop" msgstr "Podkop" msgid "Podkop Settings" -msgstr "" +msgstr "Настройки podkop" msgid "Podkop will not modify your DHCP configuration" -msgstr "" +msgstr "Podkop не будет изменять вашу конфигурацию DHCP." msgid "Proxy Configuration URL" msgstr "URL конфигурации прокси" msgid "Proxy traffic is not routed via FakeIP" -msgstr "" +msgstr "Прокси-трафик не маршрутизируется через FakeIP" msgid "Proxy traffic is routed via FakeIP" -msgstr "" +msgstr "Прокси-трафик направляется через FakeIP" msgid "Queued" -msgstr "" +msgstr "В очереди" msgid "Regional options cannot be used together" msgstr "Нельзя использовать несколько региональных опций одновременно" @@ -471,46 +471,46 @@ msgid "Remote Subnet Lists" msgstr "Удалённые списки подсетей" msgid "Restart podkop" -msgstr "" +msgstr "Перезапустить Podkop" msgid "Router DNS is not routed through sing-box" -msgstr "" +msgstr "DNS роутера не проходит через sing-box" msgid "Router DNS is routed through sing-box" -msgstr "" +msgstr "DNS роутера проходит через sing-box" msgid "Routing Excluded IPs" -msgstr "" +msgstr "Исключённые из маршрутизации IP-адреса" msgid "Rules mangle counters" -msgstr "" +msgstr "Счётчики правил mangle" msgid "Rules mangle exist" -msgstr "" +msgstr "Правила mangle существуют" msgid "Rules mangle output counters" -msgstr "" +msgstr "Счётчики правил mangle output" msgid "Rules mangle output exist" -msgstr "" +msgstr "Правила mangle output существуют" msgid "Rules proxy counters" -msgstr "" +msgstr "Счётчики правил proxy" msgid "Rules proxy exist" -msgstr "" +msgstr "Правила прокси существуют" msgid "Run Diagnostic" -msgstr "" +msgstr "Запустить диагностику" msgid "Russia inside restrictions" msgstr "Ограничения Russia inside" msgid "Sections" -msgstr "" +msgstr "Секции" msgid "Select a predefined list for routing" -msgstr "" +msgstr "Выберите предопределенный список для маршрутизации" msgid "Select between VPN and Proxy connection methods for traffic routing" msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" @@ -519,7 +519,7 @@ msgid "Select DNS protocol to use" msgstr "Выберите протокол DNS" msgid "Select how often the domain or subnet lists are updated automatically" -msgstr "" +msgstr "Выберите частоту автоматического обновления списков доменов или подсетей." msgid "Select how to configure the proxy" msgstr "Выберите способ настройки прокси" @@ -540,16 +540,16 @@ msgid "Select the DNS protocol type for the domain resolver" msgstr "Выберите тип протокола DNS для резолвера доменов" msgid "Select the list type for adding custom domains" -msgstr "" +msgstr "Выберите тип списка для добавления пользовательских доменов" msgid "Select the list type for adding custom subnets" -msgstr "" +msgstr "Выберите тип списка для добавления пользовательских подсетей" msgid "Select the network interface from which the traffic will originate" msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" msgid "Select the network interface to which the traffic will originate" -msgstr "" +msgstr "Выберите сетевой интерфейс, на который будет поступать трафик." msgid "Select the WAN interfaces to be monitored" msgstr "Выберите WAN интерфейсы для мониторинга" @@ -558,73 +558,73 @@ msgid "Services info" msgstr "Информация о сервисах" msgid "Settings" -msgstr "" +msgstr "Настройки" msgid "Show sing-box config" -msgstr "" +msgstr "Показать sing-box конфигурацию" msgid "Sing-box" msgstr "Sing-box" msgid "Sing-box autostart disabled" -msgstr "" +msgstr "Автостарт sing-box отключен" msgid "Sing-box checks" -msgstr "" +msgstr "Sing-box проверки" msgid "Sing-box checks passed" -msgstr "" +msgstr "Sing-box проверки успешно завершены" msgid "Sing-box installed" -msgstr "" +msgstr "Sing-box установлен" msgid "Sing-box listening ports" -msgstr "" +msgstr "Sing-box слушает порты" msgid "Sing-box process running" -msgstr "" +msgstr "Процесс sing-box запущен" msgid "Sing-box service exist" -msgstr "" +msgstr "Сервис sing-box существует" msgid "Sing-box version >= 1.12.4" -msgstr "" +msgstr "Версия sing-box >= 1.12.4" msgid "Source Network Interface" msgstr "Сетевой интерфейс источника" msgid "Specify a local IP address to be excluded from routing" -msgstr "" +msgstr "Укажите локальный IP-адрес, который следует исключить из маршрутизации." msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" -msgstr "" +msgstr "Укажите локальные IP-адреса или подсети, трафик которых всегда будет направляться через настроенный маршрут." msgid "Specify remote URLs to download and use domain lists" -msgstr "" +msgstr "Укажите удаленные URL-адреса для загрузки и использования списков доменов." msgid "Specify remote URLs to download and use subnet lists" -msgstr "" +msgstr "Укажите удаленные URL-адреса для загрузки и использования списков подсетей." msgid "Specify the path to the list file located on the router filesystem" -msgstr "" +msgstr "Укажите путь к файлу списка, расположенному в файловой системе маршрутизатора." msgid "Start podkop" -msgstr "" +msgstr "Запустить podkop" msgid "Stop podkop" -msgstr "" +msgstr "Остановить podkop" msgid "Successfully copied!" -msgstr "" +msgstr "Успешно скопировано!" msgid "System info" msgstr "Системная информация" msgid "Table exist" -msgstr "" +msgstr "Таблица существует" msgid "Test latency" -msgstr "" +msgstr "Измерить задержки" msgid "Text List" msgstr "Текстовый список" @@ -654,10 +654,10 @@ msgid "UDP (Unprotected DNS)" msgstr "UDP (Незащищённый DNS)" msgid "UDP over TCP" -msgstr "" +msgstr "UDP через TCP" msgid "unknown" -msgstr "" +msgstr "неизвестно" msgid "Unknown error" msgstr "Неизвестная ошибка" @@ -666,7 +666,7 @@ msgid "Uplink" msgstr "Исходящий" msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" +msgstr "URL должен начинаться с vless://, ss://, trojan:// или socks4/5://" msgid "URL must use one of the following protocols:" msgstr "URL должен использовать один из следующих протоколов:" @@ -702,13 +702,13 @@ msgid "Validation errors:" msgstr "Ошибки валидации:" msgid "View logs" -msgstr "" +msgstr "Посмотреть логи" msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." -msgstr "" +msgstr "Предупреждение: Russia inside может быть использован только с %s. %s уже есть в Russia inside и будет удален из выбранных." msgid "You can select Output Network Interface, by default autodetect" -msgstr "" +msgstr "Вы можете выбрать выходной сетевой интерфейс, по умолчанию он определяется автоматически." diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index 967c3ef..95f3786 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 22:33+0300\n" -"PO-Revision-Date: 2025-10-21 22:33+0300\n" +"POT-Creation-Date: 2025-10-21 22:45+0300\n" +"PO-Revision-Date: 2025-10-21 22:45+0300\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -36,7 +36,7 @@ msgid "Additional marking rules found" msgstr "Найдены дополнительные правила маркировки" msgid "Applicable for SOCKS and Shadowsocks proxy" -msgstr "" +msgstr "Применимо для SOCKS и Shadowsocks прокси" msgid "At least one valid domain must be specified. Comments-only content is not allowed." msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается." @@ -63,25 +63,25 @@ msgid "Cache file path cannot be empty" msgstr "Путь к файлу кэша не может быть пустым" msgid "Cannot receive DNS checks result" -msgstr "" +msgstr "Не удалось получить результаты проверки DNS" msgid "Cannot receive nftables checks result" -msgstr "" +msgstr "Не удалось получить результаты проверки nftables" msgid "Cannot receive Sing-box checks result" -msgstr "" +msgstr "Не удалось получить результаты проверки Sing-box" msgid "Checking dns, please wait" -msgstr "" +msgstr "Проверка dns, пожалуйста подождите" msgid "Checking FakeIP, please wait" -msgstr "" +msgstr "Проверка FakeIP, пожалуйста подождите" msgid "Checking nftables, please wait" -msgstr "" +msgstr "Проверка nftables, пожалуйста подождите" msgid "Checking sing-box, please wait" -msgstr "" +msgstr "Проверка sing-box, пожалуйста подождите" msgid "CIDR must be between 0 and 32" msgstr "CIDR должен быть между 0 и 32" @@ -96,7 +96,7 @@ msgid "Config File Path" msgstr "Путь к файлу конфигурации" msgid "Configuration for Podkop service" -msgstr "" +msgstr "Настройки сервиса Podkop" msgid "Configuration Type" msgstr "Тип конфигурации" @@ -108,7 +108,7 @@ msgid "Connection URL" msgstr "URL подключения" msgid "Copy" -msgstr "" +msgstr "Копировать" msgid "Currently unavailable" msgstr "Временно недоступно" @@ -126,31 +126,31 @@ msgid "Delay value cannot be empty" msgstr "Значение задержки не может быть пустым" msgid "DHCP has DNS server" -msgstr "" +msgstr "DHCP содержит DNS сервер" msgid "Diagnostics" msgstr "Диагностика" msgid "Disable autostart" -msgstr "" +msgstr "Отключить автостарт" msgid "Disable QUIC" -msgstr "" +msgstr "Отключить QUIC" msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming" -msgstr "" +msgstr "Отключить QUIC протокол для улучшения совместимости или исправления видео стриминга" msgid "Disabled" msgstr "Отключено" msgid "DNS checks" -msgstr "" +msgstr "DNS проверки" msgid "DNS checks passed" -msgstr "" +msgstr "DNS проверки успшено завершены" msgid "DNS on router" -msgstr "" +msgstr "DNS на роутере" msgid "DNS over HTTPS (DoH)" msgstr "DNS через HTTPS (DoH)" @@ -174,19 +174,19 @@ msgid "Domain Resolver" msgstr "Резолвер доменов" msgid "Dont Touch My DHCP!" -msgstr "" +msgstr "Dont Touch My DHCP!" msgid "Downlink" msgstr "Входящий" msgid "Download" -msgstr "" +msgstr "Скачать" msgid "Download Lists via Proxy/VPN" -msgstr "" +msgstr "Скачивать списки через Proxy/VPN" msgid "Download Lists via specific proxy section" -msgstr "" +msgstr "Скачивать списки через выбранную секцию" msgid "Downloading all lists via main Proxy/VPN" msgstr "Загрузка всех списков через основной прокси/VPN" @@ -198,64 +198,64 @@ msgid "Dynamic List" msgstr "Динамический список" msgid "Enable autostart" -msgstr "" +msgstr "Включить автостарт" msgid "Enable built-in DNS resolver for domains handled by this section" msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе" msgid "Enable Mixed Proxy" -msgstr "" +msgstr "Включить смешанный прокси" msgid "Enable Output Network Interface" -msgstr "" +msgstr "Включить выходной сетевой интерфейс" msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies" -msgstr "" +msgstr "Включить смешанный прокси-сервер, разрешив этому разделу маршрутизировать трафик как через HTTP, так и через SOCKS-прокси." msgid "Enable YACD" -msgstr "" +msgstr "Включить YACD" msgid "Enter complete outbound configuration in JSON format" msgstr "Введите полную конфигурацию исходящего соединения в формате JSON" msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //" -msgstr "" +msgstr "Введите доменные имена, разделяя их запятыми, пробелами или переносами строк. Вы можете добавлять комментарии, используя //" msgid "Enter domain names without protocols, e.g. example.com or sub.example.com" -msgstr "" +msgstr "Введите доменные имена без протоколов, например example.com или sub.example.com" msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses" -msgstr "" +msgstr "Введите подсети в нотации CIDR (например, 103.21.244.0/22) или отдельные IP-адреса" msgid "Exclude NTP" msgstr "Исключить NTP" msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN" -msgstr "" +msgstr "Исключите трафик протокола NTP из туннеля, чтобы предотвратить его маршрутизацию через прокси-сервер или VPN." msgid "Failed to copy!" -msgstr "" +msgstr "Не удалось скопировать!" msgid "FakeIP checks" -msgstr "" +msgstr "Проверка FakeIP" msgid "FakeIP checks failed" -msgstr "" +msgstr "Проверки FakeIP не пройдены" msgid "FakeIP checks partially passed" -msgstr "" +msgstr "Проверка FakeIP частично пройдена" msgid "FakeIP checks passed" -msgstr "" +msgstr "Проверки FakeIP пройдены" msgid "Fastest" msgstr "Самый быстрый" msgid "Fully Routed IPs" -msgstr "" +msgstr "Полностью маршрутизированные IP-адреса" msgid "Get global check" -msgstr "" +msgstr "Получить глобальную проверку" msgid "Global check" msgstr "Глобальная проверка" @@ -264,13 +264,13 @@ msgid "HTTP error" msgstr "Ошибка HTTP" msgid "Interface Monitoring" -msgstr "" +msgstr "Мониторинг интерфейса" msgid "Interface Monitoring Delay" msgstr "Задержка при мониторинге интерфейсов" msgid "Interface monitoring for Bad WAN" -msgstr "" +msgstr "Мониторинг интерфейса для Bad WAN" msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH" @@ -321,31 +321,31 @@ msgid "Invalid Shadowsocks URL: parsing failed" msgstr "Неверный URL Shadowsocks: ошибка разбора" msgid "Invalid SOCKS URL: invalid host format" -msgstr "" +msgstr "Неверный URL SOCKS: неверный формат хоста" msgid "Invalid SOCKS URL: invalid port number" -msgstr "" +msgstr "Неверный URL SOCKS: неверный номер порта" msgid "Invalid SOCKS URL: missing host and port" -msgstr "" +msgstr "Неверный URL SOCKS: отсутствует хост и порт" msgid "Invalid SOCKS URL: missing hostname or IP" -msgstr "" +msgstr "Неверный URL SOCKS: отсутствует имя хоста или IP-адрес" msgid "Invalid SOCKS URL: missing port" -msgstr "" +msgstr "Неверный URL SOCKS: отсутствует порт" msgid "Invalid SOCKS URL: missing username" -msgstr "" +msgstr "Неверный URL SOCKS: отсутствует имя пользователя" msgid "Invalid SOCKS URL: must not contain spaces" -msgstr "" +msgstr "Неверный URL SOCKS: не должен содержать пробелов" msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://" -msgstr "" +msgstr "Неверный URL-адрес SOCKS: должен начинаться с socks4://, socks4a:// или socks5://" msgid "Invalid SOCKS URL: parsing failed" -msgstr "" +msgstr "Неверный URL SOCKS: парсинг не удался" msgid "Invalid Trojan URL: must not contain spaces" msgstr "Неверный URL Trojan: не должен содержать пробелов" @@ -366,7 +366,7 @@ msgid "IP address 0.0.0.0 is not allowed" msgstr "IP-адрес 0.0.0.0 не допускается" msgid "Latest" -msgstr "" +msgstr "Последняя" msgid "List Update Frequency" msgstr "Частота обновления списков" @@ -378,16 +378,16 @@ msgid "Local Subnet Lists" msgstr "Локальные списки подсетей" msgid "Main DNS" -msgstr "" +msgstr "Основной DNS" msgid "Memory Usage" msgstr "Использование памяти" msgid "Mixed Proxy Port" -msgstr "" +msgstr "Порт смешанного прокси" msgid "Monitored Interfaces" -msgstr "" +msgstr "Наблюдаемые интерфейсы" msgid "Network Interface" msgstr "Сетевой интерфейс" diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index f8c7d1c..bfe7be9 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 19:33+0300\n" -"PO-Revision-Date: 2025-10-21 19:33+0300\n" +"POT-Creation-Date: 2025-10-21 19:45+0300\n" +"PO-Revision-Date: 2025-10-21 19:45+0300\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" From ac82cc17701b4bd0aa51b80227d146478a887776 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 21 Oct 2025 23:03:20 +0300 Subject: [PATCH 109/121] feat: final translations check --- fe-app-podkop/locales/podkop.pot | 4 +- fe-app-podkop/locales/podkop.ru.po | 4 +- luci-app-podkop/po/ru/podkop.po | 118 ++++++++++++------------ luci-app-podkop/po/templates/podkop.pot | 4 +- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/fe-app-podkop/locales/podkop.pot b/fe-app-podkop/locales/podkop.pot index bfe7be9..75fe1bb 100644 --- a/fe-app-podkop/locales/podkop.pot +++ b/fe-app-podkop/locales/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 19:45+0300\n" -"PO-Revision-Date: 2025-10-21 19:45+0300\n" +"POT-Creation-Date: 2025-10-21 20:02+0300\n" +"PO-Revision-Date: 2025-10-21 20:02+0300\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" diff --git a/fe-app-podkop/locales/podkop.ru.po b/fe-app-podkop/locales/podkop.ru.po index fef9d11..1754091 100644 --- a/fe-app-podkop/locales/podkop.ru.po +++ b/fe-app-podkop/locales/podkop.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 22:45+0300\n" -"PO-Revision-Date: 2025-10-21 22:45+0300\n" +"POT-Creation-Date: 2025-10-21 23:02+0300\n" +"PO-Revision-Date: 2025-10-21 23:02+0300\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" diff --git a/luci-app-podkop/po/ru/podkop.po b/luci-app-podkop/po/ru/podkop.po index 95f3786..1754091 100644 --- a/luci-app-podkop/po/ru/podkop.po +++ b/luci-app-podkop/po/ru/podkop.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 22:45+0300\n" -"PO-Revision-Date: 2025-10-21 22:45+0300\n" +"POT-Creation-Date: 2025-10-21 23:02+0300\n" +"PO-Revision-Date: 2025-10-21 23:02+0300\n" "Last-Translator: divocat\n" "Language-Team: none\n" "Language: ru\n" @@ -147,7 +147,7 @@ msgid "DNS checks" msgstr "DNS проверки" msgid "DNS checks passed" -msgstr "DNS проверки успшено завершены" +msgstr "DNS проверки успешно завершены" msgid "DNS on router" msgstr "DNS на роутере" @@ -393,22 +393,22 @@ msgid "Network Interface" msgstr "Сетевой интерфейс" msgid "Nftables checks" -msgstr "" +msgstr "Проверки Nftables" msgid "Nftables checks partially passed" -msgstr "" +msgstr "Проверки Nftables частично пройдена" msgid "Nftables checks passed" -msgstr "" +msgstr "Nftables проверки успешно завершены" msgid "No other marking rules found" -msgstr "" +msgstr "Другие правила маркировки не найдены" msgid "Not implement yet" -msgstr "" +msgstr "Ещё не реализовано" msgid "Not running" -msgstr "" +msgstr "Не запущено" msgid "Operation timed out" msgstr "Время ожидания истекло" @@ -423,10 +423,10 @@ msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_por msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\"" msgid "Outdated" -msgstr "" +msgstr "Устаревшая" msgid "Output Network Interface" -msgstr "" +msgstr "Выходной сетевой интерфейс" msgid "Path cannot be empty" msgstr "Путь не может быть пустым" @@ -444,22 +444,22 @@ msgid "Podkop" msgstr "Podkop" msgid "Podkop Settings" -msgstr "" +msgstr "Настройки podkop" msgid "Podkop will not modify your DHCP configuration" -msgstr "" +msgstr "Podkop не будет изменять вашу конфигурацию DHCP." msgid "Proxy Configuration URL" msgstr "URL конфигурации прокси" msgid "Proxy traffic is not routed via FakeIP" -msgstr "" +msgstr "Прокси-трафик не маршрутизируется через FakeIP" msgid "Proxy traffic is routed via FakeIP" -msgstr "" +msgstr "Прокси-трафик направляется через FakeIP" msgid "Queued" -msgstr "" +msgstr "В очереди" msgid "Regional options cannot be used together" msgstr "Нельзя использовать несколько региональных опций одновременно" @@ -471,46 +471,46 @@ msgid "Remote Subnet Lists" msgstr "Удалённые списки подсетей" msgid "Restart podkop" -msgstr "" +msgstr "Перезапустить Podkop" msgid "Router DNS is not routed through sing-box" -msgstr "" +msgstr "DNS роутера не проходит через sing-box" msgid "Router DNS is routed through sing-box" -msgstr "" +msgstr "DNS роутера проходит через sing-box" msgid "Routing Excluded IPs" -msgstr "" +msgstr "Исключённые из маршрутизации IP-адреса" msgid "Rules mangle counters" -msgstr "" +msgstr "Счётчики правил mangle" msgid "Rules mangle exist" -msgstr "" +msgstr "Правила mangle существуют" msgid "Rules mangle output counters" -msgstr "" +msgstr "Счётчики правил mangle output" msgid "Rules mangle output exist" -msgstr "" +msgstr "Правила mangle output существуют" msgid "Rules proxy counters" -msgstr "" +msgstr "Счётчики правил proxy" msgid "Rules proxy exist" -msgstr "" +msgstr "Правила прокси существуют" msgid "Run Diagnostic" -msgstr "" +msgstr "Запустить диагностику" msgid "Russia inside restrictions" msgstr "Ограничения Russia inside" msgid "Sections" -msgstr "" +msgstr "Секции" msgid "Select a predefined list for routing" -msgstr "" +msgstr "Выберите предопределенный список для маршрутизации" msgid "Select between VPN and Proxy connection methods for traffic routing" msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика" @@ -519,7 +519,7 @@ msgid "Select DNS protocol to use" msgstr "Выберите протокол DNS" msgid "Select how often the domain or subnet lists are updated automatically" -msgstr "" +msgstr "Выберите частоту автоматического обновления списков доменов или подсетей." msgid "Select how to configure the proxy" msgstr "Выберите способ настройки прокси" @@ -540,16 +540,16 @@ msgid "Select the DNS protocol type for the domain resolver" msgstr "Выберите тип протокола DNS для резолвера доменов" msgid "Select the list type for adding custom domains" -msgstr "" +msgstr "Выберите тип списка для добавления пользовательских доменов" msgid "Select the list type for adding custom subnets" -msgstr "" +msgstr "Выберите тип списка для добавления пользовательских подсетей" msgid "Select the network interface from which the traffic will originate" msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик" msgid "Select the network interface to which the traffic will originate" -msgstr "" +msgstr "Выберите сетевой интерфейс, на который будет поступать трафик." msgid "Select the WAN interfaces to be monitored" msgstr "Выберите WAN интерфейсы для мониторинга" @@ -558,73 +558,73 @@ msgid "Services info" msgstr "Информация о сервисах" msgid "Settings" -msgstr "" +msgstr "Настройки" msgid "Show sing-box config" -msgstr "" +msgstr "Показать sing-box конфигурацию" msgid "Sing-box" msgstr "Sing-box" msgid "Sing-box autostart disabled" -msgstr "" +msgstr "Автостарт sing-box отключен" msgid "Sing-box checks" -msgstr "" +msgstr "Sing-box проверки" msgid "Sing-box checks passed" -msgstr "" +msgstr "Sing-box проверки успешно завершены" msgid "Sing-box installed" -msgstr "" +msgstr "Sing-box установлен" msgid "Sing-box listening ports" -msgstr "" +msgstr "Sing-box слушает порты" msgid "Sing-box process running" -msgstr "" +msgstr "Процесс sing-box запущен" msgid "Sing-box service exist" -msgstr "" +msgstr "Сервис sing-box существует" msgid "Sing-box version >= 1.12.4" -msgstr "" +msgstr "Версия sing-box >= 1.12.4" msgid "Source Network Interface" msgstr "Сетевой интерфейс источника" msgid "Specify a local IP address to be excluded from routing" -msgstr "" +msgstr "Укажите локальный IP-адрес, который следует исключить из маршрутизации." msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route" -msgstr "" +msgstr "Укажите локальные IP-адреса или подсети, трафик которых всегда будет направляться через настроенный маршрут." msgid "Specify remote URLs to download and use domain lists" -msgstr "" +msgstr "Укажите удаленные URL-адреса для загрузки и использования списков доменов." msgid "Specify remote URLs to download and use subnet lists" -msgstr "" +msgstr "Укажите удаленные URL-адреса для загрузки и использования списков подсетей." msgid "Specify the path to the list file located on the router filesystem" -msgstr "" +msgstr "Укажите путь к файлу списка, расположенному в файловой системе маршрутизатора." msgid "Start podkop" -msgstr "" +msgstr "Запустить podkop" msgid "Stop podkop" -msgstr "" +msgstr "Остановить podkop" msgid "Successfully copied!" -msgstr "" +msgstr "Успешно скопировано!" msgid "System info" msgstr "Системная информация" msgid "Table exist" -msgstr "" +msgstr "Таблица существует" msgid "Test latency" -msgstr "" +msgstr "Измерить задержки" msgid "Text List" msgstr "Текстовый список" @@ -654,10 +654,10 @@ msgid "UDP (Unprotected DNS)" msgstr "UDP (Незащищённый DNS)" msgid "UDP over TCP" -msgstr "" +msgstr "UDP через TCP" msgid "unknown" -msgstr "" +msgstr "неизвестно" msgid "Unknown error" msgstr "Неизвестная ошибка" @@ -666,7 +666,7 @@ msgid "Uplink" msgstr "Исходящий" msgid "URL must start with vless://, ss://, trojan://, or socks4/5://" -msgstr "" +msgstr "URL должен начинаться с vless://, ss://, trojan:// или socks4/5://" msgid "URL must use one of the following protocols:" msgstr "URL должен использовать один из следующих протоколов:" @@ -702,13 +702,13 @@ msgid "Validation errors:" msgstr "Ошибки валидации:" msgid "View logs" -msgstr "" +msgstr "Посмотреть логи" msgid "Warning: %s cannot be used together with %s. Previous selections have been removed." msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены." msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection." -msgstr "" +msgstr "Предупреждение: Russia inside может быть использован только с %s. %s уже есть в Russia inside и будет удален из выбранных." msgid "You can select Output Network Interface, by default autodetect" -msgstr "" +msgstr "Вы можете выбрать выходной сетевой интерфейс, по умолчанию он определяется автоматически." diff --git a/luci-app-podkop/po/templates/podkop.pot b/luci-app-podkop/po/templates/podkop.pot index bfe7be9..75fe1bb 100644 --- a/luci-app-podkop/po/templates/podkop.pot +++ b/luci-app-podkop/po/templates/podkop.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PODKOP\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-21 19:45+0300\n" -"PO-Revision-Date: 2025-10-21 19:45+0300\n" +"POT-Creation-Date: 2025-10-21 20:02+0300\n" +"PO-Revision-Date: 2025-10-21 20:02+0300\n" "Last-Translator: divocat \n" "Language-Team: LANGUAGE \n" "Language: \n" From 2fb38286bde9c45876663f5d8c6f66a4387cb5a6 Mon Sep 17 00:00:00 2001 From: Kirill Sobakin Date: Thu, 23 Oct 2025 11:03:49 +0300 Subject: [PATCH 110/121] fix: for quick start --- podkop/files/etc/config/podkop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/podkop/files/etc/config/podkop b/podkop/files/etc/config/podkop index efff886..f7224fb 100644 --- a/podkop/files/etc/config/podkop +++ b/podkop/files/etc/config/podkop @@ -5,7 +5,7 @@ config settings 'settings' option dns_rewrite_ttl '60' list source_network_interfaces 'br-lan' option enable_output_network_interface '0' - #option output_network_interface 'eth1' + #option output_network_interface 'wan' option enable_badwan_interface_monitoring '0' #list badwan_monitored_interfaces 'wan' #option badwan_reload_delay '2000' @@ -25,7 +25,7 @@ config section 'main' option proxy_config_type 'url' option proxy_string '' option enable_udp_over_tcp '0' - #list community_lists 'russia_inside' + list community_lists 'russia_inside' #option user_domain_list_type 'dynamic' #list user_domains '2ip.ru' #option user_subnet_list_type 'dynamic' From a3ac01478f5d5129fc22e9e2e717f78ed195b03c Mon Sep 17 00:00:00 2001 From: Kirill Sobakin Date: Thu, 23 Oct 2025 11:04:23 +0300 Subject: [PATCH 111/121] fix: rm check_sing_box_connections --- podkop/files/usr/bin/podkop | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 8ec9ade..1d466fa 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1652,25 +1652,6 @@ check_dnsmasq() { done } -check_sing_box_connections() { - nolog "Checking sing-box connections..." - - if ! command -v netstat > /dev/null 2>&1; then - nolog "netstat is not installed" - return 1 - fi - - local connections=$(netstat -tuanp | grep sing-box) - if [ -z "$connections" ]; then - nolog "No active sing-box connections found" - return 1 - fi - - echo "$connections" | while read -r line; do - nolog "$line" - done -} - check_logs() { if ! command -v logread > /dev/null 2>&1; then nolog "Error: logread command not found" @@ -2562,7 +2543,6 @@ Available commands: check_sing_box Check sing-box installation and status check_github Check GitHub connectivity check_logs Show podkop logs from system journal - check_sing_box_connections Show active sing-box connections check_sing_box_logs Show sing-box logs check_dnsmasq Check DNSMasq configuration check_fakeip Test FakeIP on router @@ -2617,9 +2597,6 @@ check_github) check_logs) check_logs ;; -check_sing_box_connections) - check_sing_box_connections - ;; check_sing_box_logs) check_sing_box_logs ;; From 5573fce1b12651bebd1a149829fdcdd4649f761e Mon Sep 17 00:00:00 2001 From: Andrey Petelin Date: Thu, 23 Oct 2025 14:05:42 +0500 Subject: [PATCH 112/121] fix: disable auto_detect_interface when output_network_interface is specified --- podkop/files/usr/bin/podkop | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 1d466fa..37414d4 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -737,8 +737,12 @@ sing_box_configure_route() { local output_network_interface config_get output_network_interface "settings" "output_network_interface" - config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" true "$SB_DNS_SERVER_TAG" \ - "$output_network_interface") + if [ -z "$output_network_interface" ]; then + config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" true "$SB_DNS_SERVER_TAG") + else + config=$(sing_box_cm_configure_route "$config" "$SB_DIRECT_OUTBOUND_TAG" false "$SB_DNS_SERVER_TAG" \ + "$output_network_interface") + fi local sniff_inbounds sniff_inbounds=$(comma_string_to_json_array "$SB_TPROXY_INBOUND_TAG,$SB_DNS_INBOUND_TAG") From b5eec292e024cbf2a81f0462e4f23cd84a196f62 Mon Sep 17 00:00:00 2001 From: divocat Date: Thu, 23 Oct 2025 12:22:04 +0300 Subject: [PATCH 113/121] fix: correct nft checks output --- .../src/podkop/tabs/diagnostic/checks/runNftCheck.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index 46c46a1..5f413c9 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -42,7 +42,7 @@ export async function runNftCheck() { Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && - Boolean(data.rules_other_mark_exist); + !data.rules_other_mark_exist; const atLeastOneGood = Boolean(data.table_exist) || @@ -52,7 +52,7 @@ export async function runNftCheck() { Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || - Boolean(data.rules_other_mark_exist); + !data.rules_other_mark_exist; function getStatus() { if (allGood) { From a2eac6f103cc7fb4bbf6cd8675c6a216a3f4b0a1 Mon Sep 17 00:00:00 2001 From: divocat Date: Thu, 23 Oct 2025 12:23:45 +0300 Subject: [PATCH 114/121] fix: disable lan for output network interface --- .../htdocs/luci-static/resources/view/podkop/main.js | 4 ++-- .../htdocs/luci-static/resources/view/podkop/settings.js | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index 9f60cd7..cc8d9c8 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -2534,8 +2534,8 @@ async function runNftCheck() { throw new Error("Nftables checks failed"); } const data = nftablesChecks.data; - const allGood = Boolean(data.table_exist) && Boolean(data.rules_mangle_exist) && Boolean(data.rules_mangle_counters) && Boolean(data.rules_mangle_output_exist) && Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && Boolean(data.rules_other_mark_exist); - const atLeastOneGood = Boolean(data.table_exist) || Boolean(data.rules_mangle_exist) || Boolean(data.rules_mangle_counters) || Boolean(data.rules_mangle_output_exist) || Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || Boolean(data.rules_other_mark_exist); + const allGood = Boolean(data.table_exist) && Boolean(data.rules_mangle_exist) && Boolean(data.rules_mangle_counters) && Boolean(data.rules_mangle_output_exist) && Boolean(data.rules_mangle_output_counters) && Boolean(data.rules_proxy_exist) && Boolean(data.rules_proxy_counters) && !data.rules_other_mark_exist; + const atLeastOneGood = Boolean(data.table_exist) || Boolean(data.rules_mangle_exist) || Boolean(data.rules_mangle_counters) || Boolean(data.rules_mangle_output_exist) || Boolean(data.rules_mangle_output_counters) || Boolean(data.rules_proxy_exist) || Boolean(data.rules_proxy_counters) || !data.rules_other_mark_exist; function getStatus() { if (allGood) { return "success"; 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 3b735bc..ad678a4 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 @@ -147,6 +147,13 @@ function createSettingsContent(section) { return false; } + // Reject lan* + if ( + value.startsWith("lan") + ) { + return false; + } + // Reject tun*, wg*, vpn*, awg*, oc* if ( value.startsWith("tun") || From 25bb2355aa0b8e6c9af232b2a6c0679d30f493f5 Mon Sep 17 00:00:00 2001 From: Kirill Sobakin Date: Thu, 23 Oct 2025 12:47:45 +0300 Subject: [PATCH 115/121] fix: rm check_github, check_dnsmasq --- fe-app-podkop/src/podkop/types.ts | 3 -- podkop/files/usr/bin/podkop | 51 ------------------------------- 2 files changed, 54 deletions(-) diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index fb30465..9b8aefd 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -34,11 +34,8 @@ export namespace Podkop { // check_nft Check NFT rules // check_nft_rules Check NFT rules status // check_sing_box Check sing-box installation and status - // check_github Check GitHub connectivity // check_logs Show podkop logs from system journal - // check_sing_box_connections Show active sing-box connections // check_sing_box_logs Show sing-box logs - // check_dnsmasq Check DNSMasq configuration // check_fakeip Test FakeIP on router // clash_api Clash API interface for managing proxies and groups // show_config Display current podkop configuration diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index 37414d4..a87551b 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -1613,49 +1613,6 @@ check_nft() { nolog "NFT check completed" } -check_github() { - nolog "Checking GitHub connectivity..." - - if ! curl -m 3 github.com; then - nolog "Error: Cannot connect to GitHub" - return 1 - fi - nolog "GitHub is accessible" - - nolog "Checking lists availability:" - for url in "$DOMAINS_RU_INSIDE" "$DOMAINS_RU_OUTSIDE" "$DOMAINS_UA" "$DOMAINS_YOUTUBE" \ - "$SUBNETS_TWITTER" "$SUBNETS_META" "$SUBNETS_DISCORD"; do - local list_name=$(basename "$url") - - config_get_bool download_lists_via_proxy "settings" "download_lists_via_proxy" "0" - if [ "$download_lists_via_proxy" -eq 1 ]; then - http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -q -O /dev/null "$url" - else - wget -q -O /dev/null "$url" - fi - - if [ $? -eq 0 ]; then - nolog "- $list_name: available" - else - nolog "- $list_name: not available" - fi - done -} - -check_dnsmasq() { - nolog "Checking dnsmasq configuration..." - - local config=$(uci show dhcp.@dnsmasq[0]) - if [ -z "$config" ]; then - nolog "No dnsmasq configuration found" - return 1 - fi - - echo "$config" | while IFS='=' read -r key value; do - nolog "$key = $value" - done -} - check_logs() { if ! command -v logread > /dev/null 2>&1; then nolog "Error: logread command not found" @@ -2545,10 +2502,8 @@ Available commands: check_nft Check NFT rules check_nft_rules Check NFT rules status check_sing_box Check sing-box installation and status - check_github Check GitHub connectivity check_logs Show podkop logs from system journal check_sing_box_logs Show sing-box logs - check_dnsmasq Check DNSMasq configuration check_fakeip Test FakeIP on router clash_api Clash API interface for managing proxies and groups show_config Display current podkop configuration @@ -2595,18 +2550,12 @@ check_nft_rules) check_sing_box) check_sing_box ;; -check_github) - check_github - ;; check_logs) check_logs ;; check_sing_box_logs) check_sing_box_logs ;; -check_dnsmasq) - check_dnsmasq - ;; check_fakeip) check_fakeip ;; From 2cbaa888b248c1e26e4b990ffa932e6a76d0485d Mon Sep 17 00:00:00 2001 From: Kirill Sobakin Date: Thu, 23 Oct 2025 13:08:02 +0300 Subject: [PATCH 116/121] docs: man for 0.7.0 update --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index c7f40ee..83cec4f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,23 @@ https://podkop.net/ sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh) ``` +## Изменения 0.7.0 +Начиная с версии 0.7.0 изменена структура конфига `/etc/config/podkop`. Старые значения не совместимы с новыми. Нужно заново настроить Podkop. + +Скрипт установки обнаружит старую версию и предупредит вас об этом. Если вы согласитесь, то он сделает автоматически написанное ниже. + +При обновлении вручную нужно +0. Не ныть в issue и чатик. +1. Забэкапить старый конфиг: +``` +mv /etc/config/podkop /etc/config/podkop-070 +``` +2. Стянуть новый дефолтный конфиг: +``` +wget -O /etc/config/podkop https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/podkop/files/etc/config/podkop +``` +3. Настроить заново ваш Podkop через Luci или UCI. + # ToDo > [!IMPORTANT] From c1311fdd4bce864028c7ae8b485bb9a5ee33fee0 Mon Sep 17 00:00:00 2001 From: Kirill Sobakin Date: Thu, 23 Oct 2025 13:10:13 +0300 Subject: [PATCH 117/121] docs: fix --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 83cec4f..16ae39d 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/mai Скрипт установки обнаружит старую версию и предупредит вас об этом. Если вы согласитесь, то он сделает автоматически написанное ниже. -При обновлении вручную нужно +При обновлении вручную нужно: + 0. Не ныть в issue и чатик. 1. Забэкапить старый конфиг: ``` From 8fb8aad53b83c6c1160b8defcc8c9f0cac940786 Mon Sep 17 00:00:00 2001 From: divocat Date: Thu, 23 Oct 2025 14:25:00 +0300 Subject: [PATCH 118/121] Update fe-app-podkop/src/validators/validateVlessUrl.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- fe-app-podkop/src/validators/validateVlessUrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fe-app-podkop/src/validators/validateVlessUrl.ts b/fe-app-podkop/src/validators/validateVlessUrl.ts index 6ac5b5a..deea0a9 100644 --- a/fe-app-podkop/src/validators/validateVlessUrl.ts +++ b/fe-app-podkop/src/validators/validateVlessUrl.ts @@ -100,7 +100,7 @@ export function validateVlessUrl(url: string): ValidationResult { return { valid: false, message: - 'Invalid VLESS URL: flow xtls-rprx-vision-udp443 does not supported', + 'Invalid VLESS URL: flow xtls-rprx-vision-udp443 is not supported', }; } From e89f89ea966c05fb3a2bee263d8aac3018301445 Mon Sep 17 00:00:00 2001 From: Kirill Sobakin Date: Thu, 23 Oct 2025 14:26:17 +0300 Subject: [PATCH 119/121] fix: nano fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16ae39d..a938bc1 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/mai ``` ## Изменения 0.7.0 -Начиная с версии 0.7.0 изменена структура конфига `/etc/config/podkop`. Старые значения не совместимы с новыми. Нужно заново настроить Podkop. +Начиная с версии 0.7.0 изменена структура конфига `/etc/config/podkop`. Старые значения несовместимы с новыми. Нужно заново настроить Podkop. Скрипт установки обнаружит старую версию и предупредит вас об этом. Если вы согласитесь, то он сделает автоматически написанное ниже. From 64aa28f4e4ef41555ce9ebe19798da7c3bdd0ba4 Mon Sep 17 00:00:00 2001 From: Kirill Sobakin Date: Thu, 23 Oct 2025 14:27:14 +0300 Subject: [PATCH 120/121] feat: upgrade old configuration --- install.sh | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/install.sh b/install.sh index 8235045..3879878 100755 --- a/install.sh +++ b/install.sh @@ -61,6 +61,45 @@ pkg_install() { fi } +update_config() { + printf "\033[48;5;196m\033[1m╔══════════════════════════════════════════════════════════════════════╗\033[0m\n" + printf "\033[48;5;196m\033[1m║ ! Обнаружена старая версия podkop. ║\033[0m\n" + printf "\033[48;5;196m\033[1m║ Если продолжите обновление, вам потребуется настроить Podkop заново. ║\033[0m\n" + printf "\033[48;5;196m\033[1m║ Старая конфигурация будет сохранена в /etc/config/podkop-070 ║\033[0m\n" + printf "\033[48;5;196m\033[1m║ Подробности: LINK ║\033[0m\n" + printf "\033[48;5;196m\033[1m║ Точно хотите продолжить? ║\033[0m\n" + printf "\033[48;5;196m\033[1m╚══════════════════════════════════════════════════════════════════════╝\033[0m\n" + + echo "" + + printf "\033[48;5;196m\033[1m╔══════════════════════════════════════════════════════════════════════╗\033[0m\n" + printf "\033[48;5;196m\033[1m║ ! Detected old podkop version. ║\033[0m\n" + printf "\033[48;5;196m\033[1m║ If you continue the update, you will need to RECONFIGURE podkop. ║\033[0m\n" + printf "\033[48;5;196m\033[1m║ Your old configuration will be saved to /etc/config/podkop-070 ║\033[0m\n" + printf "\033[48;5;196m\033[1m║ Details: LINK ║\033[0m\n" + printf "\033[48;5;196m\033[1m║ Are you sure you want to continue? ║\033[0m\n" + printf "\033[48;5;196m\033[1m╚══════════════════════════════════════════════════════════════════════╝\033[0m\n" + + msg "Continue? (yes/no)" + + while true; do + read -r -p '' CONFIG_UPDATE + case $CONFIG_UPDATE in + + yes|y|Y) + mv /etc/config/podkop /etc/config/podkop-070 + wget -O /etc/config/podkop https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/podkop/files/etc/config/podkop + msg "Podkop config has been reset to default. Your old config saved in /etc/config/podkop-070" + break + ;; + *) + msg "Exit" + exit 1 + ;; + esac + done +} + main() { check_system sing_box @@ -201,6 +240,35 @@ check_system() { exit 1 fi + # Check version + # if command -v podkop > /dev/null 2>&1; then + # local version + # version=$(/usr/bin/podkop show_version 2> /dev/null) + # if [ -n "$version" ]; then + # version=$(echo "$version" | sed 's/^v//') + # local major + # local minor + # local patch + # major=$(echo "$version" | cut -d. -f1) + # minor=$(echo "$version" | cut -d. -f2) + # patch=$(echo "$version" | cut -d. -f3) + + # # Compare version: must be >= 0.7.0 + # if [ "$major" -gt 0 ] || + # [ "$major" -eq 0 ] && [ "$minor" -gt 7 ] || + # [ "$major" -eq 0 ] && [ "$minor" -eq 7 ] && [ "$patch" -ge 0 ]; then + # msg "Podkop version >= 0.7.0" + # break + # else + # msg "Podkop version < 0.7.0" + # update_config + # fi + # else + # msg "Unknown podkop version" + # update_config + # fi + # fi + if pkg_is_installed https-dns-proxy; then msg "Сonflicting package detected: https-dns-proxy. Remove?" From 272ce012d7d3c95365790ff879abfa50a14dd5cf Mon Sep 17 00:00:00 2001 From: divocat Date: Thu, 23 Oct 2025 14:28:52 +0300 Subject: [PATCH 121/121] fix: correct build --- .../htdocs/luci-static/resources/view/podkop/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index cc8d9c8..3042a78 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -308,7 +308,7 @@ function validateVlessUrl(url) { if (params.flow === "xtls-rprx-vision-udp443") { return { valid: false, - message: "Invalid VLESS URL: flow xtls-rprx-vision-udp443 does not supported" + message: "Invalid VLESS URL: flow xtls-rprx-vision-udp443 is not supported" }; } return { valid: true, message: _("Valid") };