// This file is autogenerated, please don't change manually "use strict"; "require baseclass"; "require fs"; "require uci"; // 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])$/; if (ipRegex.test(ip)) { return { valid: true, message: _("Valid") }; } return { valid: false, message: _("Invalid IP address") }; } // 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]*)?$/; if (allowDotTLD) { const dotTLD = /^\.[a-zA-Z]{2,}$/; if (dotTLD.test(domain)) { return { valid: true, message: _("Valid") }; } } if (!domainRegex.test(domain)) { return { valid: false, message: _("Invalid domain address") }; } const hostname = domain.split("/")[0]; const parts = hostname.split("."); const atLeastOneInvalidPart = parts.some((part) => part.length > 63); if (atLeastOneInvalidPart) { return { valid: false, message: _("Invalid domain address") }; } return { valid: true, message: _("Valid") }; } // src/validators/validateDns.ts function validateDNS(value) { if (!value) { return { valid: false, message: _("DNS server address cannot be empty") }; } if (validateIPV4(value).valid) { return { valid: true, message: _("Valid") }; } if (validateDomain(value).valid) { return { valid: true, message: _("Valid") }; } return { valid: false, message: _( "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH" ) }; } // src/validators/validateUrl.ts function validateUrl(url, protocols = ["http:", "https:"]) { try { const parsedUrl = new URL(url); if (!protocols.includes(parsedUrl.protocol)) { return { valid: false, message: `${_("URL must use one of the following protocols:")} ${protocols.join(", ")}` }; } return { valid: true, message: _("Valid") }; } catch (_e) { return { valid: false, message: _("Invalid URL format") }; } } // src/validators/validatePath.ts function validatePath(value) { if (!value) { return { valid: false, message: _("Path cannot be empty") }; } const pathRegex = /^\/[a-zA-Z0-9_\-/.]+$/; if (pathRegex.test(value)) { return { valid: true, message: _("Valid") }; } return { valid: false, message: _( 'Invalid path format. Path must start with "/" and contain valid characters' ) }; } // src/validators/validateSubnet.ts function validateSubnet(value) { const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(?:\/\d{1,2})?$/; if (!subnetRegex.test(value)) { return { valid: false, message: _("Invalid format. Use X.X.X.X or X.X.X.X/Y") }; } const [ip, cidr] = value.split("/"); if (ip === "0.0.0.0") { return { valid: false, message: _("IP address 0.0.0.0 is not allowed") }; } const ipCheck = validateIPV4(ip); if (!ipCheck.valid) { return ipCheck; } if (cidr) { const cidrNum = parseInt(cidr, 10); if (cidrNum < 0 || cidrNum > 32) { return { valid: false, message: _("CIDR must be between 0 and 32") }; } } return { valid: true, message: _("Valid") }; } // src/validators/bulkValidate.ts function bulkValidate(values, validate) { const results = values.map((value) => ({ ...validate(value), value })); return { valid: results.every((r) => r.valid), results }; } // src/validators/validateShadowsocksUrl.ts function validateShadowsocksUrl(url) { if (!url.startsWith("ss://")) { return { valid: false, 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") }; } const mainPart = url.includes("?") ? url.split("?")[0] : url.split("#")[0]; const encryptedPart = mainPart.split("/")[2]?.split("@")[0]; if (!encryptedPart) { return { valid: false, message: _("Invalid Shadowsocks URL: missing credentials") }; } try { const decoded = atob(encryptedPart); if (!decoded.includes(":")) { return { valid: false, message: _( "Invalid Shadowsocks URL: decoded credentials must contain method:password" ) }; } } catch (_e) { if (!encryptedPart.includes(":") && !encryptedPart.includes("-")) { return { valid: false, message: _( 'Invalid Shadowsocks URL: missing method and password separator ":"' ) }; } } const serverPart = url.split("@")[1]; if (!serverPart) { return { valid: false, message: _("Invalid Shadowsocks URL: missing server address") }; } const [server, portAndRest] = serverPart.split(":"); if (!server) { return { valid: false, message: _("Invalid Shadowsocks URL: missing server") }; } const port = portAndRest ? portAndRest.split(/[?#]/)[0] : null; if (!port) { return { valid: false, 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") }; } } catch (_e) { return { valid: false, message: _("Invalid Shadowsocks URL: parsing failed") }; } return { valid: true, message: _("Valid") }; } // src/validators/validateVlessUrl.ts function validateVlessUrl(url) { try { const parsedUrl = new URL(url); if (!url || /\s/.test(url)) { return { valid: false, message: _("Invalid VLESS URL: must not contain spaces") }; } if (parsedUrl.protocol !== "vless:") { return { valid: false, message: _("Invalid VLESS URL: must start with vless://") }; } if (!parsedUrl.username) { return { valid: false, message: _("Invalid VLESS URL: missing UUID") }; } if (!parsedUrl.hostname) { return { valid: false, message: _("Invalid VLESS URL: missing server") }; } if (!parsedUrl.port) { return { valid: false, message: _("Invalid VLESS URL: missing port") }; } if (isNaN(+parsedUrl.port) || +parsedUrl.port < 1 || +parsedUrl.port > 65535) { return { valid: false, message: _( "Invalid VLESS URL: invalid port number. Must be between 1 and 65535" ) }; } if (!parsedUrl.search) { return { valid: false, message: _("Invalid VLESS URL: missing query parameters") }; } const params = new URLSearchParams(parsedUrl.search); const type = params.get("type"); const validTypes = [ "tcp", "raw", "udp", "grpc", "http", "httpupgrade", "xhttp", "ws", "kcp" ]; if (!type || !validTypes.includes(type)) { return { valid: false, message: _( "Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws" ) }; } const security = params.get("security"); const validSecurities = ["tls", "reality", "none"]; if (!security || !validSecurities.includes(security)) { return { valid: false, message: _( "Invalid VLESS URL: security must be one of tls, reality, none" ) }; } if (security === "reality") { if (!params.get("pbk")) { return { valid: false, message: _( "Invalid VLESS URL: missing pbk parameter for reality security" ) }; } if (!params.get("fp")) { return { valid: false, message: _( "Invalid VLESS URL: missing fp parameter for reality security" ) }; } } return { valid: true, message: _("Valid") }; } catch (_e) { return { valid: false, message: _("Invalid VLESS URL: parsing failed") }; } } // src/validators/validateOutboundJson.ts function validateOutboundJson(value) { try { const parsed = JSON.parse(value); if (!parsed.type || !parsed.server || !parsed.server_port) { return { valid: false, message: _( 'Outbound JSON must contain at least "type", "server" and "server_port" fields' ) }; } return { valid: true, message: _("Valid") }; } catch { return { valid: false, message: _("Invalid JSON format") }; } } // src/validators/validateTrojanUrl.ts function validateTrojanUrl(url) { if (!url.startsWith("trojan://")) { return { valid: false, message: _("Invalid Trojan URL: must start with trojan://") }; } if (!url || /\s/.test(url)) { return { valid: false, message: _("Invalid Trojan URL: must not contain spaces") }; } try { const parsedUrl = new URL(url); if (!parsedUrl.username || !parsedUrl.hostname || !parsedUrl.port) { return { valid: false, message: _( "Invalid Trojan URL: must contain username, hostname and port" ) }; } } catch (_e) { return { valid: false, message: _("Invalid Trojan URL: parsing failed") }; } return { valid: true, message: _("Valid") }; } // src/validators/validateProxyUrl.ts function validateProxyUrl(url) { if (url.startsWith("ss://")) { return validateShadowsocksUrl(url); } if (url.startsWith("vless://")) { return validateVlessUrl(url); } if (url.startsWith("trojan://")) { return validateTrojanUrl(url); } return { valid: false, message: _("URL must start with vless:// or ss:// or trojan://") }; } // 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-main-_status > div { width: 100%; } /* 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%; } } `; // 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`; } // 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/clash/methods/createBaseApiRequest.ts async function createBaseApiRequest(fetchFn) { try { const response = await fetchFn(); 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/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") { return createBaseApiRequest( () => fetch( `${getClashApiUrl()}/group/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, { method: "GET", headers: { "Content-Type": "application/json" } } ) ); } async function triggerLatencyProxyTest(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/getConfigSections.ts async function getConfigSections() { return uci.load("podkop").then(() => uci.sections("podkop")); } // src/podkop/methods/getDashboardSections.ts async function getDashboardSections() { const configSections = await getConfigSections(); const clashProxies = await getClashProxies(); if (!clashProxies.success) { return { success: false, data: [] }; } const proxies = Object.entries(clashProxies.data.proxies).map( ([key, value]) => ({ code: key, value }) ); const data = configSections.filter((section) => section.mode !== "block").map((section) => { if (section.mode === "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.mode === "vpn") { const outbound = proxies.find( (proxy) => proxy.code === `${section[".name"]}-out` ); return { withTagSelect: false, code: outbound?.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 } ] }; } return { withTagSelect: false, code: section[".name"], displayName: section[".name"], outbounds: [] }; }); return { success: true, data }; } // src/podkop/methods/getPodkopStatus.ts async function getPodkopStatus() { const response = await executeShellCommand({ command: "/usr/bin/podkop", args: ["get_status"], timeout: 1e3 }); if (response.stdout) { return JSON.parse(response.stdout.replace(/\n/g, "")); } return { enabled: 0, status: "unknown" }; } // src/podkop/methods/getSingboxStatus.ts async function getSingboxStatus() { const response = await executeShellCommand({ command: "/usr/bin/podkop", args: ["get_sing_box_status"], timeout: 1e3 }); if (response.stdout) { return JSON.parse(response.stdout.replace(/\n/g, "")); } return { running: 0, enabled: 0, status: "unknown" }; } // src/podkop/services/tab.service.ts var TabService = class _TabService { constructor() { this.observer = null; this.lastActiveId = null; this.init(); } static getInstance() { if (!_TabService.instance) { _TabService.instance = new _TabService(); } return _TabService.instance; } init() { this.observer = new MutationObserver(() => this.handleMutations()); this.observer.observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: ["class"] }); this.notify(); } handleMutations() { this.notify(); } getTabsInfo() { const tabs = Array.from( 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") })); } getActiveTabId() { const active = document.querySelector( ".cbi-tab:not(.cbi-tab-disabled)" ); return active?.dataset.tab || null; } notify() { const tabs = this.getTabsInfo(); const activeId = this.getActiveTabId(); if (activeId !== this.lastActiveId) { this.lastActiveId = activeId; this.callback?.(activeId, tabs); } } onChange(callback) { this.callback = callback; this.notify(); } getAllTabs() { return this.getTabsInfo(); } getActiveTab() { return this.getActiveTabId(); } disconnect() { this.observer?.disconnect(); this.observer = null; } }; var TabServiceInstance = TabService.getInstance(); // src/store.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) => { acc[key] = value[key]; return acc; }, {} ); } return value; }); } function jsonEqual(a, b) { try { return jsonStableStringify(a) === jsonStableStringify(b); } catch { return false; } } var Store = class { constructor(initial) { this.listeners = /* @__PURE__ */ new Set(); this.lastHash = ""; this.value = initial; this.initial = structuredClone(initial); this.lastHash = jsonStableStringify(initial); } get() { return this.value; } set(next) { const prev = this.value; const merged = { ...prev, ...next }; if (jsonEqual(prev, merged)) return; this.value = merged; this.lastHash = jsonStableStringify(merged); const diff = {}; for (const key in merged) { if (!jsonEqual(merged[key], prev[key])) diff[key] = merged[key]; } this.listeners.forEach((cb) => cb(this.value, prev, diff)); } reset() { const prev = this.value; const next = structuredClone(this.initial); if (jsonEqual(prev, next)) return; this.value = next; this.lastHash = jsonStableStringify(next); const diff = {}; for (const key in next) { if (!jsonEqual(next[key], prev[key])) diff[key] = next[key]; } this.listeners.forEach((cb) => cb(this.value, prev, diff)); } subscribe(cb) { this.listeners.add(cb); cb(this.value, this.value, {}); return () => this.listeners.delete(cb); } unsubscribe(cb) { this.listeners.delete(cb); } patch(key, value) { this.set({ [key]: value }); } getKey(key) { return this.value[key]; } subscribeKey(key, cb) { let prev = this.value[key]; const wrapper = (val) => { if (!jsonEqual(val[key], prev)) { prev = val[key]; cb(val[key]); } }; this.listeners.add(wrapper); return () => this.listeners.delete(wrapper); } }; var initialStore = { tabService: { current: "", all: [] }, bandwidthWidget: { loading: true, failed: false, data: { up: 0, down: 0 } }, trafficTotalWidget: { loading: true, failed: false, data: { downloadTotal: 0, uploadTotal: 0 } }, systemInfoWidget: { loading: true, failed: false, data: { connections: 0, memory: 0 } }, servicesInfoWidget: { loading: true, failed: false, data: { singbox: 0, podkop: 0 } }, sectionsWidget: { loading: true, failed: false, latencyFetching: false, data: [] } }; var store = new Store(initialStore); // src/podkop/services/core.service.ts function coreService() { TabServiceInstance.onChange((activeId, tabs) => { store.set({ tabService: { current: activeId || "", all: tabs.map((tab) => tab.id) } }); }); } // src/podkop/tabs/dashboard/renderSections.ts function renderFailedState() { return E( "div", { class: "pdk_dashboard-page__outbound-section centered", style: "height: 127px" }, 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" }); } function renderDefaultState({ section, onChooseOutbound, onTestLatency, latencyFetching }) { function testLatency() { if (section.withTagSelect) { return onTestLatency(section.code); } if (section.outbounds.length) { return onTestLatency(section.outbounds[0].code); } } function renderOutbound(outbound) { function getLatencyClass() { if (!outbound.latency) { return "pdk_dashboard-page__outbound-grid__item__latency--empty"; } if (outbound.latency < 800) { return "pdk_dashboard-page__outbound-grid__item__latency--green"; } if (outbound.latency < 1500) { return "pdk_dashboard-page__outbound-grid__item__latency--yellow"; } return "pdk_dashboard-page__outbound-grid__item__latency--red"; } return E( "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) }, [ E("b", {}, outbound.displayName), E("div", { class: "pdk_dashboard-page__outbound-grid__item__footer" }, [ E( "div", { class: "pdk_dashboard-page__outbound-grid__item__type" }, outbound.type ), E( "div", { class: getLatencyClass() }, outbound.latency ? `${outbound.latency}ms` : "N/A" ) ]) ] ); } return E("div", { class: "pdk_dashboard-page__outbound-section" }, [ // Title with test latency E("div", { class: "pdk_dashboard-page__outbound-section__title-section" }, [ E( "div", { class: "pdk_dashboard-page__outbound-section__title-section__title" }, 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") ) ]), E( "div", { class: "pdk_dashboard-page__outbound-grid" }, section.outbounds.map((outbound) => renderOutbound(outbound)) ) ]); } function renderSections(props) { if (props.failed) { return renderFailedState(); } if (props.loading) { return renderLoadingState(); } return renderDefaultState(props); } // src/podkop/tabs/dashboard/renderWidget.ts function renderFailedState2() { return E( "div", { id: "", style: "height: 78px", class: "pdk_dashboard-page__widgets-section__item centered" }, _("Currently unavailable") ); } function renderLoadingState2() { return E( "div", { id: "", style: "height: 78px", class: "pdk_dashboard-page__widgets-section__item skeleton" }, "" ); } function renderDefaultState2({ title, items }) { return E("div", { class: "pdk_dashboard-page__widgets-section__item" }, [ E( "b", { class: "pdk_dashboard-page__widgets-section__item__title" }, title ), ...items.map( (item) => E( "div", { class: `pdk_dashboard-page__widgets-section__item__row ${item?.attributes?.class || ""}` }, [ E( "span", { class: "pdk_dashboard-page__widgets-section__item__row__key" }, `${item.key}: ` ), E( "span", { class: "pdk_dashboard-page__widgets-section__item__row__value" }, item.value ) ] ) ) ]); } function renderWidget(props) { if (props.loading) { return renderLoadingState2(); } if (props.failed) { return renderFailedState2(); } return renderDefaultState2(props); } // src/podkop/tabs/dashboard/renderDashboard.ts function renderDashboard() { return E( "div", { id: "dashboard-status", class: "pdk_dashboard-page" }, [ // Widgets section E("div", { class: "pdk_dashboard-page__widgets-section" }, [ E( "div", { id: "dashboard-widget-traffic" }, renderWidget({ loading: true, failed: false, title: "", items: [] }) ), E( "div", { id: "dashboard-widget-traffic-total" }, renderWidget({ loading: true, failed: false, title: "", items: [] }) ), E( "div", { id: "dashboard-widget-system-info" }, renderWidget({ loading: true, failed: false, title: "", items: [] }) ), E( "div", { id: "dashboard-widget-service-info" }, renderWidget({ loading: true, failed: false, title: "", items: [] }) ) ]), // All outbounds E( "div", { id: "dashboard-sections-grid" }, renderSections({ loading: true, failed: false, section: { code: "", displayName: "", outbounds: [], withTagSelect: false }, onTestLatency: () => { }, onChooseOutbound: () => { } }) ) ] ); } // 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"]; if (n < 1e3) { return n + " B"; } const exponent = Math.min(Math.floor(Math.log10(n) / 3), UNITS.length - 1); n = Number((n / Math.pow(1e3, exponent)).toPrecision(3)); const unit = UNITS[exponent]; return n + " " + unit; } // src/podkop/tabs/dashboard/initDashboardController.ts async function fetchDashboardSections() { const prev = store.get().sectionsWidget; store.set({ sectionsWidget: { ...prev, failed: false } }); const { data, success } = await getDashboardSections(); store.set({ sectionsWidget: { latencyFetching: false, loading: false, failed: !success, data } }); } async function fetchServicesInfo() { const [podkop, singbox] = await Promise.all([ getPodkopStatus(), getSingboxStatus() ]); store.set({ servicesInfoWidget: { loading: false, failed: false, data: { singbox: singbox.running, podkop: podkop.enabled } } }); } async function connectToClashSockets() { socket.subscribe( `${getClashWsUrl()}/traffic?token=`, (msg) => { const parsedMsg = JSON.parse(msg); store.set({ bandwidthWidget: { loading: false, failed: false, data: { up: parsedMsg.up, down: parsedMsg.down } } }); }, (_err) => { store.set({ bandwidthWidget: { loading: false, failed: true, data: { up: 0, down: 0 } } }); } ); socket.subscribe( `${getClashWsUrl()}/connections?token=`, (msg) => { const parsedMsg = JSON.parse(msg); store.set({ trafficTotalWidget: { loading: false, failed: false, data: { downloadTotal: parsedMsg.downloadTotal, uploadTotal: parsedMsg.uploadTotal } }, systemInfoWidget: { loading: false, failed: false, data: { connections: parsedMsg.connections?.length, memory: parsedMsg.memory } } }); }, (_err) => { store.set({ trafficTotalWidget: { loading: false, failed: true, data: { downloadTotal: 0, uploadTotal: 0 } }, systemInfoWidget: { loading: false, failed: true, data: { connections: 0, memory: 0 } } }); } ); } async function handleChooseOutbound(selector, tag) { await triggerProxySelector(selector, tag); await fetchDashboardSections(); } async function handleTestGroupLatency(tag) { store.set({ sectionsWidget: { ...store.get().sectionsWidget, latencyFetching: true } }); await triggerLatencyGroupTest(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { ...store.get().sectionsWidget, latencyFetching: false } }); } async function handleTestProxyLatency(tag) { store.set({ sectionsWidget: { ...store.get().sectionsWidget, latencyFetching: true } }); await triggerLatencyProxyTest(tag); await fetchDashboardSections(); store.set({ sectionsWidget: { ...store.get().sectionsWidget, latencyFetching: false } }); } async function renderSectionsWidget() { console.log("renderSectionsWidget"); const sectionsWidget = store.get().sectionsWidget; const container = document.getElementById("dashboard-sections-grid"); if (sectionsWidget.loading || sectionsWidget.failed) { const renderedWidget = renderSections({ loading: sectionsWidget.loading, failed: sectionsWidget.failed, section: { code: "", displayName: "", outbounds: [], withTagSelect: false }, onTestLatency: () => { }, onChooseOutbound: () => { }, latencyFetching: sectionsWidget.latencyFetching }); return preserveScrollForPage(() => { container.replaceChildren(renderedWidget); }); } const renderedWidgets = sectionsWidget.data.map( (section) => renderSections({ loading: sectionsWidget.loading, failed: sectionsWidget.failed, section, latencyFetching: sectionsWidget.latencyFetching, onTestLatency: (tag) => { if (section.withTagSelect) { return handleTestGroupLatency(tag); } return handleTestProxyLatency(tag); }, onChooseOutbound: (selector, tag) => { handleChooseOutbound(selector, tag); } }) ); return preserveScrollForPage(() => { container.replaceChildren(...renderedWidgets); }); } async function renderBandwidthWidget() { console.log("renderBandwidthWidget"); const traffic = store.get().bandwidthWidget; const container = document.getElementById("dashboard-widget-traffic"); if (traffic.loading || traffic.failed) { const renderedWidget2 = renderWidget({ loading: traffic.loading, failed: traffic.failed, title: "", items: [] }); return container.replaceChildren(renderedWidget2); } const renderedWidget = renderWidget({ loading: traffic.loading, failed: traffic.failed, title: _("Traffic"), items: [ { key: _("Uplink"), value: `${prettyBytes(traffic.data.up)}/s` }, { key: _("Downlink"), value: `${prettyBytes(traffic.data.down)}/s` } ] }); container.replaceChildren(renderedWidget); } async function renderTrafficTotalWidget() { console.log("renderTrafficTotalWidget"); const trafficTotalWidget = store.get().trafficTotalWidget; const container = document.getElementById("dashboard-widget-traffic-total"); if (trafficTotalWidget.loading || trafficTotalWidget.failed) { const renderedWidget2 = renderWidget({ loading: trafficTotalWidget.loading, failed: trafficTotalWidget.failed, title: "", items: [] }); return container.replaceChildren(renderedWidget2); } const renderedWidget = renderWidget({ loading: trafficTotalWidget.loading, failed: trafficTotalWidget.failed, title: _("Traffic Total"), items: [ { key: _("Uplink"), value: String(prettyBytes(trafficTotalWidget.data.uploadTotal)) }, { key: _("Downlink"), value: String(prettyBytes(trafficTotalWidget.data.downloadTotal)) } ] }); container.replaceChildren(renderedWidget); } async function renderSystemInfoWidget() { console.log("renderSystemInfoWidget"); const systemInfoWidget = store.get().systemInfoWidget; const container = document.getElementById("dashboard-widget-system-info"); if (systemInfoWidget.loading || systemInfoWidget.failed) { const renderedWidget2 = renderWidget({ loading: systemInfoWidget.loading, failed: systemInfoWidget.failed, title: "", items: [] }); return container.replaceChildren(renderedWidget2); } const renderedWidget = renderWidget({ loading: systemInfoWidget.loading, failed: systemInfoWidget.failed, title: _("System info"), items: [ { key: _("Active Connections"), value: String(systemInfoWidget.data.connections) }, { key: _("Memory Usage"), value: String(prettyBytes(systemInfoWidget.data.memory)) } ] }); container.replaceChildren(renderedWidget); } async function renderServicesInfoWidget() { console.log("renderServicesInfoWidget"); const servicesInfoWidget = store.get().servicesInfoWidget; const container = document.getElementById("dashboard-widget-service-info"); if (servicesInfoWidget.loading || servicesInfoWidget.failed) { const renderedWidget2 = renderWidget({ loading: servicesInfoWidget.loading, failed: servicesInfoWidget.failed, title: "", items: [] }); return container.replaceChildren(renderedWidget2); } const renderedWidget = renderWidget({ loading: servicesInfoWidget.loading, failed: servicesInfoWidget.failed, title: _("Services info"), items: [ { key: _("Podkop"), 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" } }, { key: _("Sing-box"), 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" } } ] }); container.replaceChildren(renderedWidget); } async function onStoreUpdate(next, prev, diff) { if (diff.sectionsWidget) { renderSectionsWidget(); } if (diff.bandwidthWidget) { renderBandwidthWidget(); } if (diff.trafficTotalWidget) { renderTrafficTotalWidget(); } if (diff.systemInfoWidget) { renderSystemInfoWidget(); } if (diff.servicesInfoWidget) { renderServicesInfoWidget(); } } async function initDashboardController() { onMount("dashboard-status").then(() => { store.unsubscribe(onStoreUpdate); store.reset(); store.subscribe(onStoreUpdate); fetchDashboardSections(); fetchServicesInfo(); connectToClashSockets(); }); } return baseclass.extend({ ALLOWED_WITH_RUSSIA_INSIDE, BOOTSTRAP_DNS_SERVER_OPTIONS, BUTTON_FEEDBACK_TIMEOUT, CACHE_TIMEOUT, COMMAND_SCHEDULING, COMMAND_TIMEOUT, DIAGNOSTICS_INITIAL_DELAY, DIAGNOSTICS_UPDATE_INTERVAL, DNS_SERVER_OPTIONS, DOMAIN_LIST_OPTIONS, ERROR_POLL_INTERVAL, FAKEIP_CHECK_DOMAIN, FETCH_TIMEOUT, IP_CHECK_DOMAIN, REGIONAL_OPTIONS, PODKOP_LUCI_APP_VERSION, STATUS_COLORS, TabService, TabServiceInstance, UPDATE_INTERVAL_OPTIONS, bulkValidate, coreService, createBaseApiRequest, executeShellCommand, getBaseUrl, getClashApiUrl, getClashConfig, getClashGroupDelay, getClashProxies, getClashVersion, getClashWsUrl, getConfigSections, getDashboardSections, getPodkopStatus, getProxyUrlName, getSingboxStatus, initDashboardController, injectGlobalStyles, maskIP, onMount, parseValueList, preserveScrollForPage, renderDashboard, splitProxyString, triggerLatencyGroupTest, triggerLatencyProxyTest, triggerProxySelector, validateDNS, validateDomain, validateIPV4, validateOutboundJson, validatePath, validateProxyUrl, validateShadowsocksUrl, validateSubnet, validateTrojanUrl, validateUrl, validateVlessUrl, withTimeout });