// This file is autogenerated, please don't change manually "use strict"; "require baseclass"; "require fs"; "require uci"; "require ui"; "require rpc"; // src/validators/validateIp.ts function isIPv4(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])$/; return ipRegex.test(ip); } function isIPv6(ip) { if (!ip.includes(":") || ip.includes("%")) { return false; } try { new URL(`http://[${ip}]/`); return true; } catch (_e) { return false; } } function validateIP(ip) { if (isIPv4(ip) || isIPv6(ip)) { return { valid: true, message: _("Valid") }; } return { valid: false, message: _("Invalid IP address") }; } // src/validators/validateDomain.ts function asciiHostname(hostname) { if (!hostname || /[\s@:/]/.test(hostname) || hostname.startsWith(".") || hostname.endsWith(".")) { return null; } try { return new URL(`http://${hostname}`).hostname; } catch { return null; } } function validAsciiDomain(hostname, requireDot = true) { if (!hostname || hostname.length > 253) { return false; } const parts = hostname.split("."); if (parts.some((part) => !part || part.length > 63)) { return false; } if (parts.some((part) => !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(part))) { return false; } if (!requireDot) { return true; } if (parts.length < 2) { return false; } const tld = parts[parts.length - 1]; return /^(?:[a-z]{2,}|xn--[a-z0-9-]{2,59})$/.test(tld); } function validateDomain(domain, allowDotTLD = false) { const normalized = `${domain || ""}`.trim(); if (allowDotTLD) { const dotTld = normalized.startsWith(".") ? normalized.slice(1) : ""; const ascii2 = asciiHostname(dotTld); if (ascii2 && !ascii2.includes(".") && validAsciiDomain(ascii2, false)) { return { valid: true, message: _("Valid") }; } } const slashIndex = normalized.indexOf("/"); const hostname = slashIndex >= 0 ? normalized.slice(0, slashIndex) : normalized; const path = slashIndex >= 0 ? normalized.slice(slashIndex) : ""; if (path && /\s/.test(path)) { return { valid: false, message: _("Invalid domain address") }; } const ascii = asciiHostname(hostname); if (!ascii || !validAsciiDomain(ascii)) { return { valid: false, message: _("Invalid domain address") }; } return { valid: true, message: _("Valid") }; } // src/validators/hostPort.ts function unbracketHost(host) { if (host.startsWith("[") && host.endsWith("]")) { return host.slice(1, -1); } return host; } function isValidHost(host) { const normalizedHost = unbracketHost(host); return isIPv4(normalizedHost) || isIPv6(normalizedHost) || validateDomain(normalizedHost).valid; } function isValidPort(port) { const normalized = String(port ?? ""); const value = Number(normalized); return /^\d+$/.test(normalized) && value >= 1 && value <= 65535; } function parseHostPort(value) { if (!value) { return null; } if (value.startsWith("[")) { const end = value.indexOf("]"); if (end <= 1 || value.slice(end + 1, end + 2) !== ":") { return null; } const parsed2 = { host: value.slice(1, end), port: value.slice(end + 2) }; return isValidHost(parsed2.host) ? parsed2 : null; } const firstColon = value.indexOf(":"); const lastColon = value.lastIndexOf(":"); if (firstColon < 0 || firstColon !== lastColon) { return null; } const parsed = { host: value.slice(0, firstColon), port: value.slice(firstColon + 1) }; return isValidHost(parsed.host) ? parsed : null; } // src/validators/validateDns.ts function validateDNS(value) { if (!value) { return { valid: false, message: _("DNS server address cannot be empty") }; } const [addressPart, ...pathParts] = value.split("/"); const parsedHostPort = parseHostPort(addressPart); const host = parsedHostPort ? parsedHostPort.host : unbracketHost(addressPart); const domainValue = parsedHostPort ? host + (pathParts.length > 0 ? `/${pathParts.join("/")}` : "") : value.replace(/:(\d+)(?=\/|$)/, ""); if (parsedHostPort && !isValidPort(parsedHostPort.port)) { return { valid: false, message: _("Invalid DNS server port") }; } if (validateIP(host).valid) { return { valid: true, message: _("Valid") }; } if (validateDomain(domainValue).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:"]) { if (!url.length) { return { valid: false, message: _("Invalid URL format") }; } const hasValidProtocol = protocols.some((p) => url.indexOf(p + "//") === 0); if (!hasValidProtocol) return { valid: false, message: _("URL must use one of the following protocols:") + " " + protocols.join(", ") }; try { const parsed = new URL(url); const host = parsed.hostname.startsWith("[") ? parsed.hostname.slice(1, -1) : parsed.hostname; if ((isValidHost(host) || host === "localhost") && (!parsed.port || isValidPort(parsed.port))) { return { valid: true, message: _("Valid") }; } } catch (_e) { return { valid: false, message: _("Invalid URL format") }; } 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 trimmed = value.trim(); if (/^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/.test(trimmed)) { return { valid: true, message: _("Valid") }; } const [ip, cidr, extra] = trimmed.split("/"); if (!ip || extra !== void 0 || !isIPv4(ip) && !isIPv6(ip)) { return { valid: false, message: _("Invalid format. Use an IP address or CIDR subnet") }; } if ((ip === "0.0.0.0" || ip === "::") && cidr == null) { return { valid: false, message: _("Unspecified IP address is not allowed") }; } if (cidr) { if (!/^\d+$/.test(cidr)) { return { valid: false, message: _("Invalid CIDR prefix") }; } const cidrNum = parseInt(cidr, 10); const maxPrefix = isIPv6(ip) ? 128 : 32; if (cidrNum < 0 || cidrNum > maxPrefix) { return { valid: false, message: _( "CIDR must be between 0 and 32 for IPv4 or 0 and 128 for IPv6" ) }; } } 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/validateOutboundJson.ts var SERVER_OUTBOUND_TYPES = /* @__PURE__ */ new Set([ "vless", "vmess", "trojan", "shadowsocks", "socks", "http", "hysteria2", "hysteria" ]); function invalid(message) { return { valid: false, message }; } function isPlainObject(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function nonEmptyString(value) { return typeof value === "string" && value.trim().length > 0; } function validateServerPort(value) { return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535; } function validateOutbounds(value) { return Array.isArray(value) && value.length > 0 && value.every((item) => nonEmptyString(item)); } function validateOutboundJson(value, usedTags = []) { const normalized = `${value || ""}`.trim(); if (!normalized.length) { return invalid(_("JSON outbound cannot be empty")); } let parsed; try { parsed = JSON.parse(normalized); } catch { return { valid: false, message: _("Invalid JSON format") }; } if (!isPlainObject(parsed)) { return invalid(_("JSON outbound must be a JSON object")); } if (!nonEmptyString(parsed.type)) { return invalid(_("JSON outbound must contain a non-empty type field")); } if (!nonEmptyString(parsed.tag)) { return invalid(_("JSON outbound must contain a non-empty tag field")); } const tag = parsed.tag.trim(); if (usedTags.some((usedTag) => `${usedTag || ""}`.trim() === tag)) { return invalid(_("Duplicate JSON outbound tag")); } const type = parsed.type.trim().toLowerCase(); if ((type === "selector" || type === "urltest") && !validateOutbounds(parsed.outbounds)) { return invalid( _( "Selector and URLTest outbounds must contain a non-empty outbounds array" ) ); } if (SERVER_OUTBOUND_TYPES.has(type)) { if (!nonEmptyString(parsed.server)) { return invalid( _("Server outbound must contain a non-empty server field") ); } if (!validateServerPort(parsed.server_port)) { return invalid( _("Server outbound must contain a numeric server_port from 1 to 65535") ); } } else if (parsed.server_port !== void 0 && !validateServerPort(parsed.server_port)) { return invalid(_("server_port must be a number from 1 to 65535")); } if (parsed.outbounds !== void 0 && !validateOutbounds(parsed.outbounds)) { return invalid(_("outbounds must be a non-empty array of strings")); } if (parsed.detour !== void 0 && !nonEmptyString(parsed.detour)) { return invalid(_("detour must be a non-empty string")); } return { valid: true, message: _("Valid") }; } // 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 parsedHostPort = parseHostPort(serverPart); if (!parsedHostPort) { return { valid: false, message: _("Invalid Shadowsocks URL: invalid server and port") }; } const { host: server, port: portAndRest } = parsedHostPort; 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") }; } if (!isValidPort(port)) { 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/helpers/parseQueryString.ts function parseQueryString(query) { const clean = query.startsWith("?") ? query.slice(1) : query; return clean.split("&").filter(Boolean).reduce( (acc, pair) => { const [rawKey, rawValue = ""] = pair.split("="); if (!rawKey) { return acc; } const key = decodeURIComponent(rawKey); const value = decodeURIComponent(rawValue); return { ...acc, [key]: value }; }, {} ); } // src/validators/validateVlessUrl.ts function validateVlessUrl(url) { try { if (!url.startsWith("vless://")) return { valid: false, message: "Invalid VLESS URL: must start with vless://" }; if (/\s/.test(url)) return { valid: false, message: "Invalid VLESS URL: must not contain spaces" }; const body = url.slice("vless://".length); const [mainPart] = body.split("#"); const [userHostPort, queryString] = mainPart.split("?"); if (!userHostPort) return { valid: false, message: "Invalid VLESS URL: missing host and UUID" }; const [userPart, hostPortPart] = userHostPort.split("@"); if (!userPart) return { valid: false, message: "Invalid VLESS URL: missing UUID" }; if (!hostPortPart) return { valid: false, message: "Invalid VLESS URL: missing server" }; const parsedHostPort = parseHostPort(hostPortPart); if (!parsedHostPort) return { valid: false, message: "Invalid VLESS URL: invalid host and port" }; const { host, port } = parsedHostPort; if (!host) return { valid: false, message: "Invalid VLESS URL: missing hostname" }; if (!port) return { valid: false, message: "Invalid VLESS URL: missing port" }; const cleanedPort = port.replace("/", ""); if (!isValidPort(cleanedPort)) return { valid: false, message: "Invalid VLESS URL: invalid port number" }; if (!queryString) return { valid: false, message: "Invalid VLESS URL: missing query parameters" }; const params = parseQueryString(queryString); const validTypes = [ "tcp", "raw", "udp", "grpc", "http", "httpupgrade", "xhttp", "ws", "kcp" ]; const validSecurities = ["tls", "reality", "none"]; const transportType = params.type || "tcp"; if (!validTypes.includes(transportType)) return { valid: false, 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" }; if (params.security === "reality") { if (!params.pbk) return { valid: false, message: "Invalid VLESS URL: missing pbk for reality" }; if (!params.fp) return { valid: false, 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 is not supported" }; } return { valid: true, message: _("Valid") }; } catch (_e) { return { valid: false, message: _("Invalid VLESS URL: parsing failed") }; } } // src/validators/validateVmessUrl.ts function decodeBase64Json(value) { const normalized = value.replace(/-/g, "+").replace(/_/g, "/"); const padded = normalized.padEnd( normalized.length + (4 - normalized.length % 4) % 4, "=" ); const binary = atob(padded); const decoded = decodeURIComponent( Array.from(binary).map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`).join("") ); return JSON.parse(decoded); } function validateVmessUrl(url) { try { if (!url.startsWith("vmess://")) { return { valid: false, message: "Invalid VMess URL: must start with vmess://" }; } if (/\s/.test(url)) { return { valid: false, message: "Invalid VMess URL: must not contain spaces" }; } const body = url.slice("vmess://".length); const [encoded] = body.split("#"); if (!encoded) { return { valid: false, message: "Invalid VMess URL: missing encoded config" }; } const config = decodeBase64Json(encoded); if (!config || typeof config !== "object") { return { valid: false, message: "Invalid VMess URL: invalid config" }; } const { add, port, id } = config; if (!add || typeof add !== "string" || !isValidHost(add)) { return { valid: false, message: "Invalid VMess URL: invalid server" }; } if (!isValidPort(port)) { return { valid: false, message: "Invalid VMess URL: invalid port number" }; } if (!id || typeof id !== "string") { return { valid: false, message: "Invalid VMess URL: missing UUID" }; } return { valid: true, message: _("Valid") }; } catch (_e) { return { valid: false, message: _("Invalid VMess URL: parsing failed") }; } } // src/validators/validateTrojanUrl.ts function validateTrojanUrl(url) { try { 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") }; } const body = url.slice("trojan://".length); const [mainPart] = body.split("#"); const [userHostPort] = mainPart.split("?"); const [userPart, hostPortPart] = userHostPort.split("@"); if (!userHostPort) return { valid: false, 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" }; const parsedHostPort = parseHostPort(hostPortPart); if (!parsedHostPort) return { valid: false, message: "Invalid Trojan URL: invalid host and port" }; const { host, port } = parsedHostPort; if (!host) return { valid: false, message: "Invalid Trojan URL: missing hostname" }; if (!port) return { valid: false, message: "Invalid Trojan URL: missing port" }; if (!isValidPort(port)) return { valid: false, message: "Invalid Trojan URL: invalid port number" }; } catch (_e) { return { valid: false, message: _("Invalid Trojan URL: parsing failed") }; } 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 parsedHostPort = parseHostPort(hostPortPart); if (!parsedHostPort) { return { valid: false, message: _("Invalid SOCKS URL: invalid host and port") }; } const { host, port } = parsedHostPort; if (!host) { return { valid: false, message: _("Invalid SOCKS URL: missing hostname or IP") }; } if (!port) { return { valid: false, message: _("Invalid SOCKS URL: missing port") }; } if (!isValidPort(port)) { return { valid: false, message: _("Invalid SOCKS URL: invalid port number") }; } } catch (_e) { return { valid: false, message: _("Invalid SOCKS URL: parsing failed") }; } return { valid: true, message: _("Valid") }; } // src/validators/validateHysteriaUrl.ts function validateHysteria2Url(url) { try { const isHY2 = url.startsWith("hysteria2://"); const isHY2Short = url.startsWith("hy2://"); if (!isHY2 && !isHY2Short) { return { valid: false, message: _("Invalid HY2 URL: must start with hysteria2:// or hy2://") }; } if (/\s/.test(url)) { return { valid: false, message: _("Invalid HY2 URL: must not contain spaces") }; } const prefix = isHY2 ? "hysteria2://" : "hy2://"; const body = url.slice(prefix.length); const [mainPart] = body.split("#"); const [authHostPort, queryString] = mainPart.split("?"); if (!authHostPort) return { valid: false, message: _("Invalid HY2 URL: missing credentials/server") }; const [passwordPart, hostPortPart] = authHostPort.split("@"); if (!passwordPart) return { valid: false, message: _("Invalid HY2 URL: missing password") }; if (!hostPortPart) return { valid: false, message: _("Invalid HY2 URL: missing host & port") }; const parsedHostPort = parseHostPort(hostPortPart); if (!parsedHostPort) { return { valid: false, message: _("Invalid HY2 URL: invalid host & port") }; } const { host, port } = parsedHostPort; if (!host) { return { valid: false, message: _("Invalid HY2 URL: missing host") }; } if (!port) { return { valid: false, message: _("Invalid HY2 URL: missing port") }; } const cleanedPort = port.replace("/", ""); const portEntries = cleanedPort.split(","); const isValidPortEntry = (entry) => { if (!entry) { return false; } if (!entry.includes("-")) { return isValidPort(entry); } const rangeParts = entry.split("-"); if (rangeParts.length !== 2) { return false; } const [start, end] = rangeParts; return isValidPort(start) && isValidPort(end) && Number(start) <= Number(end); }; if (!portEntries.every(isValidPortEntry)) { return { valid: false, message: _("Invalid HY2 URL: invalid port number") }; } if (queryString) { const params = parseQueryString(queryString); const paramsKeys = Object.keys(params); if (paramsKeys.includes("insecure") && !["0", "1"].includes(params.insecure)) { return { valid: false, message: _("Invalid HY2 URL: insecure must be 0 or 1") }; } const validObfsTypes = ["none", "salamander"]; if (paramsKeys.includes("obfs") && !validObfsTypes.includes(params.obfs)) { return { valid: false, message: _("Invalid HY2 URL: unsupported obfs type") }; } if (paramsKeys.includes("obfs") && params.obfs !== "none" && !params["obfs-password"]) { return { valid: false, message: _( "Invalid HY2 URL: obfs-password required when obfs is set" ) }; } if (paramsKeys.includes("sni") && !params.sni) { return { valid: false, message: _("Invalid HY2 URL: sni cannot be empty") }; } if (paramsKeys.includes("mport") && (!params.mport || !params.mport.split(",").every(isValidPortEntry))) { return { valid: false, message: _("Invalid HY2 URL: invalid port number") }; } } return { valid: true, message: _("Valid") }; } catch (_e) { return { valid: false, message: _("Invalid HY2 URL: parsing failed") }; } } // src/validators/validateTuicUrl.ts function isValidUuid(value) { return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( value ); } function validateTuicUrl(url) { try { if (!url.startsWith("tuic://")) { return { valid: false, message: _("Invalid TUIC URL: must start with tuic://") }; } if (/\s/.test(url)) { return { valid: false, message: _("Invalid TUIC URL: must not contain spaces") }; } const body = url.slice("tuic://".length); const [mainPart] = body.split("#"); const [authHostPort, queryString] = mainPart.split("?"); if (!authHostPort) return { valid: false, message: _("Invalid TUIC URL: missing credentials/server") }; const [uuidPasswordPart, hostPortPart] = authHostPort.split("@"); if (!uuidPasswordPart) return { valid: false, message: _("Invalid TUIC URL: missing UUID") }; if (!hostPortPart) return { valid: false, message: _("Invalid TUIC URL: missing host & port") }; const [uuidPart, passwordPart] = uuidPasswordPart.split(":"); const uuid = uuidPart || ""; const _password = passwordPart || ""; if (!uuid) { return { valid: false, message: _("Invalid TUIC URL: missing UUID") }; } if (!isValidUuid(uuid)) { return { valid: false, message: _("Invalid TUIC URL: UUID must be a valid UUID v4 format") }; } const parsedHostPort = parseHostPort(hostPortPart); if (!parsedHostPort) { return { valid: false, message: _("Invalid TUIC URL: invalid host & port") }; } const { host, port } = parsedHostPort; if (!host) { return { valid: false, message: _("Invalid TUIC URL: missing host") }; } if (!port) { return { valid: false, message: _("Invalid TUIC URL: missing port") }; } const cleanedPort = port.replace("/", ""); if (!isValidPort(cleanedPort)) { return { valid: false, message: _("Invalid TUIC URL: invalid port number") }; } if (queryString) { const params = parseQueryString(queryString); const paramsKeys = Object.keys(params); const validCc = ["bbr", "cubic", "new_reno"]; if (paramsKeys.includes("congestion_control") && !validCc.includes(params.congestion_control)) { return { valid: false, message: _( "Invalid TUIC URL: congestion_control must be bbr, cubic, or new_reno" ) }; } const validUdpMode = ["native", "quic", "qux"]; if (paramsKeys.includes("udp_relay_mode") && !validUdpMode.includes(params.udp_relay_mode)) { return { valid: false, message: _( "Invalid TUIC URL: udp_relay_mode must be native, quic, or qux" ) }; } if (paramsKeys.includes("insecure") && !["0", "1"].includes(params.insecure)) { return { valid: false, message: _("Invalid TUIC URL: insecure must be 0 or 1") }; } if (paramsKeys.includes("zero_rtt_handshake") && !["0", "1", "true", "false"].includes(params.zero_rtt_handshake)) { return { valid: false, message: _( "Invalid TUIC URL: zero_rtt_handshake must be 0, 1, true, or false" ) }; } if (paramsKeys.includes("sni") && !params.sni) { return { valid: false, message: _("Invalid TUIC URL: sni cannot be empty") }; } } return { valid: true, message: _("Valid") }; } catch (_e) { return { valid: false, message: _("Invalid TUIC URL: parsing failed") }; } } // src/validators/validateHttpProxyUrl.ts function validateHttpProxyUrl(url) { try { if (!/^https?:\/\//.test(url)) { return { valid: false, message: _( "Invalid HTTP proxy URL: must start with http:// or https://" ) }; } if (!url || /\s/.test(url)) { return { valid: false, message: _("Invalid HTTP proxy URL: must not contain spaces") }; } const body = url.replace(/^https?:\/\//, ""); if (/[/?#]/.test(body)) { return { valid: false, message: _( "Invalid HTTP proxy URL: path, query, and fragment are not supported" ) }; } const atIndex = body.lastIndexOf("@"); const credentials = atIndex >= 0 ? body.slice(0, atIndex) : ""; const hostPortPart = atIndex >= 0 ? body.slice(atIndex + 1) : body; if (credentials) { const [username] = credentials.split(":"); if (!username) { return { valid: false, message: _("Invalid HTTP proxy URL: missing username") }; } } if (!hostPortPart) { return { valid: false, message: _("Invalid HTTP proxy URL: missing host and port") }; } const parsedHostPort = parseHostPort(hostPortPart); if (!parsedHostPort) { return { valid: false, message: _("Invalid HTTP proxy URL: invalid host and port") }; } const { host, port } = parsedHostPort; if (!host) { return { valid: false, message: _("Invalid HTTP proxy URL: missing hostname or IP") }; } if (!port) { return { valid: false, message: _("Invalid HTTP proxy URL: missing port") }; } if (!isValidPort(port)) { return { valid: false, message: _("Invalid HTTP proxy URL: invalid port number") }; } } catch (_e) { return { valid: false, message: _("Invalid HTTP proxy URL: parsing failed") }; } return { valid: true, message: _("Valid") }; } // src/validators/validateProxyUrl.ts function validateProxyUrl(url) { const trimmedUrl = url.trim(); if (trimmedUrl.startsWith("ss://")) { return validateShadowsocksUrl(trimmedUrl); } if (trimmedUrl.startsWith("vless://")) { return validateVlessUrl(trimmedUrl); } if (trimmedUrl.startsWith("vmess://")) { return validateVmessUrl(trimmedUrl); } if (trimmedUrl.startsWith("trojan://")) { return validateTrojanUrl(trimmedUrl); } if (/^socks(4|4a|5):\/\//.test(trimmedUrl)) { return validateSocksUrl(trimmedUrl); } if (/^https?:\/\//.test(trimmedUrl)) { return validateHttpProxyUrl(trimmedUrl); } if (trimmedUrl.startsWith("hysteria2://") || trimmedUrl.startsWith("hy2://")) { return validateHysteria2Url(trimmedUrl); } if (trimmedUrl.startsWith("tuic://")) { return validateTuicUrl(trimmedUrl); } return { valid: false, message: _( "URL must start with vless://, vmess://, ss://, trojan://, socks4://, socks4a://, socks5://, http://, https://, hysteria2://, hy2://, or tuic://" ) }; } // src/helpers/parseValueList.ts function parseValueList(value) { return value.split(/\r?\n/).map((line) => { const stripped = line.replace( /^(full|keyword|regex):[ \t]*(\/\/|#).*$/, "" ); return stripped.split("//")[0].split("#")[0]; }).join(" ").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean); } // src/helpers/getProxyUrlName.ts function getProxyUrlName(url) { try { const [_link, hash] = url.split("#"); if (!hash) { return ""; } return decodeURIComponent(hash); } catch { return ""; } } // src/helpers/formatOutboundType.ts function formatOutboundType(type, transport) { const cleanType = (type || "").trim(); const cleanTransport = (transport || "").trim().toUpperCase(); if (!cleanType) { return cleanTransport ? `(${cleanTransport})` : ""; } if (!cleanTransport) { return cleanType; } if (cleanType.toUpperCase().includes(`(${cleanTransport})`)) { return cleanType; } return `${cleanType} (${cleanTransport})`; } // src/helpers/getProxyUrlTransport.ts function getProxyUrlTransport(url) { if (!url || typeof url !== "string") { return void 0; } try { const trimmed = url.trim(); if (trimmed.startsWith("vmess://")) { const b64 = trimmed.slice(8).split("#")[0]; if (typeof atob === "function") { const jsonStr = atob(b64.replace(/[\r\n]/g, "")); const obj = JSON.parse(jsonStr); if (obj && typeof obj.net === "string" && obj.net.trim()) { return obj.net.trim().toLowerCase(); } } } const [base] = trimmed.split("#"); const queryIdx = base.indexOf("?"); if (queryIdx !== -1) { const query = base.slice(queryIdx + 1); const params = new URLSearchParams(query); const transport = params.get("type"); if (transport && transport.trim()) { return transport.trim().toLowerCase(); } } } catch { } return void 0; } // src/tachyon/tabs/dashboard/partials/renderFlagEmojis.ts var FLAG_EMOJI_PATTERN = /([\u{1f1e6}-\u{1f1ff}]{2}|\u{1f3f4}[\u{e0061}-\u{e007a}]+\u{e007f})/gu; var EXACT_FLAG_EMOJI_PATTERN = /^([\u{1f1e6}-\u{1f1ff}]{2}|\u{1f3f4}[\u{e0061}-\u{e007a}]+\u{e007f})$/u; function renderFlagEmojis(value) { return value.split(FLAG_EMOJI_PATTERN).filter(Boolean).map( (part) => EXACT_FLAG_EMOJI_PATTERN.test(part) ? E("span", { class: "tachyon_dashboard-page__flag-emoji" }, part) : part ); } // 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/tachyon/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); if (this.logs.length > 2e3) { this.logs.shift(); } switch (level) { case "error": console.error(message); break; case "warn": console.warn(message); break; case "info": console.info(message); break; default: if (typeof localStorage !== "undefined" && localStorage.getItem("tachyon_debug") === "true") { 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() available only in browser"); return; } downloadAsTxt(this.getLogs(), filename); } }; var logger = new Logger(); // 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; logger.debug("[SHELL]", `[${operationName}] took ${elapsed.toFixed(2)} ms`); } } // src/constants.ts var TACHYON_UCI_PACKAGE = "tachyon"; var TACHYON_LUCI_APP_VERSION = "__COMPILED_VERSION_VARIABLE__"; var TACHYON_ACTION_PROVIDERS_AVAILABILITY_EVENT = "tachyon:action-providers-availability"; var FAKEIP_CHECK_DOMAIN = "fakeip.podkop.fyi"; var IP_CHECK_DOMAIN = "ip.podkop.fyi"; var DEFAULT_LATENCY_TEST_URL = "https://www.gstatic.com/generate_204"; var LATENCY_TEST_URL_OPTIONS = [ DEFAULT_LATENCY_TEST_URL, "https://cp.cloudflare.com/generate_204", "https://captive.apple.com", "https://connectivity-check.ubuntu.com" ]; var DOMAIN_LIST_OPTIONS = { russia_inside: "Russia inside", russia_outside: "Russia outside", ukraine_inside: "Ukraine", geoblock: "Geo Block", block: "RKN 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", roblox: "Roblox", ads_hagezi_pro: "Ads (Hagezi Pro)", supercell: "Supercell", github: "GitHub", twitch: "Twitch", hetzner: "Hetzner ASN", ovh: "OVH ASN", digitalocean: "Digital Ocean ASN", cloudfront: "CloudFront ASN" }; var DNS_SERVERS_BY_PROTOCOL = { udp: { "1.1.1.1": "1.1.1.1 (Cloudflare)", "1.0.0.1": "1.0.0.1 (Cloudflare Secondary)", "1.1.1.2": "1.1.1.2 (Cloudflare Security)", "1.1.1.3": "1.1.1.3 (Cloudflare Family)", "8.8.8.8": "8.8.8.8 (Google)", "8.8.4.4": "8.8.4.4 (Google Secondary)", "9.9.9.9": "9.9.9.9 (Quad9 Filtered)", "149.112.112.112": "149.112.112.112 (Quad9 Secondary)", "9.9.9.10": "9.9.9.10 (Quad9 Unsecured)", "94.140.14.14": "94.140.14.14 (AdGuard Default)", "94.140.15.15": "94.140.15.15 (AdGuard Default Secondary)", "94.140.14.140": "94.140.14.140 (AdGuard Non-filtering)", "94.140.14.15": "94.140.14.15 (AdGuard Family)", "76.76.2.0": "76.76.2.0 (Control D Uncensored)", "76.76.2.1": "76.76.2.1 (Control D Malware)", "76.76.2.2": "76.76.2.2 (Control D Adblock)", "194.242.2.2": "194.242.2.2 (Mullvad No-Logs)", "194.242.2.3": "194.242.2.3 (Mullvad Adblock)", "194.242.2.4": "194.242.2.4 (Mullvad Extended)", "185.222.222.222": "185.222.222.222 (DNS.SB)", "193.110.81.0": "193.110.81.0 (DNS0.EU Zero)", "193.17.47.1": "193.17.47.1 (CZ.NIC ODVR)", "208.67.222.222": "208.67.222.222 (OpenDNS)", "223.5.5.5": "223.5.5.5 (AliDNS)", "185.228.168.9": "185.228.168.9 (CleanBrowsing Security)", "77.88.8.8": "77.88.8.8 (Yandex)", "77.88.8.1": "77.88.8.1 (Yandex Secondary)", "77.88.8.88": "77.88.8.88 (Yandex Safe)" }, doh: { "https://cloudflare-dns.com/dns-query": "Cloudflare", "https://security.cloudflare-dns.com/dns-query": "Cloudflare Security", "https://family.cloudflare-dns.com/dns-query": "Cloudflare Family", "https://dns.google/dns-query": "Google", "https://dns.quad9.net/dns-query": "Quad9 (Filtered)", "https://dns10.quad9.net/dns-query": "Quad9 (Unfiltered)", "https://dns.adguard-dns.com/dns-query": "AdGuard Default", "https://unfiltered.adguard-dns.com/dns-query": "AdGuard Non-filtering", "https://family.adguard-dns.com/dns-query": "AdGuard Family", "https://dns.mullvad.net/dns-query": "Mullvad Base", "https://adblock.dns.mullvad.net/dns-query": "Mullvad Adblock", "https://extended.dns.mullvad.net/dns-query": "Mullvad Extended", "https://dns.nextdns.io/dns-query": "NextDNS", "https://freedns.controld.com/p0": "Control D Uncensored", "https://freedns.controld.com/p1": "Control D Malware", "https://freedns.controld.com/p2": "Control D Adblock", "https://doh.dns.sb/dns-query": "DNS.SB", "https://zero.dns0.eu": "DNS0.EU Zero", "https://odvr.nic.cz/doh": "CZ.NIC ODVR", "https://dns.alidns.com/dns-query": "AliDNS", "https://doh.opendns.com/dns-query": "OpenDNS", "https://doh.cleanbrowsing.org/doh/security-filter/": "CleanBrowsing Security", "https://doh.cleanbrowsing.org/doh/family-filter/": "CleanBrowsing Family", "https://common.dot.dns.yandex.net/dns-query": "Yandex" }, dot: { "1.1.1.1": "1.1.1.1 (Cloudflare)", "1.0.0.1": "1.0.0.1 (Cloudflare Secondary)", "security.cloudflare-dns.com": "Cloudflare Security", "family.cloudflare-dns.com": "Cloudflare Family", "dns.google": "Google", "dns.quad9.net": "Quad9 (Filtered)", "dns10.quad9.net": "Quad9 (Unfiltered)", "dns.adguard-dns.com": "AdGuard Default", "unfiltered.adguard-dns.com": "AdGuard Non-filtering", "family.adguard-dns.com": "AdGuard Family", "dns.mullvad.net": "Mullvad Base", "adblock.dns.mullvad.net": "Mullvad Adblock", "extended.dns.mullvad.net": "Mullvad Extended", "dns.nextdns.io": "NextDNS", "p0.freedns.controld.com": "Control D Uncensored", "p1.freedns.controld.com": "Control D Malware", "p2.freedns.controld.com": "Control D Adblock", "dot.sb": "DNS.SB", "zero.dns0.eu": "DNS0.EU Zero", "odvr.nic.cz": "CZ.NIC ODVR", "dns.alidns.com": "AliDNS", "security-filter-dns.cleanbrowsing.org": "CleanBrowsing Security", "family-filter-dns.cleanbrowsing.org": "CleanBrowsing Family", "common.dot.dns.yandex.net": "Yandex", "safe.dot.dns.yandex.net": "Yandex Safe" }, doq: { "1.1.1.1:784": "Cloudflare", "1.0.0.1:784": "Cloudflare Secondary", "dns.google:784": "Google", "dns.adguard-dns.com:785": "AdGuard Default", "unfiltered.adguard-dns.com:785": "AdGuard Non-filtering", "dns.mullvad.net:784": "Mullvad Base", "adblock.dns.mullvad.net:784": "Mullvad Adblock", "dns.nextdns.io:784": "NextDNS", "p0.freedns.controld.com:853": "Control D Uncensored", "p2.freedns.controld.com:853": "Control D Adblock", "zero.dns0.eu:853": "DNS0.EU Zero" } }; var BOOTSTRAP_DNS_SERVER_OPTIONS = { "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)", "149.112.112.112": "149.112.112.112 (Quad9 DNS)", "94.140.14.14": "94.140.14.14 (AdGuard DNS)", "94.140.15.15": "94.140.15.15 (AdGuard DNS)", "76.76.2.0": "76.76.2.0 (Control D)", "194.242.2.2": "194.242.2.2 (Mullvad DNS)", "185.222.222.222": "185.222.222.222 (DNS.SB)", "193.110.81.0": "193.110.81.0 (DNS0.EU)", "193.17.47.1": "193.17.47.1 (CZ.NIC ODVR)", "208.67.222.222": "208.67.222.222 (OpenDNS)", "223.5.5.5": "223.5.5.5 (AliDNS)", "77.88.8.8": "77.88.8.8 (Yandex DNS)", "77.88.8.1": "77.88.8.1 (Yandex DNS)" }; var COMMAND_TIMEOUT = 1e4; // src/helpers/executeShellCommand.ts async function executeShellCommand({ command, args, timeout = COMMAND_TIMEOUT }) { try { return await withTimeout( fs.exec(command, args), timeout, [command, ...args].join(" ") ); } catch (err) { const error = err; const code = typeof error?.code === "number" ? error.code : 1; return { stdout: "", stderr: error?.message, code }; } } // src/helpers/onMount.ts function getTarget(target) { if (typeof target === "string") { return document.getElementById(target); } return target; } async function onMount(target) { return new Promise((resolve) => { let observer = null; const resolveIfMountedAndVisible = () => { const mountedTarget = getTarget(target); if (mountedTarget && mountedTarget.isConnected && mountedTarget.offsetParent !== null) { observer?.disconnect(); resolve(mountedTarget); return true; } return false; }; if (resolveIfMountedAndVisible()) { return; } observer = new MutationObserver(() => { resolveIfMountedAndVisible(); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style", "hidden"] }); }); } // src/helpers/getClashApiUrl.ts function canUseDirectClashApi() { const loc = typeof window !== "undefined" ? window.location : void 0; return Boolean(loc?.hostname && loc.protocol !== "https:"); } function getClashWsUrl() { const { hostname } = window.location; return `ws://${hostname}:9090`; } function getClashHttpUrl() { const { hostname } = window.location; return `http://${hostname}:9090`; } function getClashUIUrl() { const { hostname } = window.location; return `http://${hostname}:9090/ui`; } // 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 : []; } // src/helpers/isCopyableProxyLink.ts var COPYABLE_PROXY_URI_RE = /^(vless|vmess|trojan|ss|ssr|hysteria2|hy2|tuic|socks4|socks4a|socks5|http|https):\/\//i; function isCopyableProxyLink(link) { return COPYABLE_PROXY_URI_RE.test((link || "").trim()); } // src/icons/index.ts function createIcon(className, children, extraAttrs = {}) { return svgEl( "svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: className, ...extraAttrs }, children ); } function renderBookOpenTextIcon24() { return createIcon("lucide lucide-book-open-text-icon lucide-book-open-text", [ svgEl("path", { d: "M12 7v14" }), svgEl("path", { d: "M16 12h2" }), svgEl("path", { d: "M16 8h2" }), svgEl("path", { d: "M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" }), svgEl("path", { d: "M6 12h2" }), svgEl("path", { d: "M6 8h2" }) ]); } function renderCheckIcon24() { return createIcon("lucide lucide-check-icon lucide-check", [ svgEl("path", { d: "M20 6 9 17l-5-5" }) ]); } function renderCircleAlertIcon24() { return createIcon( "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" }) ], { width: "24", height: "24" } ); } function renderCircleCheckBigIcon24() { return createIcon( "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" }) ] ); } function renderCircleCheckIcon24() { return createIcon( "lucide lucide-circle-check-icon lucide-circle-check", [ svgEl("circle", { cx: "12", cy: "12", r: "10" }), svgEl("path", { d: "M9 12l2 2 4-4" }) ], { width: "24", height: "24" } ); } function renderCirclePlayIcon24() { return createIcon("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" }) ]); } function renderCircleSlashIcon24() { return createIcon( "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" }) ], { width: "24", height: "24" } ); } function renderCircleStopIcon24() { return createIcon("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" }) ]); } function renderCircleXIcon24() { return createIcon( "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" }) ], { width: "24", height: "24" } ); } function renderCogIcon24() { return createIcon("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" }) ]); } function renderCopyIcon24() { return createIcon( "lucide lucide-copy-icon lucide-copy", [ svgEl("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }), svgEl("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }) ], { width: "24", height: "24" } ); } function renderDownloadIcon24() { return createIcon("lucide lucide-download-icon lucide-download", [ svgEl("path", { d: "M12 15V3" }), svgEl("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), svgEl("path", { d: "m7 10 5 5 5-5" }) ]); } function renderGlobeIcon24() { return createIcon( "lucide lucide-globe-icon lucide-globe", [ svgEl("circle", { cx: "12", cy: "12", r: "10" }), svgEl("path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }), svgEl("path", { d: "M2 12h20" }) ], { width: "14", height: "14" } ); } function renderInfoIcon24() { return createIcon( "lucide lucide-info-icon lucide-info", [ svgEl("circle", { cx: "12", cy: "12", r: "10" }), svgEl("path", { d: "M12 16v-4" }), svgEl("path", { d: "M12 8h.01" }) ], { width: "24", height: "24" } ); } function renderLinkIcon24() { return createIcon( "lucide lucide-link-icon lucide-link", [ svgEl("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }), svgEl("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" }) ], { width: "24", height: "24" } ); } function renderLoaderCircleIcon24() { return createIcon( "lucide lucide-loader-circle 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" }) ], { width: "24", height: "24" } ); } function renderPauseIcon24() { return createIcon("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" }) ]); } function renderPlayIcon24() { return createIcon("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" }) ]); } function renderRotateCcwIcon24() { return createIcon("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" }) ]); } function renderSearchIcon24() { return createIcon("lucide lucide-search-icon lucide-search", [ svgEl("path", { d: "m21 21-4.34-4.34" }), svgEl("circle", { cx: "11", cy: "11", r: "8" }) ]); } function renderSendIcon24() { return createIcon( "lucide lucide-send-icon lucide-send", [ svgEl("path", { d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z" }), svgEl("path", { d: "m21.854 2.147-10.94 10.939" }) ], { width: "24", height: "24" } ); } function renderSquareChartGanttIcon24() { return createIcon( "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" }) ] ); } function renderTriangleAlertIcon24() { return createIcon("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" }) ]); } function renderXIcon24() { return createIcon("lucide lucide-x-icon lucide-x", [ svgEl("path", { d: "M18 6 6 18" }), svgEl("path", { d: "m6 6 12 12" }) ]); } function renderHeartIcon24() { return createIcon( "lucide lucide-heart-icon lucide-heart", [ svgEl("path", { d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" }) ], { width: "24", height: "24" } ); } // 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/tachyon/helpers/formatServiceStatusMessage.ts function formatServiceStatusMessage(msg) { if (!msg) return ""; const normalMatch = msg.match(/^([a-zA-Z0-9_-]+) provider status is normal$/); if (normalMatch) { const provider = normalMatch[1]; return `${_("Provider status is normal")}: ${provider}`; } const notReadyWithDetailMatch = msg.match( /^action=([^ ]+) is configured, but the Tachyon-managed ([^ ]+) runtime is not ready \((.+)\)$/ ); if (notReadyWithDetailMatch) { const [, action, bin, detail] = notReadyWithDetailMatch; return `${_("Action is configured, but runtime is not ready")} (${action} -> ${bin}): ${detail}`; } const notReadyMatch = msg.match( /^action=([^ ]+) is configured, but the Tachyon-managed ([^ ]+) runtime is not ready$/ ); if (notReadyMatch) { const [, action, bin] = notReadyMatch; return `${_("Action is configured, but runtime is not ready")} (${action} -> ${bin})`; } const notAvailablePathMatch = msg.match( /^action=([^ ]+) is configured, but (.+) (?:provider|ciadpi) is not available at (.+)$/ ); if (notAvailablePathMatch) { const [, action, provider, path] = notAvailablePathMatch; return `${_("Action is configured, but binary is not available")} (${action} -> ${provider}): ${path}`; } const overlapMatch = msg.match( /^external NFQUEUE rules overlap with the Tachyon (.+) range (.+)$/ ); if (overlapMatch) { const [, provider, range] = overlapMatch; return `${_("External NFQUEUE rules overlap with Tachyon")} ${provider} (${range})`; } if (msg === "legacy zapret runtime paths are still present and should be migrated") { return _( "Legacy zapret runtime paths are still present and should be migrated" ); } const unexpectedProcessesMatch = msg.match( /^unexpected Tachyon-managed ([^ ]+) processes are running without matching action=([^ ]+) rules$/ ); if (unexpectedProcessesMatch) { const [, bin, action] = unexpectedProcessesMatch; return `${_("Unexpected background processes")} (${bin}, action=${action})`; } if (msg.startsWith("standalone ")) { return _( "Standalone service is active alongside Tachyon; policy or port conflicts are possible" ); } const packageInstalledNoBinMatch = msg.match( /^(.+) package is installed, but (?:the provider binary|ciadpi) is not available at (.+)$/ ); if (packageInstalledNoBinMatch) { const [, provider, path] = packageInstalledNoBinMatch; return `${_("Package is installed, but binary missing")} (${provider}): ${path}`; } const notInstalledMatch = msg.match( /^(.+) (?:provider|package) is not installed; (?:action=([^ ]+) is unavailable|native Tailscale is unavailable)$/ ); if (notInstalledMatch) { const [, provider, action] = notInstalledMatch; return `${_("Package is not installed")} (${provider})${action ? `: action=${action} ${_("is unavailable")}` : ""}`; } if (msg.includes("ciadpi has restarted after exiting")) { return _( "ByeDPI restarted after exiting; strategy or traffic load may be unstable" ); } if (msg.includes( "native Tailscale is configured, but the tailscale package is missing" )) { return _( "Native Tailscale is configured, but tailscale package is missing" ); } if (msg.includes( "native Tailscale is configured, but tailscaled is not running" )) { return _( "Native Tailscale is configured, but tailscaled is not running for every section" ); } if (msg.includes("no server section uses native mode")) { return _( "Tailscale package is installed, but no server section uses native mode" ); } return _(msg); } // src/tachyon/tabs/dashboard/partials/renderSections.ts function renderFailedState() { return E( "div", { class: "tachyon_dashboard-page__outbound-section centered", style: "height: 127px" }, E("span", {}, [E("span", {}, _("Dashboard currently unavailable"))]) ); } function renderLoadingState() { return E("div", { id: "dashboard-sections-grid-skeleton", class: "tachyon_dashboard-page__outbound-section skeleton", style: "height: 127px" }); } function isValidHttpUrl(url) { return Boolean(url && /^https?:\/\/\S+$/i.test(url)); } function formatBytes(value) { if (typeof value !== "number" || !Number.isFinite(value) || value < 0) { return void 0; } return prettyBytes(value); } function formatDate(seconds) { if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) { return void 0; } const date = new Date(seconds * 1e3); if (Number.isNaN(date.getTime())) { return void 0; } return date.toLocaleDateString(void 0, { year: "numeric", month: "2-digit", day: "2-digit" }); } function renderMetadataAction(label, url) { if (!isValidHttpUrl(url)) { return void 0; } return E( "a", { class: "btn tachyon_dashboard-page__subscription-meta__action", href: url, target: "_blank", rel: "noopener noreferrer", title: label, "aria-label": label }, renderLinkIcon24() ); } function renderSubscriptionMetadata(metadata) { if (!metadata || Object.keys(metadata).length <= 1) { return void 0; } const title = metadata.title || metadata.fileName; const traffic = metadata.traffic; const used = formatBytes(traffic?.used) || "0 B"; const total = traffic?.isUnlimited ? "∞" : formatBytes(traffic?.total) || "0 B"; const expire = formatDate(metadata.expire); const refillDate = formatDate(metadata.refillDate); const rows = [ traffic ? { label: _("Traffic"), value: `${used} / ${total}` } : void 0, expire ? { label: _("Expires"), value: expire } : void 0, refillDate ? { label: _("Refill"), value: refillDate } : void 0 ].filter(Boolean); const actions = [ renderMetadataAction("Profile", metadata.webPageUrl), renderMetadataAction("Support", metadata.supportUrl), renderMetadataAction("More details", metadata.announceUrl) ].filter(Boolean); return E("div", { class: "tachyon_dashboard-page__subscription-meta" }, [ E("div", { class: "tachyon_dashboard-page__subscription-meta__main" }, [ E( "div", { class: "tachyon_dashboard-page__subscription-meta__heading" }, _("Subscription info:") ), title ? E( "div", { class: "tachyon_dashboard-page__subscription-meta__title" }, title ) : "", rows.length ? E( "div", { class: "tachyon_dashboard-page__subscription-meta__facts" }, rows.map( (row) => E( "div", { class: "tachyon_dashboard-page__subscription-meta__fact" }, [ E( "span", { class: "tachyon_dashboard-page__subscription-meta__fact-key" }, row.label ), E( "span", { class: "tachyon_dashboard-page__subscription-meta__fact-value" }, row.value ) ] ) ) ) : "", actions.length ? E( "div", { class: "tachyon_dashboard-page__subscription-meta__actions" }, actions ) : "" ]), metadata.announce ? E( "blockquote", { class: "tachyon_dashboard-page__subscription-meta__announce" }, metadata.announce ) : "" ]); } function renderSubscriptionUpdateAction(section, subscriptionUpdating, onUpdateSubscription) { if (!section.subscriptionSourceCount) { return void 0; } return E( "button", { type: "button", class: "btn tachyon_dashboard-page__outbound-section__subscription-update", "aria-label": _("Update subscriptions"), disabled: subscriptionUpdating ? true : void 0, click: (event) => { event.preventDefault(); event.stopPropagation(); if (subscriptionUpdating) { return; } onUpdateSubscription(section); } }, subscriptionUpdating ? [renderLoaderCircleIcon24(), _("Update subscriptions")] : _("Update subscriptions") ); } function getLatencyTestLabel(latencyProgress) { const total = Math.trunc(Number(latencyProgress?.total ?? 0)); if (!Number.isFinite(total) || total <= 0) { return _("Test latency"); } const completedValue = Number(latencyProgress?.completed ?? 0); const completed = Number.isFinite(completedValue) ? Math.trunc(completedValue) : 0; return `${_("Test latency")}: ${Math.min( Math.max(0, completed), total )}/${total}`; } function renderDefaultState({ section, onChooseOutbound, onCopyOutbound, onShowUrlTestInfo, onShowPriorityInfo, onTestLatency, onUpdateSubscription, latencyFetching, latencyProgress, subscriptionUpdating, selectorSwitchingTag, onTestSingleOutbound, testingOutboundCodes, isCollapsed, onToggleCollapse }) { const isConnectionNode = [ "vpn", "awg", "warp", "anytls", "snell", "mieru", "sudoku", "masque", "openvpn" ].includes(section.action || ""); const isServiceNode = ["zapret", "zapret2", "byedpi", "wdtt", "olcrtc", "fptn"].includes( section.action || "" ) || Boolean(section.serviceStatus); if (isServiceNode) { const ss = section.serviceStatus; const serviceType = ss?.serviceType || (["zapret", "zapret2", "byedpi", "wdtt", "olcrtc", "fptn"].includes( section.action || "" ) ? section.action : "zapret"); const typeLabel = serviceType === "zapret" ? "Zapret" : serviceType === "zapret2" ? "Zapret2" : serviceType === "byedpi" ? "ByeDPI" : serviceType === "wdtt" ? "WDTT" : serviceType === "olcrtc" ? "OlcRTC" : "FPTN"; const statusColor = ss ? ss.ready ? "var(--success-color-medium, green)" : ss.conflict ? "var(--error-color-medium, red)" : ss.configured ? "var(--warn-color-medium, orange)" : "var(--primary-color-low, lightgray)" : "var(--primary-color-low, lightgray)"; const statusText = ss ? ss.ready ? _("Running") : ss.conflict ? _("Conflict") : ss.configured ? _("Stopped") : _("Not configured") : _("Unknown"); return E("div", { class: "tachyon_dashboard-page__outbound-section" }, [ E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section", style: "cursor: default;" }, [ E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section__title", style: "display: flex; align-items: center; gap: 8px;" }, [ E("span", {}, section.displayName), E( "span", { style: "font-size: 12px; opacity: 0.6; font-weight: normal;" }, typeLabel ) ] ) ] ), E( "div", { style: "display: flex; flex-wrap: wrap; gap: 16px; padding: 8px 16px 12px;" }, [ E("div", { style: "display: flex; align-items: center; gap: 6px;" }, [ E( "span", { style: "opacity: 0.7; font-size: 13px;" }, _("Status") + ":" ), E( "span", { style: `font-size: 13px; font-weight: 500; color: ${statusColor};` }, statusText ) ]), ss && ss.restartCount > 0 ? E( "div", { style: "display: flex; align-items: center; gap: 6px;" }, [ E( "span", { style: "opacity: 0.7; font-size: 13px;" }, _("Restarts") + ":" ), E( "span", { style: "font-size: 13px; font-weight: 500; color: var(--warn-color-medium, orange);" }, `${ss.restartCount}` ) ] ) : "", ss && ss.unstable ? E( "div", { style: "display: flex; align-items: center; gap: 6px;" }, [ E( "span", { style: "font-size: 13px; font-weight: 500; color: var(--error-color-medium, red);" }, _("Unstable") ) ] ) : "" ] ), ss?.statusMessage ? E( "div", { style: "padding: 0 16px 8px; font-size: 12px; opacity: 0.6; word-break: break-word;" }, formatServiceStatusMessage(ss.statusMessage) ) : "" ]); } function testLatency() { if (section.withTagSelect) { return onTestLatency( section.latencyTestCodes?.length ? section.latencyTestCodes : section.latencyTestCode || section.code ); } if (section.outbounds.length) { return onTestLatency(section.outbounds[0].code); } } function renderOutbound(outbound) { const isTestingSingle = Boolean( testingOutboundCodes && testingOutboundCodes[outbound.code] ); function getLatencyClass() { if (isConnectionNode) { if (latencyFetching) { return "tachyon_dashboard-page__outbound-grid__item__latency--yellow"; } if (outbound.latency === -1 || !outbound.runtimeAvailable) { return "tachyon_dashboard-page__outbound-grid__item__latency--red"; } if (!outbound.latency) { return "tachyon_dashboard-page__outbound-grid__item__latency--green"; } } if (!outbound.latency) { return "tachyon_dashboard-page__outbound-grid__item__latency--empty"; } if (outbound.latency === -1 || outbound.latency < 0) { return "tachyon_dashboard-page__outbound-grid__item__latency--red"; } if (outbound.latency < 800) { return "tachyon_dashboard-page__outbound-grid__item__latency--green"; } if (outbound.latency < 1500) { return "tachyon_dashboard-page__outbound-grid__item__latency--yellow"; } return "tachyon_dashboard-page__outbound-grid__item__latency--red"; } const connectionStatusText = latencyFetching ? `● ${_("Checking...")}` : outbound.latency && outbound.latency > 0 ? `● ${outbound.latency} ms` : outbound.latency === -1 ? `● ${_("Not responding")}` : outbound.runtimeAvailable ? `● ${_("Connected")}` : `● ${_("Not connected")}`; const canCopyLink = Boolean(outbound.canCopyLink) || isCopyableProxyLink(outbound.link); const selectorSwitching = Boolean(selectorSwitchingTag); const outboundSwitching = selectorSwitchingTag === outbound.code; const canChooseOutbound = section.withTagSelect && outbound.runtimeAvailable !== false && !selectorSwitching && !outbound.selected; const className = [ "tachyon_dashboard-page__outbound-grid__item", outbound.selected ? "tachyon_dashboard-page__outbound-grid__item--active" : "", canChooseOutbound ? "tachyon_dashboard-page__outbound-grid__item--selectable" : "", section.withTagSelect && !canChooseOutbound ? "tachyon_dashboard-page__outbound-grid__item--disabled" : "", outboundSwitching ? "tachyon_dashboard-page__outbound-grid__item--switching" : "" ].filter(Boolean).join(" "); if (isConnectionNode) { return E( "div", { class: className, style: "display: flex; align-items: center; justify-content: space-between; padding: 12px; min-width: 0; gap: 16px;" }, [ E( "div", { style: "display: flex; align-items: center; gap: 12px; min-width: 0;" }, [ E( "b", { style: "overflow-wrap: anywhere; word-break: break-all; min-width: 0;" }, renderFlagEmojis(outbound.displayName) ), outbound.prefix ? E( "span", { class: "tachyon_dashboard-page__outbound-grid__item__prefix-badge" }, renderFlagEmojis(outbound.prefix) ) : "", E( "span", { style: "opacity: 0.7; font-size: 13px; white-space: nowrap; flex-shrink: 0;" }, [formatOutboundType(outbound.type, outbound.transport)].filter( Boolean ) ), E( "div", { class: getLatencyClass(), style: "white-space: nowrap; flex-shrink: 0;" }, connectionStatusText ) ] ), E( "button", { type: "button", class: "btn dashboard-sections-grid-item-test-latency", style: "padding: 4px 12px; height: 30px; flex-shrink: 0;", disabled: latencyFetching ? true : void 0, click: (event) => { event.preventDefault(); event.stopPropagation(); if (latencyFetching) return; testLatency(); } }, latencyFetching ? [ renderLoaderCircleIcon24(), E( "span", { class: "dashboard-sections-grid-item-test-latency__label" }, _("Checking...") ) ] : E( "span", { class: "dashboard-sections-grid-item-test-latency__label" }, _("Check Connection") ) ) ] ); } const isManualUrlTest = Boolean(outbound.urlTestInfo?.isManualSelection); const isManualPriority = Boolean(outbound.priorityInfo?.isManualSelection); const activeServerName = outbound.urlTestInfo?.selectedName || outbound.priorityInfo?.selectedName || ""; const baseType = formatOutboundType(outbound.type, outbound.transport); const typeLabel = isManualUrlTest || isManualPriority ? `${baseType} (${_("Manual")})` : outbound.urlTestInfo || outbound.priorityInfo ? `${baseType} (${_("Auto")})` : baseType; return E( "div", { class: className, "aria-busy": outboundSwitching ? "true" : void 0, "aria-disabled": section.withTagSelect && !canChooseOutbound ? "true" : void 0, click: () => canChooseOutbound && onChooseOutbound(section.sectionName, section.code, outbound.code) }, [ ...outboundSwitching ? [ svgEl( "svg", { class: "tachyon_dashboard-page__outbound-grid__item__snake" }, [ svgEl("rect", { width: "100%", height: "100%", fill: "none", rx: 4, ry: 4, pathLength: 100 }) ] ) ] : [], E( "div", { class: "tachyon_dashboard-page__outbound-grid__item__header" }, [ E( "div", { class: "tachyon_dashboard-page__outbound-grid__item__title-wrapper" }, [ E("b", {}, renderFlagEmojis(outbound.displayName)), outbound.prefix ? E( "span", { class: "tachyon_dashboard-page__outbound-grid__item__prefix-badge" }, renderFlagEmojis(outbound.prefix) ) : "" ] ), ...canCopyLink ? [ E( "button", { type: "button", class: "btn tachyon_dashboard-page__outbound-grid__item__copy-button", title: _("Copy proxy link"), "aria-label": _("Copy proxy link"), click: (event) => { event.stopPropagation(); onCopyOutbound(section, outbound); } }, renderCopyIcon24() ) ] : [], ...outbound.urlTestInfo ? [ E( "button", { type: "button", class: "btn tachyon_dashboard-page__outbound-grid__item__copy-button", title: _("URLTest details"), "aria-label": _("URLTest details"), click: (event) => { event.stopPropagation(); onShowUrlTestInfo(section, outbound); } }, renderInfoIcon24() ) ] : [], ...outbound.priorityInfo ? [ E( "button", { type: "button", class: "btn tachyon_dashboard-page__outbound-grid__item__copy-button", title: _("Priority details"), "aria-label": _("Priority details"), click: (event) => { event.stopPropagation(); onShowPriorityInfo(section, outbound); } }, renderInfoIcon24() ) ] : [] ] ), ...activeServerName ? [ E( "div", { class: "tachyon_dashboard-page__outbound-grid__item__active-server", style: "font-size: 13px; font-weight: 500; opacity: 0.95; margin: 4px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; gap: 4px;", title: activeServerName }, renderFlagEmojis(activeServerName) ) ] : [], E( "div", { class: "tachyon_dashboard-page__outbound-grid__item__footer" }, [ E( "div", { class: "tachyon_dashboard-page__outbound-grid__item__type" }, typeLabel ), E( "div", { class: [ getLatencyClass(), !isConnectionNode ? "tachyon_dashboard-page__outbound-grid__item__latency--clickable" : "", isTestingSingle ? "tachyon_dashboard-page__outbound-grid__item__latency--testing" : "" ].filter(Boolean).join(" "), style: isConnectionNode ? void 0 : "cursor: pointer;", title: isConnectionNode ? void 0 : _("Test node latency"), "aria-label": isConnectionNode ? void 0 : _("Test node latency"), click: isConnectionNode ? void 0 : (event) => { event?.stopPropagation?.(); event?.preventDefault?.(); if (isTestingSingle || latencyFetching) return; onTestSingleOutbound?.( section.sectionName, outbound.code ); } }, isTestingSingle ? [renderLoaderCircleIcon24(), E("span", {}, _("Checking..."))] : isConnectionNode ? connectionStatusText : outbound.latency && outbound.latency > 0 ? `${outbound.latency}ms` : outbound.latency === -1 || outbound.latency < 0 ? _("Not responding") : "N/A" ) ] ) ] ); } if (isConnectionNode) { return E( "div", { class: "tachyon_dashboard-page__outbound-section", style: "border: none; padding: 0;" }, [ E( "div", { style: "display: flex; flex-direction: column; gap: 8px; padding: 0;" }, [...section.outbounds.map((outbound) => renderOutbound(outbound))] ) ] ); } const metadataNodes = (section.subscriptionMetadata || []).map((metadata) => renderSubscriptionMetadata(metadata)).filter(Boolean); const subscriptionUpdateAction = renderSubscriptionUpdateAction( section, subscriptionUpdating, onUpdateSubscription ); const canTestLatency = !isServiceNode && (section.withTagSelect || section.outbounds && section.outbounds.length > 0); return E("div", { class: "tachyon_dashboard-page__outbound-section" }, [ // Title with test latency E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section", click: (e) => { if (e.target && e.target.closest("button")) return; onToggleCollapse?.(); }, style: "cursor: pointer; user-select: none;" }, [ E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section__title", style: "display: flex; align-items: center; gap: 8px;" }, [ svgEl( "svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", style: `transition: transform 0.2s; transform: rotate(${isCollapsed ? "-90deg" : "0deg"})` }, [svgEl("polyline", { points: "6 9 12 15 18 9" })] ), E("span", {}, section.displayName), isCollapsed ? (() => { const selectedOutbound = section.outbounds.find( (o) => o.selected ); if (!selectedOutbound) return ""; const isConnectionNode2 = [ "vpn", "awg", "warp", "anytls", "snell", "mieru", "sudoku", "masque", "openvpn" ].includes(section.action || ""); function getLatencyColor() { if (isConnectionNode2) { if (latencyFetching) return "var(--warn-color-medium, orange)"; if (selectedOutbound.latency === -1) return "var(--error-color-medium, red)"; if (selectedOutbound.latency && selectedOutbound.latency > 0) { if (selectedOutbound.latency < 800) return "var(--success-color-medium, green)"; if (selectedOutbound.latency < 1500) return "var(--warn-color-medium, orange)"; return "var(--error-color-medium, red)"; } return selectedOutbound.runtimeAvailable ? "var(--success-color-medium, green)" : "var(--error-color-medium, red)"; } if (!selectedOutbound.latency) return "var(--primary-color-low, lightgray)"; if (selectedOutbound.latency === -1 || selectedOutbound.latency < 0) return "var(--error-color-medium, red)"; if (selectedOutbound.latency < 800) return "var(--success-color-medium, green)"; if (selectedOutbound.latency < 1500) return "var(--warn-color-medium, orange)"; return "var(--error-color-medium, red)"; } let latencyText = ""; if (isConnectionNode2) { latencyText = latencyFetching ? _("Checking...") : selectedOutbound.latency && selectedOutbound.latency > 0 ? `${selectedOutbound.latency}ms` : selectedOutbound.latency === -1 || selectedOutbound.latency < 0 ? _("Not responding") : selectedOutbound.runtimeAvailable ? _("Connected") : _("Not connected"); } else { latencyText = selectedOutbound.latency && selectedOutbound.latency > 0 ? `${selectedOutbound.latency}ms` : selectedOutbound.latency === -1 || selectedOutbound.latency < 0 ? _("Not responding") : ""; } return E( "span", { style: "font-size: 13px; font-weight: normal; margin-left: 8px; display: inline-flex; align-items: center; gap: 6px;" }, [ E( "span", { style: "opacity: 0.7;" }, selectedOutbound.displayName ), latencyText ? E( "span", { style: `color: ${getLatencyColor()};` }, latencyText ) : "" ] ); })() : "" ] ), E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section__actions" }, [ ...subscriptionUpdateAction ? [subscriptionUpdateAction] : [], ...canTestLatency ? [ E( "button", { type: "button", class: "btn dashboard-sections-grid-item-test-latency", "data-latency-section": section.sectionName, disabled: latencyFetching ? true : void 0, click: (event) => { event.preventDefault(); event.stopPropagation(); if (latencyFetching) { return; } testLatency(); } }, latencyFetching ? [ renderLoaderCircleIcon24(), E( "span", { class: "dashboard-sections-grid-item-test-latency__label" }, isConnectionNode ? _("Checking...") : getLatencyTestLabel(latencyProgress) ) ] : E( "span", { class: "dashboard-sections-grid-item-test-latency__label" }, isConnectionNode ? _("Check Connection") : _("Test latency") ) ) ] : [] ] ) ] ), !isCollapsed ? E("div", { class: "tachyon_dashboard-page__outbound-grid" }, [ ...metadataNodes, ...section.outbounds.map((outbound) => renderOutbound(outbound)) ]) : "" ]); } function renderSections(props) { if (props.failed) { return renderFailedState(); } if (props.loading) { return renderLoadingState(); } return renderDefaultState(props); } // src/tachyon/tabs/dashboard/partials/renderWidget.ts function renderFailedState2() { return E( "div", { id: "", style: "height: 78px", class: "tachyon_dashboard-page__widgets-section__item centered" }, _("Currently unavailable") ); } function renderLoadingState2() { return E( "div", { id: "", style: "height: 78px", class: "tachyon_dashboard-page__widgets-section__item skeleton" }, "" ); } function renderDefaultState2({ title, items }) { return E("div", { class: "tachyon_dashboard-page__widgets-section__item" }, [ E( "b", { class: "tachyon_dashboard-page__widgets-section__item__title" }, title ), ...items.map( (item) => E( "div", { class: `tachyon_dashboard-page__widgets-section__item__row ${item?.attributes?.class || ""}` }, [ E( "span", { class: "tachyon_dashboard-page__widgets-section__item__row__key" }, `${item.key}: ` ), E( "span", { class: "tachyon_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/tachyon/tabs/dashboard/partials/renderConnections.ts function renderConnections(connections, isCollapsed, onToggleCollapse) { if (connections.length === 0) { return E("div", { class: "tachyon_dashboard-page__outbound-section" }, [ E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section", style: "cursor: pointer; user-select: none;", click: onToggleCollapse }, [ E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section__title", style: "display: flex; align-items: center; gap: 8px;" }, [ svgEl( "svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", style: `transition: transform 0.2s; transform: rotate(${isCollapsed ? "-90deg" : "0deg"})` }, [svgEl("polyline", { points: "6 9 12 15 18 9" })] ), _("Active Clients") ] ) ] ), isCollapsed ? "" : E( "div", { class: "tachyon_dashboard-page__outbound-section centered", style: "height: 60px;" }, _("No active clients") ) ]); } const rows = connections.map((c) => { return E( "div", { class: "tachyon_dashboard-page__widgets-section__item__row", style: "padding: 8px 0; border-bottom: 1px solid rgba(128, 128, 128, 0.1); display: flex; justify-content: space-between;" }, [ E("div", {}, [ E("b", {}, c.name ? `${c.name} (${c.ip})` : c.ip), E( "span", { style: "opacity: 0.7; font-size: 13px; margin-left: 8px;" }, `(${c.count} conns)` ) ]), E( "div", { style: "font-size: 13px;" }, `▲ ${prettyBytes(c.upload)} | ▼ ${prettyBytes(c.download)}` ) ] ); }); return E("div", { class: "tachyon_dashboard-page__outbound-section" }, [ E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section", style: "cursor: pointer; user-select: none;", click: onToggleCollapse }, [ E( "div", { class: "tachyon_dashboard-page__outbound-section__title-section__title", style: "display: flex; align-items: center; gap: 8px;" }, [ svgEl( "svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", style: `transition: transform 0.2s; transform: rotate(${isCollapsed ? "-90deg" : "0deg"})` }, [svgEl("polyline", { points: "6 9 12 15 18 9" })] ), _("Active Clients") ] ) ] ), isCollapsed ? "" : E( "div", { class: "tachyon_dashboard-page__outbound-grid", style: "padding: 12px; display: block;" }, rows ) ]); } // src/tachyon/tabs/dashboard/render.ts function render() { return E( "div", { id: "dashboard-status", class: "tachyon_dashboard-page" }, [ E( "div", { class: "tachyon_dashboard-page__service-stopped", role: "status" }, _( "Tachyon service is stopped. Start the service to display the dashboard." ) ), E("div", { class: "tachyon_dashboard-page__content" }, [ E("div", { class: "tachyon_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: [] }) ), E( "div", { id: "dashboard-widget-tailscale", style: "display:none" }, renderWidget({ loading: true, failed: false, title: "", items: [] }) ) ]), E("div", { id: "dashboard-connections-grid" }, []), E( "div", { id: "dashboard-sections-grid" }, renderSections({ loading: true, failed: false, section: { code: "", sectionName: "", displayName: "", outbounds: [], withTagSelect: false }, onTestLatency: () => { }, onChooseOutbound: () => { }, onCopyOutbound: () => { }, onShowUrlTestInfo: () => { }, onShowPriorityInfo: () => { }, onUpdateSubscription: () => { }, latencyFetching: false, latencyProgress: void 0, subscriptionUpdating: false, selectorSwitchingTag: void 0 }) ) ]) ] ); } // 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"); textarea.value = text; document.body.appendChild(textarea); textarea.select(); try { document.execCommand("copy"); showToast(_("Copied"), "success"); } catch (_err) { showToast(_("Failed to copy!"), "error"); console.error("copyToClipboard - e", _err); } document.body.removeChild(textarea); } // src/tachyon/methods/custom/getConfigSections.ts async function getConfigSections() { return uci.load(TACHYON_UCI_PACKAGE).then(() => uci.sections(TACHYON_UCI_PACKAGE)); } // src/tachyon/runtimeTags.ts var RESERVED_RUNTIME_TAGS = /* @__PURE__ */ new Set([ "dns-server", "fakeip-server", "bootstrap-dns-server", "fakeip-dns-rule-tag", "fakeip-ruleset-dns-rule-tag", "service-fakeip-dns-rule-tag", "tproxy-in", "tproxy6-in", "dns-in", "service-mixed-in", "direct-out", "bypass-out" ]); function allocateRuntimeTag(base, postfix) { let suffix = 1; let candidate = `${base}-${postfix}`; while (RESERVED_RUNTIME_TAGS.has(candidate)) { candidate = `${base}-${postfix}-${suffix}`; suffix += 1; } return candidate; } function getOutboundTagBySection(sectionName) { return allocateRuntimeTag(sectionName, "out"); } // src/tachyon/methods/shell/callBaseMethod.ts async function callBaseMethod(method, args = [], command = "/usr/bin/tachyon", options = {}) { try { const response = await executeShellCommand({ command, args: [method, ...args], timeout: options.timeout ?? 15e3 }); const exitCode = response.code ?? 0; if (exitCode !== 0 && !(options.allowNonZeroWithStdout && response.stdout)) { return { success: false, error: response.stderr || response.stdout || "" }; } if (response.stdout) { try { return { success: true, data: JSON.parse(response.stdout) }; } catch (_e) { return { success: true, data: response.stdout }; } } return { success: false, error: response.stderr || "" }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "" }; } } // src/tachyon/types.ts var Tachyon; ((Tachyon2) => { let AvailableMethods; ((AvailableMethods2) => { AvailableMethods2["CHECK_DNS_AVAILABLE"] = "check_dns_available"; AvailableMethods2["CHECK_FAKEIP"] = "check_fakeip"; AvailableMethods2["CHECK_NFT_RULES"] = "check_nft_rules"; AvailableMethods2["CHECK_ZAPRET_RUNTIME"] = "check_zapret_runtime"; AvailableMethods2["CHECK_ZAPRET2_RUNTIME"] = "check_zapret2_runtime"; AvailableMethods2["CHECK_BYEDPI_RUNTIME"] = "check_byedpi_runtime"; AvailableMethods2["CHECK_INBOUNDS_CONFIG"] = "check_inbounds_config"; AvailableMethods2["GET_STATUS"] = "get_status"; AvailableMethods2["GET_OUTBOUND_METADATA"] = "get_outbound_metadata"; AvailableMethods2["GET_SUBSCRIPTION_METADATA"] = "get_subscription_metadata"; AvailableMethods2["CHECK_SING_BOX"] = "check_sing_box"; AvailableMethods2["CHECK_INBOUNDS"] = "check_inbounds"; AvailableMethods2["GET_SING_BOX_STATUS"] = "get_sing_box_status"; AvailableMethods2["GET_ZAPRET_STATUS"] = "get_zapret_status"; AvailableMethods2["GET_TAILSCALE_PEERS"] = "get_tailscale_peers"; AvailableMethods2["GET_ZAPRET2_STATUS"] = "get_zapret2_status"; AvailableMethods2["GET_BYEDPI_STATUS"] = "get_byedpi_status"; AvailableMethods2["GET_WDTT_STATUS"] = "get_wdtt_status"; AvailableMethods2["GET_OLCRTC_STATUS"] = "get_olcrtc_status"; AvailableMethods2["GET_FPTN_STATUS"] = "get_fptn_status"; AvailableMethods2["CLASH_API"] = "clash_api"; AvailableMethods2["ENABLE"] = "enable"; AvailableMethods2["DISABLE"] = "disable"; AvailableMethods2["GLOBAL_CHECK"] = "global_check"; AvailableMethods2["DOCTOR"] = "doctor"; AvailableMethods2["AI_DOCTOR"] = "ai_doctor"; AvailableMethods2["AI_DOCTOR_LAST"] = "ai_doctor_last"; AvailableMethods2["APPLY_QUICK_FIX"] = "apply_quick_fix"; AvailableMethods2["LAN_CLIENTS"] = "lan_clients"; AvailableMethods2["TOGGLE_CLIENT_BYPASS"] = "toggle_client_bypass"; AvailableMethods2["SHOW_SING_BOX_CONFIG"] = "show_sing_box_config"; AvailableMethods2["CHECK_LOGS"] = "check_logs"; AvailableMethods2["CHECK_SING_BOX_LOGS"] = "check_sing_box_logs"; AvailableMethods2["GET_SYSTEM_INFO"] = "get_system_info"; AvailableMethods2["GET_SERVER_CAPABILITIES"] = "get_server_capabilities"; AvailableMethods2["GET_UI_CAPABILITIES"] = "get_ui_capabilities"; AvailableMethods2["GET_UI_STATE"] = "get_ui_state"; AvailableMethods2["SERVICE_ACTION_ASYNC"] = "service_action_async"; AvailableMethods2["SERVICE_ACTION_STATUS"] = "service_action_status"; AvailableMethods2["LATENCY_TEST_ASYNC"] = "latency_test_async"; AvailableMethods2["LATENCY_TEST_STATUS"] = "latency_test_status"; AvailableMethods2["UI_ACTION_ACK"] = "ui_action_ack"; AvailableMethods2["COMPONENT_ACTION_ASYNC"] = "component_action_async"; AvailableMethods2["COMPONENT_ACTION_STATUS"] = "component_action_status"; AvailableMethods2["COMPONENT_ACTION_LOG"] = "component_action_log"; AvailableMethods2["COMPONENT_UPDATE_CHECK_CACHE"] = "component_update_check_cache"; AvailableMethods2["COMPONENT_LIST_RELEASES"] = "component_list_releases"; AvailableMethods2["COMPONENT_INSTALL_VERSION"] = "component_install_version"; AvailableMethods2["SUBSCRIPTION_UPDATE_ASYNC"] = "subscription_update_async"; AvailableMethods2["SUBSCRIPTION_UPDATE_STATUS"] = "subscription_update_status"; AvailableMethods2["SERVICE_HEALTH_CHECK"] = "service_health_check"; AvailableMethods2["FUZZER_START"] = "fuzzer_start"; AvailableMethods2["FUZZER_STATUS"] = "fuzzer_status"; AvailableMethods2["FUZZER_STOP"] = "fuzzer_stop"; AvailableMethods2["FUZZER_APPLY"] = "fuzzer_apply"; AvailableMethods2["FUZZER_STRATEGIES"] = "fuzzer_strategies"; AvailableMethods2["FUZZER_AI_SYNTHESIZE"] = "fuzzer_ai_synthesize"; AvailableMethods2["FUZZER_GET_PATTERNS"] = "fuzzer_get_patterns"; AvailableMethods2["FUZZER_SAVE_PATTERNS"] = "fuzzer_save_patterns"; AvailableMethods2["FUZZER_RESET_PATTERNS"] = "fuzzer_reset_patterns"; AvailableMethods2["FUZZER_DETECT_DPI"] = "fuzzer_detect_dpi"; AvailableMethods2["FUZZER_AUTO_APPLY"] = "fuzzer_auto_apply"; AvailableMethods2["FUZZER_HISTORY"] = "fuzzer_history"; AvailableMethods2["FUZZER_CLEAR_HISTORY"] = "fuzzer_clear_history"; AvailableMethods2["DNS_BENCHMARK_START"] = "dns_benchmark_async"; AvailableMethods2["DNS_BENCHMARK_STATUS"] = "dns_benchmark_status"; AvailableMethods2["DNS_BENCHMARK_STOP"] = "dns_benchmark_stop"; AvailableMethods2["DNS_BENCHMARK_APPLY"] = "dns_benchmark_apply"; AvailableMethods2["LEAK_CHECK"] = "leak_check"; AvailableMethods2["LEAK_CHECK_ASYNC"] = "leak_check_async"; AvailableMethods2["LEAK_CHECK_STATUS"] = "leak_check_status"; AvailableMethods2["CHECK_IP_LEAK"] = "check_ip_leak"; AvailableMethods2["CHECK_DNS_LEAK"] = "check_dns_leak"; })(AvailableMethods = Tachyon2.AvailableMethods || (Tachyon2.AvailableMethods = {})); let AvailableClashAPIMethods; ((AvailableClashAPIMethods2) => { AvailableClashAPIMethods2["GET_PROXIES"] = "get_proxies"; AvailableClashAPIMethods2["GET_CONNECTIONS"] = "get_connections"; AvailableClashAPIMethods2["GET_PROXY_LATENCY"] = "get_proxy_latency"; AvailableClashAPIMethods2["GET_PROXY_LATENCIES"] = "get_proxy_latencies"; AvailableClashAPIMethods2["GET_GROUP_LATENCY"] = "get_group_latency"; AvailableClashAPIMethods2["SET_GROUP_PROXY"] = "set_group_proxy"; AvailableClashAPIMethods2["CLOSE_CONNECTION"] = "close_connection"; AvailableClashAPIMethods2["CLOSE_ALL_CONNECTIONS"] = "close_all_connections"; })(AvailableClashAPIMethods = Tachyon2.AvailableClashAPIMethods || (Tachyon2.AvailableClashAPIMethods = {})); })(Tachyon || (Tachyon = {})); // src/tachyon/helpers/isTransientRpcError.ts var TRANSIENT_RPC_ERROR_PATTERNS = [ "no related rpc reply", "request aborted", "operation was aborted", "failed to execute", "networkerror", "fetch failed", "connection refused", "connection reset", "connection timed out", "timeout", "bad gateway", "service unavailable", "ubus error", "ipc error", "access denied", "доступ запрещён", "доступ запрещен", "permission denied", "unauthorized" ]; function isTransientRpcError(message) { if (!message) { return false; } const normalized = message.toLowerCase(); return TRANSIENT_RPC_ERROR_PATTERNS.some( (pattern) => normalized.includes(pattern) ); } // src/tachyon/methods/shell/index.ts var SUBSCRIPTION_UPDATE_RPC_TIMEOUT_MS = 15e3; var SUBSCRIPTION_UPDATE_POLL_INTERVAL_MS = 1500; var UI_ACTION_RPC_TIMEOUT_MS = 15e3; var UI_ACTION_TRANSIENT_RPC_GRACE_MS = 3e4; var SERVICE_ACTION_TIMEOUT_MS = 2 * 60 * 1e3; var SERVICE_ACTION_POLL_INTERVAL_MS = 1e3; var LATENCY_TEST_TIMEOUT_MS = 30 * 1e3; var LATENCY_TEST_POLL_INTERVAL_MS = 1e3; var COMPONENT_ACTION_RPC_TIMEOUT_MS = 45e3; var COMPONENT_ACTION_POLL_INTERVAL_MS = 1e3; var COMPONENT_ACTION_SELF_UPDATE_SETTLE_MS = 2e3; var COMPONENT_ACTION_TRANSIENT_RPC_GRACE_MS = 3e4; var COMPONENT_ACTION_MIN_ELAPSED_FOR_SELF_UPDATE_MS = 2e3; var COMPONENT_ACTION_SELF_UPDATE_HARD_TIMEOUT_MS = 12e4; var COMPONENT_ACTION_GENERAL_HARD_TIMEOUT_MS = 15 * 60 * 1e3; var COMPONENT_ACTION_STATE_DIR = "/var/run/tachyon/component-actions"; var GET_UI_STATE_RPC_TIMEOUT_MS = 3e3; function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function translate(message) { return typeof _ === "function" ? _(message) : message; } function parseJsonObjectOutput(output) { if (!output) { return null; } try { return JSON.parse(output); } catch (_error) { const jsonMatch = output.match(/(\{[\s\S]*\})\s*$/); if (!jsonMatch) { return null; } try { return JSON.parse(jsonMatch[1]); } catch (_jsonError) { return null; } } } function parseJsonArrayOutput(output) { if (!output) { return null; } try { const parsed = JSON.parse(output); if (Array.isArray(parsed)) { return parsed; } return null; } catch (_error) { const jsonMatch = output.match(/(\[[\s\S]*\])\s*$/); if (!jsonMatch) { return null; } try { const parsed = JSON.parse(jsonMatch[1]); return Array.isArray(parsed) ? parsed : null; } catch (_jsonError) { return null; } } } function parseComponentActionOutput(output) { return parseJsonObjectOutput(output); } function parseComponentActionResult(response) { return parseComponentActionOutput(response.stdout); } function parseComponentActionStartResult(response) { const parsedResponse = parseComponentActionResult(response); if (!parsedResponse) { return null; } return parsedResponse; } function parseSubscriptionUpdateStartResult(response) { return parseJsonObjectOutput( response.stdout ); } function parseSubscriptionUpdateJobState(response) { return parseJsonObjectOutput( response.stdout ); } function parseUiActionStartResult(response) { return parseJsonObjectOutput(response.stdout); } function parseServiceActionState(response) { return parseJsonObjectOutput(response.stdout); } function parseLatencyActionState(response) { return parseJsonObjectOutput(response.stdout); } function isComponentActionJobId(jobId) { return /^[A-Za-z0-9._-]+$/.test(jobId) && jobId !== "." && jobId !== ".."; } async function readComponentActionState(jobId) { if (!isComponentActionJobId(jobId)) { return null; } try { return parseComponentActionOutput( await fs.read(`${COMPONENT_ACTION_STATE_DIR}/${jobId}.json`) ); } catch (_error) { return null; } } async function readTachyonVersion() { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: ["show_version"], timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); if ((response.code ?? 0) !== 0 || !response.stdout) { return ""; } return response.stdout.trim(); } async function isComponentActionStillRunning(jobId, component, action) { const response = await callBaseMethod( Tachyon.AvailableMethods.GET_UI_STATE, [], "/usr/bin/tachyon", { timeout: GET_UI_STATE_RPC_TIMEOUT_MS } ); return response.success && response.data.actions.component.some( (state) => state.job_id === jobId && state.component === component && state.action === action && state.running ); } function componentActionFailure(response, parsedResponse) { return { success: false, error: parsedResponse?.message || response.stderr || _("Failed to execute") }; } function uiActionFailure(response, parsedResponse, fallback = _("Failed to execute")) { return { success: false, error: parsedResponse?.message || response.stderr || fallback }; } function createTransientRpcGraceTracker(graceMs) { let failureStartedAt = 0; return { reset() { failureStartedAt = 0; }, shouldContinue(error) { if (!isTransientRpcError(error)) { failureStartedAt = 0; return false; } if (!failureStartedAt) { failureStartedAt = Date.now(); } return Date.now() - failureStartedAt < graceMs; } }; } var TachyonShellMethods = { checkDNSAvailable: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_DNS_AVAILABLE ), checkFakeIP: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_FAKEIP ), checkNftRules: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_NFT_RULES ), checkZapretRuntime: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_ZAPRET_RUNTIME ), checkZapret2Runtime: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_ZAPRET2_RUNTIME ), checkByedpiRuntime: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_BYEDPI_RUNTIME ), checkInboundsConfig: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_INBOUNDS_CONFIG ), getStatus: async () => callBaseMethod(Tachyon.AvailableMethods.GET_STATUS), getOutboundMetadata: async (section) => callBaseMethod( Tachyon.AvailableMethods.GET_OUTBOUND_METADATA, [section] ), getSubscriptionMetadata: async (section) => callBaseMethod(Tachyon.AvailableMethods.GET_SUBSCRIPTION_METADATA, [section]), checkSingBox: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_SING_BOX ), checkInbounds: async () => callBaseMethod( Tachyon.AvailableMethods.CHECK_INBOUNDS ), getSingBoxStatus: async () => callBaseMethod( Tachyon.AvailableMethods.GET_SING_BOX_STATUS, [], "/usr/bin/tachyon", { allowNonZeroWithStdout: true } ), getTailscalePeers: async () => callBaseMethod( Tachyon.AvailableMethods.GET_TAILSCALE_PEERS, [], "/usr/bin/tachyon", { allowNonZeroWithStdout: true } ), getZapretStatus: async () => callBaseMethod( Tachyon.AvailableMethods.GET_ZAPRET_STATUS, [], "/usr/bin/tachyon", { allowNonZeroWithStdout: true } ), getZapret2Status: async () => callBaseMethod( Tachyon.AvailableMethods.GET_ZAPRET2_STATUS, [], "/usr/bin/tachyon", { allowNonZeroWithStdout: true } ), getByedpiStatus: async () => callBaseMethod( Tachyon.AvailableMethods.GET_BYEDPI_STATUS, [], "/usr/bin/tachyon", { allowNonZeroWithStdout: true } ), getWdttStatus: async () => callBaseMethod( Tachyon.AvailableMethods.GET_WDTT_STATUS, [], "/usr/bin/tachyon", { allowNonZeroWithStdout: true } ), getOlcrtcStatus: async () => callBaseMethod( Tachyon.AvailableMethods.GET_OLCRTC_STATUS, [], "/usr/bin/tachyon", { allowNonZeroWithStdout: true } ), getFptnStatus: async () => callBaseMethod( Tachyon.AvailableMethods.GET_FPTN_STATUS, [], "/usr/bin/tachyon", { allowNonZeroWithStdout: true } ), getClashApiProxies: async () => callBaseMethod(Tachyon.AvailableMethods.CLASH_API, [ Tachyon.AvailableClashAPIMethods.GET_PROXIES ]), getClashApiConnections: async () => callBaseMethod(Tachyon.AvailableMethods.CLASH_API, [ Tachyon.AvailableClashAPIMethods.GET_CONNECTIONS ]), getClashApiProxyLatency: async (tag, timeout = "5000") => callBaseMethod( Tachyon.AvailableMethods.CLASH_API, [Tachyon.AvailableClashAPIMethods.GET_PROXY_LATENCY, tag, timeout] ), getClashApiProxyLatencies: async (tags) => callBaseMethod( Tachyon.AvailableMethods.CLASH_API, [ Tachyon.AvailableClashAPIMethods.GET_PROXY_LATENCIES, JSON.stringify(tags), "5000" ] ), getClashApiGroupLatency: async (tag) => callBaseMethod( Tachyon.AvailableMethods.CLASH_API, [Tachyon.AvailableClashAPIMethods.GET_GROUP_LATENCY, tag, "10000"] ), setClashApiGroupProxy: async (group, proxy) => callBaseMethod(Tachyon.AvailableMethods.CLASH_API, [ Tachyon.AvailableClashAPIMethods.SET_GROUP_PROXY, group, proxy ]), closeClashApiConnection: async (connectionId) => callBaseMethod(Tachyon.AvailableMethods.CLASH_API, [ Tachyon.AvailableClashAPIMethods.CLOSE_CONNECTION, connectionId ]), closeAllClashApiConnections: async () => callBaseMethod(Tachyon.AvailableMethods.CLASH_API, [ Tachyon.AvailableClashAPIMethods.CLOSE_ALL_CONNECTIONS ]), enable: async () => callBaseMethod( Tachyon.AvailableMethods.ENABLE, [], "/etc/init.d/tachyon" ), disable: async () => callBaseMethod( Tachyon.AvailableMethods.DISABLE, [], "/etc/init.d/tachyon" ), globalCheck: async (masked = true) => callBaseMethod(Tachyon.AvailableMethods.GLOBAL_CHECK, [ masked ? "masked" : "raw" ]), doctor: async () => callBaseMethod( Tachyon.AvailableMethods.DOCTOR, [], "/usr/bin/tachyon", { timeout: 3e4 } ), aiDoctor: async () => callBaseMethod( Tachyon.AvailableMethods.AI_DOCTOR, [], "/usr/bin/tachyon", { timeout: 6e4 } ), aiDoctorLast: async () => callBaseMethod( Tachyon.AvailableMethods.AI_DOCTOR_LAST, [], "/usr/bin/tachyon", { timeout: 1e4 } ), applyQuickFix: async (fixCode) => callBaseMethod( Tachyon.AvailableMethods.APPLY_QUICK_FIX, [fixCode], "/usr/bin/tachyon", { timeout: 3e4 } ), getLanClients: async () => callBaseMethod(Tachyon.AvailableMethods.LAN_CLIENTS, [], "/usr/bin/tachyon", { timeout: 1e4 }), toggleClientBypass: async (ip) => callBaseMethod( Tachyon.AvailableMethods.TOGGLE_CLIENT_BYPASS, [ip], "/usr/bin/tachyon", { timeout: 15e3 } ), showSingBoxConfig: async (masked = true) => callBaseMethod(Tachyon.AvailableMethods.SHOW_SING_BOX_CONFIG, [ masked ? "masked" : "raw" ]), checkLogs: async () => callBaseMethod(Tachyon.AvailableMethods.CHECK_LOGS), checkSingBoxLogs: async () => callBaseMethod(Tachyon.AvailableMethods.CHECK_SING_BOX_LOGS), getSystemInfo: async () => callBaseMethod( Tachyon.AvailableMethods.GET_SYSTEM_INFO ), getServerCapabilities: async () => callBaseMethod( Tachyon.AvailableMethods.GET_SERVER_CAPABILITIES ), getUiCapabilities: async () => callBaseMethod( Tachyon.AvailableMethods.GET_UI_CAPABILITIES ), getUiState: async () => callBaseMethod( Tachyon.AvailableMethods.GET_UI_STATE, [], "/usr/bin/tachyon", { timeout: GET_UI_STATE_RPC_TIMEOUT_MS } ), serviceActionStart: async (action) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.SERVICE_ACTION_ASYNC, action], timeout: UI_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseUiActionStartResult(response); if ((response.code ?? 0) !== 0 || !parsedResponse?.success || !parsedResponse.job_id) { return uiActionFailure( response, parsedResponse, _("Service action failed") ); } return { success: true, data: parsedResponse }; }, serviceActionStatus: async (jobId) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.SERVICE_ACTION_STATUS, jobId], timeout: UI_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseServiceActionState(response); if ((response.code ?? 0) !== 0 || !parsedResponse) { return uiActionFailure( response, parsedResponse, _("Service action failed") ); } return { success: true, data: parsedResponse }; }, waitServiceActionJob: async (jobId, startedAt = Date.now()) => { while (Date.now() - startedAt < SERVICE_ACTION_TIMEOUT_MS) { await sleep(SERVICE_ACTION_POLL_INTERVAL_MS); const response = await TachyonShellMethods.serviceActionStatus(jobId); if (!response.success) { return response; } if (response.data.running) { continue; } return response; } return { success: false, error: _("Operation timed out") }; }, latencyTestStart: async (latencyType, section, tag, timeout) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [ Tachyon.AvailableMethods.LATENCY_TEST_ASYNC, latencyType, section, tag, ...timeout ? [timeout] : [] ], timeout: UI_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseUiActionStartResult(response); if ((response.code ?? 0) !== 0 || !parsedResponse?.success || !parsedResponse.job_id) { return uiActionFailure( response, parsedResponse, _("Latency test failed") ); } return { success: true, data: parsedResponse }; }, latencyTestStatus: async (jobId) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.LATENCY_TEST_STATUS, jobId], timeout: UI_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseLatencyActionState(response); if ((response.code ?? 0) !== 0 || !parsedResponse) { return uiActionFailure( response, parsedResponse, _("Latency test failed") ); } return { success: true, data: parsedResponse }; }, waitLatencyTestJob: async (jobId, startedAt = Date.now()) => { const transientRpc = createTransientRpcGraceTracker( UI_ACTION_TRANSIENT_RPC_GRACE_MS ); while (Date.now() - startedAt < LATENCY_TEST_TIMEOUT_MS) { await sleep(LATENCY_TEST_POLL_INTERVAL_MS); const response = await TachyonShellMethods.latencyTestStatus(jobId); if (!response.success) { if (transientRpc.shouldContinue(response.error)) { continue; } return response; } transientRpc.reset(); if (response.data.running) { continue; } return response; } return { success: false, error: _("Operation timed out") }; }, uiActionAck: async (kind, jobId) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.UI_ACTION_ACK, kind, jobId], timeout: UI_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseUiActionStartResult(response); if ((response.code ?? 0) !== 0 || !parsedResponse?.success) { return uiActionFailure(response, parsedResponse); } return { success: true, data: parsedResponse }; }, componentActionStart: async (component, action, targetVersion) => { const args = [ Tachyon.AvailableMethods.COMPONENT_ACTION_ASYNC, component, action ]; if (targetVersion) { args.push(targetVersion); } const response = await executeShellCommand({ command: "/usr/bin/tachyon", args, timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseComponentActionStartResult(response); if ((response.code ?? 0) !== 0 || !parsedResponse?.success || !parsedResponse.job_id) { return componentActionFailure(response, parsedResponse); } return { success: true, data: parsedResponse }; }, componentActionStatus: async (jobId) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.COMPONENT_ACTION_STATUS, jobId], timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseComponentActionResult(response); if ((response.code ?? 0) !== 0 || !parsedResponse) { return componentActionFailure(response, parsedResponse); } return { success: true, data: parsedResponse }; }, componentActionLog: async (jobId, offset = 0) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [ Tachyon.AvailableMethods.COMPONENT_ACTION_LOG, jobId, String(Math.max(0, Math.floor(offset))) ], timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseJsonObjectOutput(response.stdout); if ((response.code ?? 0) !== 0 || !parsedResponse) { return { success: false, error: parsedResponse?.success === false ? _("Operation log is not available") : response.stderr || _("Failed to read operation log") }; } return { success: true, data: parsedResponse }; }, componentUpdateCheckCache: async () => callBaseMethod( Tachyon.AvailableMethods.COMPONENT_UPDATE_CHECK_CACHE ), componentListReleases: async (component, count = 3) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [ Tachyon.AvailableMethods.COMPONENT_LIST_RELEASES, component, String(count) ], timeout: 25e3 }); const parsed = parseJsonArrayOutput( response.stdout ); if ((response.code ?? 0) !== 0 || !parsed) { return { success: false, error: response.stderr || _("Failed to fetch releases") }; } return { success: true, data: parsed }; }, componentInstallVersion: async (component, tag) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [ Tachyon.AvailableMethods.COMPONENT_INSTALL_VERSION, component, tag ], timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); const parsed = parseJsonObjectOutput( response.stdout ); if ((response.code ?? 0) !== 0 || !parsed) { return { success: false, error: response.stderr || _("Failed to install version") }; } return { success: true, data: parsed }; }, waitComponentActionJob: async (jobId, component, action, expectedLatestVersion) => { const jobStartedAt = Date.now(); const isSelfUpdate = component === "tachyon" && (action === "install" || action === "reinstall" || action === "install_version"); const targetVersion = expectedLatestVersion || ""; let baselineVersion = ""; if (isSelfUpdate) { baselineVersion = await readTachyonVersion(); } let selfUpdateVersionMatchedAt = 0; const transientRpc = createTransientRpcGraceTracker( COMPONENT_ACTION_TRANSIENT_RPC_GRACE_MS ); const versionsMatch = (a, b) => { const cleanA = a.replace(/^v/i, "").trim(); const cleanB = b.replace(/^v/i, "").trim(); return cleanA === cleanB; }; const isDifferentVersion = Boolean( targetVersion && !versionsMatch(targetVersion, baselineVersion) ); const confirmedByVersion = async () => { if (!isSelfUpdate) return ""; if (Date.now() - jobStartedAt < COMPONENT_ACTION_MIN_ELAPSED_FOR_SELF_UPDATE_MS) { return ""; } const version = await readTachyonVersion(); if (!version) return ""; if (isDifferentVersion && versionsMatch(version, targetVersion)) { return version; } if (!targetVersion && baselineVersion && !versionsMatch(version, baselineVersion)) { return version; } return ""; }; const confirmedSameVersionReinstall = async () => { if (!isSelfUpdate || !baselineVersion) { return ""; } if (targetVersion && !versionsMatch(targetVersion, baselineVersion)) { return ""; } const version = await readTachyonVersion(); if (version && versionsMatch(version, baselineVersion)) { return version; } return ""; }; const settleVersion = async (version) => { if (!selfUpdateVersionMatchedAt) { selfUpdateVersionMatchedAt = Date.now(); return false; } if (Date.now() - selfUpdateVersionMatchedAt < COMPONENT_ACTION_SELF_UPDATE_SETTLE_MS) { return false; } if (isDifferentVersion) { if (await confirmedByVersion() === version) { return true; } } else { if (await confirmedSameVersionReinstall() === version) { return true; } } selfUpdateVersionMatchedAt = 0; return false; }; const selfUpdateResult = (installedVersion) => ({ success: true, data: { success: true, component, action, message: translate("Tachyon has been installed"), current_version: installedVersion, latest_version: expectedLatestVersion, changed: true, status: "latest" } }); const jobDoneResult = (data) => ({ success: true, data }); while (true) { await sleep(COMPONENT_ACTION_POLL_INTERVAL_MS); const stateResponse = await readComponentActionState(jobId); if (isSelfUpdate && Date.now() - jobStartedAt >= COMPONENT_ACTION_SELF_UPDATE_HARD_TIMEOUT_MS) { if (stateResponse && !stateResponse.running) { return jobDoneResult(stateResponse); } const version = await readTachyonVersion(); if (targetVersion && version && version !== targetVersion) { return { success: false, error: _("Tachyon update did not complete within the timeout") }; } return selfUpdateResult(version || baselineVersion); } if (!isSelfUpdate && Date.now() - jobStartedAt >= COMPONENT_ACTION_GENERAL_HARD_TIMEOUT_MS) { if (stateResponse && !stateResponse.running) { return jobDoneResult(stateResponse); } return { success: false, error: _("Component action timed out") }; } if (stateResponse && !stateResponse.running) { if (isSelfUpdate && stateResponse.success === false) { const version = await confirmedByVersion() || await confirmedSameVersionReinstall(); if (version) { if (await settleVersion(version)) { return selfUpdateResult(version); } continue; } } return jobDoneResult(stateResponse); } const statusResponse = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.COMPONENT_ACTION_STATUS, jobId], timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); const parsedResponse = parseComponentActionResult(statusResponse); if ((statusResponse.code ?? 0) !== 0 || !parsedResponse) { if (isSelfUpdate) { const version = await confirmedByVersion() || await confirmedSameVersionReinstall(); if (version) { if (await settleVersion(version)) { return selfUpdateResult(version); } continue; } transientRpc.reset(); continue; } if (stateResponse?.running) { transientRpc.reset(); continue; } if (await isComponentActionStillRunning(jobId, component, action)) { transientRpc.reset(); continue; } const failure = componentActionFailure(statusResponse, parsedResponse); if (transientRpc.shouldContinue(failure.error)) { continue; } return failure; } transientRpc.reset(); if (parsedResponse.running) { continue; } if (isSelfUpdate && parsedResponse.success === false) { const version = await confirmedByVersion() || await confirmedSameVersionReinstall(); if (version) { if (await settleVersion(version)) { return selfUpdateResult(version); } continue; } } return jobDoneResult(parsedResponse); } }, subscriptionUpdateStart: async (section, sourceIndex) => { const startArgs = [ Tachyon.AvailableMethods.SUBSCRIPTION_UPDATE_ASYNC, ...section ? [section] : [], ...section && sourceIndex !== void 0 ? [String(sourceIndex)] : [] ]; const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: startArgs, timeout: SUBSCRIPTION_UPDATE_RPC_TIMEOUT_MS }); const parsedResponse = parseSubscriptionUpdateStartResult(response); if ((response.code ?? 0) !== 0 || !parsedResponse?.success || !parsedResponse.job_id) { return { success: false, error: parsedResponse?.message || response.stderr || _("Subscription update failed") }; } return { success: true, data: parsedResponse }; }, subscriptionUpdateStatus: async (jobId) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.SUBSCRIPTION_UPDATE_STATUS, jobId], timeout: SUBSCRIPTION_UPDATE_RPC_TIMEOUT_MS }); const parsedResponse = parseSubscriptionUpdateJobState(response); if ((response.code ?? 0) !== 0 || !parsedResponse) { return { success: false, error: response.stderr || _("Subscription update failed") }; } return { success: true, data: parsedResponse }; }, waitSubscriptionUpdateJob: async (jobId) => { const transientRpc = createTransientRpcGraceTracker( UI_ACTION_TRANSIENT_RPC_GRACE_MS ); while (true) { await sleep(SUBSCRIPTION_UPDATE_POLL_INTERVAL_MS); const response = await TachyonShellMethods.subscriptionUpdateStatus(jobId); if (!response.success) { if (transientRpc.shouldContinue(response.error)) { continue; } return response; } transientRpc.reset(); if (response.data.running) { continue; } return response; } }, getWatchdogStatus: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: ["watchdog", "status"], timeout: 5e3 }); return { success: true, data: { running: (response.code ?? 1) === 0 } }; }, watchdogStart: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: ["watchdog_start"], timeout: 8e3 }); return { success: (response.code ?? 1) === 0 }; }, watchdogStop: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: ["watchdog_stop"], timeout: 8e3 }); return { success: (response.code ?? 1) === 0 }; }, /** * Run an arbitrary UCI command via shell — used for saving Smart Detect * settings and per-device routing IPs from the Advanced Settings panel. */ uciRunCommand: async (args) => { const response = await executeShellCommand({ command: "/sbin/uci", args, timeout: 5e3 }); return { success: (response.code ?? 1) === 0 }; }, startFuzzer: async (engine = "zapret2", target = "youtube", customUrl, ruleSection, customFile, mode) => { const args = [ Tachyon.AvailableMethods.FUZZER_START, engine, target ]; if (customUrl) args.push(customUrl); else args.push(""); if (ruleSection) args.push(ruleSection); else args.push(""); if (customFile) args.push(customFile); else args.push(""); if (mode) args.push(mode); const response = await executeShellCommand({ command: "/usr/bin/tachyon", args, timeout: 1e4 }); let parsed = null; try { parsed = JSON.parse(response.stdout?.trim() || "{}"); } catch { parsed = null; } if ((response.code ?? 1) === 0 && parsed && parsed.success) { return { success: true, data: parsed }; } return { success: false, error: parsed?.error || response.stderr || _("Failed to start fuzzer") }; }, getFuzzerStatus: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.FUZZER_STATUS], timeout: 8e3 }); let parsed = null; try { parsed = JSON.parse(response.stdout?.trim() || "{}"); } catch { parsed = null; } if ((response.code ?? 1) === 0 && parsed) { return { success: true, data: parsed }; } return { success: false, error: response.stderr || _("Failed to get fuzzer status") }; }, stopFuzzer: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.FUZZER_STOP], timeout: 8e3 }); if ((response.code ?? 1) === 0) { return { success: true, data: void 0 }; } return { success: false, error: response.stderr || _("Failed to stop fuzzer") }; }, applyFuzzerStrategy: async (engine, args, targetRuleOrGlobal) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [ Tachyon.AvailableMethods.FUZZER_APPLY, engine, args, targetRuleOrGlobal || "global" ], timeout: 1e4 }); let parsed = null; try { parsed = JSON.parse(response.stdout?.trim() || "{}"); } catch { parsed = null; } if ((response.code ?? 1) === 0 && parsed && parsed.success) { return { success: true, data: parsed }; } return { success: false, error: parsed?.error || response.stderr || _("Failed to apply strategy") }; }, getFuzzerStrategies: async (mode) => { const args = [Tachyon.AvailableMethods.FUZZER_STRATEGIES]; if (mode) args.push(mode); const response = await executeShellCommand({ command: "/usr/bin/tachyon", args, timeout: 8e3 }); let parsed = null; try { parsed = JSON.parse( response.stdout?.trim() || "{}" ); } catch { parsed = null; } if ((response.code ?? 1) === 0 && parsed) { return { success: true, data: parsed }; } return { success: false, error: response.stderr || _("Failed to get fuzzer strategies") }; }, fuzzerAiSynthesize: async (engine, target, customUrl, userPrompt) => { const args = [ Tachyon.AvailableMethods.FUZZER_AI_SYNTHESIZE, engine, target ]; if (customUrl) args.push(customUrl); else args.push(""); if (userPrompt) args.push(userPrompt); const response = await executeShellCommand({ command: "/usr/bin/tachyon", args, timeout: 65e3 }); let parsed = null; try { parsed = JSON.parse( response.stdout?.trim() || "{}" ); } catch { parsed = null; } if ((response.code ?? 1) === 0 && parsed && parsed.success) { return { success: true, data: parsed }; } return { success: false, error: parsed?.error || response.stderr || _("Failed to synthesize AI strategies") }; }, getFuzzerPatterns: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.FUZZER_GET_PATTERNS], timeout: 8e3 }); let parsed = null; try { parsed = JSON.parse(response.stdout?.trim() || "{}"); } catch { parsed = null; } if ((response.code ?? 1) === 0 && parsed && parsed.success) { return { success: true, data: parsed }; } return { success: false, error: response.stderr || _("Failed to get fuzzer patterns") }; }, saveFuzzerPatterns: async (patterns) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [ Tachyon.AvailableMethods.FUZZER_SAVE_PATTERNS, JSON.stringify(patterns) ], timeout: 1e4 }); let parsed = null; try { parsed = JSON.parse(response.stdout?.trim() || "{}"); } catch { parsed = null; } if ((response.code ?? 1) === 0 && parsed && parsed.success) { return { success: true, data: { message: parsed.message || _("Patterns saved successfully") } }; } return { success: false, error: parsed?.error || response.stderr || _("Failed to save fuzzer patterns") }; }, resetFuzzerPatterns: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.FUZZER_RESET_PATTERNS], timeout: 8e3 }); let parsed = null; try { parsed = JSON.parse(response.stdout?.trim() || "{}"); } catch { parsed = null; } if ((response.code ?? 1) === 0 && parsed && parsed.success) { return { success: true, data: { message: parsed.message || _("Patterns reset to factory defaults"), patterns: parsed.patterns } }; } return { success: false, error: parsed?.error || response.stderr || _("Failed to reset fuzzer patterns") }; }, detectFuzzerDpi: async (target, customUrl) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [ Tachyon.AvailableMethods.FUZZER_DETECT_DPI, target, ...customUrl ? [customUrl] : [] ], timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); const parsed = parseJsonObjectOutput( response.stdout ); if ((response.code ?? 0) !== 0 || !parsed) { return { success: false, error: response.stderr || _("Failed to detect DPI type") }; } return { success: true, data: parsed }; }, autoApplyFuzzerStrategy: async (targetRule) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [ Tachyon.AvailableMethods.FUZZER_AUTO_APPLY, ...targetRule ? [targetRule] : [] ], timeout: UI_ACTION_RPC_TIMEOUT_MS }); const parsed = parseJsonObjectOutput( response.stdout ); if ((response.code ?? 0) !== 0 || !parsed?.success) { return { success: false, error: parsed?.error || response.stderr || _("Failed to auto-apply strategy") }; } return { success: true, data: parsed }; }, getFuzzerHistory: async (limit) => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.FUZZER_HISTORY, String(limit || 20)], timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); const parsed = parseJsonObjectOutput( response.stdout ); if ((response.code ?? 0) !== 0 || !parsed) { return { success: false, error: response.stderr || _("Failed to get fuzzer history") }; } return { success: true, data: parsed }; }, clearFuzzerHistory: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.FUZZER_CLEAR_HISTORY], timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS }); const parsed = parseJsonObjectOutput(response.stdout); if ((response.code ?? 0) !== 0 || !parsed?.success) { return { success: false, error: response.stderr || _("Failed to clear fuzzer history") }; } return { success: true, data: { message: _("History cleared") } }; }, startDnsBenchmark: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.DNS_BENCHMARK_START], timeout: 1e4 }); const parsed = parseJsonObjectOutput(response.stdout); if ((response.code ?? 0) !== 0 || !parsed?.success) { return { success: false, error: parsed?.error || response.stderr || _("Failed to start DNS benchmark") }; } return { success: true, data: { message: parsed.message, running: parsed.running } }; }, getDnsBenchmarkStatus: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.DNS_BENCHMARK_STATUS], timeout: 8e3 }); const parsed = parseJsonObjectOutput( response.stdout ); if ((response.code ?? 0) !== 0 || !parsed) { return { success: false, error: response.stderr || _("Failed to get DNS benchmark status") }; } return { success: true, data: parsed }; }, stopDnsBenchmark: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.DNS_BENCHMARK_STOP], timeout: 8e3 }); if ((response.code ?? 0) === 0) { return { success: true, data: void 0 }; } return { success: false, error: response.stderr || _("Failed to stop DNS benchmark") }; }, applyDnsBenchmark: async () => { const response = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.DNS_BENCHMARK_APPLY], timeout: 15e3 }); const parsed = parseJsonObjectOutput(response.stdout); if ((response.code ?? 0) !== 0 || !parsed?.success) { return { success: false, error: parsed?.error || response.stderr || _("Failed to apply DNS configuration") }; } return { success: true, data: { message: parsed.message || _("Configuration applied"), recommendation: parsed.recommendation } }; }, leakCheck: async (onProgress) => { const startResponse = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.LEAK_CHECK_ASYNC], timeout: 5e3 }); const startParsed = parseJsonObjectOutput(startResponse.stdout); if ((startResponse.code ?? 0) === 0 && startParsed?.success && startParsed.job_id) { const jobId = startParsed.job_id; const startedAt = Date.now(); const MAX_WAIT_MS = 35e3; const POLL_INTERVAL_MS = 800; while (Date.now() - startedAt < MAX_WAIT_MS) { await sleep(POLL_INTERVAL_MS); const statusResponse = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.LEAK_CHECK_STATUS, jobId], timeout: 4e3 }); const statusParsed = parseJsonObjectOutput( statusResponse.stdout ); if (statusParsed) { if (typeof statusParsed.progress === "number" && onProgress) { onProgress(statusParsed.progress, statusParsed.stage || ""); } if (!statusParsed.running) { if (statusParsed.success && statusParsed.data) { return { success: true, data: statusParsed.data }; } return { success: false, error: statusParsed.error || _("Failed to execute IP and DNS leak check") }; } } } return { success: false, error: _("IP and DNS leak check timed out") }; } const syncResponse = await executeShellCommand({ command: "/usr/bin/tachyon", args: [Tachyon.AvailableMethods.LEAK_CHECK], timeout: 15e3 }); const parsed = parseJsonObjectOutput( syncResponse.stdout ); if ((syncResponse.code ?? 0) !== 0 || !parsed) { return { success: false, error: syncResponse.stderr || _("Failed to execute IP and DNS leak check") }; } return { success: true, data: parsed }; } }; // src/tachyon/methods/custom/getDashboardSections.ts var DASHBOARD_SECTION_CACHE_DIR = "/var/run/tachyon/section-cache"; var CLASH_API_FETCH_TIMEOUT_MS = 1200; var directClashApiFailedAt = 0; var DIRECT_CLASH_API_COOLDOWN_MS = 6e4; function getDisplayName(section) { return section.label || section[".name"]; } function getSettingsSection(configSections) { return configSections.find((section) => section[".type"] === "settings"); } function getClashApiSecret(configSections) { return getSettingsSection(configSections)?.yacd_secret_key || ""; } function canFetchClashApiDirectly() { if (Date.now() - directClashApiFailedAt < DIRECT_CLASH_API_COOLDOWN_MS) { return false; } return canUseDirectClashApi() && typeof fetch === "function"; } async function getClashApiProxies(configSections) { if (canFetchClashApiDirectly()) { const secret = getClashApiSecret(configSections); const controller = new AbortController(); const timeoutId = setTimeout( () => controller.abort(), CLASH_API_FETCH_TIMEOUT_MS ); try { const response = await fetch(`${getClashHttpUrl()}/proxies`, { headers: secret ? { Authorization: `Bearer ${secret}` } : void 0, signal: controller.signal }); if (response.ok) { directClashApiFailedAt = 0; return { success: true, data: await response.json() }; } directClashApiFailedAt = Date.now(); } catch (_error) { directClashApiFailedAt = Date.now(); } finally { clearTimeout(timeoutId); } } return TachyonShellMethods.getClashApiProxies(); } function getListValues(value) { if (!value) { return []; } if (Array.isArray(value)) { return value.map((item) => `${item}`.trim()).filter(Boolean); } return `${value}`.split(/\s+/).map((item) => item.trim()).filter(Boolean); } function childSections(configSections, type) { return configSections.filter((section) => section[".type"] === type); } function ownedChildSections(parent, children) { return children.filter( (section) => section.section === parent[".name"] ); } function childSectionsByOwner(children, ownerKey, ownerValue) { return children.filter((section) => section[ownerKey] === ownerValue); } function compactSettingsMap(settings) { return Object.keys(settings).length ? JSON.stringify(settings) : void 0; } function hydrateConfigSections(configSections) { const subscriptionUrls = childSections(configSections, "subscription_url"); const interfaces = childSections(configSections, "section_interface"); const urltests = childSections(configSections, "urltest"); const priorityGroups = childSections(configSections, "priority_group"); const priorityLevels = childSections(configSections, "priority_level"); return configSections.map((section) => { if (section[".type"] !== "section") { return section; } const next = { ...section }; const subscriptionUrlItems = ownedChildSections(next, subscriptionUrls); const interfaceItems = ownedChildSections(next, interfaces); const urltestItems = ownedChildSections(next, urltests); const priorityGroupItems = ownedChildSections(next, priorityGroups); if (subscriptionUrlItems.length) { const settings = {}; next.subscription_urls = subscriptionUrlItems.map((item) => item.url || "").filter(Boolean); subscriptionUrlItems.forEach((item) => { if (!item.url) { return; } settings[item.url] = { subscription_update_enabled: item.subscription_update_enabled, subscription_update_interval: item.subscription_update_interval, download_via_proxy_enabled: item.download_via_proxy_enabled, download_via_proxy_section: item.download_via_proxy_section, auto_user_agent: item.auto_user_agent, user_agent: item.user_agent, auto_hwid: item.auto_hwid, hwid: item.hwid, custom_device_headers: item.custom_device_headers, device_os: item.device_os, ver_os: item.ver_os, device_model: item.device_model, device_locale: item.device_locale, app_version: item.app_version, accept_language: item.accept_language, show_dashboard_metadata: item.show_dashboard_metadata, prefix_nodes: item.prefix_nodes, node_prefix: item.node_prefix, include_urltest_groups: item.include_urltest_groups, hide_urltest_group_outbounds: item.hide_urltest_group_outbounds, hide_detour_outbounds: item.hide_detour_outbounds }; }); next.subscription_url_settings = compactSettingsMap(settings); } if (interfaceItems.length) { next.interfaces = interfaceItems.map((item) => item.name || "").filter(Boolean); } if (urltestItems.length) { const settings = {}; next.urltests = urltestItems.map((item) => item[".name"]); urltestItems.forEach((item) => { settings[item[".name"]] = { display_name: item.name || item.display_name, urltest_check_interval: item.check_interval, urltest_tolerance: item.tolerance, urltest_testing_url: item.testing_url, idle_timeout: item.idle_timeout, interrupt_exist_connections: item.interrupt_exist_connections, pin_dashboard: item.pin_dashboard, urltest_filter_mode: item.filter_mode, detect_server_country: item.detect_server_country, urltest_include_countries: item.include_countries, urltest_include_outbounds: item.include_outbounds, urltest_include_regex: item.include_regex, urltest_exclude_countries: item.exclude_countries, urltest_exclude_outbounds: item.exclude_outbounds, urltest_exclude_regex: item.exclude_regex }; }); next.urltest_settings = compactSettingsMap(settings); } if (priorityGroupItems.length) { const settings = {}; next.priority_groups = priorityGroupItems.map((item) => item[".name"]); priorityGroupItems.forEach((item) => { const groupId = item[".name"]; const levels = childSectionsByOwner(priorityLevels, "group", groupId).map( (level, index) => ({ id: level[".name"], displayName: level.name || level[".name"], order: Number.parseInt(level.order || `${index}`, 10) || 0, direct: level.direct === "1", filterMode: level.filter_mode || "include", detectServerCountry: level.detect_server_country || "flag_emoji", country: getListValues(level.country), serverName: getListValues(level.server_name), regex: getListValues(level.regex), excludeCountries: getListValues(level.exclude_countries), excludeOutbounds: getListValues(level.exclude_outbounds), excludeRegex: getListValues(level.exclude_regex) }) ).sort( (left, right) => left.order === right.order ? left.id.localeCompare(right.id) : left.order - right.order ); settings[groupId] = { name: item.name, health_url: item.health_url, active_check_interval: item.active_check_interval, check_timeout: item.check_timeout, recovery_check_interval: item.recovery_check_interval, pick_fastest: item.pick_fastest, switch_to_faster_same_priority: item.switch_to_faster_same_priority, fastest_check_interval: item.fastest_check_interval, interrupt_exist_connections: item.interrupt_exist_connections, pin_dashboard: item.pin_dashboard, levels }; }); next.priority_group_settings = compactSettingsMap(settings); } return next; }); } function getManualProxyLinks(section) { return getListValues(section.selector_proxy_links); } function getConnectionInterfaces(section) { const values = getListValues(section.interfaces); return values.length ? values : getListValues(section.interface); } function getJsonOutbounds(section) { const values = getListValues(section.outbound_jsons); return values.length ? values : getListValues(section.outbound_json); } var SINGLE_ENDPOINT_ACTIONS = /* @__PURE__ */ new Set([ "vpn", "awg", "warp", "anytls", "snell", "mieru", "sudoku", "masque", "openvpn" ]); var ACTION_DISPLAY_NAMES = { awg: "AmneziaWG", warp: "WARP", anytls: "AnyTLS", snell: "Snell", mieru: "Mieru", sudoku: "Sudoku", masque: "MASQUE", openvpn: "OpenVPN", fptn: "FPTN" }; function isConnectionAction(action) { return Boolean( action && (SINGLE_ENDPOINT_ACTIONS.has(action) || ["connection", "proxy", "outbound"].includes(action)) ); } function isServiceAction(action) { return Boolean( action && ["zapret", "zapret2", "byedpi", "wdtt", "olcrtc", "fptn"].includes( action ) ); } function hasSubscriptionSources(section) { return getSubscriptionSourceCount(section) > 0; } function getSubscriptionSourceCount(section) { return getListValues(section.subscription_urls).length; } function shouldSortByLatency(section) { return section.sort_by_latency === "1"; } function hasConfiguredUrlTestList(section) { return getListValues(section.urltests).length > 0; } function hasConfiguredPriorityList(section) { return getListValues(section.priority_groups).length > 0; } function getUrlTestIds(section) { const values = getListValues(section.urltests); return values.length ? values : section.urltest_enabled === "1" ? ["urltest"] : []; } function isUrlTestEnabled(section) { return getUrlTestIds(section).length > 0; } function shouldUseProxyGroup(section) { return getManualProxyLinks(section).length > 0 || hasSubscriptionSources(section) || getConnectionInterfaces(section).length > 0 || getJsonOutbounds(section).length > 0 || isUrlTestEnabled(section) || hasConfiguredPriorityList(section); } function getSectionProxyConfigType(section) { if (hasSubscriptionSources(section)) { return "subscription"; } if (isUrlTestEnabled(section) && shouldUseProxyGroup(section)) { return "urltest"; } if (getManualProxyLinks(section).length > 0) { return "selector"; } if (getJsonOutbounds(section).length > 0) { return "outbound"; } if (getConnectionInterfaces(section).length > 0) { return "interface"; } if (hasConfiguredPriorityList(section)) { return "selector"; } return void 0; } function getJsonOutboundDisplayName(section) { try { const parsedOutbound = JSON.parse(section.outbound_json || "{}"); return parsedOutbound?.tag ? decodeURIComponent(parsedOutbound.tag) : ""; } catch (_error) { return ""; } } function buildManualLinkByCode(section) { const sectionName = section[".name"]; return new Map( getManualProxyLinks(section).map((link, index) => [ getOutboundTagBySection(`${sectionName}-${index + 1}`), link ]) ); } function getProxyEntryByCode(proxies) { return new Map(proxies.map((proxy) => [proxy.code, proxy])); } function uniqueCodes(codes) { return Array.from(new Set(codes.filter(Boolean))); } function isSelectorOutbound(outbound) { return outbound.type?.toLowerCase() === "selector"; } function isUrlTestProxyEntry(entry) { return entry?.value?.type?.toLowerCase() === "urltest"; } function getLatencySortValue(outbound) { const latency = Number(outbound.latency); return Number.isFinite(latency) && latency > 0 ? latency : Number.POSITIVE_INFINITY; } function sortOutboundsForDashboard(outbounds, options = {}) { const pinnedCodes = [ ...options.pinnedCode ? [options.pinnedCode] : [], ...options.pinnedCodes || [] ].filter(Boolean); const pinnedRank = new Map( pinnedCodes.map((code, index) => [code, index]) ); const sortByLatency = options.sortByLatency === true; return outbounds.map((outbound, index) => ({ outbound, index })).sort((left, right) => { const leftPinned = pinnedRank.has(left.outbound.code); const rightPinned = pinnedRank.has(right.outbound.code); if (leftPinned !== rightPinned) { return leftPinned ? -1 : 1; } if (leftPinned && rightPinned) { const rankDiff = (pinnedRank.get(left.outbound.code) ?? 0) - (pinnedRank.get(right.outbound.code) ?? 0); if (rankDiff !== 0) { return rankDiff; } } if (sortByLatency) { const latencyDiff = getLatencySortValue(left.outbound) - getLatencySortValue(right.outbound); if (latencyDiff !== 0) { return latencyDiff; } } return left.index - right.index; }).map((item) => item.outbound); } function sortUrlTestMembers(outbounds) { return outbounds.map((outbound, index) => ({ outbound, index })).sort((left, right) => { if (left.outbound.selected !== right.outbound.selected) { return left.outbound.selected ? -1 : 1; } const latencyDiff = getLatencySortValue(left.outbound) - getLatencySortValue(right.outbound); if (latencyDiff !== 0) { return latencyDiff; } return left.index - right.index; }).map((item) => item.outbound); } function isSafeSectionName(sectionName) { return /^[A-Za-z0-9_-]+$/.test(sectionName); } function objectMap(value) { if (!value || typeof value !== "object" || Array.isArray(value)) { return {}; } return Object.fromEntries( Object.entries(value).filter(([, item]) => typeof item === "string").map(([key, item]) => [key, item]) ); } function itemSettingsMap(value) { if (!value) { return {}; } try { const parsed = JSON.parse(value); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return {}; } return Object.fromEntries( Object.entries(parsed).filter( ([, item]) => item && typeof item === "object" && !Array.isArray(item) ) ); } catch (_error) { return {}; } } function itemSettingString(settings, key, fallback = "") { const value = settings?.[key]; return typeof value === "string" ? value : fallback; } function itemSettingBoolean(settings, key, fallback) { const value = settings?.[key]; if (value == null || value === "") { return fallback; } return value === "1" || value === "true"; } function isUrlTestFilteringEnabled(settings) { return ["exclude", "include", "mixed"].includes( itemSettingString(settings, "urltest_filter_mode", "disabled") ); } function getUrlTestTag(sectionName, id) { return getOutboundTagBySection( id === "urltest" ? `${sectionName}-urltest` : `${sectionName}-urltest-${id}` ); } function getPriorityTag(sectionName, id) { return getOutboundTagBySection(`${sectionName}-priority-${id}`); } function getUrlTestDisplayName(section, id, settings) { return itemSettingString( settings, "display_name", id === "urltest" && !hasConfiguredUrlTestList(section) ? _("Fastest") : id ); } function getUrlTestConfigs(section) { const settingsMap = itemSettingsMap(section.urltest_settings); const sectionName = section[".name"]; return getUrlTestIds(section).map((id) => { const settings = settingsMap[id] || {}; const filteringEnabled = isUrlTestFilteringEnabled(settings); return { id, code: getUrlTestTag(sectionName, id), displayName: getUrlTestDisplayName(section, id, settings), settings, pinDashboard: itemSettingBoolean(settings, "pin_dashboard", true), showDetectedCountries: filteringEnabled && itemSettingString(settings, "detect_server_country", "flag_emoji") === "country_is" }; }); } function priorityLevelConfigsFromSettings(settings) { const value = settings?.levels; if (!Array.isArray(value)) { return []; } return value.flatMap((item, index) => { if (!item || typeof item !== "object" || Array.isArray(item)) { return []; } const level = item; const id = `${level.id || ""}`.trim(); if (!id) { return []; } return [ { id, displayName: `${level.displayName || id}`, order: typeof level.order === "number" && Number.isFinite(level.order) ? level.order : index, direct: Boolean(level.direct), filterMode: `${level.filterMode || "include"}` || "include", detectServerCountry: `${level.detectServerCountry || "flag_emoji"}` || "flag_emoji", country: getListValues(level.country), serverName: getListValues(level.serverName), regex: getListValues(level.regex), excludeCountries: getListValues( level.excludeCountries ), excludeOutbounds: getListValues( level.excludeOutbounds ), excludeRegex: getListValues(level.excludeRegex), outbounds: getListValues(level.outbounds) } ]; }).sort( (left, right) => left.order === right.order ? left.id.localeCompare(right.id) : left.order - right.order ); } function getPriorityGroupIds(section) { return getListValues(section.priority_groups); } function getPriorityConfigs(section) { const settingsMap = itemSettingsMap(section.priority_group_settings); const sectionName = section[".name"]; return getPriorityGroupIds(section).map((id) => { const settings = settingsMap[id] || {}; const levels = priorityLevelConfigsFromSettings(settings); return { id, code: getPriorityTag(sectionName, id), displayName: itemSettingString(settings, "name", id), settings, pinDashboard: itemSettingBoolean(settings, "pin_dashboard", true), healthUrl: itemSettingString( settings, "health_url", "https://www.gstatic.com/generate_204" ), activeCheckInterval: itemSettingString( settings, "active_check_interval", "5s" ), checkTimeout: itemSettingString(settings, "check_timeout", "2s"), recoveryCheckInterval: itemSettingString( settings, "recovery_check_interval", "15s" ), pickFastest: itemSettingBoolean(settings, "pick_fastest", false), switchToFasterSamePriority: itemSettingBoolean( settings, "switch_to_faster_same_priority", false ), fastestCheckInterval: itemSettingString( settings, "fastest_check_interval", "3m" ), interruptExistConnections: itemSettingBoolean( settings, "interrupt_exist_connections", true ), showDetectedCountries: levels.some( (level) => level.detectServerCountry === "country_is" ), levels }; }); } async function readDashboardSectionCache(sectionName) { if (!isSafeSectionName(sectionName)) { return void 0; } try { const raw = await fs.read( `${DASHBOARD_SECTION_CACHE_DIR}/${sectionName}.json` ); const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return void 0; } return parsed; } catch (_error) { return void 0; } } function getUrlTestGroups(dashboardCache) { const groups = dashboardCache?.urltestGroups; if (!groups || typeof groups !== "object" || Array.isArray(groups)) { return {}; } return groups; } function getPriorityGroups(dashboardCache) { const groups = dashboardCache?.priorityGroups; if (!groups || typeof groups !== "object" || Array.isArray(groups)) { return {}; } return groups; } function getConfiguredSubscriptionPrefixes(section) { const settings = itemSettingsMap(section.subscription_url_settings); const prefixes = []; for (const url of Object.keys(settings)) { const item = settings[url]; if (item && item.prefix_nodes === "1") { const p = typeof item.node_prefix === "string" ? item.node_prefix.trim() : ""; if (p && !prefixes.includes(p)) { prefixes.push(p); } } } return prefixes; } function resolveOutboundNameAndPrefix({ code, entry, link, outboundMetadata, preferMetadata = false, configuredPrefixes = [] }) { const metadataName = outboundMetadata?.names?.[code]; const rawName = (preferMetadata ? metadataName : getProxyUrlName(link)) || (preferMetadata ? getProxyUrlName(link) : metadataName) || entry?.value?.name || code; let prefix = outboundMetadata?.prefixes?.[code]?.trim() || ""; if (!prefix && configuredPrefixes.length > 0) { for (const p of configuredPrefixes) { if (!p) continue; if (rawName.startsWith(p + " ") || rawName.startsWith(p + "-") || code.startsWith(p + " ") || code.startsWith(p + "-") || entry?.value?.name?.startsWith(p + " ") || entry?.value?.name?.startsWith(p + "-")) { prefix = p; break; } } } let displayName = rawName; if (prefix) { if (displayName.startsWith(prefix + " ")) { const stripped = displayName.slice(prefix.length + 1).trim(); if (stripped) { displayName = stripped; } } else if (displayName.startsWith(prefix + "-")) { const stripped = displayName.slice(prefix.length + 1).trim(); if (stripped) { displayName = stripped; } } } return { displayName, prefix: prefix || void 0 }; } function buildUrlTestInfo({ code, displayName, entry, groupCache, proxyByCode, manualLinkByCode, cachedProxyLinks, outboundMetadata, showDetectedCountries, selectorNow, selectorCodes = [], configuredPrefixes = [] }) { const childCodes = uniqueCodes( groupCache?.outbounds?.length ? groupCache.outbounds : entry?.value.all || [] ); const isChildSelectedInSelector = Boolean( selectorNow && selectorNow !== code && childCodes.includes(selectorNow) && !selectorCodes.includes(selectorNow) ); const selectedCode = isChildSelectedInSelector ? selectorNow : entry?.value.now || ""; const isManualSelection = isChildSelectedInSelector; const outbounds = sortUrlTestMembers( childCodes.flatMap((childCode) => { const childEntry = proxyByCode.get(childCode); const link = manualLinkByCode.get(childCode) || cachedProxyLinks.get(childCode) || ""; const canCopyLink = isCopyableProxyLink(link); const resolved = resolveOutboundNameAndPrefix({ code: childCode, entry: childEntry, link, outboundMetadata, preferMetadata: cachedProxyLinks.has(childCode), configuredPrefixes }); return [ { code: childCode, displayName: resolved.displayName, prefix: resolved.prefix, latency: childEntry?.value?.history?.[0]?.delay || 0, type: childEntry?.value?.type || "", transport: outboundMetadata?.transports?.[childCode] || getProxyUrlTransport(link), selected: selectedCode === childCode, link, canCopyLink, country: showDetectedCountries ? outboundMetadata?.countries?.[childCode] : void 0 } ]; }) ); const selectedName = outbounds.find((outbound) => outbound.code === selectedCode)?.displayName || selectedCode; return { code, displayName: groupCache?.displayName || displayName, selectedCode: selectedCode || void 0, selectedName: selectedName || void 0, isManualSelection, url: groupCache?.url, interval: groupCache?.interval, tolerance: groupCache?.tolerance, idleTimeout: groupCache?.idle_timeout || "30m", interruptExistConnections: groupCache?.interrupt_exist_connections, outbounds }; } function buildPriorityInfo({ config, entry, groupCache, proxyByCode, manualLinkByCode, cachedProxyLinks, outboundMetadata, showDetectedCountries, selectorNow, selectorCodes = [], configuredPrefixes = [] }) { const cacheLevels = Array.isArray(groupCache?.levels) ? groupCache.levels : []; const configLevelById = new Map( config.levels.map((level) => [level.id, level]) ); const levels = (cacheLevels.length ? cacheLevels.map((level, index) => { const id = `${level.id || ""}`; const configLevel = id ? configLevelById.get(id) : void 0; return { id: id || configLevel?.id || `level-${index + 1}`, displayName: level.displayName || configLevel?.displayName || `${index + 1}`, order: typeof level.order === "number" && Number.isFinite(level.order) ? level.order : configLevel?.order ?? index, direct: level.direct ?? configLevel?.direct ?? false, filterMode: level.filter_mode || configLevel?.filterMode || "include", detectServerCountry: level.detect_server_country || configLevel?.detectServerCountry || "flag_emoji", country: configLevel?.country || [], serverName: configLevel?.serverName || [], regex: configLevel?.regex || [], excludeCountries: configLevel?.excludeCountries || [], excludeOutbounds: configLevel?.excludeOutbounds || [], excludeRegex: configLevel?.excludeRegex || [], outbounds: uniqueCodes(level.outbounds || []) }; }) : config.levels.map((level) => ({ ...level, outbounds: uniqueCodes(level.outbounds || []) }))).sort( (left, right) => left.order === right.order ? left.id.localeCompare(right.id) : left.order - right.order ); const allChildCodes = levels.flatMap((l) => l.outbounds); const isChildSelectedInSelector = Boolean( selectorNow && selectorNow !== config.code && allChildCodes.includes(selectorNow) && !selectorCodes.includes(selectorNow) ); const selectedCode = isChildSelectedInSelector ? selectorNow : entry?.value.now || ""; const isManualSelection = isChildSelectedInSelector; const outbounds = levels.flatMap((level, levelIndex) => { const members = uniqueCodes(level.outbounds || []).map((childCode) => { const childEntry = proxyByCode.get(childCode); const link = manualLinkByCode.get(childCode) || cachedProxyLinks.get(childCode) || ""; const canCopyLink = isCopyableProxyLink(link); const resolved = resolveOutboundNameAndPrefix({ code: childCode, entry: childEntry, link, outboundMetadata, preferMetadata: cachedProxyLinks.has(childCode), configuredPrefixes }); return { code: childCode, displayName: resolved.displayName, prefix: resolved.prefix, latency: childEntry?.value?.history?.[0]?.delay || 0, type: childEntry?.value?.type || "", transport: outboundMetadata?.transports?.[childCode] || getProxyUrlTransport(link), selected: selectedCode === childCode, link, canCopyLink, country: showDetectedCountries ? outboundMetadata?.countries?.[childCode] : void 0, levelIndex, levelName: level.displayName, levelId: level.id }; }); if (!config.pickFastest) { return members; } return members.map((outbound, index) => ({ outbound, index })).sort((left, right) => { const latencyDiff = getLatencySortValue(left.outbound) - getLatencySortValue(right.outbound); return latencyDiff !== 0 ? latencyDiff : left.index - right.index; }).map((item) => item.outbound); }); const selectedName = outbounds.find((outbound) => outbound.code === selectedCode)?.displayName || selectedCode; return { code: config.code, displayName: groupCache?.displayName || config.displayName, selectedCode: selectedCode || void 0, selectedName: selectedName || void 0, isManualSelection, healthUrl: groupCache?.health_url || config.healthUrl, activeCheckInterval: groupCache?.active_check_interval || config.activeCheckInterval, checkTimeout: groupCache?.check_timeout || config.checkTimeout, recoveryCheckInterval: groupCache?.recovery_check_interval || config.recoveryCheckInterval, pickFastest: groupCache?.pick_fastest ?? config.pickFastest, switchToFasterSamePriority: groupCache?.switch_to_faster_same_priority ?? config.switchToFasterSamePriority, fastestCheckInterval: groupCache?.fastest_check_interval || config.fastestCheckInterval, interruptExistConnections: groupCache?.interrupt_exist_connections ?? config.interruptExistConnections, outbounds }; } function buildProxyGroupOutbounds(section, proxies, outboundMetadata, urltestGroups = {}, priorityGroups = {}, cachedProxyLinks = /* @__PURE__ */ new Map()) { const sectionName = section[".name"]; const configuredPrefixes = getConfiguredSubscriptionPrefixes(section); const proxyByCode = getProxyEntryByCode(proxies); const selectorTag = getOutboundTagBySection(sectionName); const selector = proxyByCode.get(selectorTag); const urlTestConfigs = getUrlTestConfigs(section); const urlTestConfigByCode = new Map( urlTestConfigs.map((config) => [config.code, config]) ); const priorityConfigs = getPriorityConfigs(section); const priorityConfigByCode = new Map( priorityConfigs.map((config) => [config.code, config]) ); const urlTestEntries = urlTestConfigs.map((config) => ({ config, entry: proxyByCode.get(config.code) })); const priorityEntries = priorityConfigs.map((config) => ({ config, entry: proxyByCode.get(config.code) })); const manualLinkByCode = buildManualLinkByCode(section); const selectorCodes = selector?.value?.all ?? []; const urlTestCodes = urlTestConfigs.map((config) => config.code); const priorityCodes = priorityConfigs.map((config) => config.code); const showDetectedCountries = urlTestConfigs.some((config) => config.showDetectedCountries) || priorityConfigs.some((config) => config.showDetectedCountries); const builtInUrltestCode = urlTestCodes[0] || ""; const fallbackCodes = uniqueCodes([ ...urlTestCodes, ...priorityCodes, ...urlTestEntries.flatMap(({ entry }) => entry?.value.all || []), ...priorityEntries.flatMap(({ entry }) => entry?.value.all || []) ]); const groupCodes = uniqueCodes([ ...selectorCodes.length ? selectorCodes : fallbackCodes, ...urlTestCodes, ...priorityCodes ]); const selectorNow = selector?.value?.now; const outbounds = uniqueCodes(groupCodes).flatMap((code) => { const item = proxyByCode.get(code); const urlTestConfig = urlTestConfigByCode.get(code); const priorityConfig = priorityConfigByCode.get(code); if (!item && !urlTestConfig && !priorityConfig) { return []; } const link = manualLinkByCode.get(code) || cachedProxyLinks.get(code) || ""; const canCopyLink = isCopyableProxyLink(link); const resolved = resolveOutboundNameAndPrefix({ code, entry: item, link, outboundMetadata, preferMetadata: cachedProxyLinks.has(code), configuredPrefixes }); const displayName = priorityConfig?.displayName || urlTestConfig?.displayName || resolved.displayName; const prefix = priorityConfig || urlTestConfig ? void 0 : resolved.prefix; const isRuntimeUrlTest = isUrlTestProxyEntry(item); const urlTestInfo = urlTestConfig || isRuntimeUrlTest ? buildUrlTestInfo({ code, displayName, entry: item, groupCache: urltestGroups[code], proxyByCode, manualLinkByCode, cachedProxyLinks, outboundMetadata, showDetectedCountries: urlTestConfig?.showDetectedCountries || showDetectedCountries, selectorNow, selectorCodes, configuredPrefixes }) : void 0; const priorityInfo = priorityConfig ? buildPriorityInfo({ config: priorityConfig, entry: item, groupCache: priorityGroups[code], proxyByCode, manualLinkByCode, cachedProxyLinks, outboundMetadata, showDetectedCountries: priorityConfig.showDetectedCountries, selectorNow, selectorCodes, configuredPrefixes }) : void 0; const isUrlTestChildSelected = Boolean( urlTestInfo?.isManualSelection && urlTestInfo?.selectedCode && urlTestInfo.outbounds.some((m) => m.code === selectorNow) ); const isPriorityChildSelected = Boolean( priorityInfo?.isManualSelection && priorityInfo?.selectedCode && priorityInfo.outbounds.some((m) => m.code === selectorNow) ); const isSelected = selectorNow === code || isUrlTestChildSelected || isPriorityChildSelected; const activeMemberLatency = isUrlTestChildSelected && urlTestInfo?.outbounds.find((m) => m.code === selectorNow)?.latency || isPriorityChildSelected && priorityInfo?.outbounds.find((m) => m.code === selectorNow)?.latency || urlTestInfo?.outbounds.find( (m) => m.selected || m.code === urlTestInfo.selectedCode )?.latency || priorityInfo?.outbounds.find( (m) => m.selected || m.code === priorityInfo.selectedCode )?.latency || 0; const itemDelay = Number(item?.value.history?.[0]?.delay); const validItemDelay = Number.isFinite(itemDelay) && itemDelay > 0 ? itemDelay : 0; const latency = validItemDelay || (activeMemberLatency > 0 ? activeMemberLatency : 0); const isGroupType = Boolean( priorityConfig || urlTestConfig || isRuntimeUrlTest ); return [ { code, displayName, prefix, latency, type: priorityConfig ? "Priority" : item?.value.type || "URLTest", transport: isGroupType ? void 0 : outboundMetadata?.transports?.[code] || getProxyUrlTransport(link), selected: isSelected, link, canCopyLink, country: showDetectedCountries ? outboundMetadata?.countries?.[code] : void 0, runtimeAvailable: item ? void 0 : false, urlTestInfo, priorityInfo } ]; }); const sortedOutbounds = sortOutboundsForDashboard(outbounds, { pinnedCodes: [ ...urlTestEntries.filter(({ config }) => config.pinDashboard).map(({ config }) => config.code), ...priorityEntries.filter(({ config }) => config.pinDashboard).map(({ config }) => config.code) ], sortByLatency: shouldSortByLatency(section) }); const latencyTestCodes = sortedOutbounds.filter( (outbound) => outbound.runtimeAvailable !== false && !isSelectorOutbound(outbound) && !outbound.priorityInfo ).map((outbound) => outbound.code); return { selector, latencyTestCode: selector?.code || builtInUrltestCode, latencyTestCodes: latencyTestCodes.length > 0 ? latencyTestCodes : void 0, outbounds: sortedOutbounds }; } function metadataMatchesCurrentSource(sectionName, sourceCount, metadata) { const legacyMetadata = metadata; const sourceIndex = metadata.sourceIndex ?? legacyMetadata.source_index; const sourceSection = metadata.sourceSection || legacyMetadata.source_section || ""; const hasSourceIndex = typeof sourceIndex === "number"; const hasSourceSection = sourceSection !== ""; if (!hasSourceIndex && !hasSourceSection) { return sourceCount <= 1; } if (sourceCount > 1 && !hasSourceSection) { return false; } if (hasSourceIndex && (sourceIndex < 1 || sourceIndex > sourceCount)) { return false; } if (hasSourceSection) { const expectedSourcePrefix = `${sectionName}-subscription-`; if (!sourceSection.startsWith(expectedSourcePrefix)) { return false; } const sourceSectionIndex = Number( sourceSection.slice(expectedSourcePrefix.length) ); if (!Number.isInteger(sourceSectionIndex) || sourceSectionIndex < 1 || sourceSectionIndex > sourceCount) { return false; } if (hasSourceIndex && sourceIndex !== sourceSectionIndex) { return false; } } return true; } function getSubscriptionMetadataSourceIndex(sectionName, sourceCount, metadata) { const legacyMetadata = metadata; const sourceIndex = metadata.sourceIndex ?? legacyMetadata.source_index; if (typeof sourceIndex === "number") { return sourceIndex; } const sourceSection = metadata.sourceSection || legacyMetadata.source_section || ""; const expectedSourcePrefix = `${sectionName}-subscription-`; if (sourceSection.startsWith(expectedSourcePrefix)) { const parsed = Number(sourceSection.slice(expectedSourcePrefix.length)); return Number.isInteger(parsed) ? parsed : void 0; } return sourceCount <= 1 ? 1 : void 0; } function isSubscriptionMetadataVisible(section, sourceCount, metadata) { const sourceIndex = getSubscriptionMetadataSourceIndex( section[".name"], sourceCount, metadata ); if (!sourceIndex || sourceIndex < 1 || sourceIndex > sourceCount) { return true; } const sourceEntry = getListValues(section.subscription_urls)[sourceIndex - 1]; const settings = itemSettingsMap(section.subscription_url_settings)[sourceEntry]; return settings?.show_dashboard_metadata !== "0"; } function getSubscriptionMetadata(section, sourceCount, dashboardCache) { if (!dashboardCache?.subscriptionMetadata) { return void 0; } const metadataItems = Array.isArray(dashboardCache.subscriptionMetadata) ? dashboardCache.subscriptionMetadata : [dashboardCache.subscriptionMetadata]; const visibleMetadataItems = metadataItems.filter( (metadata) => metadata && Object.keys(metadata).length > 1 && metadataMatchesCurrentSource(section[".name"], sourceCount, metadata) && isSubscriptionMetadataVisible(section, sourceCount, metadata) ); if (visibleMetadataItems.length > 0) { return visibleMetadataItems; } return void 0; } function getOutboundMetadata(dashboardCache) { const metadata = dashboardCache?.outboundMetadata; if (!metadata || typeof metadata !== "object") { return void 0; } return { names: objectMap(metadata.names), countries: objectMap(metadata.countries), transports: objectMap(metadata.transports), protocols: objectMap(metadata.protocols), securities: objectMap(metadata.securities) }; } function getCachedProxyLinks(dashboardCache) { return new Map( Object.entries(objectMap(dashboardCache?.links)).filter( ([, link]) => isCopyableProxyLink(link) ) ); } async function getDashboardSections(options = {}) { const includeSubscriptionCopyState = options.includeSubscriptionCopyState ?? true; const configSections = hydrateConfigSections(await getConfigSections()); const clashProxies = await getClashApiProxies(configSections); if (!clashProxies.success || !clashProxies.data?.proxies) { return { success: false, data: [] }; } const proxies = Object.entries(clashProxies.data.proxies).map( ([key, value]) => ({ code: key, value }) ); const serviceStatusCache = /* @__PURE__ */ new Map(); const getServiceStatus = (serviceType) => { if (!serviceStatusCache.has(serviceType)) { serviceStatusCache.set( serviceType, (async () => { try { if (serviceType === "zapret") { const result = await TachyonShellMethods.getZapretStatus(); if (result.success && result.data) { const s = result.data; return { serviceType: "zapret", configured: Boolean(s.configured), ready: Boolean(s.ready), conflict: Boolean(s.conflict), runningProcesses: s.running_process_count, expectedProcesses: s.expected_process_count, restartCount: s.restart_count, unstable: Boolean(s.runtime_unstable), statusMessage: s.status_message }; } } else if (serviceType === "zapret2") { const result = await TachyonShellMethods.getZapret2Status(); if (result.success && result.data) { const s = result.data; return { serviceType: "zapret2", configured: Boolean(s.configured), ready: Boolean(s.ready), conflict: Boolean(s.conflict), runningProcesses: s.running_process_count, expectedProcesses: s.expected_process_count, restartCount: 0, unstable: false, statusMessage: s.status_message }; } } else if (serviceType === "byedpi") { const result = await TachyonShellMethods.getByedpiStatus(); if (result.success && result.data) { const s = result.data; return { serviceType: "byedpi", configured: Boolean(s.configured), ready: Boolean(s.ready), conflict: Boolean(s.conflict), runningProcesses: s.running_process_count, expectedProcesses: s.expected_process_count, restartCount: s.restart_count, unstable: Boolean(s.runtime_unstable), statusMessage: s.status_message }; } } else if (serviceType === "wdtt") { const result = await TachyonShellMethods.getWdttStatus(); if (result.success && result.data) { const s = result.data; return { serviceType: "wdtt", configured: Boolean(s.configured), ready: Boolean(s.ready), conflict: Boolean(s.conflict), runningProcesses: s.running_process_count, expectedProcesses: s.expected_process_count, restartCount: s.restart_count, unstable: Boolean(s.runtime_unstable), statusMessage: s.status_message }; } } else if (serviceType === "olcrtc") { const result = await TachyonShellMethods.getOlcrtcStatus(); if (result.success && result.data) { const s = result.data; return { serviceType: "olcrtc", configured: Boolean(s.configured), ready: Boolean(s.ready), conflict: Boolean(s.conflict), runningProcesses: s.running_process_count, expectedProcesses: s.expected_process_count, restartCount: s.restart_count, unstable: Boolean(s.runtime_unstable), statusMessage: s.status_message }; } } else if (serviceType === "fptn") { const result = await TachyonShellMethods.getFptnStatus(); if (result.success && result.data) { const s = result.data; return { serviceType: "fptn", configured: Boolean(s.configured), ready: Boolean(s.ready), conflict: false, runningProcesses: s.service_running ? 1 : 0, expectedProcesses: s.configured ? 1 : 0, restartCount: 0, unstable: false, statusMessage: s.status_message || (s.ready ? _("Running") : _("Stopped")) }; } } } catch (_error) { } return void 0; })() ); } return serviceStatusCache.get(serviceType); }; const data = await Promise.all( configSections.filter( (section) => section.enabled !== "0" && (isConnectionAction(section.action) || isServiceAction(section.action)) ).map(async (section) => { const displayName = getDisplayName(section); const sectionName = section[".name"]; const sectionAction = section.action; const proxyConfigType = getSectionProxyConfigType(section); if (isConnectionAction(sectionAction) && shouldUseProxyGroup(section)) { const subscriptionSourceCount = getSubscriptionSourceCount(section); const subscriptionEnabled = subscriptionSourceCount > 0; const dashboardCache = await readDashboardSectionCache(sectionName); const outboundMetadata = getOutboundMetadata(dashboardCache); const subscriptionMetadata = subscriptionEnabled ? getSubscriptionMetadata( section, subscriptionSourceCount, dashboardCache ) : void 0; const cachedProxyLinks = includeSubscriptionCopyState ? getCachedProxyLinks(dashboardCache) : /* @__PURE__ */ new Map(); const urltestGroups = getUrlTestGroups(dashboardCache); const priorityGroups = getPriorityGroups(dashboardCache); const { selector, latencyTestCode, latencyTestCodes, outbounds } = buildProxyGroupOutbounds( section, proxies, outboundMetadata, urltestGroups, priorityGroups, cachedProxyLinks ); return { withTagSelect: true, code: selector?.code || sectionName, sectionName, displayName, action: sectionAction, latencyTestCode, latencyTestCodes, proxyConfigType, subscriptionSourceCount, subscriptionMetadata, outbounds }; } if (SINGLE_ENDPOINT_ACTIONS.has(sectionAction || "")) { const outboundTag = getOutboundTagBySection(sectionName); const outbound = proxies.find((proxy) => proxy.code === outboundTag); const defaultLabel = ACTION_DISPLAY_NAMES[sectionAction || ""] || (sectionAction || "").toUpperCase(); const customName = outbound?.value?.name && outbound.value.name !== outboundTag ? outbound.value.name : ""; return { withTagSelect: false, code: outbound?.code || sectionName, sectionName, displayName, action: sectionAction, outbounds: [ { code: outbound?.code || sectionName, displayName: section.label || section.interface || customName || defaultLabel, latency: outbound?.value?.history?.length ? outbound.value.history[0].delay > 0 ? outbound.value.history[0].delay : -1 : 0, type: ACTION_DISPLAY_NAMES[sectionAction || ""] || outbound?.value?.type || (sectionAction || "").toUpperCase(), selected: true, canCopyLink: false, runtimeAvailable: Boolean(outbound) } ] }; } if (sectionAction === "outbound") { const outboundTag = getOutboundTagBySection(sectionName); const outbound = proxies.find((proxy) => proxy.code === outboundTag); return { withTagSelect: false, code: outbound?.code || sectionName, sectionName, displayName, action: sectionAction, outbounds: [ { code: outbound?.code || sectionName, displayName: getJsonOutboundDisplayName(section) || outbound?.value?.name || "", latency: outbound?.value?.history?.[0]?.delay || 0, type: outbound?.value?.type || "", selected: true, canCopyLink: false } ] }; } if (isServiceAction(sectionAction)) { const serviceType = sectionAction; const serviceStatus = await getServiceStatus(serviceType); const statusLabel = serviceStatus ? serviceStatus.ready ? _("Running") : serviceStatus.conflict ? _("Conflict") : serviceStatus.configured ? _("Stopped") : _("Not configured") : _("Unknown"); return { withTagSelect: false, code: sectionName, sectionName, displayName, action: sectionAction, serviceStatus, outbounds: [ { code: sectionName, displayName: statusLabel, latency: 0, type: serviceType.toUpperCase(), selected: true, canCopyLink: false } ] }; } return { withTagSelect: false, code: sectionName, sectionName, displayName, action: sectionAction, outbounds: [] }; }) ); return { success: true, data }; } // src/tachyon/methods/custom/getClashApiSecret.ts async function getClashApiSecret2() { const sections = await getConfigSections(); const settings = sections.find((section) => section[".type"] === "settings"); return settings?.yacd_secret_key || ""; } // src/tachyon/methods/custom/index.ts var CustomTachyonMethods = { getConfigSections, getDashboardSections, getClashApiSecret: getClashApiSecret2 }; // src/tachyon/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/tachyon/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/tachyon/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/tachyon/methods/fakeip/index.ts var RemoteFakeIPMethods = { getFakeIpCheck, getIpCheck }; // src/tachyon/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(); } cleanTabId(tab) { if (!tab) return null; return tab.replace(/^(?:cbi-tachyon-|tab-)/, ""); } getTabsInfo() { const tabs = Array.from( document.querySelectorAll(".cbi-tab, .cbi-tab-disabled") ); return tabs.map((el) => ({ el, id: this.cleanTabId(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 this.cleanTabId(active?.dataset.tab); } 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(); } }; var TabServiceInstance = TabService.getInstance(); // src/tachyon/tabs/diagnostic/helpers/getCheckTitle.ts function getCheckTitle(name) { return `${name} ${_("checks")}`; } // src/tachyon/tabs/diagnostic/checks/constants.ts var DIAGNOSTICS_CHECKS = /* @__PURE__ */ ((DIAGNOSTICS_CHECKS2) => { DIAGNOSTICS_CHECKS2["DNS"] = "DNS"; DIAGNOSTICS_CHECKS2["SINGBOX"] = "SINGBOX"; DIAGNOSTICS_CHECKS2["NFT"] = "NFT"; DIAGNOSTICS_CHECKS2["ZAPRET"] = "ZAPRET"; DIAGNOSTICS_CHECKS2["ZAPRET2"] = "ZAPRET2"; DIAGNOSTICS_CHECKS2["BYEDPI"] = "BYEDPI"; DIAGNOSTICS_CHECKS2["FAKEIP"] = "FAKEIP"; DIAGNOSTICS_CHECKS2["OUTBOUNDS"] = "OUTBOUNDS"; DIAGNOSTICS_CHECKS2["INBOUNDS"] = "INBOUNDS"; return DIAGNOSTICS_CHECKS2; })(DIAGNOSTICS_CHECKS || {}); var DIAGNOSTICS_CHECKS_MAP = { ["DNS" /* DNS */]: { order: 1, title: getCheckTitle("DNS"), code: "DNS" /* DNS */ }, ["SINGBOX" /* SINGBOX */]: { order: 2, title: getCheckTitle("Sing-box"), code: "SINGBOX" /* SINGBOX */ }, ["NFT" /* NFT */]: { order: 4, title: getCheckTitle("Nftables"), code: "NFT" /* NFT */ }, ["ZAPRET" /* ZAPRET */]: { order: 5, title: getCheckTitle("Zapret"), code: "ZAPRET" /* ZAPRET */ }, ["BYEDPI" /* BYEDPI */]: { order: 7, title: getCheckTitle("ByeDPI"), code: "BYEDPI" /* BYEDPI */ }, ["ZAPRET2" /* ZAPRET2 */]: { order: 6, title: getCheckTitle("Zapret2"), code: "ZAPRET2" /* ZAPRET2 */ }, ["OUTBOUNDS" /* OUTBOUNDS */]: { order: 8, title: getCheckTitle("Outbounds"), code: "OUTBOUNDS" /* OUTBOUNDS */ }, ["FAKEIP" /* FAKEIP */]: { order: 9, title: getCheckTitle("FakeIP"), code: "FAKEIP" /* FAKEIP */ }, ["INBOUNDS" /* INBOUNDS */]: { order: 3, title: getCheckTitle("Inbounds"), code: "INBOUNDS" /* INBOUNDS */ } }; // src/tachyon/tabs/diagnostic/diagnostic.store.ts function createDiagnosticCheck(code, description) { const meta = DIAGNOSTICS_CHECKS_MAP[code]; return { code, title: meta.title, order: meta.order, description, items: [], state: "skipped" }; } function getDiagnosticsChecks(description, options = {}) { const checks = ["DNS" /* DNS */, "SINGBOX" /* SINGBOX */]; if (options.includeInbounds === true) { checks.push("INBOUNDS" /* INBOUNDS */); } checks.push("NFT" /* NFT */); if (options.includeZapret) { checks.push("ZAPRET" /* ZAPRET */); } if (options.includeZapret2) { checks.push("ZAPRET2" /* ZAPRET2 */); } if (options.includeByedpi) { checks.push("BYEDPI" /* BYEDPI */); } checks.push("OUTBOUNDS" /* OUTBOUNDS */, "FAKEIP" /* FAKEIP */); return checks.map((code) => createDiagnosticCheck(code, description)); } function getLoadingDiagnosticsChecks(options = {}) { return { diagnosticsChecks: getDiagnosticsChecks(_("Pending"), options) }; } var initialDiagnosticStore = { diagnosticsSystemInfo: { loading: true, loaded: false, providerInfoLoaded: false, tachyon_version: "loading", tachyon_commit_sha: "", tachyon_latest_version: "loading", luci_app_version: "loading", sing_box_version: "loading", sing_box_extended: 0, sing_box_tiny: 0, sing_box_compressed: 0, sing_box_lx: 0, sing_box_tailscale: 1, sing_box_repo_url: "", sing_box_backup_version: "", sing_box_backup_time: 0, zapret_version: "loading", zapret_installed: 0, zapret_backup_version: "", zapret_backup_time: 0, zapret2_version: "loading", zapret2_installed: 0, zapret2_backup_version: "", zapret2_backup_time: 0, byedpi_version: "loading", byedpi_installed: 0, byedpi_backup_version: "", byedpi_backup_time: 0, wdtt_version: "loading", wdtt_installed: 0, wdtt_backup_version: "", wdtt_backup_time: 0, olcrtc_version: "loading", olcrtc_installed: 0, olcrtc_backup_version: "", olcrtc_backup_time: 0, tailscale_version: "loading", tailscale_installed: 0, tailscale_backup_version: "", tailscale_backup_time: 0, fptn_version: "loading", fptn_installed: 0, fptn_backup_version: "", fptn_backup_time: 0, server_inbounds_enabled_count: -1, direct_bypass_enabled: 0, direct_bypass_address: "", direct_bypass_port: "", torrserver_running: 0, torrserver_direct_available: 0, torrserver_direct_enabled: 0, torrserver_direct_active: 0, openwrt_version: "loading", device_model: "loading" }, diagnosticsActions: { restart: { loading: false }, start: { loading: false }, stop: { loading: false }, enable: { loading: false }, disable: { loading: false }, globalCheck: { loading: false }, doctor: { loading: false }, aiDoctor: { loading: false }, viewLogs: { loading: false }, showSingBoxConfig: { loading: false }, generateBugReport: { loading: false }, checkServices: { loading: false } }, diagnosticsRunAction: { loading: false }, diagnosticsChecks: getDiagnosticsChecks(_("Not running")), updatesActions: { tachyonCheck: { loading: false }, tachyonInstall: { loading: false }, tachyonReinstall: { loading: false }, tachyonRollback: { loading: false }, singBoxCheck: { loading: false }, singBoxInstall: { loading: false }, singBoxRollback: { loading: false }, singBoxInstallExtended: { loading: false }, singBoxInstallExtendedCompressed: { loading: false }, singBoxInstallLx: { loading: false }, singBoxInstallTiny: { loading: false }, singBoxInstallStable: { loading: false }, zapretCheck: { loading: false }, zapretInstall: { loading: false }, zapretRemove: { loading: false }, zapretRollback: { loading: false }, zapret2Check: { loading: false }, zapret2Install: { loading: false }, zapret2Remove: { loading: false }, zapret2Rollback: { loading: false }, byedpiCheck: { loading: false }, byedpiInstall: { loading: false }, byedpiRemove: { loading: false }, byedpiRollback: { loading: false }, wdttCheck: { loading: false }, wdttInstall: { loading: false }, wdttRemove: { loading: false }, wdttRollback: { loading: false }, olcrtcCheck: { loading: false }, olcrtcInstall: { loading: false }, olcrtcRemove: { loading: false }, olcrtcRollback: { loading: false }, fptnCheck: { loading: false }, fptnInstall: { loading: false }, fptnRemove: { loading: false }, fptnRollback: { loading: false }, tailscaleCheck: { loading: false }, tailscaleInstall: { loading: false }, tailscaleRemove: { loading: false }, tailscaleRollback: { loading: false }, directBypassEnable: { loading: false }, directBypassDisable: { loading: false }, torrserverDirectEnable: { loading: false }, torrserverDirectDisable: { loading: false } }, updatesChecks: { tachyon: { status: null, latest_version: "", release_url: "" }, sing_box: { status: null, latest_version: "", release_url: "" }, zapret: { status: null, latest_version: "", release_url: "" }, zapret2: { status: null, latest_version: "", release_url: "" }, byedpi: { status: null, latest_version: "", release_url: "" }, wdtt: { status: null, latest_version: "", release_url: "" }, olcrtc: { status: null, latest_version: "", release_url: "" }, fptn: { status: null, latest_version: "", release_url: "" }, tailscale: { status: null, latest_version: "", release_url: "" }, direct_bypass: { status: null, latest_version: "", release_url: "" }, torrserver_direct: { status: null, latest_version: "", release_url: "" } } }; // src/tachyon/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) => { acc[key] = value[key]; return acc; }, {} ); } return value; }); } function jsonEqual(a, b) { try { return jsonStableStringify(a) === jsonStableStringify(b); } catch { return false; } } var StoreService = class { constructor(initial) { this.listeners = /* @__PURE__ */ new Set(); this.value = initial; this.initial = structuredClone(initial); } get() { return this.value; } set(next) { const prev = this.value; const diff = {}; for (const key in next) { if (next[key] === prev[key]) continue; if (!jsonEqual(next[key], prev[key])) diff[key] = next[key]; } if (Object.keys(diff).length === 0) { return; } const merged = { ...prev, ...next }; this.value = merged; this.listeners.forEach((cb) => cb(this.value, prev, diff)); } reset(keys) { const prev = this.value; 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; 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, tachyonRunning: 0, tachyonEnabled: 0, tachyonStatus: "", watchdogRunning: 0 } }, tailscaleWidget: { loading: true, failed: false, data: null }, sectionsWidget: { loading: true, failed: false, latencyFetchingSections: {}, latencyProgressSections: {}, selectorSwitchingSections: {}, subscriptionUpdatingSections: {}, data: [] }, ...initialDiagnosticStore }; var store = new StoreService(initialStore); // src/tachyon/services/tachyonLogWatcher.service.ts var TachyonLogWatcher = class _TachyonLogWatcher { constructor() { this.intervalMs = 5e3; this.lastLines = /* @__PURE__ */ new Set(); this.maxTrackedLines = 500; this.running = false; this.paused = false; this.checking = false; if (typeof document !== "undefined") { document.addEventListener("visibilitychange", () => { if (document.hidden) this.pause(); else this.resume(); }); } } static getInstance() { if (!_TachyonLogWatcher.instance) { _TachyonLogWatcher.instance = new _TachyonLogWatcher(); } return _TachyonLogWatcher.instance; } init(fetcher, options) { this.fetcher = fetcher; this.onNewLog = options?.onNewLog; this.intervalMs = options?.intervalMs ?? 5e3; this.maxTrackedLines = options?.maxTrackedLines ?? 500; this.lastLines = /* @__PURE__ */ new Set(); logger.info( "[TachyonLogWatcher]", `initialized (interval: ${this.intervalMs}ms)` ); } normalizeLines(raw) { return raw.split("\n").filter(Boolean).slice(-this.maxTrackedLines); } async checkOnce() { if (!this.fetcher) { logger.warn("[TachyonLogWatcher]", "fetcher not found"); return; } if (this.paused) { logger.debug("[TachyonLogWatcher]", "skipped check — tab not visible"); return; } if (this.checking) { logger.debug( "[TachyonLogWatcher]", "skipped check — previous check is running" ); return; } this.checking = true; try { const raw = await this.fetcher(); const lines = this.normalizeLines(raw); for (const line of lines) { if (this.lastLines.has(line)) { continue; } this.lastLines.add(line); this.onNewLog?.(line); } if (this.lastLines.size > this.maxTrackedLines) { this.lastLines = new Set( Array.from(this.lastLines).slice(-this.maxTrackedLines) ); } } catch (err) { logger.error("[TachyonLogWatcher]", "failed to read logs:", err); } finally { this.checking = false; } } start() { if (this.running) return; if (!this.fetcher) { logger.warn("[TachyonLogWatcher]", "attempted to start without fetcher"); return; } this.running = true; void this.checkOnce(); this.timer = setInterval(() => this.checkOnce(), this.intervalMs); logger.info( "[TachyonLogWatcher]", `started (interval: ${this.intervalMs}ms)` ); } stop() { if (!this.running) return; this.running = false; if (this.timer) clearInterval(this.timer); logger.info("[TachyonLogWatcher]", "stopped"); } pause() { if (!this.running || this.paused) return; this.paused = true; logger.info("[TachyonLogWatcher]", "paused (tab not visible)"); } resume() { if (!this.running || !this.paused) return; this.paused = false; logger.info("[TachyonLogWatcher]", "resumed (tab active)"); void this.checkOnce(); } reset() { this.lastLines = /* @__PURE__ */ new Set(); this.checking = false; logger.info("[TachyonLogWatcher]", "log history reset"); } }; // src/tachyon/services/logNotificationDeduper.service.ts var LOG_NOTIFICATION_STORAGE_KEY = "tachyon:shown-log-error-notifications:v1"; var MAX_STORED_LOG_NOTIFICATIONS = 500; function getSessionStorage() { if (typeof window === "undefined") { return null; } try { return window.sessionStorage; } catch { return null; } } function readStoredKeys(storage) { if (!storage) { return []; } try { const parsed = JSON.parse( storage.getItem(LOG_NOTIFICATION_STORAGE_KEY) || "[]" ); return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : []; } catch { return []; } } function writeStoredKeys(storage, keys) { if (!storage) { return; } try { storage.setItem( LOG_NOTIFICATION_STORAGE_KEY, JSON.stringify(keys.slice(-MAX_STORED_LOG_NOTIFICATIONS)) ); } catch { } } var SUPPRESSED_LOG_PATTERNS = [ /another component action is already running/i, /subscription dns resolution failed for rule.*retrying in/i ]; function isErrorLogLine(line) { const lower = line.toLowerCase(); if (SUPPRESSED_LOG_PATTERNS.some((pattern) => pattern.test(lower))) { return false; } return lower.includes("[error]") || lower.includes("[fatal]") || lower.includes("sing-box") && lower.includes("rule-set") && /\b(error|fatal)\b/.test(lower); } function getTachyonLogNotification(line) { if (isErrorLogLine(line)) { return { kind: "error", line }; } const update = line.match( /\[component-update\]\s+(tachyon|sing_box|zapret|zapret2|byedpi|wdtt|olcrtc)\s+(\S+)/i ); if (!update) { return null; } return { kind: "component-update", line, component: update[1].toLowerCase(), version: update[2] }; } function getLogNotificationKey(line) { return line.trim(); } var LogNotificationDeduper = class { constructor(storage = getSessionStorage()) { this.storage = storage; this.seenKeys = new Set(readStoredKeys(storage)); } shouldNotify(line) { if (!getTachyonLogNotification(line)) { return false; } const key = getLogNotificationKey(line); if (!key || this.seenKeys.has(key)) { return false; } this.seenKeys.add(key); writeStoredKeys(this.storage, Array.from(this.seenKeys)); return true; } }; // src/tachyon/helpers/getComponentActionKey.ts var componentActionKeyMap = { "tachyon:check_update": "tachyonCheck", "tachyon:install": "tachyonInstall", "tachyon:install_version": "tachyonInstall", "tachyon:reinstall": "tachyonReinstall", "tachyon:rollback": "tachyonRollback", "sing_box:check_update": "singBoxCheck", "sing_box:install": "singBoxInstall", "sing_box:install_version": "singBoxInstall", "sing_box:rollback": "singBoxRollback", "sing_box:install_extended": "singBoxInstallExtended", "sing_box:install_extended_compressed": "singBoxInstallExtendedCompressed", "sing_box:install_lx": "singBoxInstallLx", "sing_box:install_tiny": "singBoxInstallTiny", "sing_box:install_stable": "singBoxInstallStable", "zapret:check_update": "zapretCheck", "zapret:install": "zapretInstall", "zapret:install_version": "zapretInstall", "zapret:remove": "zapretRemove", "zapret:rollback": "zapretRollback", "zapret2:check_update": "zapret2Check", "zapret2:install": "zapret2Install", "zapret2:install_version": "zapret2Install", "zapret2:remove": "zapret2Remove", "zapret2:rollback": "zapret2Rollback", "byedpi:check_update": "byedpiCheck", "byedpi:install": "byedpiInstall", "byedpi:install_version": "byedpiInstall", "byedpi:remove": "byedpiRemove", "byedpi:rollback": "byedpiRollback", "wdtt:check_update": "wdttCheck", "wdtt:install": "wdttInstall", "wdtt:install_version": "wdttInstall", "wdtt:remove": "wdttRemove", "wdtt:rollback": "wdttRollback", "olcrtc:check_update": "olcrtcCheck", "olcrtc:install": "olcrtcInstall", "olcrtc:install_version": "olcrtcInstall", "olcrtc:remove": "olcrtcRemove", "olcrtc:rollback": "olcrtcRollback", "fptn:check_update": "fptnCheck", "fptn:install": "fptnInstall", "fptn:install_version": "fptnInstall", "fptn:remove": "fptnRemove", "fptn:rollback": "fptnRollback", "tailscale:check_update": "tailscaleCheck", "tailscale:install": "tailscaleInstall", "tailscale:install_version": "tailscaleInstall", "tailscale:remove": "tailscaleRemove", "tailscale:rollback": "tailscaleRollback", "direct_bypass:enable": "directBypassEnable", "direct_bypass:disable": "directBypassDisable", "torrserver_direct:enable": "torrserverDirectEnable", "torrserver_direct:disable": "torrserverDirectDisable" }; function getComponentActionKey(component, action) { return componentActionKeyMap[`${component}:${action}`]; } // src/tachyon/helpers/singBoxVariant.ts function isExtendedSingBoxVersion(version) { return String(version || "").includes("extended") || String(version || "").includes("-lx"); } function isVersionPlaceholder(version) { const normalized = String(version || "").trim().toLowerCase(); if (!normalized || normalized === "loading" || normalized === "unknown" || normalized === "not installed") { return true; } return typeof _ === "function" && normalized === _("unknown").toLowerCase() || typeof _ === "function" && normalized === _("Not installed").toLowerCase(); } function formatSingBoxVersion(value) { const version = String(value.sing_box_version || ""); if (!version || version === "not installed") { return _("Not installed"); } if (isVersionPlaceholder(version)) { return version; } const normalizedValue = normalizeSingBoxVariantFields(value); let variant = ""; if (normalizedValue.sing_box_extended && normalizedValue.sing_box_compressed) { variant = _("compressed"); } else if (normalizedValue.sing_box_extended && normalizedValue.sing_box_lx) { variant = _("lx"); } else if (normalizedValue.sing_box_extended) { variant = _("extended"); } else if (normalizedValue.sing_box_tiny) { variant = _("tiny"); } return variant ? `${version} (${variant})` : version; } function normalizeSingBoxVariantFields(value) { const version = String(value.sing_box_version || ""); const versionExtended = isExtendedSingBoxVersion(version); const versionLx = version.includes("-lx"); const singBoxExtended = Boolean(value.sing_box_extended) || versionExtended; const singBoxLx = singBoxExtended && (Boolean(value.sing_box_lx) || versionLx); return { ...value, sing_box_extended: singBoxExtended ? 1 : 0, sing_box_tiny: singBoxExtended ? 0 : value.sing_box_tiny ? 1 : 0, sing_box_compressed: singBoxExtended && value.sing_box_compressed ? 1 : 0, sing_box_lx: singBoxLx ? 1 : 0, sing_box_tailscale: singBoxExtended || value.sing_box_tailscale ? 1 : 0 }; } function renderSingBoxVariantBadge(value) { const version = String(value.sing_box_version || ""); if (!version || isVersionPlaceholder(version)) { return null; } const normalized = normalizeSingBoxVariantFields(value); const badge = document.createElement("span"); badge.className = "tachyon-badge"; badge.style.display = "inline-block"; badge.style.marginLeft = "8px"; badge.style.padding = "2px 8px"; badge.style.borderRadius = "9999px"; badge.style.fontSize = "11px"; badge.style.fontWeight = "600"; badge.style.lineHeight = "16px"; badge.style.verticalAlign = "middle"; if (normalized.sing_box_lx) { badge.textContent = "Leadaxe (lx)"; badge.style.backgroundColor = "rgba(16, 185, 129, 0.15)"; badge.style.color = "#10b981"; badge.style.border = "1px solid rgba(16, 185, 129, 0.35)"; } else if (normalized.sing_box_compressed) { badge.textContent = "Extended (compressed)"; badge.style.backgroundColor = "rgba(59, 130, 246, 0.15)"; badge.style.color = "#3b82f6"; badge.style.border = "1px solid rgba(59, 130, 246, 0.35)"; } else if (normalized.sing_box_extended) { badge.textContent = "Extended (shtorm-7)"; badge.style.backgroundColor = "rgba(59, 130, 246, 0.15)"; badge.style.color = "#3b82f6"; badge.style.border = "1px solid rgba(59, 130, 246, 0.35)"; } else if (normalized.sing_box_tiny) { badge.textContent = "Tiny"; badge.style.backgroundColor = "rgba(245, 158, 11, 0.15)"; badge.style.color = "#f59e0b"; badge.style.border = "1px solid rgba(245, 158, 11, 0.35)"; } else { badge.textContent = "Official"; badge.style.backgroundColor = "rgba(107, 114, 128, 0.15)"; badge.style.color = "#6b7280"; badge.style.border = "1px solid rgba(107, 114, 128, 0.35)"; } return badge; } // src/tachyon/services/localActionOverlay.service.ts var componentActions = /* @__PURE__ */ new Set(); var subscriptionSections = /* @__PURE__ */ new Set(); var latencySections = /* @__PURE__ */ new Set(); var serviceActions = /* @__PURE__ */ new Set(); function setMembership(set, value, active) { if (active) { set.add(value); } else { set.delete(value); } } function setLocalComponentAction(action, active) { setMembership(componentActions, action, active); } function setLocalSubscriptionAction(section, active) { if (section) { setMembership(subscriptionSections, section, active); } } function setLocalLatencyAction(section, active) { if (section) { setMembership(latencySections, section, active); } } function setLocalServiceAction(action, active) { if (action === "restart" || action === "start" || action === "stop") { setMembership(serviceActions, action, active); } } function getLocalActionOverlay() { return { componentActions: new Set(componentActions), subscriptionSections: new Set(subscriptionSections), latencySections: new Set(latencySections), serviceActions: new Set(serviceActions) }; } // src/tachyon/services/uiState.service.ts function isRunningAction(state) { return state.running === true; } function getEmptyUpdatesActions() { return { tachyonCheck: { loading: false }, tachyonInstall: { loading: false }, tachyonReinstall: { loading: false }, tachyonRollback: { loading: false }, singBoxCheck: { loading: false }, singBoxInstall: { loading: false }, singBoxRollback: { loading: false }, singBoxInstallExtended: { loading: false }, singBoxInstallExtendedCompressed: { loading: false }, singBoxInstallLx: { loading: false }, singBoxInstallTiny: { loading: false }, singBoxInstallStable: { loading: false }, zapretCheck: { loading: false }, zapretInstall: { loading: false }, zapretRemove: { loading: false }, zapretRollback: { loading: false }, zapret2Check: { loading: false }, zapret2Install: { loading: false }, zapret2Remove: { loading: false }, zapret2Rollback: { loading: false }, byedpiCheck: { loading: false }, byedpiInstall: { loading: false }, byedpiRemove: { loading: false }, byedpiRollback: { loading: false }, wdttCheck: { loading: false }, wdttInstall: { loading: false }, wdttRemove: { loading: false }, wdttRollback: { loading: false }, olcrtcCheck: { loading: false }, olcrtcInstall: { loading: false }, olcrtcRemove: { loading: false }, olcrtcRollback: { loading: false }, fptnCheck: { loading: false }, fptnInstall: { loading: false }, fptnRemove: { loading: false }, fptnRollback: { loading: false }, tailscaleCheck: { loading: false }, tailscaleInstall: { loading: false }, tailscaleRemove: { loading: false }, tailscaleRollback: { loading: false }, directBypassEnable: { loading: false }, directBypassDisable: { loading: false }, torrserverDirectEnable: { loading: false }, torrserverDirectDisable: { loading: false } }; } function getEmptyDiagnosticsActions() { return { ...store.get().diagnosticsActions, restart: { loading: false }, start: { loading: false }, stop: { loading: false } }; } function normalizeLatencyProgress(progress) { const total = Math.trunc(Number(progress?.total ?? 0)); if (!Number.isFinite(total) || total <= 0) { return void 0; } const completedValue = Number(progress?.completed ?? 0); const failedValue = Number(progress?.failed ?? 0); const completed = Number.isFinite(completedValue) ? Math.trunc(completedValue) : 0; const failed2 = Number.isFinite(failedValue) ? Math.trunc(failedValue) : 0; return { completed: Math.min(Math.max(0, completed), total), total, failed: Math.max(0, failed2) }; } function applyServiceState(uiState) { const currentSystemInfo = store.get().diagnosticsSystemInfo; const nextSystemInfo = { ...currentSystemInfo, providerInfoLoaded: true, zapret_installed: uiState.capabilities.zapret_installed, zapret2_installed: uiState.capabilities.zapret2_installed, byedpi_installed: uiState.capabilities.byedpi_installed, wdtt_installed: uiState.capabilities.wdtt_installed, olcrtc_installed: uiState.capabilities.olcrtc_installed, server_inbounds_enabled_count: uiState.capabilities.server_inbounds_enabled_count }; nextSystemInfo.sing_box_extended = uiState.capabilities.sing_box_extended; nextSystemInfo.sing_box_tiny = uiState.capabilities.sing_box_tiny; nextSystemInfo.sing_box_compressed = uiState.capabilities.sing_box_compressed; nextSystemInfo.sing_box_lx = uiState.capabilities.sing_box_lx; nextSystemInfo.sing_box_tailscale = uiState.capabilities.sing_box_tailscale; store.set({ servicesInfoWidget: { loading: false, failed: false, data: { singbox: uiState.service.sing_box.running, singboxMemoryMb: uiState.service.sing_box.memory_rss_mb, tachyonRunning: uiState.service.tachyon.running, tachyonEnabled: uiState.service.tachyon.enabled, tachyonStatus: uiState.service.tachyon.status, watchdogRunning: store.get().servicesInfoWidget.data.watchdogRunning, zapret2Running: uiState.service.zapret2 ? uiState.service.zapret2.running : void 0, zapret2MemoryMb: uiState.service.zapret2 ? uiState.service.zapret2.memory_rss_mb : void 0 } }, diagnosticsSystemInfo: normalizeSingBoxVariantFields(nextSystemInfo) }); } function applyActionState(actions = {}) { const current = store.get(); const localOverlay = getLocalActionOverlay(); const currentLatencyProgressSections = current.sectionsWidget.latencyProgressSections; const subscriptionUpdatingSections = {}; const latencyFetchingSections = {}; const latencyProgressSections = {}; const updatesActions = getEmptyUpdatesActions(); const diagnosticsActions = getEmptyDiagnosticsActions(); for (const state of actions.subscription || []) { if (isRunningAction(state) && state.section) { subscriptionUpdatingSections[state.section] = true; } } for (const state of actions.latency || []) { if (isRunningAction(state) && state.section) { latencyFetchingSections[state.section] = true; const progress = normalizeLatencyProgress(state.progress); if (progress) { latencyProgressSections[state.section] = progress; } else if (currentLatencyProgressSections[state.section]) { latencyProgressSections[state.section] = currentLatencyProgressSections[state.section]; } } } for (const state of actions.component || []) { if (!isRunningAction(state)) { continue; } const key = getComponentActionKey(state.component, state.action); if (key) { updatesActions[key] = { loading: true }; } } for (const state of actions.service || []) { if (!isRunningAction(state)) { continue; } if (state.action === "start") { diagnosticsActions.start = { loading: true }; } else if (state.action === "stop") { diagnosticsActions.stop = { loading: true }; } else if (state.action === "restart" || state.action === "reload") { diagnosticsActions.restart = { loading: true }; } } for (const section of localOverlay.subscriptionSections) { subscriptionUpdatingSections[section] = true; } for (const section of localOverlay.latencySections) { latencyFetchingSections[section] = true; if (!latencyProgressSections[section] && currentLatencyProgressSections[section]) { latencyProgressSections[section] = currentLatencyProgressSections[section]; } } for (const key of localOverlay.componentActions) { updatesActions[key] = { loading: true }; } for (const action of localOverlay.serviceActions) { diagnosticsActions[action] = { loading: true }; } store.set({ sectionsWidget: { ...current.sectionsWidget, subscriptionUpdatingSections, latencyFetchingSections, latencyProgressSections }, updatesActions, diagnosticsActions }); } function applyUiStateToStore(uiState) { applyServiceState(uiState); applyActionState(uiState.actions); } // src/tachyon/services/runtimeUiState.service.ts var RUNTIME_UI_STATE_REFRESH_MIN_INTERVAL_MS = 500; var RUNTIME_UI_STATE_IDLE_POLL_INTERVAL_MS = 1e4; var RUNTIME_UI_STATE_ACTIVE_INITIAL_POLL_INTERVAL_MS = 1e3; var RUNTIME_UI_STATE_ACTIVE_SLOW_POLL_INTERVAL_MS = 2e3; var RUNTIME_UI_STATE_ACTIVE_SLOW_THRESHOLD_MS = 4e3; var RUNTIME_UI_STATE_HIDDEN_POLL_INTERVAL_MS = 6e4; var runtimeUiStateRefreshPromise = null; var lastRuntimeUiStateRefreshAt = 0; var lastRuntimeUiState; var runtimeStateResumeRefreshRegistered = false; var runtimeStatePollTimer = null; var runtimeStatePollingStarted = false; var runtimeStateHasRunningAction = false; var runningActionStartedAt = 0; var runtimeUiStateListeners = /* @__PURE__ */ new Set(); function isDocumentVisible() { return typeof document === "undefined" || !document.visibilityState || document.visibilityState === "visible"; } function hasRunningAction(uiState) { return Object.values(uiState.actions).some( (actions) => actions.some((action) => action.running) ); } function getNextPollDelay() { if (!isDocumentVisible()) { return RUNTIME_UI_STATE_HIDDEN_POLL_INTERVAL_MS; } if (!runtimeStateHasRunningAction) { runningActionStartedAt = 0; return RUNTIME_UI_STATE_IDLE_POLL_INTERVAL_MS; } if (runningActionStartedAt === 0) { runningActionStartedAt = Date.now(); } const elapsed = Date.now() - runningActionStartedAt; return elapsed < RUNTIME_UI_STATE_ACTIVE_SLOW_THRESHOLD_MS ? RUNTIME_UI_STATE_ACTIVE_INITIAL_POLL_INTERVAL_MS : RUNTIME_UI_STATE_ACTIVE_SLOW_POLL_INTERVAL_MS; } function scheduleRuntimeUiStatePoll(delay = getNextPollDelay()) { if (!runtimeStatePollingStarted || runtimeStatePollTimer || typeof window === "undefined") { return; } runtimeStatePollTimer = window.setTimeout(() => { runtimeStatePollTimer = null; void refreshRuntimeUiState().catch(() => void 0).finally(() => { scheduleRuntimeUiStatePoll(); }); }, delay); } function notifyRuntimeUiStateListeners(uiState) { for (const listener of runtimeUiStateListeners) { try { listener(uiState); } catch (error) { logger.error("[RUNTIME_UI_STATE]", "listener failed", error); } } } async function refreshRuntimeUiState({ force = false } = {}) { if (!isDocumentVisible()) { return void 0; } if (runtimeUiStateRefreshPromise) { return runtimeUiStateRefreshPromise; } const now = Date.now(); if (!force && now - lastRuntimeUiStateRefreshAt < RUNTIME_UI_STATE_REFRESH_MIN_INTERVAL_MS) { return void 0; } lastRuntimeUiStateRefreshAt = now; if (force) { runningActionStartedAt = now; } const promise = TachyonShellMethods.getUiState().then((response) => { if (!response.success) { return void 0; } applyUiStateToStore(response.data); lastRuntimeUiState = response.data; runtimeStateHasRunningAction = hasRunningAction(response.data); if (!runtimeStateHasRunningAction) { runningActionStartedAt = 0; } notifyRuntimeUiStateListeners(response.data); return response.data; }).catch((error) => { logger.error("[RUNTIME_UI_STATE]", "refresh failed", error); return void 0; }).finally(() => { if (runtimeUiStateRefreshPromise === promise) { runtimeUiStateRefreshPromise = null; } }); runtimeUiStateRefreshPromise = promise; return runtimeUiStateRefreshPromise; } function subscribeRuntimeUiState(listener) { runtimeUiStateListeners.add(listener); if (lastRuntimeUiState) { try { listener(lastRuntimeUiState); } catch (error) { logger.error("[RUNTIME_UI_STATE]", "listener failed", error); } } return () => { runtimeUiStateListeners.delete(listener); }; } function getCachedRuntimeUiState() { return lastRuntimeUiState; } function registerRuntimeStateResumeRefresh() { if (runtimeStateResumeRefreshRegistered || typeof window === "undefined") { return; } runtimeStateResumeRefreshRegistered = true; const refreshOnResume = () => { if (!isDocumentVisible()) { return; } void refreshRuntimeUiState({ force: true }); }; document.addEventListener("visibilitychange", refreshOnResume); window.addEventListener("pageshow", refreshOnResume); window.addEventListener("focus", refreshOnResume); } function startRuntimeUiStatePolling() { if (runtimeStatePollingStarted || typeof window === "undefined") { return; } runtimeStatePollingStarted = true; void refreshRuntimeUiState({ force: true }).finally(() => { scheduleRuntimeUiStatePoll(); }); } // src/tachyon/services/core.service.ts var LOG_WATCHER_INTERVAL_MS = 3e4; var LOG_WATCHER_START_DELAY_MS = 5e3; function componentDisplayName(component) { const names = { tachyon: "Tachyon", sing_box: "sing-box", zapret: "Zapret", zapret2: "Zapret2", byedpi: "ByeDPI", wdtt: "WDTT", olcrtc: "OlcRTC" }; return names[component] || component; } function showLogNotification(notification) { if (notification.kind === "component-update") { const message = _("New version %s is available for %s").replace("%s", notification.version).replace("%s", componentDisplayName(notification.component)); ui.addNotification( _("Component update available"), E("div", {}, message), "warning", "tachyon-component-update-notification" ); return; } ui.addNotification( _("Tachyon Error"), E("div", {}, notification.line), "error", "tachyon-log-error-notification" ); } function coreService(options = {}) { TabServiceInstance.onChange((activeId, tabs) => { logger.info("[TAB]", activeId); store.set({ tabService: { current: activeId || "", all: tabs.map((tab) => tab.id) } }); }); const watcher = TachyonLogWatcher.getInstance(); const logNotificationDeduper = new LogNotificationDeduper(); watcher.init( async () => { const logs = await TachyonShellMethods.checkLogs(); if (logs.success) { return logs.data; } return ""; }, { intervalMs: LOG_WATCHER_INTERVAL_MS, onNewLog: (line) => { if (logNotificationDeduper.shouldNotify(line)) { const notification = getTachyonLogNotification(line); if (notification) { showLogNotification(notification); } } } } ); const startWatcher = async () => { if (options.waitForLogWatcherStart) { await Promise.resolve().then(() => options.waitForLogWatcherStart?.()).catch(() => null); } watcher.start(); }; const scheduleStartWatcher = () => window.setTimeout(() => { void startWatcher(); }, options.logWatcherStartDelayMs ?? LOG_WATCHER_START_DELAY_MS); if (typeof window !== "undefined") { scheduleStartWatcher(); } else { void startWatcher(); } registerRuntimeStateResumeRefresh(); startRuntimeUiStatePolling(); } // src/tachyon/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(); this.reconnectTimers = /* @__PURE__ */ new Map(); this.reconnectAttempts = /* @__PURE__ */ new Map(); } static getInstance() { if (!_SocketManager.instance) { _SocketManager.instance = new _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) { logger.error( "[SOCKET]", `resetAll: failed to close socket ${url}`, err ); } } for (const timer of this.reconnectTimers.values()) { clearTimeout(timer); } this.reconnectTimers.clear(); this.reconnectAttempts.clear(); this.sockets.clear(); this.listeners.clear(); this.errorListeners.clear(); this.connected.clear(); logger.info("[SOCKET]", "All connections and state have been reset."); } scheduleReconnect(url) { if (this.reconnectTimers.has(url) || (this.listeners.get(url)?.size || 0) === 0) { return; } const attempts = this.reconnectAttempts.get(url) || 0; if (attempts >= 10) { logger.error( "[SOCKET]", `Max reconnect attempts (10) reached for ${url}` ); return; } const delay = Math.min(1e3 * Math.pow(2, attempts), 3e4); this.reconnectAttempts.set(url, attempts + 1); logger.info( "[SOCKET]", `Scheduling reconnect to ${url} in ${delay}ms (attempt ${attempts + 1})` ); const timer = setTimeout(() => { this.reconnectTimers.delete(url); if ((this.listeners.get(url)?.size || 0) > 0) { logger.info("[SOCKET]", `Attempting reconnect to ${url}`); this.connect(url); } }, delay); this.reconnectTimers.set(url, timer); } connect(url) { if (this.sockets.has(url)) return; let ws; try { ws = new WebSocket(url); } catch (err) { logger.error( "[SOCKET]", `failed to construct WebSocket for ${url}:`, err ); this.triggerError(url, err instanceof Event ? err : String(err)); this.scheduleReconnect(url); return; } this.sockets.set(url, ws); this.connected.set(url, false); if (!this.listeners.has(url)) this.listeners.set(url, /* @__PURE__ */ new Set()); if (!this.errorListeners.has(url)) this.errorListeners.set(url, /* @__PURE__ */ new Set()); ws.addEventListener("open", () => { this.connected.set(url, true); this.reconnectAttempts.set(url, 0); logger.info("[SOCKET]", "Connected to", url); }); ws.addEventListener("message", (event) => { const handlers = this.listeners.get(url); if (handlers) { for (const handler of handlers) { try { handler(event.data); } catch (err) { logger.error("[SOCKET]", `Handler error for ${url}:`, err); } } } }); ws.addEventListener("close", () => { this.connected.set(url, false); logger.warn("[SOCKET]", `Disconnected: ${url}`); this.triggerError(url, "Connection closed"); if (this.sockets.get(url) === ws) { this.sockets.delete(url); this.scheduleReconnect(url); } }); ws.addEventListener("error", (err) => { logger.error("[SOCKET]", `Socket error for ${url}:`, err); this.triggerError(url, err); }); } subscribe(url, listener, onError) { if (!this.errorListeners.has(url)) { this.errorListeners.set(url, /* @__PURE__ */ new Set()); } if (onError) { this.errorListeners.get(url)?.add(onError); } if (!this.listeners.has(url)) { this.listeners.set(url, /* @__PURE__ */ new Set()); } this.listeners.get(url)?.add(listener); if (!this.sockets.has(url)) { this.connect(url); } } unsubscribe(url, listener, onError) { this.listeners.get(url)?.delete(listener); if (onError) { this.errorListeners.get(url)?.delete(onError); } if (this.listeners.get(url)?.size === 0) { this.disconnect(url); } } // 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 { logger.warn("[SOCKET]", `Cannot send: not connected to ${url}`); this.triggerError(url, "Not connected"); } } disconnect(url) { const timer = this.reconnectTimers.get(url); if (timer) { clearTimeout(timer); this.reconnectTimers.delete(url); } this.reconnectAttempts.delete(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) { logger.error("[SOCKET]", `Error handler threw for ${url}:`, e); } } } } }; var socket = SocketManager.getInstance(); // src/tachyon/services/uiActionNotification.service.ts var UI_ACTION_NOTIFICATION_STORAGE_KEY = "tachyon:owned-ui-action-notifications:v1"; var MAX_STORED_UI_ACTION_NOTIFICATIONS = 100; function getSessionStorage2() { if (typeof window === "undefined") { return null; } try { return window.sessionStorage; } catch { return null; } } function getNotificationKey(kind, jobId) { return `${kind}:${jobId}`; } function isStoredNotification(value) { if (!value || typeof value !== "object") { return false; } const candidate = value; return (candidate.kind === "component" || candidate.kind === "subscription") && typeof candidate.jobId === "string" && typeof candidate.notified === "boolean" && typeof candidate.updatedAt === "number"; } function readStoredNotifications(storage) { if (!storage) { return []; } try { const parsed = JSON.parse( storage.getItem(UI_ACTION_NOTIFICATION_STORAGE_KEY) || "[]" ); return Array.isArray(parsed) ? parsed.filter(isStoredNotification) : []; } catch { return []; } } function writeStoredNotifications(storage, notifications) { if (!storage) { return; } try { storage.setItem( UI_ACTION_NOTIFICATION_STORAGE_KEY, JSON.stringify( notifications.sort((a, b) => a.updatedAt - b.updatedAt).slice(-MAX_STORED_UI_ACTION_NOTIFICATIONS) ) ); } catch { } } var UiActionNotificationTracker = class { constructor(storage = getSessionStorage2()) { this.notifications = /* @__PURE__ */ new Map(); this.storage = storage; for (const notification of readStoredNotifications(storage)) { this.notifications.set( getNotificationKey(notification.kind, notification.jobId), notification ); } } markOwned(kind, jobId) { if (!jobId) { return; } const key = getNotificationKey(kind, jobId); const current = this.notifications.get(key); this.notifications.set(key, { kind, jobId, notified: current?.notified ?? false, updatedAt: Date.now() }); this.persist(); } shouldNotify(kind, jobId) { if (!jobId) { return false; } const key = getNotificationKey(kind, jobId); const current = this.notifications.get(key); if (!current || current.notified) { return false; } this.notifications.set(key, { ...current, notified: true, updatedAt: Date.now() }); this.persist(); return true; } persist() { writeStoredNotifications( this.storage, Array.from(this.notifications.values()) ); } }; var uiActionNotifications = new UiActionNotificationTracker(); function markUiActionOwned(kind, jobId) { uiActionNotifications.markOwned(kind, jobId); } function shouldNotifyOwnedUiAction(kind, jobId) { return uiActionNotifications.shouldNotify(kind, jobId); } // src/tachyon/fetchers/fetchServicesInfo.ts var latestServicesInfoRequestId = 0; function getSettledMethodResponse(scope, result) { if (result.status === "fulfilled") { return result.value; } logger.error("[SERVICES_INFO]", `${scope} failed`, result.reason); return { success: false, error: result.reason instanceof Error ? result.reason.message : "" }; } async function fetchServicesInfo() { const requestId = ++latestServicesInfoRequestId; const uiState = await refreshRuntimeUiState({ force: true }); if (requestId !== latestServicesInfoRequestId) { return; } if (uiState) { return uiState; } const [tachyonResult, singboxResult, watchdogResult] = await Promise.allSettled([ TachyonShellMethods.getStatus(), TachyonShellMethods.getSingBoxStatus(), TachyonShellMethods.getWatchdogStatus() ]); if (requestId !== latestServicesInfoRequestId) { return; } const tachyon = getSettledMethodResponse("getStatus", tachyonResult); const singbox = getSettledMethodResponse("getSingBoxStatus", singboxResult); const watchdog = getSettledMethodResponse( "getWatchdogStatus", watchdogResult ); const previousData = store.get().servicesInfoWidget.data; store.set({ servicesInfoWidget: { loading: false, failed: !tachyon.success || !singbox.success, data: { singbox: singbox.success ? singbox.data.running : previousData.singbox, singboxMemoryMb: singbox.success ? singbox.data.memory_rss_mb : previousData.singboxMemoryMb, tachyonRunning: tachyon.success ? tachyon.data.running : previousData.tachyonRunning, tachyonEnabled: tachyon.success ? tachyon.data.enabled : previousData.tachyonEnabled, tachyonStatus: tachyon.success ? tachyon.data.status : previousData.tachyonStatus, watchdogRunning: watchdog.success ? Number(watchdog.data.running) : previousData.watchdogRunning, zapret2Running: previousData.zapret2Running, zapret2MemoryMb: previousData.zapret2MemoryMb } } }); return void 0; } // src/tachyon/fetchers/fetchHostnames.ts var callHostHints = rpc.declare({ object: "luci-rpc", method: "getHostHints", expect: { "": {} } }); async function fetchHostnames() { const hostnames = /* @__PURE__ */ new Map(); try { const hints = await callHostHints(); if (hints && typeof hints === "object") { for (const mac of Object.keys(hints)) { const hint = hints[mac]; if (hint && hint.name && hint.ipaddrs && Array.isArray(hint.ipaddrs)) { for (const ip of hint.ipaddrs) { if (!hostnames.has(ip)) { hostnames.set(ip, hint.name); } } } } } } catch (e) { console.error("fetchHostnames error", e); } return hostnames; } // src/tachyon/helpers/isActiveLuciTab.ts function isActiveLuciTab(tabId) { if (typeof document === "undefined") { return false; } return Boolean( document.querySelector( `.cbi-tab[data-tab="${tabId}"]:not(.cbi-tab-disabled), .cbi-tab[data-tab="cbi-tachyon-${tabId}"]:not(.cbi-tab-disabled)` ) ); } // src/tachyon/helpers/restoredActionLoading.ts function shouldShowLoadingForRestoredAction(state) { return state.running === true; } // src/tachyon/helpers/serviceAvailability.ts function getServiceAvailability({ loading: loading2, failed: failed2, running }) { if (loading2) { return "loading"; } if (failed2) { return "unavailable"; } return running ? "running" : "stopped"; } // src/tachyon/helpers/capCollectionSize.ts var DEFAULT_MAX_ENTRIES = 500; function capSetSize(set, max = DEFAULT_MAX_ENTRIES) { while (set.size > max) { const oldest = set.values().next().value; if (oldest === void 0) { return; } set.delete(oldest); } } function capMapSize(map, max = DEFAULT_MAX_ENTRIES) { while (map.size > max) { const oldest = map.keys().next().value; if (oldest === void 0) { return; } map.delete(oldest); } } // src/tachyon/tabs/dashboard/initController.ts var DASHBOARD_EXPANDED_SECTIONS_KEY = "tachyon_dashboard_expanded_sections"; var expandedSections = new Set( JSON.parse(localStorage.getItem(DASHBOARD_EXPANDED_SECTIONS_KEY) || "[]") ); function toggleSectionExpanded(sectionCode) { if (expandedSections.has(sectionCode)) { expandedSections.delete(sectionCode); } else { expandedSections.add(sectionCode); if (sectionCode === "active_clients") { void fetchConnections(); } } localStorage.setItem( DASHBOARD_EXPANDED_SECTIONS_KEY, JSON.stringify(Array.from(expandedSections)) ); void renderSectionsWidget(); void renderConnectionsWidget(); } var SECTIONS_REFRESH_INTERVAL_MS = 15e3; var LATENCY_TEST_BUTTON_CLASS = "dashboard-sections-grid-item-test-latency"; var LATENCY_TEST_BUTTON_LABEL_CLASS = "dashboard-sections-grid-item-test-latency__label"; var sectionsRefreshTimer = null; var sectionsRefreshPromise = null; var sectionsRefreshQueued = false; var actionStateUnsubscribe = null; var dashboardMounted = false; var dashboardMountId = 0; var dashboardDataUpdatesStarted = false; var dashboardDataUpdatesId = 0; var connectionsRefreshTimer = null; var currentConnections = []; var directSocketsFailed = false; var lastTrafficPollTime = 0; var lastUploadTotal = 0; var lastDownloadTotal = 0; var pageUnloading = false; var followedSubscriptionJobs = /* @__PURE__ */ new Set(); var followedLatencyJobs = /* @__PURE__ */ new Set(); var handledSubscriptionJobs = /* @__PURE__ */ new Set(); var handledLatencyJobs = /* @__PURE__ */ new Set(); var customProxyLatencies = /* @__PURE__ */ new Map(); var singleTestingOutboundCodes = {}; var isTestingAllSections = false; var dashboardVisibilityPaused = false; if (typeof window !== "undefined") { window.addEventListener("pagehide", () => { pageUnloading = true; }); window.addEventListener("pageshow", () => { pageUnloading = false; }); } if (typeof document !== "undefined" && typeof document.addEventListener === "function") { document.addEventListener("visibilitychange", () => { if (document.hidden) { if (dashboardDataUpdatesStarted) { stopDashboardDataUpdates(); dashboardVisibilityPaused = true; } } else if (dashboardVisibilityPaused) { dashboardVisibilityPaused = false; if (dashboardMounted && getDashboardServiceAvailability() !== "stopped") { startDashboardDataUpdates(); } } }); } async function fetchDashboardSectionsOnce(mountId) { if (getDashboardServiceAvailability() === "stopped") { return false; } const prev = store.get().sectionsWidget; const hasRenderedData = prev.data.length > 0; store.set({ sectionsWidget: { ...prev, failed: false, loading: prev.loading && !hasRenderedData } }); try { const { data, success } = await CustomTachyonMethods.getDashboardSections(); if (!dashboardMounted || mountId !== dashboardMountId || getDashboardServiceAvailability() === "stopped") { return false; } if (!success) { throw new Error("failed to fetch dashboard sections"); } const current = store.get().sectionsWidget; store.set({ sectionsWidget: { ...current, loading: false, failed: false, data } }); return true; } catch (error) { logger.error("[DASHBOARD]", "fetchDashboardSections: failed", error); if (!dashboardMounted || mountId !== dashboardMountId || getDashboardServiceAvailability() === "stopped") { return false; } const current = store.get().sectionsWidget; store.set({ sectionsWidget: { ...current, loading: false, failed: current.data.length === 0, data: current.data } }); return false; } } async function fetchDashboardSections(options = {}) { if (sectionsRefreshPromise) { if (options.force) { sectionsRefreshQueued = true; } return sectionsRefreshPromise; } const mountId = dashboardMountId; const promise = (async () => { let success = false; do { sectionsRefreshQueued = false; success = await fetchDashboardSectionsOnce(mountId); } while (sectionsRefreshQueued && dashboardMounted && mountId === dashboardMountId); return success; })(); sectionsRefreshPromise = promise; try { return await promise; } finally { if (sectionsRefreshPromise === promise) { sectionsRefreshPromise = null; } } } function setSubscriptionUpdating(sectionName, updating, local = false) { if (local || !updating) { setLocalSubscriptionAction(sectionName, updating && local); } const sectionsWidget = store.get().sectionsWidget; const subscriptionUpdatingSections = { ...sectionsWidget.subscriptionUpdatingSections }; if (updating) { subscriptionUpdatingSections[sectionName] = true; } else { delete subscriptionUpdatingSections[sectionName]; } store.set({ sectionsWidget: { ...sectionsWidget, subscriptionUpdatingSections } }); } function setSelectorSwitching(sectionName, tag) { const sectionsWidget = store.get().sectionsWidget; const selectorSwitchingSections = { ...sectionsWidget.selectorSwitchingSections }; if (tag) { selectorSwitchingSections[sectionName] = tag; } else { delete selectorSwitchingSections[sectionName]; } store.set({ sectionsWidget: { ...sectionsWidget, selectorSwitchingSections } }); } function setLatencyFetching(sectionName, fetching, local = false, progress) { if (local || !fetching) { setLocalLatencyAction(sectionName, fetching && local); } const sectionsWidget = store.get().sectionsWidget; const latencyFetchingSections = { ...sectionsWidget.latencyFetchingSections }; const latencyProgressSections = { ...sectionsWidget.latencyProgressSections }; if (fetching) { latencyFetchingSections[sectionName] = true; if (progress) { latencyProgressSections[sectionName] = progress; } } else { delete latencyFetchingSections[sectionName]; delete latencyProgressSections[sectionName]; } store.set({ sectionsWidget: { ...sectionsWidget, latencyFetchingSections, latencyProgressSections } }); } async function completeSubscriptionUpdateJob(jobId, sectionName, response) { if (pageUnloading) { setSubscriptionUpdating(sectionName, false); return; } if (jobId && handledSubscriptionJobs.has(jobId)) { setSubscriptionUpdating(sectionName, false); return; } const shouldNotify = jobId ? shouldNotifyOwnedUiAction("subscription", jobId) : false; const failed2 = !response.success || !response.data.success; const message = response.success ? response.data.message || _("Failed to update subscriptions") : response.error || _("Failed to update subscriptions"); if (failed2 && isTransientRpcError(message)) { void refreshRuntimeUiState({ force: true }); return; } if (jobId) { handledSubscriptionJobs.add(jobId); capSetSize(handledSubscriptionJobs); } setSubscriptionUpdating(sectionName, false); if (jobId && response.success) { void TachyonShellMethods.uiActionAck("subscription", jobId); } if (failed2) { if (shouldNotify) { showToast(_("Failed to update subscriptions"), "error"); } return; } if (shouldNotify) { showToast(_("Subscriptions updated"), "success"); } void fetchDashboardSections({ force: true }); void fetchServicesInfo(); void fetchTailscalePeers(); } async function followSubscriptionUpdateState(state) { const jobId = state.job_id; const sectionName = state.section || ""; if (!jobId || !sectionName || followedSubscriptionJobs.has(jobId)) { return; } if (!state.running && handledSubscriptionJobs.has(jobId)) { return; } followedSubscriptionJobs.add(jobId); if (shouldShowLoadingForRestoredAction(state)) { setSubscriptionUpdating(sectionName, true); } try { const response = state.running ? await TachyonShellMethods.waitSubscriptionUpdateJob(jobId) : { success: true, data: state }; await completeSubscriptionUpdateJob(jobId, sectionName, response); } catch (error) { logger.error("[DASHBOARD]", "followSubscriptionUpdateState failed", error); if (!pageUnloading) { const message = error instanceof Error ? error.message : _("Failed to update subscriptions"); setSubscriptionUpdating(sectionName, false); if (!isTransientRpcError(message)) { showToast(_("Failed to update subscriptions"), "error"); } } } finally { followedSubscriptionJobs.delete(jobId); } } async function completeLatencyTestJob(jobId, sectionName) { setLatencyFetching(sectionName, false); if (pageUnloading) { return; } if (jobId && handledLatencyJobs.has(jobId)) { return; } if (jobId) { handledLatencyJobs.add(jobId); capSetSize(handledLatencyJobs); } if (jobId) { void TachyonShellMethods.uiActionAck("latency", jobId); } void fetchDashboardSections({ force: true }); } async function followLatencyTestState(state) { const jobId = state.job_id; const sectionName = state.section || ""; if (!jobId || !sectionName || followedLatencyJobs.has(jobId)) { return; } if (!state.running && handledLatencyJobs.has(jobId)) { return; } followedLatencyJobs.add(jobId); if (shouldShowLoadingForRestoredAction(state)) { setLatencyFetching(sectionName, true); } try { if (state.running) { await TachyonShellMethods.waitLatencyTestJob(jobId); } await completeLatencyTestJob(jobId, sectionName); } catch (error) { logger.error("[DASHBOARD]", "followLatencyTestState failed", error); if (!pageUnloading) { setLatencyFetching(sectionName, false); } } finally { followedLatencyJobs.delete(jobId); } } function followDashboardActionsFromUiState(uiState) { for (const state of uiState.actions.subscription || []) { if (state.running || state.job_id && state.section) { void followSubscriptionUpdateState(state); } else if (state.job_id && !handledSubscriptionJobs.has(state.job_id)) { handledSubscriptionJobs.add(state.job_id); void TachyonShellMethods.uiActionAck("subscription", state.job_id); } } for (const state of uiState.actions.latency || []) { if (state.running || state.job_id && state.section) { void followLatencyTestState(state); } else if (state.job_id && !handledLatencyJobs.has(state.job_id)) { handledLatencyJobs.add(state.job_id); void TachyonShellMethods.uiActionAck("latency", state.job_id); } } } function startActionStateWatcher() { if (actionStateUnsubscribe) { return; } actionStateUnsubscribe = subscribeRuntimeUiState((uiState) => { if (dashboardMounted) { followDashboardActionsFromUiState(uiState); } }); } function stopActionStateWatcher() { if (!actionStateUnsubscribe) { return; } actionStateUnsubscribe(); actionStateUnsubscribe = null; } async function connectToClashSockets(dataUpdatesId) { const mountId = dashboardMountId; const clashApiSecret = await getClashApiSecret2(); if (!dashboardMounted || mountId !== dashboardMountId || dataUpdatesId !== dashboardDataUpdatesId || getDashboardServiceAvailability() === "stopped") { return; } if (!canUseDirectClashApi()) { directSocketsFailed = true; logger.info( "[DASHBOARD]", "direct Clash API websocket unavailable, relying on polling fallback" ); return; } socket.subscribe( `${getClashWsUrl()}/traffic?token=${clashApiSecret}`, (msg) => { if (dataUpdatesId !== dashboardDataUpdatesId || getDashboardServiceAvailability() === "stopped") { return; } directSocketsFailed = false; const parsedMsg = JSON.parse(msg); store.set({ bandwidthWidget: { loading: false, failed: false, data: { up: parsedMsg.up, down: parsedMsg.down } } }); }, (_err) => { if (dataUpdatesId !== dashboardDataUpdatesId || getDashboardServiceAvailability() === "stopped") { return; } directSocketsFailed = true; logger.warn( "[DASHBOARD]", "connectToClashSockets - traffic: socket failed, using polling fallback", getClashWsUrl() ); } ); socket.subscribe( `${getClashWsUrl()}/connections?token=${clashApiSecret}`, (msg) => { if (dataUpdatesId !== dashboardDataUpdatesId || getDashboardServiceAvailability() === "stopped") { return; } directSocketsFailed = false; 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) => { if (dataUpdatesId !== dashboardDataUpdatesId || getDashboardServiceAvailability() === "stopped") { return; } directSocketsFailed = true; logger.warn( "[DASHBOARD]", "connectToClashSockets - connections: socket failed, using polling fallback", getClashWsUrl() ); } ); } function getDashboardServiceAvailability() { const service = store.get().servicesInfoWidget; return getServiceAvailability({ loading: service.loading, failed: service.failed, running: service.data.tachyonRunning }); } function stopDashboardDataUpdates() { dashboardDataUpdatesStarted = false; dashboardDataUpdatesId += 1; if (sectionsRefreshTimer) { clearInterval(sectionsRefreshTimer); sectionsRefreshTimer = null; } if (connectionsRefreshTimer) { clearInterval(connectionsRefreshTimer); connectionsRefreshTimer = null; } sectionsRefreshQueued = false; socket.resetAll(); } function startDashboardDataUpdates() { if (dashboardDataUpdatesStarted || !dashboardMounted || getDashboardServiceAvailability() === "stopped") { return; } if (typeof document !== "undefined" && document.hidden) { dashboardVisibilityPaused = true; return; } dashboardDataUpdatesStarted = true; const dataUpdatesId = ++dashboardDataUpdatesId; void fetchDashboardSections({ force: true }); void connectToClashSockets(dataUpdatesId); sectionsRefreshTimer = setInterval(() => { void fetchDashboardSections(); }, SECTIONS_REFRESH_INTERVAL_MS); void fetchConnections(); connectionsRefreshTimer = setInterval(() => { void fetchConnections(); }, SECTIONS_REFRESH_INTERVAL_MS); } function syncDashboardServiceAvailability() { const availability = getDashboardServiceAvailability(); const stopped = availability === "stopped"; const container = document.getElementById("dashboard-status"); container?.classList.toggle( "tachyon_dashboard-page--service-stopped", stopped ); if (stopped || availability === "loading") { stopDashboardDataUpdates(); return; } startDashboardDataUpdates(); } async function handleChooseOutbound(sectionName, selector, tag) { const sectionsWidget = store.get().sectionsWidget; const section = sectionsWidget.data.find( (item) => item.sectionName === sectionName ); if (!section?.withTagSelect || sectionsWidget.selectorSwitchingSections[sectionName]) { return; } setSelectorSwitching(sectionName, tag); try { const res = await TachyonShellMethods.setClashApiGroupProxy(selector, tag); if (res && !res.success && res.error) { showToast( res.message || res.error || _("Failed to switch proxy"), "error" ); } await fetchDashboardSections({ force: true }); } catch (error) { showToast(error?.message || _("Failed to switch proxy"), "error"); } finally { setSelectorSwitching(sectionName); } } function getInitialLatencyProgress(latencyType, tag) { if (latencyType !== "proxy_list") { return void 0; } try { const tags = JSON.parse(tag); if (!Array.isArray(tags)) { return void 0; } const total = tags.filter( (item) => typeof item === "string" && item.length > 0 ).length; return total > 0 ? { completed: 0, total, failed: 0 } : void 0; } catch { return void 0; } } async function handleTestLatency(latencyType, sectionName, tag, timeout) { if (store.get().sectionsWidget.latencyFetchingSections[sectionName]) { return; } setLatencyFetching( sectionName, true, true, getInitialLatencyProgress(latencyType, tag) ); let jobId = ""; let ownsJobFollow = false; let completed = false; try { if (latencyType === "proxy") { const parsedTag = tag.startsWith("[") ? JSON.parse(tag)[0] : tag; const response = await TachyonShellMethods.getClashApiProxyLatency( parsedTag, timeout ); if (response.success && response.data) { const delay = response.data?.delay; if (typeof delay === "number" && delay > 0) { customProxyLatencies.set(tag, delay); } else { const groupDelays = Object.values(response.data || {}).filter( (v) => typeof v === "number" && v > 0 ); customProxyLatencies.set( tag, groupDelays.length > 0 ? Math.min(...groupDelays) : -1 ); } } else { customProxyLatencies.set(tag, -1); } capMapSize(customProxyLatencies); setLatencyFetching(sectionName, false); completed = true; void fetchDashboardSections({ force: true }); } else { const startResponse = await TachyonShellMethods.latencyTestStart( latencyType, sectionName, tag, timeout ); if (!startResponse.success) { setLatencyFetching(sectionName, false); return; } jobId = startResponse.data.job_id; if (!jobId) { setLatencyFetching(sectionName, false); return; } followedLatencyJobs.add(jobId); ownsJobFollow = true; await TachyonShellMethods.waitLatencyTestJob(jobId); await completeLatencyTestJob(jobId, sectionName); completed = true; } } catch (error) { logger.error("[DASHBOARD]", "handleTestLatency: failed", error); } finally { if (ownsJobFollow) { followedLatencyJobs.delete(jobId); } if (!completed) { setLatencyFetching(sectionName, false); } } } async function handleTestSingleOutbound(_sectionName, outboundCode) { if (singleTestingOutboundCodes[outboundCode]) { return; } singleTestingOutboundCodes[outboundCode] = true; void renderSectionsWidget(); try { const response = await TachyonShellMethods.getClashApiProxyLatency( outboundCode, "2000" ); if (response.success && response.data) { const delay = response.data?.delay; if (typeof delay === "number" && delay > 0) { customProxyLatencies.set(outboundCode, delay); } else { const groupDelays = Object.values(response.data || {}).filter( (v) => typeof v === "number" && v > 0 ); customProxyLatencies.set( outboundCode, groupDelays.length > 0 ? Math.min(...groupDelays) : -1 ); } } else { customProxyLatencies.set(outboundCode, -1); } capMapSize(customProxyLatencies); } catch (error) { logger.warn("[DASHBOARD]", "Failed single proxy latency test", error); customProxyLatencies.set(outboundCode, -1); } finally { delete singleTestingOutboundCodes[outboundCode]; void renderSectionsWidget(); } } async function handleTestAllSections() { if (isTestingAllSections) { return; } isTestingAllSections = true; void renderSectionsWidget(); try { const sectionsWidget = store.get().sectionsWidget; const SERVICE_TYPES = /* @__PURE__ */ new Set(["SING_BOX", "ZAPRET", "ZAPRET2", "BYEDPI"]); for (const section of sectionsWidget.data) { if (!section.outbounds || section.outbounds.length === 0) { continue; } if (sectionsWidget.latencyFetchingSections[section.sectionName]) { continue; } const testable = section.outbounds.filter( (o) => !SERVICE_TYPES.has(o.type) ); if (testable.length === 0) { continue; } if (section.withTagSelect) { const tag = section.latencyTestCodes?.length ? section.latencyTestCodes : section.latencyTestCode || section.code; if (Array.isArray(tag)) { await handleTestLatency( "proxy_list", section.sectionName, JSON.stringify(tag), section.latencyTestTimeout ); } else { await handleTestLatency( "group", section.sectionName, tag, section.latencyTestTimeout ); } } else { await handleTestLatency( "proxy", section.sectionName, testable[0].code, section.latencyTestTimeout ); } } } finally { isTestingAllSections = false; void renderSectionsWidget(); } } function handleCopyOutbound(outbound) { const link = outbound.link; if (link && isCopyableProxyLink(link)) { copyToClipboard(link); return; } showToast(_("Proxy link is unavailable"), "error"); } function formatUrlTestModalValue(value) { if (typeof value === "boolean") { return value ? _("Yes") : _("No"); } const text = `${value ?? ""}`.trim(); return text || _("No"); } function getUrlTestLatencyClass(latency) { if (!latency) { return "tachyon_dashboard-page__outbound-grid__item__latency--empty"; } if (latency < 800) { return "tachyon_dashboard-page__outbound-grid__item__latency--green"; } if (latency < 1500) { return "tachyon_dashboard-page__outbound-grid__item__latency--yellow"; } return "tachyon_dashboard-page__outbound-grid__item__latency--red"; } function formatUrlTestLatency(latency) { return latency ? `${latency}ms` : "N/A"; } function renderDetailsUrl(value) { const url = `${value ?? ""}`.trim(); if (!/^https?:\/\//i.test(url)) { return E("span", {}, formatUrlTestModalValue(value)); } return E( "a", { class: "tachyon_dashboard-page__urltest-details__url", href: url, target: "_blank", rel: "noopener noreferrer" }, url ); } function getDetectedCountryFlag(country) { const code = `${country || ""}`.trim().toUpperCase(); if (!/^[A-Z]{2}$/.test(code)) { return ""; } return String.fromCodePoint( ...code.split("").map((char) => 127462 + char.charCodeAt(0) - 65) ); } function renderDetailsMemberName(member) { const countryFlag = getDetectedCountryFlag(member.country); const elements = countryFlag ? [ E( "span", { class: "tachyon_dashboard-page__urltest-details__country-badge" }, countryFlag ), ...renderFlagEmojis(member.displayName) ] : renderFlagEmojis(member.displayName); if (member.prefix) { return [ ...elements, E( "span", { class: "tachyon_dashboard-page__outbound-grid__item__prefix-badge", style: "margin-left: 6px;" }, renderFlagEmojis(member.prefix) ) ]; } return elements; } function renderUrlTestSelectedValue(info) { const selectedMember = info.outbounds.find((member) => member.selected); const selectedName = selectedMember?.displayName || info.selectedName || info.selectedCode || ""; const name = formatUrlTestModalValue(selectedName); if (name === _("No")) { return E("span", {}, name); } const modeBadge = info.isManualSelection ? E( "span", { class: "badge badge-warning", style: "margin-left: 8px; padding: 2px 6px; font-size: 11px; background: rgba(255, 152, 0, 0.2); color: #ff9800; border-radius: 3px;" }, _("Manual") ) : E( "span", { class: "badge badge-info", style: "margin-left: 8px; padding: 2px 6px; font-size: 11px; background: rgba(33, 150, 243, 0.2); color: #2196f3; border-radius: 3px;" }, _("Auto") ); return E( "span", { class: "tachyon_dashboard-page__urltest-details__selected-value" }, [ E( "span", { class: "tachyon_dashboard-page__urltest-details__selected-name" }, selectedMember ? renderDetailsMemberName(selectedMember) : name ), ...selectedMember?.type ? [ E( "span", { class: "tachyon_dashboard-page__urltest-details__selected-type" }, selectedMember.type ) ] : [], ...selectedMember ? [ E( "span", { class: getUrlTestLatencyClass(selectedMember.latency) }, formatUrlTestLatency(selectedMember.latency) ) ] : [], modeBadge ] ); } function renderUrlTestCopyButton(title, onClick) { return E( "button", { type: "button", class: "btn tachyon_dashboard-page__urltest-details__copy-button", title, "aria-label": title, click: onClick }, renderCopyIcon24() ); } function renderCommonDetailsModal(info, fields, renderMemberName, isPriority, section, outbound) { const isManual = Boolean(info.isManualSelection); return E("div", { class: "tachyon_dashboard-page__urltest-details" }, [ E( "dl", { class: "tachyon_dashboard-page__urltest-details__params" }, fields.map( ({ label, value, children }) => E("div", { class: "tachyon_dashboard-page__urltest-details__param" }, [ E("dt", {}, label), E( "dd", {}, children || [E("span", {}, formatUrlTestModalValue(value))] ) ]) ) ), E("div", { class: "tachyon_dashboard-page__urltest-details__outbounds" }, [ E( "div", { class: "tachyon_dashboard-page__urltest-details__outbounds-title" }, _("Nodes") ), E( "div", { class: "tachyon_dashboard-page__urltest-details__table" }, info.outbounds.length ? info.outbounds.map((member) => { const isMemberActive = member.selected || member.code === info.selectedCode; return E( "div", { class: [ "tachyon_dashboard-page__urltest-details__row", isMemberActive ? "tachyon_dashboard-page__urltest-details__row--active" : "" ].filter(Boolean).join(" ") }, [ E( "div", { class: "tachyon_dashboard-page__urltest-details__row-name" }, isPriority ? [ E( "b", { class: "tachyon_dashboard-page__urltest-details__priority-name" }, renderMemberName(member) ), ...member.type ? [ E( "span", { class: "tachyon_dashboard-page__urltest-details__row-type" }, formatOutboundType( member.type, member.transport ) ) ] : [] ] : [ E("b", {}, renderMemberName(member)), ...member.type ? [ E( "span", { class: "tachyon_dashboard-page__urltest-details__row-type" }, formatOutboundType( member.type, member.transport ) ) ] : [] ] ), E( "div", { class: "tachyon_dashboard-page__urltest-details__row-actions" }, [ E( "div", { class: "tachyon_dashboard-page__urltest-details__row-meta" }, [ E( "span", { class: getUrlTestLatencyClass(member.latency) }, formatUrlTestLatency(member.latency) ) ] ), ...section && section.withTagSelect ? isMemberActive ? [ E( "span", { class: "badge badge-success tachyon_dashboard-page__urltest-details__active-badge" }, [ svgEl( "svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, [ svgEl("polyline", { points: "20 6 9 17 4 12" }) ] ), _("Active") ] ) ] : isPriority || section.outbounds.some( (o) => o.code === member.code ) ? [ E( "button", { type: "button", class: "btn cbi-button cbi-button-action tachyon_dashboard-page__urltest-details__select-btn", click: async (event) => { event.preventDefault(); const btn = event.currentTarget; if (btn) { btn.disabled = true; } const targetSelector = isPriority ? outbound?.code || section.code : section.code; await handleChooseOutbound( section.sectionName, targetSelector, member.code ); const updatedSection = store.get().sectionsWidget.data.find( (s) => s.sectionName === section.sectionName ); const updatedOutbound = updatedSection?.outbounds.find( (o) => o.code === outbound?.code ); if (updatedSection && updatedOutbound) { if (isPriority) { handleShowPriorityInfo( updatedSection, updatedOutbound ); } else { handleShowUrlTestInfo( updatedSection, updatedOutbound ); } } else { ui.hideModal(); } } }, _("Select") ) ] : [] : [], member.canCopyLink ? renderUrlTestCopyButton( _("Copy proxy link"), (event) => { event.preventDefault(); void handleCopyOutbound(member); } ) : E("span", { class: "tachyon_dashboard-page__urltest-details__copy-placeholder" }) ] ) ] ); }) : [ E( "div", { class: "tachyon_dashboard-page__urltest-details__empty" }, _("Node list is empty") ) ] ) ]), E("div", { class: "tachyon_dashboard-page__urltest-details__footer" }, [ ...section && outbound && isManual ? [ E( "button", { type: "button", class: "btn cbi-button cbi-button-apply", click: async (event) => { event.preventDefault(); const btn = event.currentTarget; if (btn) { btn.disabled = true; } await handleChooseOutbound( section.sectionName, section.code, outbound.code ); const updatedSection = store.get().sectionsWidget.data.find( (s) => s.sectionName === section.sectionName ); const updatedOutbound = updatedSection?.outbounds.find( (o) => o.code === outbound.code ); if (updatedSection && updatedOutbound) { if (isPriority) { handleShowPriorityInfo(updatedSection, updatedOutbound); } else { handleShowUrlTestInfo(updatedSection, updatedOutbound); } } else { ui.hideModal(); } } }, _("Switch to auto-selection") ) ] : [], E( "button", { type: "button", class: "btn cbi-button cbi-button-neutral", click: () => { ui.hideModal(); } }, _("Close") ) ]) ]); } function renderUrlTestInfoModal(outbound, section) { const info = outbound.urlTestInfo; if (!info) { return E("div", {}, _("URLTest details are unavailable")); } const fields = [ { label: _("Selected"), children: [renderUrlTestSelectedValue(info)] }, { label: _("Testing URL"), children: [renderDetailsUrl(info.url)] }, { label: _("Interval"), value: info.interval }, { label: _("Tolerance"), value: info.tolerance }, { label: _("Idle timeout"), value: info.idleTimeout }, { label: _("Interrupt connections"), value: info.interruptExistConnections } ]; return renderCommonDetailsModal( info, fields, (member) => renderDetailsMemberName(member), false, section, outbound ); } function handleShowUrlTestInfo(section, outbound) { if (!outbound.urlTestInfo) { return; } ui.showModal( `${_("URLTest details")}: ${outbound.urlTestInfo.displayName || outbound.displayName}`, renderUrlTestInfoModal(outbound, section) ); } function renderPrioritySelectedValue(info) { const selectedMember = info.outbounds.find((member) => member.selected); const selectedName = selectedMember?.displayName || info.selectedName || info.selectedCode || ""; const name = formatUrlTestModalValue(selectedName); if (name === _("No")) { return E("span", {}, name); } const modeBadge = info.isManualSelection ? E( "span", { class: "badge badge-warning", style: "margin-left: 8px; padding: 2px 6px; font-size: 11px; background: rgba(255, 152, 0, 0.2); color: #ff9800; border-radius: 3px;" }, _("Manual") ) : E( "span", { class: "badge badge-info", style: "margin-left: 8px; padding: 2px 6px; font-size: 11px; background: rgba(33, 150, 243, 0.2); color: #2196f3; border-radius: 3px;" }, _("Auto") ); return E( "span", { class: "tachyon_dashboard-page__urltest-details__selected-value" }, [ E( "span", { class: [ "tachyon_dashboard-page__urltest-details__selected-name", selectedMember ? "tachyon_dashboard-page__urltest-details__priority-name" : "" ].filter(Boolean).join(" ") }, selectedMember ? renderPriorityMemberName(selectedMember) : name ), ...selectedMember?.type ? [ E( "span", { class: "tachyon_dashboard-page__urltest-details__selected-type" }, selectedMember.type ) ] : [], ...selectedMember ? [ E( "span", { class: getUrlTestLatencyClass(selectedMember.latency) }, formatUrlTestLatency(selectedMember.latency) ) ] : [], modeBadge ] ); } function renderPriorityMemberName(member) { const countryFlag = getDetectedCountryFlag(member.country); const flagElements = countryFlag ? [ E( "span", { class: "tachyon_dashboard-page__urltest-details__country-badge" }, countryFlag ) ] : []; return [ ...flagElements, E( "span", { class: "tachyon_dashboard-page__urltest-details__priority-level" }, `[${member.levelName}]` ), ...renderFlagEmojis(member.displayName) ]; } function renderPriorityInfoModal(outbound, section) { const info = outbound.priorityInfo; if (!info) { return E("div", {}, _("Priority details are unavailable")); } const fields = [ { label: _("Selected"), children: [renderPrioritySelectedValue(info)] }, { label: _("Check URL"), children: [renderDetailsUrl(info.healthUrl)] }, { label: _("Check interval"), value: info.activeCheckInterval }, { label: _("Unavailability timeout"), value: info.checkTimeout }, { label: _("Higher-level check interval"), value: info.recoveryCheckInterval }, { label: _("Select the fastest node"), value: info.pickFastest }, { label: _("Automatically select the fastest node in the current level"), value: info.switchToFasterSamePriority }, ...info.switchToFasterSamePriority ? [ { label: _("Faster server search interval"), value: info.fastestCheckInterval } ] : [], { label: _("Interrupt connections"), value: info.interruptExistConnections } ]; return renderCommonDetailsModal( info, fields, (member) => renderPriorityMemberName(member), true, section, outbound ); } function handleShowPriorityInfo(section, outbound) { if (!outbound.priorityInfo) { return; } ui.showModal( `${_("Priority details")}: ${outbound.priorityInfo.displayName || outbound.displayName}`, renderPriorityInfoModal(outbound, section) ); } async function handleUpdateSubscription(section) { if (store.get().sectionsWidget.subscriptionUpdatingSections[section.sectionName]) { return; } setSubscriptionUpdating(section.sectionName, true, true); let jobId = ""; let ownsJobFollow = false; try { const startResponse = await TachyonShellMethods.subscriptionUpdateStart( section.sectionName ); if (!startResponse.success) { throw new Error(startResponse.error); } jobId = startResponse.data.job_id; markUiActionOwned("subscription", jobId); if (followedSubscriptionJobs.has(jobId)) { return; } followedSubscriptionJobs.add(jobId); ownsJobFollow = true; const response = await TachyonShellMethods.waitSubscriptionUpdateJob(jobId); await completeSubscriptionUpdateJob(jobId, section.sectionName, response); } catch (error) { logger.error("[DASHBOARD]", "handleUpdateSubscription: failed", error); if (!pageUnloading) { const message = error instanceof Error ? error.message : _("Failed to update subscriptions"); setSubscriptionUpdating(section.sectionName, false); if (!isTransientRpcError(message)) { showToast(_("Failed to update subscriptions"), "error"); } } } finally { if (ownsJobFollow) { followedSubscriptionJobs.delete(jobId); } } } function shallowRecordEqual(left, right) { const leftKeys = Object.keys(left); const rightKeys = Object.keys(right); if (leftKeys.length !== rightKeys.length) { return false; } return leftKeys.every((key) => left[key] === right[key]); } function canUpdateLatencyProgressInline(prev, next) { return prev.loading === next.loading && prev.failed === next.failed && prev.data === next.data && shallowRecordEqual( prev.latencyFetchingSections, next.latencyFetchingSections ) && shallowRecordEqual( prev.subscriptionUpdatingSections, next.subscriptionUpdatingSections ) && shallowRecordEqual( prev.selectorSwitchingSections, next.selectorSwitchingSections ); } function findLatencyTestButton(container, sectionName) { return Array.from( container.querySelectorAll( `.${LATENCY_TEST_BUTTON_CLASS}` ) ).find((button) => button.dataset.latencySection === sectionName); } function updateLatencyProgressInline(sectionsWidget) { const container = document.getElementById("dashboard-sections-grid"); if (!container) { return false; } for (const section of sectionsWidget.data) { if (!sectionsWidget.latencyFetchingSections[section.sectionName]) { continue; } const button = findLatencyTestButton(container, section.sectionName); const label = button?.querySelector( `.${LATENCY_TEST_BUTTON_LABEL_CLASS}` ); if (!label) { return false; } const isConnectionNode = [ "vpn", "awg", "warp", "anytls", "snell", "mieru", "sudoku", "masque", "openvpn" ].includes(section.action || ""); const text = isConnectionNode ? _("Checking Connection...") : getLatencyTestLabel( sectionsWidget.latencyProgressSections[section.sectionName] ); if (label.textContent !== text) { label.textContent = text; } } return true; } async function renderSectionsWidget() { logger.debug("[DASHBOARD]", "renderSectionsWidget"); const sectionsWidget = store.get().sectionsWidget; const container = document.getElementById("dashboard-sections-grid"); if (!container) { return; } const SERVICE_TYPES = /* @__PURE__ */ new Set(["SING_BOX", "ZAPRET", "ZAPRET2", "BYEDPI"]); const sectionsWithCustomLatencies = sectionsWidget.data.map((section) => ({ ...section, outbounds: section.outbounds.map((outbound) => { const customLatency = customProxyLatencies.get(outbound.code); const latency = SERVICE_TYPES.has(outbound.type) || customLatency == null ? outbound.latency : customLatency; const urlTestInfo = outbound.urlTestInfo ? { ...outbound.urlTestInfo, outbounds: outbound.urlTestInfo.outbounds.map((member) => ({ ...member, latency: customProxyLatencies.has(member.code) ? customProxyLatencies.get(member.code) : member.latency })) } : void 0; const priorityInfo = outbound.priorityInfo ? { ...outbound.priorityInfo, outbounds: outbound.priorityInfo.outbounds.map((member) => ({ ...member, latency: customProxyLatencies.has(member.code) ? customProxyLatencies.get(member.code) : member.latency })) } : void 0; return { ...outbound, latency, urlTestInfo, priorityInfo }; }) })); if (sectionsWidget.loading || sectionsWidget.failed) { const renderedWidget = renderSections({ loading: sectionsWidget.loading, failed: sectionsWidget.failed, section: { code: "", sectionName: "", displayName: "", outbounds: [], withTagSelect: false }, isCollapsed: false, onToggleCollapse: () => { }, onTestLatency: () => { }, onChooseOutbound: () => { }, onCopyOutbound: () => { }, onShowUrlTestInfo: () => { }, onShowPriorityInfo: () => { }, onUpdateSubscription: () => { }, latencyFetching: false, latencyProgress: void 0, subscriptionUpdating: false, selectorSwitchingTag: void 0 }); return preserveScrollForPage(() => { container.replaceChildren(renderedWidget); }); } const hasTestableSections = sectionsWithCustomLatencies.some( (s) => s.outbounds && s.outbounds.length > 0 && !SERVICE_TYPES.has(s.outbounds[0]?.type) ); const testAllHeader = hasTestableSections ? E( "div", { class: "tachyon_dashboard-page__sections-header" }, [ E( "div", { class: "tachyon_dashboard-page__sections-header__title" }, [ svgEl( "svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, [ svgEl("circle", { cx: "6", cy: "19", r: "3" }), svgEl("path", { d: "M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15" }), svgEl("circle", { cx: "18", cy: "5", r: "3" }) ] ), E("span", {}, _("Routing sections")) ] ), E( "div", { class: "tachyon_dashboard-page__sections-header__actions" }, [ E( "button", { type: "button", id: "dashboard-test-all-sections-button", class: "btn", disabled: isTestingAllSections ? true : void 0, click: () => { void handleTestAllSections(); } }, isTestingAllSections ? [ renderLoaderCircleIcon24(), E("span", {}, _("Testing all sections...")) ] : E("span", {}, _("Test all sections")) ) ] ) ] ) : null; const renderedWidgets = sectionsWithCustomLatencies.map( (section) => renderSections({ loading: sectionsWidget.loading, failed: sectionsWidget.failed, section, isCollapsed: !expandedSections.has(section.code), onToggleCollapse: () => toggleSectionExpanded(section.code), latencyFetching: Boolean( sectionsWidget.latencyFetchingSections[section.sectionName] ), latencyProgress: sectionsWidget.latencyProgressSections[section.sectionName], subscriptionUpdating: Boolean( sectionsWidget.subscriptionUpdatingSections[section.sectionName] ), selectorSwitchingTag: sectionsWidget.selectorSwitchingSections[section.sectionName], onTestSingleOutbound: (sectionName, outboundCode) => { void handleTestSingleOutbound(sectionName, outboundCode); }, testingOutboundCodes: singleTestingOutboundCodes, onTestLatency: (tag) => { if (section.withTagSelect) { if (Array.isArray(tag)) { return handleTestLatency( "proxy_list", section.sectionName, JSON.stringify(tag) ); } return handleTestLatency("group", section.sectionName, tag); } return handleTestLatency( "proxy", section.sectionName, Array.isArray(tag) ? JSON.stringify(tag) : tag, section.latencyTestTimeout ); }, onChooseOutbound: (sectionName, selector, tag) => { void handleChooseOutbound(sectionName, selector, tag); }, onCopyOutbound: (_section, outbound) => { handleCopyOutbound(outbound); }, onShowUrlTestInfo: (section2, outbound) => { handleShowUrlTestInfo(section2, outbound); }, onShowPriorityInfo: (section2, outbound) => { handleShowPriorityInfo(section2, outbound); }, onUpdateSubscription: (section2) => { void handleUpdateSubscription(section2); } }) ); return preserveScrollForPage(() => { container.replaceChildren( ...testAllHeader ? [testAllHeader] : [], ...renderedWidgets ); }); } function renderStoreWidget(containerId, storeKey, title, getItems, debugName) { logger.debug("[DASHBOARD]", debugName); const widgetState = store.get()[storeKey]; const container = document.getElementById(containerId); if (!container) return; if (widgetState.loading || widgetState.failed) { const renderedWidget2 = renderWidget({ loading: widgetState.loading, failed: widgetState.failed, title: "", items: [] }); return container.replaceChildren(renderedWidget2); } const renderedWidget = renderWidget({ loading: widgetState.loading, failed: widgetState.failed, title, items: getItems(widgetState.data) }); container.replaceChildren(renderedWidget); } async function fetchConnections() { const shouldFetchHostnames = expandedSections.has("active_clients"); const needsFallbackPolling = directSocketsFailed || !canUseDirectClashApi(); if (!needsFallbackPolling && !shouldFetchHostnames) { return; } try { const [res, hostnames] = await Promise.all([ TachyonShellMethods.getClashApiConnections(), shouldFetchHostnames ? fetchHostnames() : Promise.resolve(/* @__PURE__ */ new Map()) ]); if (res.success && res.data && typeof res.data === "object") { const payload = res.data; if (needsFallbackPolling) { const downloadTotal = Number(payload.downloadTotal) || 0; const uploadTotal = Number(payload.uploadTotal) || 0; const memory = Number(payload.memory) || 0; const connCount = Array.isArray(payload.connections) ? payload.connections.length : 0; const now = Date.now(); if (lastTrafficPollTime > 0) { const dt = Math.max(0.5, (now - lastTrafficPollTime) / 1e3); const up = Math.max( 0, Math.round((uploadTotal - lastUploadTotal) / dt) ); const down = Math.max( 0, Math.round((downloadTotal - lastDownloadTotal) / dt) ); store.set({ bandwidthWidget: { loading: false, failed: false, data: { up, down } } }); } lastTrafficPollTime = now; lastUploadTotal = uploadTotal; lastDownloadTotal = downloadTotal; store.set({ trafficTotalWidget: { loading: false, failed: false, data: { downloadTotal, uploadTotal } }, systemInfoWidget: { loading: false, failed: false, data: { connections: connCount, memory } } }); } if (shouldFetchHostnames && Array.isArray(payload.connections)) { const connectionsList = payload.connections; const map = /* @__PURE__ */ new Map(); for (const conn of connectionsList) { const ip = conn.metadata?.sourceIP; if (!ip) continue; const up = Number(conn.upload) || 0; const down = Number(conn.download) || 0; if (map.has(ip)) { const existing = map.get(ip); existing.count++; existing.upload += up; existing.download += down; } else { const name = hostnames.get(ip); map.set(ip, { ip, count: 1, upload: up, download: down, name }); } } currentConnections = Array.from(map.values()).sort( (a, b) => b.download + b.upload - (a.download + a.upload) ); } } } catch (_e) { } renderConnectionsWidget(); } function renderConnectionsWidget() { const container = document.getElementById("dashboard-connections-grid"); if (!container) return; container.replaceChildren( renderConnections( currentConnections, !expandedSections.has("active_clients"), () => toggleSectionExpanded("active_clients") ) ); } async function renderBandwidthWidget() { renderStoreWidget( "dashboard-widget-traffic", "bandwidthWidget", _("Traffic"), (data) => [ { key: _("Uplink"), value: `${prettyBytes(data.up)}/s` }, { key: _("Downlink"), value: `${prettyBytes(data.down)}/s` } ], "renderBandwidthWidget" ); } async function renderTrafficTotalWidget() { renderStoreWidget( "dashboard-widget-traffic-total", "trafficTotalWidget", _("Traffic Total"), (data) => [ { key: _("Uplink"), value: String(prettyBytes(data.uploadTotal)) }, { key: _("Downlink"), value: String(prettyBytes(data.downloadTotal)) } ], "renderTrafficTotalWidget" ); } async function renderSystemInfoWidget() { renderStoreWidget( "dashboard-widget-system-info", "systemInfoWidget", _("System info"), (data) => [ { key: _("Active Connections"), value: String(data.connections) }, { key: _("Memory Usage"), value: String(prettyBytes(data.memory)) } ], "renderSystemInfoWidget" ); } async function renderServicesInfoWidget() { renderStoreWidget( "dashboard-widget-service-info", "servicesInfoWidget", _("Services info"), (data) => { const items = [ { key: "Tachyon", value: data.tachyonRunning ? _("✔ Running") : _("✘ Stopped"), attributes: { class: data.tachyonRunning ? "tachyon_dashboard-page__widgets-section__item__row--success" : "tachyon_dashboard-page__widgets-section__item__row--error" } }, { key: "Sing-box", value: data.singbox ? data.singboxMemoryMb ? `${_("✔ Running")} (${data.singboxMemoryMb} MB)` : _("✔ Running") : _("✘ Stopped"), attributes: { class: data.singbox ? "tachyon_dashboard-page__widgets-section__item__row--success" : "tachyon_dashboard-page__widgets-section__item__row--error" } } ]; if (data.zapret2Running) { items.push({ key: "Zapret2", value: data.zapret2MemoryMb ? `${_("✔ Running")} (${data.zapret2MemoryMb} MB)` : _("✔ Running"), attributes: { class: "tachyon_dashboard-page__widgets-section__item__row--success" } }); } return items; }, "renderServicesInfoWidget" ); } var latestTailscalePeersRequestId = 0; async function fetchTailscalePeers() { const requestId = ++latestTailscalePeersRequestId; try { const response = await TachyonShellMethods.getTailscalePeers(); if (requestId !== latestTailscalePeersRequestId) return; store.set({ tailscaleWidget: { loading: false, failed: !response.success, data: response.success ? response.data : null } }); } catch { if (requestId !== latestTailscalePeersRequestId) return; store.set({ tailscaleWidget: { loading: false, failed: true, data: store.get().tailscaleWidget.data } }); } } function peerRow(name, online) { return { key: name, value: online ? _("✔ Online") : _("✘ Offline"), attributes: { class: online ? "tachyon_dashboard-page__widgets-section__item__row--success" : "tachyon_dashboard-page__widgets-section__item__row--error" } }; } async function renderTailscaleWidget() { const widgetState = store.get().tailscaleWidget; const container = document.getElementById("dashboard-widget-tailscale"); if (!container) return; if (!widgetState.loading && !widgetState.failed && (!widgetState.data || !widgetState.data.configured)) { container.style.display = "none"; container.replaceChildren(); return; } container.style.display = ""; renderStoreWidget( "dashboard-widget-tailscale", "tailscaleWidget", _("Tailscale"), (data) => { if (!data || !data.configured) return []; const items = [ { key: _("State"), value: data.backend_state || "—", attributes: { class: data.backend_state === "Running" ? "tachyon_dashboard-page__widgets-section__item__row--success" : "tachyon_dashboard-page__widgets-section__item__row--error" } } ]; if (data.self && data.self.ips.length) { items.push({ key: _("This router"), value: data.self.ips[0] }); } for (const peer of data.peers.slice(0, 5)) { if (!peer.online && !peer.name) continue; items.push( peerRow(peer.name || peer.dns_name || peer.ips[0], peer.online) ); } if (data.peers.length > 5) { items.push({ key: _("Other peers"), value: `+${data.peers.length - 5}` }); } return items; }, "renderTailscaleWidget" ); } async function onStoreUpdate(next, prev, diff) { if (diff.sectionsWidget) { const inlineUpdated = canUpdateLatencyProgressInline( prev.sectionsWidget, next.sectionsWidget ) && updateLatencyProgressInline(next.sectionsWidget); if (!inlineUpdated) { renderSectionsWidget(); } } if (diff.bandwidthWidget) { renderBandwidthWidget(); } if (diff.trafficTotalWidget) { renderTrafficTotalWidget(); } if (diff.systemInfoWidget) { renderSystemInfoWidget(); } if (diff.servicesInfoWidget) { syncDashboardServiceAvailability(); renderServicesInfoWidget(); } if (diff.tailscaleWidget) { renderTailscaleWidget(); } } async function onPageMount() { onPageUnmount(); dashboardMounted = true; dashboardMountId += 1; const mountId = dashboardMountId; const hasRuntimeSnapshot = Boolean(getCachedRuntimeUiState()); if (!hasRuntimeSnapshot) { const uiState = await refreshRuntimeUiState({ force: true }); if (!dashboardMounted || mountId !== dashboardMountId) { return; } if (!uiState) { void fetchServicesInfo(); void fetchTailscalePeers(); } } store.subscribe(onStoreUpdate); startActionStateWatcher(); void renderSectionsWidget(); void renderConnectionsWidget(); void renderBandwidthWidget(); void renderTrafficTotalWidget(); void renderSystemInfoWidget(); void renderServicesInfoWidget(); void renderTailscaleWidget(); syncDashboardServiceAvailability(); void fetchTailscalePeers(); if (hasRuntimeSnapshot) { void refreshRuntimeUiState({ force: true }); } } function onPageUnmount() { dashboardMounted = false; dashboardMountId += 1; dashboardVisibilityPaused = false; directSocketsFailed = false; lastTrafficPollTime = 0; lastUploadTotal = 0; lastDownloadTotal = 0; stopDashboardDataUpdates(); stopActionStateWatcher(); sectionsRefreshQueued = false; sectionsRefreshPromise = null; store.unsubscribe(onStoreUpdate); store.reset(["bandwidthWidget", "trafficTotalWidget", "systemInfoWidget"]); } var dashboardLifecycleRegistered = false; var dashboardControllerInitialized = false; function registerLifecycleListeners() { if (dashboardLifecycleRegistered) { return; } dashboardLifecycleRegistered = true; store.subscribe((next, prev, diff) => { if (diff.tabService && next.tabService.current !== prev.tabService.current) { 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) { logger.debug( "[DASHBOARD]", "registerLifecycleListeners", "onPageUnmount" ); return onPageUnmount(); } } }); } async function initController() { if (dashboardControllerInitialized) { return; } dashboardControllerInitialized = true; onMount("dashboard-status").then(() => { logger.debug("[DASHBOARD]", "initController", "onMount"); registerLifecycleListeners(); if (store.get().tabService.current === "dashboard" || isActiveLuciTab("dashboard")) { onPageMount(); } }); } // src/tachyon/tabs/dashboard/styles.ts var styles = ` @font-face { font-family: "Twemoji Country Flags"; src: url("/luci-static/resources/view/tachyon/fonts/TwemojiCountryFlags.woff2") format("woff2"); font-display: swap; font-style: normal; font-weight: normal; unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F; } #cbi-${TACHYON_UCI_PACKAGE}-dashboard-_mount_node > .cbi-value-title { display: none; } #cbi-${TACHYON_UCI_PACKAGE}-dashboard-_mount_node > .cbi-value-field { margin-left: 0; width: 100%; } #cbi-${TACHYON_UCI_PACKAGE}-dashboard-_mount_node > div { width: 100%; } #cbi-${TACHYON_UCI_PACKAGE}-dashboard > h3 { display: none; } .tachyon_dashboard-page { display: block; width: 100%; box-sizing: border-box; --dashboard-grid-columns: 4; --dashboard-grid-min-width: 180px; } .tachyon_dashboard-page__service-stopped { display: none; width: 100%; min-height: 180px; margin-top: 10px; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; border: 1px dashed var(--border-color-high, #555); border-radius: 6px; color: var(--text-color-medium, #888); background: transparent; font-family: inherit; font-size: inherit; font-weight: inherit; line-height: inherit; font-style: italic; text-align: center; } .tachyon_dashboard-page--service-stopped { display: grid; grid-template-columns: repeat(var(--dashboard-grid-columns), minmax(var(--dashboard-grid-min-width), 1fr)); gap: 10px; } .tachyon_dashboard-page--service-stopped .tachyon_dashboard-page__service-stopped { display: flex; grid-column: 1 / -1; } .tachyon_dashboard-page--service-stopped .tachyon_dashboard-page__content { display: none; } @media (max-width: 900px) { .tachyon_dashboard-page { --dashboard-grid-columns: 2; } } @media (max-width: 560px) { .tachyon_dashboard-page { --dashboard-grid-columns: 1; --dashboard-grid-min-width: 0; } } .tachyon_dashboard-page__widgets-section { margin-top: 10px; display: grid; grid-template-columns: repeat(var(--dashboard-grid-columns), minmax(var(--dashboard-grid-min-width), 1fr)); grid-gap: 10px; } .tachyon_dashboard-page__widgets-section__item { border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 10px; min-width: 0; } .tachyon_dashboard-page__widgets-section__item__title {} .tachyon_dashboard-page__widgets-section__item__row {} .tachyon_dashboard-page__widgets-section__item__row--success .tachyon_dashboard-page__widgets-section__item__row__value { color: var(--success-color-medium, green); } .tachyon_dashboard-page__widgets-section__item__row--error .tachyon_dashboard-page__widgets-section__item__row__value { color: var(--error-color-medium, red); } .tachyon_dashboard-page__widgets-section__item__row__key {} .tachyon_dashboard-page__widgets-section__item__row__value {} .tachyon_dashboard-page__outbound-section { margin-top: 10px; border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 10px; width: 100%; box-sizing: border-box; } .tachyon_dashboard-page__outbound-section__title-section { display: flex; align-items: center; justify-content: space-between; gap: 8px 10px; min-width: 0; } .tachyon_dashboard-page__outbound-section__title-section__title { color: var(--text-color-high); font-weight: 700; min-width: 0; overflow-wrap: anywhere; } .tachyon_dashboard-page__outbound-section__title-section__actions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex: 0 1 auto; flex-wrap: wrap; } .tachyon_dashboard-page .btn.tachyon_dashboard-page__outbound-section__subscription-update { min-width: 0; width: auto; min-height: 28px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; } .tachyon_dashboard-page__outbound-section__subscription-update svg { width: 15px; height: 15px; display: block; flex: 0 0 auto; } .tachyon_dashboard-page__outbound-section__subscription-update[disabled] { cursor: not-allowed; opacity: 0.65; } .tachyon_dashboard-page .btn.dashboard-sections-grid-item-test-latency { min-width: 0; width: auto; min-height: 28px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; } .tachyon_dashboard-page .btn.dashboard-sections-grid-item-test-latency svg { width: 15px; height: 15px; display: block; flex: 0 0 auto; } .tachyon_dashboard-page .btn.dashboard-sections-grid-item-test-latency[disabled] { cursor: not-allowed; opacity: 0.65; } .tachyon_dashboard-page__sections-header { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; margin-bottom: 4px; padding: 0 2px; gap: 12px; min-width: 0; } .tachyon_dashboard-page__sections-header__title { display: flex; align-items: center; gap: 8px; color: var(--text-color-high); font-size: 15px; font-weight: 700; letter-spacing: 0.2px; min-width: 0; overflow-wrap: anywhere; } .tachyon_dashboard-page__sections-header__title svg { flex-shrink: 0; opacity: 0.85; } .tachyon_dashboard-page__sections-header__actions { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex-shrink: 0; } .tachyon_dashboard-page .btn#dashboard-test-all-sections-button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; vertical-align: middle; padding: 4px 14px; height: 30px; min-height: 28px; font-size: 13px; } #dashboard-test-all-sections-button svg { width: 15px; height: 15px; display: block; flex: 0 0 15px; } #dashboard-test-all-sections-button[disabled] { cursor: not-allowed; opacity: 0.65; } @media (max-width: 480px) { .tachyon_dashboard-page__sections-header { flex-wrap: wrap; gap: 8px; } } .tachyon_dashboard-page__outbound-grid { margin-top: 5px; display: grid; grid-template-columns: repeat(var(--dashboard-grid-columns), minmax(var(--dashboard-grid-min-width), 1fr)); grid-gap: 10px; } .tachyon_dashboard-page__subscription-meta { --subscription-meta-action-size: 28px; --subscription-meta-action-gap: 6px; grid-column: 1 / -1; border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 8px 10px; background: var(--background-color-high, transparent); } .tachyon_dashboard-page__subscription-meta__main { display: flex; align-items: center; gap: 6px 10px; min-width: 0; } .tachyon_dashboard-page__subscription-meta__heading { flex: 0 0 auto; color: var(--text-color-high); font-weight: 700; line-height: 1.25; white-space: nowrap; } .tachyon_dashboard-page__subscription-meta__title { flex: 0 1 auto; color: var(--text-color-high); font-weight: 700; line-height: 1.25; word-break: break-word; } .tachyon_dashboard-page__subscription-meta__facts { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 5px 12px; } .tachyon_dashboard-page__subscription-meta__fact { display: flex; align-items: baseline; gap: 4px; min-width: 0; line-height: 1.25; } .tachyon_dashboard-page__subscription-meta__fact-key { color: var(--text-color-medium); font-size: 12px; white-space: nowrap; } .tachyon_dashboard-page__subscription-meta__fact-value { color: var(--text-color-high); font-weight: 600; overflow-wrap: anywhere; } .tachyon_dashboard-page__subscription-meta__actions { flex: 0 0 auto; margin-left: auto; display: flex; justify-content: flex-end; gap: var(--subscription-meta-action-gap); } .tachyon_dashboard-page .btn.tachyon_dashboard-page__subscription-meta__action { width: var(--subscription-meta-action-size); height: var(--subscription-meta-action-size); min-width: var(--subscription-meta-action-size); min-height: var(--subscription-meta-action-size); padding: 2px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; line-height: 1; margin: 0; } .tachyon_dashboard-page__subscription-meta__action svg { width: 15px; height: 15px; display: block; flex: 0 0 auto; } .tachyon_dashboard-page__subscription-meta__announce { margin: 6px 0 0; border-left: 3px solid var(--primary-color-medium, dodgerblue); padding: 4px 8px; background: var(--background-color-low, rgba(0, 0, 0, 0.04)); color: var(--text-color-medium); font-style: italic; line-height: 1.25; overflow-wrap: anywhere; } @media (max-width: 700px) { .tachyon_dashboard-page__subscription-meta__main { align-items: flex-start; flex-wrap: wrap; } .tachyon_dashboard-page__subscription-meta__heading, .tachyon_dashboard-page__subscription-meta__title { order: 1; } .tachyon_dashboard-page__subscription-meta__actions { order: 2; } .tachyon_dashboard-page__subscription-meta__facts { order: 3; flex-basis: 100%; } .tachyon_dashboard-page__subscription-meta__title { max-width: calc(100% - 42px); } } .tachyon_dashboard-page__outbound-grid__item { border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 10px; transition: border 0.2s ease; min-width: 0; position: relative; } .tachyon_dashboard-page__outbound-grid__item--selectable { cursor: pointer; } .tachyon_dashboard-page__outbound-grid__item--selectable:hover { border-color: var(--primary-color-high, dodgerblue); } .tachyon_dashboard-page__outbound-grid__item--active { border-color: var(--success-color-medium, green); } .tachyon_dashboard-page__outbound-grid__item--disabled { cursor: default; } .tachyon_dashboard-page__outbound-grid__item--switching { border-color: transparent !important; overflow: hidden; cursor: wait; } .tachyon_dashboard-page__outbound-grid__item__snake { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9999; box-sizing: border-box; } .tachyon_dashboard-page__outbound-grid__item__snake rect { stroke: var(--primary-color-high, dodgerblue); stroke-width: 4; animation: tachyon-dashboard-selector-snake-svg 1.2s linear infinite; } @keyframes tachyon-dashboard-selector-snake-svg { 0% { stroke-dasharray: 30 70; stroke-dashoffset: 100; } 100% { stroke-dasharray: 30 70; stroke-dashoffset: 0; } } .tachyon_dashboard-page__outbound-grid__item__header { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; min-width: 0; } .tachyon_dashboard-page__outbound-grid__item__title-wrapper { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; flex: 1 1 auto; } .tachyon_dashboard-page__outbound-grid__item__header b { min-width: 0; line-height: 1.25; overflow-wrap: anywhere; } .tachyon_dashboard-page__outbound-grid__item__prefix-badge { display: inline-flex; align-items: center; font-size: 11px; font-weight: 500; line-height: 1.3; padding: 1px 6px; border-radius: 4px; background: rgba(128, 128, 128, 0.2); color: var(--text-color-medium, #999); max-width: 100%; overflow-wrap: anywhere; word-break: break-word; } .tachyon_dashboard-page .btn.tachyon_dashboard-page__outbound-grid__item__copy-button { width: 22px; height: 22px; min-width: 22px; min-height: 22px; padding: 1px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; line-height: 1; } .tachyon_dashboard-page__outbound-grid__item__copy-button svg { width: 13px; height: 13px; display: block; flex: 0 0 auto; } .tachyon_dashboard-page__outbound-grid__item__footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 10px; min-width: 0; } .tachyon_dashboard-page__outbound-grid__item__type { min-width: 0; overflow-wrap: anywhere; } .tachyon_dashboard-page__outbound-grid__item__latency--empty { color: var(--primary-color-low, lightgray); } .tachyon_dashboard-page__outbound-grid__item__latency--green { color: var(--success-color-medium, green); } .tachyon_dashboard-page__outbound-grid__item__latency--yellow { color: var(--warn-color-medium, orange); } .tachyon_dashboard-page__outbound-grid__item__latency--red { color: var(--error-color-medium, red); } .tachyon_dashboard-page__outbound-grid__item__latency--clickable { cursor: pointer; user-select: none; transition: opacity 0.15s ease, background-color 0.15s ease; border-radius: 4px; padding: 1px 4px; } .tachyon_dashboard-page__outbound-grid__item__latency--clickable:hover { opacity: 0.8; background: rgba(128, 128, 128, 0.15); } .tachyon_dashboard-page__outbound-grid__item__latency--testing { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; opacity: 0.8; } .tachyon_dashboard-page__outbound-grid__item__latency--testing svg { width: 12px; height: 12px; display: block; flex: 0 0 auto; } .tachyon_dashboard-page__urltest-details { box-sizing: border-box; width: min(760px, calc(100vw - 56px)); max-width: 100%; padding-top: 10px; } .tachyon_dashboard-page__urltest-details__params { display: grid; grid-template-columns: minmax(120px, max-content) minmax(0, 1fr); gap: 8px 16px; margin: 0 0 18px; } .tachyon_dashboard-page__urltest-details__param { display: contents; } .tachyon_dashboard-page__urltest-details__param dt { color: var(--text-color-medium, #666); line-height: 1.35; } .tachyon_dashboard-page__urltest-details__param dd { display: flex; align-items: center; gap: 8px; min-width: 0; margin: 0; } .tachyon_dashboard-page__urltest-details__param dd span { min-width: 0; overflow-wrap: anywhere; } .tachyon_dashboard-page__urltest-details__url { min-width: 0; color: var(--primary-color-high, #337ab7); text-decoration: none; overflow-wrap: anywhere; } .tachyon_dashboard-page__urltest-details__url:hover { text-decoration: underline; } .tachyon_dashboard-page__urltest-details__selected-value { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; max-width: 100%; padding: 0; border: 0; color: inherit; background: transparent; box-sizing: border-box; line-height: 1.3; } .tachyon_dashboard-page__urltest-details__selected-name { min-width: 0; font-weight: 600; overflow-wrap: anywhere; } .tachyon_dashboard-page__urltest-details__selected-type { color: var(--text-color-medium, #666); } .tachyon_dashboard-page__urltest-details__outbounds-title { margin-bottom: 8px; font-weight: 600; } .tachyon_dashboard-page__urltest-details__table { display: flex; flex-direction: column; gap: 6px; width: 100%; box-sizing: border-box; max-height: min(48vh, 480px); overflow-x: hidden; overflow-y: auto; padding-right: 4px; } .tachyon_dashboard-page__urltest-details__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; min-width: 0; padding: 8px 10px; box-sizing: border-box; border: 1px solid var(--border-color-low, rgba(128, 128, 128, 0.15)); border-radius: 6px; background: var(--background-color-card, rgba(128, 128, 128, 0.04)); transition: background 0.15s ease, border-color 0.15s ease; } .tachyon_dashboard-page__urltest-details__row:hover { background: rgba(128, 128, 128, 0.08); } .tachyon_dashboard-page__urltest-details__row--active { border-color: #4caf50; background: rgba(76, 175, 80, 0.08); } .tachyon_dashboard-page__urltest-details__row-name { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1 1 auto; overflow: hidden; } .tachyon_dashboard-page__urltest-details__row-name b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; line-height: 1.3; } .tachyon_dashboard-page__urltest-details__priority-name { display: inline-flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tachyon_dashboard-page__urltest-details__priority-number { margin-right: 4px; color: var(--text-color-medium, #aaa); font-family: monospace; font-size: 12px; font-weight: 600; } .tachyon_dashboard-page__urltest-details__priority-level { padding: 1px 6px; border-radius: 4px; color: var(--text-color-medium, #aaa); background: rgba(128, 128, 128, 0.15); font-size: 11px; font-weight: 400; white-space: nowrap; } .tachyon_dashboard-page__urltest-details__country-badge { display: inline-flex; align-items: center; user-select: none; padding: 2px 4px; border: 1px solid rgba(128, 128, 128, 0.25); border-radius: 4px; background: rgba(128, 128, 128, 0.15); line-height: 1; flex-shrink: 0; } .tachyon_dashboard-page__flag-emoji, .tachyon_dashboard-page__urltest-details__country-badge { font-family: "Twemoji Country Flags"; font-style: normal; font-weight: normal; } .tachyon_dashboard-page__urltest-details__priority-node { color: var(--text-color-high, #fff); font-weight: 600; } .tachyon_dashboard-page__urltest-details__row-type { font-size: 11px; color: var(--text-color-medium, #888); background: rgba(128, 128, 128, 0.12); padding: 1px 6px; border-radius: 4px; white-space: nowrap; flex-shrink: 0; } .tachyon_dashboard-page__urltest-details__row-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; } .tachyon_dashboard-page__urltest-details__row-meta { display: flex; align-items: center; white-space: nowrap; min-width: 48px; justify-content: flex-end; font-weight: 500; font-size: 12px; } .tachyon_dashboard-page__urltest-details__select-btn { padding: 2px 10px !important; font-size: 12px !important; height: 26px !important; line-height: 20px !important; white-space: nowrap !important; border-radius: 4px !important; cursor: pointer; } .tachyon_dashboard-page__urltest-details__active-badge { display: inline-flex !important; align-items: center !important; gap: 4px !important; padding: 3px 8px !important; font-size: 11px !important; font-weight: 600 !important; color: #4caf50 !important; background: rgba(76, 175, 80, 0.15) !important; border: 1px solid rgba(76, 175, 80, 0.3) !important; border-radius: 4px !important; white-space: nowrap !important; } .tachyon_dashboard-page__urltest-details__copy-button { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 24px; width: 24px; min-width: 24px; height: 24px; padding: 0; box-sizing: border-box; border-radius: 4px; opacity: 0.7; transition: opacity 0.15s ease; } .tachyon_dashboard-page__urltest-details__copy-button:hover { opacity: 1; } .tachyon_dashboard-page__urltest-details__copy-button svg { width: 14px; height: 14px; } .tachyon_dashboard-page__urltest-details__copy-placeholder { display: block; width: 24px; min-width: 24px; height: 1px; } .tachyon_dashboard-page__urltest-details__empty { margin-top: 4px; padding: 24px 0; border: 1px dashed var(--border-color-high, #555); border-radius: 4px; color: var(--text-color-medium, #888); background: rgba(128, 128, 128, 0.02); font-style: italic; text-align: center; } .tachyon_dashboard-page__urltest-details__footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border-color-low, rgba(128, 128, 128, 0.2)); } @media (max-width: 560px) { .tachyon_dashboard-page__urltest-details__params { grid-template-columns: 1fr; } .tachyon_dashboard-page__urltest-details__row { flex-wrap: wrap; gap: 6px; } .tachyon_dashboard-page__urltest-details__row-actions { width: 100%; justify-content: flex-end; } } `; // src/tachyon/tabs/dashboard/index.ts var DashboardTab = { render, initController, styles }; // src/tachyon/tabs/diagnostic/renderDiagnostic.ts function render2() { return E( "div", { id: "diagnostic-status", class: "tachyon_diagnostic-page" }, [ E("div", { class: "tachyon_diagnostic-page__left-bar" }, [ E("div", { id: "tachyon_diagnostic-page-run-check" }), E("div", { class: "tachyon_diagnostic-page__checks", id: "tachyon_diagnostic-page-checks" }) ]), E("div", { class: "tachyon_diagnostic-page__right-bar" }, [ E("div", { id: "tachyon_diagnostic-page-wiki" }), E("div", { id: "tachyon_diagnostic-page-actions" }), E("div", { id: "tachyon_diagnostic-page-system-info" }) ]) ] ); } // src/tachyon/helpers/notifyActionFailure.ts function extractMessage(response) { if (!response || typeof response !== "object") { return null; } const record = response; for (const key of ["error", "message", "stderr"]) { const value = record[key]; if (typeof value === "string" && value.trim() !== "") { return value.trim(); } } const data = record.data; if (data && typeof data === "object") { const dataError = data.error; if (typeof dataError === "string" && dataError.trim() !== "") { return dataError.trim(); } } return null; } function notifyActionFailure(context, response, fallbackLabel) { logger.error("[DIAGNOSTIC]", context, response); const detail = extractMessage(response); showToast( (fallbackLabel ?? _("Action failed")) + (detail ? ": " + detail : ""), "error" ); } // src/tachyon/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/tachyon/tabs/diagnostic/helpers/getMeta.ts function getMeta({ allGood, atLeastOneGood }) { if (allGood) { return { state: "success", description: _("Checks passed") }; } if (atLeastOneGood) { return { state: "warning", description: _("Issues detected") }; } return { state: "error", description: _("Checks failed") }; } // src/tachyon/tabs/diagnostic/checks/getDnsCheckPresentation.ts function getDnsCheckPresentation(data) { const dhcpManagedManually = Boolean(data.dont_touch_dhcp); const dhcpCheckOk = dhcpManagedManually || Boolean(data.dhcp_config_status); const allGood = Boolean(data.dns_on_router) && dhcpCheckOk && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status); const atLeastOneGood = Boolean(data.dns_on_router) || dhcpCheckOk || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status); const meta = getMeta({ atLeastOneGood, allGood }); const state = dhcpManagedManually && meta.state === "success" ? "warning" : meta.state; const description = dhcpManagedManually && meta.state === "success" ? _("Checks passed with manual DHCP") : meta.description; const dhcpItemState = dhcpManagedManually ? "warning" : data.dhcp_config_status ? "success" : "error"; const dhcpItemKey = dhcpManagedManually ? _("DHCP is managed manually") : _("DHCP has DNS server"); return { state, description, dhcpItemState, dhcpItemKey }; } // src/tachyon/tabs/diagnostic/checks/runDnsCheck.ts async function runDnsCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); const dnsChecks = await TachyonShellMethods.checkDNSAvailable(); if (!dnsChecks.success) { updateCheckStore({ order, code, title, description: _("Cannot receive checks result"), state: "error", items: [] }); throw new Error("DNS checks failed"); } const data = dnsChecks.data; const { state, description, dhcpItemState, dhcpItemKey } = getDnsCheckPresentation(data); updateCheckStore({ order, code, title, description, state, items: [ ...insertIf( data.dns_type === "doh" || data.dns_type === "dot" || data.bootstrap_dns_server_count > 1 || !data.bootstrap_dns_status, [ { state: data.bootstrap_dns_status ? "success" : "error", key: data.bootstrap_dns_server_count > 1 ? _("Active Bootstrap DNS") : _("Bootstrap DNS"), value: data.bootstrap_dns_server_count > 1 ? `${data.bootstrap_dns_server} (${data.bootstrap_dns_server_index + 1}/${data.bootstrap_dns_server_count})` : data.bootstrap_dns_server } ] ), { state: data.dns_status ? "success" : "error", key: data.dns_server_count > 1 ? _("Active Main DNS") : _("Main DNS"), value: data.dns_server_count > 1 ? `${data.dns_server} [${data.dns_type}] (${data.dns_server_index + 1}/${data.dns_server_count})` : `${data.dns_server} [${data.dns_type}]` }, { state: data.dns_on_router ? "success" : "error", key: _("DNS on router"), value: "" }, { state: dhcpItemState, key: dhcpItemKey, value: "" } ] }); if (state === "error") { throw new Error("DNS checks failed"); } } // src/tachyon/tabs/diagnostic/checks/runSingBoxCheck.ts async function runSingBoxCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); const singBoxChecks = await TachyonShellMethods.checkSingBox(); if (!singBoxChecks.success) { updateCheckStore({ order, code, title, description: _("Cannot receive 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); const { state, description } = getMeta({ atLeastOneGood, allGood }); updateCheckStore({ order, code, title, description, state, 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 is compatible (newer than 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 || !data.sing_box_process_running) { throw new Error("Sing-box checks failed"); } } // src/tachyon/tabs/diagnostic/checks/runInboundsCheck.ts function serverPrefix(item) { return `${item.label}:`; } function formatListen(item) { return `${item.listen}:${Number(item.listen_port || 0)} [${item.required_proto}]`; } function getPortConflictItem(item) { const key = `${serverPrefix(item)} ${_("Port conflict")}`; if (item.port_conflict === 1) { return { state: "error", key, value: `${_("Used by")}: ${item.port_conflict_owners || _("unknown")}` }; } return { state: "success", key, value: _("No conflict detected") }; } function getPublicHostItem(item, wanIp) { const key = `${serverPrefix(item)} ${_("Public host")}`; if (!item.public_host) { return { state: "warning", key, value: _("Not configured") }; } if (item.public_host_resolved === 0) { return { state: "warning", key, value: `${item.public_host} (${_("Does not resolve")})` }; } if (item.public_host_public === 0) { return { state: "warning", key, value: `${item.public_host} (${_("Not public")})` }; } if (item.public_host_matches_wan === 0) { return { state: "warning", key, value: `${item.public_host_ips || item.public_host} / ${_("WAN")}: ${wanIp || _("Not detected")}` }; } return { state: "success", key, value: item.public_host }; } function getServerItems(item, wanIp) { const prefix = serverPrefix(item); const items = [ { state: item.runtime_ok ? "success" : "error", key: `${prefix} ${_("Generated inbound")}`, value: `${item.tag} [${item.protocol}]` } ]; if (item.protocol === "tailscale") { items.push( { state: "success", key: `${prefix} ${_("Tailscale endpoint")}`, value: _("No public firewall port required") }, { state: item.routes_configured ? "success" : "warning", key: `${prefix} ${_("Routing rules")}`, value: item.routing_mode } ); return items; } if (item.protocol === "json_inbound") { items.push({ state: item.routes_configured ? "success" : "warning", key: `${prefix} ${_("Routing rules")}`, value: item.routing_mode }); return items; } items.push( { state: item.listening === 1 && item.port_conflict !== 1 ? "success" : "error", key: `${prefix} ${_("Listening port")}`, value: formatListen(item) }, getPortConflictItem(item), { state: item.firewall_required === 0 ? "warning" : item.firewall_open === 1 ? "success" : "error", key: `${prefix} ${_("Firewall WAN port")}`, value: item.firewall_required === 0 ? _("Not required for this listen address") : `${item.required_proto}/${Number(item.listen_port || 0)}` }, { state: item.routes_configured ? "success" : "warning", key: `${prefix} ${_("Routing rules")}`, value: item.routing_mode }, getPublicHostItem(item, wanIp) ); return items; } async function runInboundsCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.INBOUNDS; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); const inboundsChecks = await TachyonShellMethods.checkInbounds(); if (!inboundsChecks.success) { updateCheckStore({ order, code, title, description: _("Cannot receive checks result"), state: "error", items: [] }); throw new Error("Inbounds checks failed"); } const data = inboundsChecks.data; if (!data.enabled_count) { updateCheckStore({ order, code, title, description: _("No enabled server inbounds configured"), state: "skipped", items: [] }); return; } const items = [ { state: data.wan_public ? "success" : "warning", key: _("WAN public IP"), value: data.wan_ip || _("Not detected") } ]; data.items.forEach((item) => { items.push(...getServerItems(item, data.wan_ip)); }); const allGood = items.every((item) => item.state === "success"); const atLeastOneGood = items.some((item) => item.state !== "error"); const { state, description } = getMeta({ atLeastOneGood, allGood }); updateCheckStore({ order, code, title, description, state, items }); if (!atLeastOneGood) { throw new Error("Inbounds checks failed"); } } // src/tachyon/tabs/diagnostic/checks/runNftCheck.ts async function runNftCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); await RemoteFakeIPMethods.getFakeIpCheck(); await RemoteFakeIPMethods.getIpCheck(); const nftablesChecks = await TachyonShellMethods.checkNftRules(); if (!nftablesChecks.success) { updateCheckStore({ order, code, title, description: _("Cannot receive 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) && !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; const { state, description } = getMeta({ atLeastOneGood, allGood }); updateCheckStore({ order, code, title, description, state, 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" : "warning", 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/tachyon/tabs/diagnostic/checks/runFakeIPCheck.ts var PROXY_ACTIONS = /* @__PURE__ */ new Set([ "connection", "proxy", "outbound", "vpn", "awg", "warp", "anytls", "snell", "mieru", "sudoku", "masque", "openvpn" ]); async function runFakeIPCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); const routerFakeIPResponse = await TachyonShellMethods.checkFakeIP(); const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck(); const checkIPResponse = await RemoteFakeIPMethods.getIpCheck(); const browserFakeIPCheckUnavailable = !checkFakeIPResponse.success; const browserFakeIPCheckMessage = checkFakeIPResponse.success ? "" : checkFakeIPResponse.message; let hasProxySection = true; try { const cachedSections = store.get().sectionsWidget?.data; const sections = cachedSections && cachedSections.length > 0 ? cachedSections : (await getDashboardSections({ includeSubscriptionCopyState: false })).data || []; if (sections.length > 0) { hasProxySection = sections.some( (s) => s.action && PROXY_ACTIONS.has(s.action) ); } } catch (_e) { hasProxySection = true; } const checks = { singBoxFakeIP: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip, browserFakeIP: checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip, canComparePublicIP: checkFakeIPResponse.success && checkIPResponse.success, differentIP: checkFakeIPResponse.success && checkIPResponse.success && checkFakeIPResponse.data.IP !== checkIPResponse.data.IP }; const fakeIPWorks = checks.singBoxFakeIP && checks.browserFakeIP; const isDirectOnly = fakeIPWorks && !hasProxySection && !checks.differentIP; const { state, description } = fakeIPWorks ? checks.differentIP || isDirectOnly ? { state: "success", description: _("Checks passed") } : { state: "warning", description: _("FakeIP works; public IP comparison is inconclusive") } : browserFakeIPCheckUnavailable && checks.singBoxFakeIP ? { state: "warning", description: _("Browser FakeIP check could not be completed") } : getMeta({ allGood: false, atLeastOneGood: checks.singBoxFakeIP || checks.browserFakeIP }); updateCheckStore({ order, code, title, description, state, items: [ { state: checks.singBoxFakeIP ? "success" : "error", key: checks.singBoxFakeIP ? _("Sing-box FakeIP DNS works") : _("Sing-box FakeIP DNS does not work"), value: routerFakeIPResponse.success ? routerFakeIPResponse.data.IP : "" }, { state: browserFakeIPCheckUnavailable ? "warning" : checks.browserFakeIP ? "success" : "error", key: browserFakeIPCheckUnavailable ? _("Browser FakeIP check could not be completed") : checks.browserFakeIP ? _("Browser is using FakeIP correctly") : _("Browser is not using FakeIP"), value: browserFakeIPCheckMessage }, ...insertIf(checks.browserFakeIP, [ { state: checks.differentIP || isDirectOnly ? "success" : "warning", key: !checks.canComparePublicIP ? _("Could not compare FakeIP and control public IPs") : checks.differentIP ? _("FakeIP and control checks use different public IPs") : _("FakeIP and control checks use the same public IP"), value: isDirectOnly ? _("Direct connection via ISP") : "" } ]) ] }); } // src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts function getCheckItemsMeta(items) { if (items.some((item) => item.state === "error")) { return { state: "error", description: _("Checks failed") }; } if (items.some((item) => item.state === "warning")) { return { state: "warning", description: _("Issues detected") }; } return { state: "success", description: _("Checks passed") }; } // src/tachyon/tabs/diagnostic/checks/runZapretCheck.ts async function runZapretCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.ZAPRET; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); const zapretStatus = await TachyonShellMethods.getZapretStatus(); if (!zapretStatus.success) { updateCheckStore({ order, code, title, description: _("Cannot receive checks result"), state: "error", items: [] }); throw new Error("Zapret checks failed"); } const data = zapretStatus.data; const providerAvailable = Boolean(data.provider_available ?? data.installed); const packageInstalled = Boolean(data.package_installed); const hasZapretRules = Number(data.enabled_rule_count || 0) > 0; const queueOverlap = Boolean(data.queue_overlap); const standaloneServiceRunning = Boolean(data.standalone_service_running); const standaloneConflict = hasZapretRules && standaloneServiceRunning; const expectedProcesses = Number(data.expected_process_count || 0); const runningProcesses = Number(data.running_process_count || 0); const supervisorProcesses = Number(data.supervisor_process_count || 0); const tachyonRuntimeReady = !hasZapretRules || runningProcesses === expectedProcesses && supervisorProcesses === expectedProcesses; const unexpectedRuntime = !hasZapretRules && (runningProcesses > 0 || supervisorProcesses > 0); const outboundsConfigured = Boolean(data.outbounds_configured); const items = [ { state: providerAvailable ? "success" : hasZapretRules ? "error" : "warning", key: providerAvailable ? _("Zapret provider binary is available") : _("Zapret provider binary is not available"), value: data.provider_path || "" }, { state: packageInstalled ? "success" : hasZapretRules ? "error" : "warning", key: packageInstalled ? _("Zapret package is installed") : _("Zapret package is not installed"), value: "" }, { state: hasZapretRules && !providerAvailable ? "error" : "success", key: hasZapretRules ? _("There are rules using Zapret") : _("No rules use Zapret"), value: "" }, { state: unexpectedRuntime || !tachyonRuntimeReady ? "error" : "success", key: hasZapretRules ? tachyonRuntimeReady ? _("Tachyon-managed nfqws runtime is ready") : _("Tachyon-managed nfqws runtime is not ready") : unexpectedRuntime ? _("Unexpected Tachyon-managed nfqws runtime is running") : _("Tachyon-managed nfqws runtime is not running"), value: hasZapretRules ? `${runningProcesses}/${expectedProcesses}` : "" }, { state: queueOverlap ? "error" : "success", key: queueOverlap ? _("NFQUEUE range overlaps with another rule") : _("NFQUEUE range is available"), value: `${Number(data.queue_base || 0)}-${Number(data.queue_range_end || 0)}` }, { state: !hasZapretRules || outboundsConfigured ? "success" : "error", key: outboundsConfigured ? _("Zapret sing-box outbound is configured") : _("Zapret sing-box outbound is not configured"), value: "" }, { state: standaloneConflict ? "warning" : "success", key: standaloneServiceRunning ? hasZapretRules ? _("Standalone Zapret is active together with Tachyon Zapret rules") : _("Standalone Zapret service is active") : _("Standalone Zapret service is inactive"), value: "" } ]; const { state, description } = getCheckItemsMeta(items); updateCheckStore({ order, code, title, description, state, items }); } // src/tachyon/tabs/diagnostic/checks/runZapret2Check.ts async function runZapret2Check() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.ZAPRET2; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); const zapret2Status = await TachyonShellMethods.getZapret2Status(); if (!zapret2Status.success) { updateCheckStore({ order, code, title, description: _("Cannot receive checks result"), state: "error", items: [] }); throw new Error("Zapret2 checks failed"); } const data = zapret2Status.data; const providerAvailable = Boolean(data.provider_available ?? data.installed); const packageInstalled = Boolean(data.package_installed); const hasZapret2Rules = Number(data.enabled_rule_count || 0) > 0; const queueOverlap = Boolean(data.queue_overlap); const expectedProcesses = Number(data.expected_process_count || 0); const runningProcesses = Number(data.running_process_count || 0); const supervisorProcesses = Number(data.supervisor_process_count || 0); const tachyonRuntimeReady = !hasZapret2Rules || runningProcesses === expectedProcesses && supervisorProcesses === expectedProcesses; const unexpectedRuntime = !hasZapret2Rules && (runningProcesses > 0 || supervisorProcesses > 0); const outboundsConfigured = Boolean(data.outbounds_configured); const routesConfigured = Boolean(data.routes_configured); const standaloneServiceEnabled = Boolean(data.standalone_service_enabled); const standaloneServiceRunning = Boolean(data.standalone_service_running); const standaloneConflict = hasZapret2Rules && standaloneServiceRunning; const standaloneAutostartRisk = hasZapret2Rules && standaloneServiceEnabled && !standaloneServiceRunning; const items = [ { state: providerAvailable ? "success" : hasZapret2Rules ? "error" : "warning", key: providerAvailable ? _("Zapret2 provider binary is available") : _("Zapret2 provider binary is not available"), value: data.provider_path || "" }, { state: packageInstalled ? "success" : hasZapret2Rules ? "error" : "warning", key: packageInstalled ? _("Zapret2 package is installed") : _("Zapret2 package is not installed"), value: "" }, { state: hasZapret2Rules && !providerAvailable ? "error" : "success", key: hasZapret2Rules ? _("There are rules using Zapret2") : _("No rules use Zapret2"), value: "" }, { state: unexpectedRuntime || !tachyonRuntimeReady ? "error" : "success", key: hasZapret2Rules ? tachyonRuntimeReady ? _("Tachyon-managed nfqws2 runtime is ready") : _("Tachyon-managed nfqws2 runtime is not ready") : unexpectedRuntime ? _("Unexpected Tachyon-managed nfqws2 runtime is running") : _("Tachyon-managed nfqws2 runtime is not running"), value: hasZapret2Rules ? `${runningProcesses}/${expectedProcesses}` : "" }, { state: queueOverlap ? "error" : "success", key: queueOverlap ? _("NFQUEUE range overlaps with another rule") : _("NFQUEUE range is available"), value: `${Number(data.queue_base || 0)}-${Number(data.queue_range_end || 0)}` }, { state: !hasZapret2Rules || outboundsConfigured ? "success" : "error", key: outboundsConfigured ? _("Zapret2 sing-box outbound is configured") : _("Zapret2 sing-box outbound is not configured"), value: "" }, { state: !hasZapret2Rules || routesConfigured ? "success" : "error", key: routesConfigured ? _("Zapret2 sing-box route rules are configured") : _("Zapret2 sing-box route rules are not configured"), value: "" }, { state: standaloneConflict ? "error" : standaloneAutostartRisk ? "warning" : "success", key: standaloneServiceRunning ? hasZapret2Rules ? _( "Standalone Zapret2 is active together with Tachyon Zapret2 rules" ) : _("Standalone Zapret2 service is active") : standaloneAutostartRisk ? _("Standalone Zapret2 autostart is enabled") : _("Standalone Zapret2 service is inactive"), value: "" } ]; const { state, description } = getCheckItemsMeta(items); updateCheckStore({ order, code, title, description, state, items }); } // src/tachyon/tabs/diagnostic/checks/runByedpiCheck.ts async function runByedpiCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.BYEDPI; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); const byedpiStatus = await TachyonShellMethods.getByedpiStatus(); if (!byedpiStatus.success) { updateCheckStore({ order, code, title, description: _("Cannot receive checks result"), state: "error", items: [] }); throw new Error("ByeDPI checks failed"); } const data = byedpiStatus.data; const providerAvailable = Boolean(data.provider_available ?? data.installed); const packageInstalled = Boolean(data.package_installed); const hasByedpiRules = Number(data.enabled_rule_count || 0) > 0; const expectedProcesses = Number(data.expected_process_count || 0); const runningProcesses = Number(data.running_process_count || 0); const supervisorProcesses = Number(data.supervisor_process_count || 0); const restartCount = Number(data.restart_count || 0); const runtimeUnstable = Boolean(data.runtime_unstable); const tachyonRuntimeReady = !hasByedpiRules || runningProcesses === expectedProcesses && supervisorProcesses === expectedProcesses; const unexpectedRuntime = !hasByedpiRules && (runningProcesses > 0 || supervisorProcesses > 0); const outboundsConfigured = Boolean(data.outbounds_configured); const standaloneServiceEnabled = Boolean(data.standalone_service_enabled); const standaloneServiceRunning = Boolean(data.standalone_service_running); const standaloneConflict = hasByedpiRules && standaloneServiceRunning; const standaloneAutostartRisk = hasByedpiRules && standaloneServiceEnabled && !standaloneServiceRunning; const items = [ { state: providerAvailable ? "success" : hasByedpiRules ? "error" : "warning", key: providerAvailable ? _("ByeDPI provider binary is available") : _("ByeDPI provider binary is not available"), value: data.provider_path || "" }, { state: packageInstalled ? "success" : "warning", key: packageInstalled ? _("ByeDPI package is installed") : _("ByeDPI package is not installed"), value: "" }, { state: hasByedpiRules && !providerAvailable ? "error" : "success", key: hasByedpiRules ? _("There are rules using ByeDPI") : _("No rules use ByeDPI"), value: "" }, { state: unexpectedRuntime || !tachyonRuntimeReady ? "error" : runtimeUnstable ? "warning" : "success", key: hasByedpiRules ? runtimeUnstable ? _("Tachyon-managed ciadpi runtime has restarted") : tachyonRuntimeReady ? _("Tachyon-managed ciadpi runtime is ready") : _("Tachyon-managed ciadpi runtime is not ready") : unexpectedRuntime ? _("Unexpected Tachyon-managed ciadpi runtime is running") : _("Tachyon-managed ciadpi runtime is not running"), value: hasByedpiRules ? runtimeUnstable ? `${restartCount}` : `${runningProcesses}/${expectedProcesses}` : "" }, { state: !hasByedpiRules || outboundsConfigured ? "success" : "error", key: outboundsConfigured ? _("ByeDPI sing-box outbound is configured") : _("ByeDPI sing-box outbound is not configured"), value: `${data.listen_address}:${Number(data.port_base || 0)}` }, { state: standaloneConflict ? "error" : standaloneAutostartRisk ? "warning" : "success", key: standaloneServiceRunning ? hasByedpiRules ? _("Standalone ByeDPI is active together with Tachyon ByeDPI rules") : _("Standalone ByeDPI service is active") : standaloneAutostartRisk ? _("Standalone ByeDPI autostart is enabled") : _("Standalone ByeDPI service is inactive"), value: "" } ]; const { state, description } = getCheckItemsMeta(items); updateCheckStore({ order, code, title, description, state, items }); } // src/tachyon/services/systemInfo.service.ts var UNKNOWN_SYSTEM_INFO = { loading: false, loaded: false, providerInfoLoaded: false, tachyon_version: _("unknown"), tachyon_commit_sha: "", tachyon_latest_version: _("unknown"), luci_app_version: _("unknown"), sing_box_version: _("unknown"), sing_box_extended: 0, sing_box_tiny: 0, sing_box_compressed: 0, sing_box_lx: 0, sing_box_tailscale: 1, sing_box_repo_url: "", zapret_version: _("unknown"), zapret_installed: 0, zapret2_version: _("unknown"), zapret2_installed: 0, byedpi_version: _("unknown"), byedpi_installed: 0, wdtt_version: _("unknown"), wdtt_installed: 0, olcrtc_version: _("unknown"), olcrtc_installed: 0, tailscale_version: _("unknown"), tailscale_installed: 0, fptn_version: _("unknown"), fptn_installed: 0, server_inbounds_enabled_count: -1, openwrt_version: _("unknown"), device_model: _("unknown") }; var systemInfoPromise = null; var latestSystemInfoRequestId = 0; function hasLoadedSystemInfo() { const systemInfo = store.get().diagnosticsSystemInfo; return Boolean(systemInfo.loaded) && !systemInfo.loading; } async function ensureSystemInfo({ force = false, silent = false } = {}) { if (!force && hasLoadedSystemInfo()) { return store.get().diagnosticsSystemInfo; } if (systemInfoPromise && !force) { return systemInfoPromise; } const requestId = ++latestSystemInfoRequestId; const currentSystemInfo = store.get().diagnosticsSystemInfo; if (!silent) { store.set({ diagnosticsSystemInfo: { ...currentSystemInfo, loading: true } }); } const promise = (async () => { try { const systemInfo = await TachyonShellMethods.getSystemInfo(); if (requestId !== latestSystemInfoRequestId) { return store.get().diagnosticsSystemInfo; } if (systemInfo.success) { const nextSystemInfo = normalizeSingBoxVariantFields({ ...UNKNOWN_SYSTEM_INFO, loading: false, loaded: true, providerInfoLoaded: true, server_inbounds_enabled_count: currentSystemInfo.server_inbounds_enabled_count, ...systemInfo.data }); store.set({ diagnosticsSystemInfo: nextSystemInfo }); return nextSystemInfo; } } catch (error) { logger.error("[SYSTEM_INFO]", "ensureSystemInfo failed", error); } if (requestId === latestSystemInfoRequestId && !silent) { const latestSystemInfo = store.get().diagnosticsSystemInfo; const nextSystemInfo = { ...UNKNOWN_SYSTEM_INFO, loading: false, loaded: false, providerInfoLoaded: latestSystemInfo.providerInfoLoaded, zapret_installed: latestSystemInfo.zapret_installed, zapret2_installed: latestSystemInfo.zapret2_installed, byedpi_installed: latestSystemInfo.byedpi_installed, wdtt_installed: latestSystemInfo.wdtt_installed, olcrtc_installed: latestSystemInfo.olcrtc_installed, server_inbounds_enabled_count: latestSystemInfo.server_inbounds_enabled_count }; store.set({ diagnosticsSystemInfo: nextSystemInfo }); return nextSystemInfo; } return store.get().diagnosticsSystemInfo; })(); systemInfoPromise = promise; try { return await promise; } finally { if (systemInfoPromise === promise) { systemInfoPromise = null; } } } // src/partials/button/styles.ts var styles2 = ` .tachyon-partial-button { text-align: center; } .tachyon-partial-button--with-icon { display: inline-flex; align-items: center; justify-content: center; gap: 5px; } .tachyon-partial-button--loading { } .tachyon-partial-button--disabled { } .tachyon-partial-button__icon { flex: 0 0 auto; } .tachyon-partial-button__icon { display: inline-flex; align-items: center; justify-content: center; } .tachyon-partial-button__icon svg { width: 16px; height: 16px; display: block; flex: 0 0 auto; } `; // src/partials/modal/styles.ts var styles3 = ` .tachyon-partial-modal__body { width: 100%; box-sizing: border-box; } .tachyon-partial-modal__content { max-height: 75vh; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; border-radius: 4px; box-sizing: border-box; width: 100%; } .tachyon-partial-modal__footer { display: flex; justify-content: flex-end; align-items: center; flex-wrap: wrap; gap: 10px; } .tachyon-partial-modal__footer button { margin-left: 0; } .tachyon-partial-modal__checkbox { display: inline-flex; align-items: center; gap: 8px; margin-right: auto; cursor: pointer; user-select: none; } .tachyon-partial-modal__checkbox-text { line-height: 1.2; } @media (max-width: 640px) { .tachyon-partial-modal__content { max-height: 82vh; } .tachyon-partial-modal__footer { flex-direction: column-reverse; align-items: stretch; } .tachyon-partial-modal__footer button { width: 100%; min-height: 38px; } .tachyon-partial-modal__checkbox { margin-right: 0; margin-bottom: 6px; width: 100%; } } `; // src/partials/button/renderButton.ts function renderButton({ classNames = [], disabled, loading: loading2, onClick, text, icon }) { const hasIcon = !!loading2 || !!icon; function getWrappedIcon() { const iconWrap = E("span", { class: "tachyon-partial-button__icon" }); if (loading2) { iconWrap.appendChild(renderLoaderCircleIcon24()); return iconWrap; } if (icon) { iconWrap.appendChild(icon()); return iconWrap; } return iconWrap; } function getClass() { return [ "btn", "tachyon-partial-button", ...insertIf(Boolean(disabled), ["tachyon-partial-button--disabled"]), ...insertIf(Boolean(loading2), ["tachyon-partial-button--loading"]), ...insertIf(Boolean(hasIcon), ["tachyon-partial-button--with-icon"]), ...classNames ].filter(Boolean).join(" "); } function getDisabled() { if (loading2 || disabled) { return true; } return void 0; } return E( "button", { type: "button", class: getClass(), disabled: getDisabled(), click: onClick }, [...insertIf(hasIcon, [getWrappedIcon()]), E("span", {}, text)] ); } // src/partials/modal/renderModal.ts function renderModal(text, name, options) { let rawText = text ?? ""; let currentText = ""; let refreshInFlight = false; let pendingRefresh = false; let pendingForcedRefresh = false; let refreshSessionId = 0; let timer; let observer; let autoRefreshEnabled = options?.initialAutoRefresh ?? Boolean(options?.getText); let maskValuesEnabled = options?.initialMaskValues ?? true; let shouldScrollToBottomOnMount = Boolean(options?.startAtEnd); let autoRefreshInput; let maskValuesInput; const getDisplayText = (value) => { if (maskValuesEnabled && options?.maskText) { return options.maskText(value); } return value; }; const codeEl = E("code", {}, ""); const contentEl = E( "pre", { class: "tachyon-partial-modal__content" }, codeEl ); const stopRefreshTimer = () => { if (timer) { clearInterval(timer); timer = void 0; } }; const destroyLiveRefresh = () => { refreshSessionId += 1; pendingRefresh = false; pendingForcedRefresh = false; stopRefreshTimer(); observer?.disconnect(); observer = void 0; }; const scrollToBottom = () => { contentEl.scrollTop = contentEl.scrollHeight; }; const scheduleInitialScrollToBottom = () => { if (!shouldScrollToBottomOnMount || !body.isConnected) { return; } shouldScrollToBottomOnMount = false; requestAnimationFrame(() => { scrollToBottom(); }); }; const updateText = (nextText) => { const normalizedText = nextText ?? ""; const shouldStickToBottom = shouldScrollToBottomOnMount || contentEl.scrollTop + contentEl.clientHeight >= contentEl.scrollHeight - 16; if (normalizedText === currentText) { if (shouldStickToBottom) { requestAnimationFrame(() => { scrollToBottom(); }); } return; } currentText = normalizedText; codeEl.textContent = currentText; if (shouldStickToBottom) { requestAnimationFrame(() => { scrollToBottom(); }); } }; const updateDisplayedTextFromRaw = () => { updateText(getDisplayText(rawText)); }; const refreshText = async (force = false) => { if (!options?.getText || !force && !autoRefreshEnabled || refreshInFlight) { return; } if (!body.isConnected) { return; } refreshInFlight = true; const sessionId = refreshSessionId; try { const nextText = await options.getText({ maskValues: options.maskText ? false : maskValuesEnabled }); if (!body.isConnected || !force && !autoRefreshEnabled) { return; } if (sessionId !== refreshSessionId) { return; } const normalizedText = nextText ?? ""; rawText = normalizedText; updateText(getDisplayText(normalizedText)); } catch (error) { console.warn("[renderModal] failed to refresh modal content", error); } finally { refreshInFlight = false; if (pendingRefresh) { const shouldForceRefresh = pendingForcedRefresh; pendingRefresh = false; pendingForcedRefresh = false; if ((shouldForceRefresh || autoRefreshEnabled) && body.isConnected) { void refreshText(shouldForceRefresh); } } } }; const requestRefresh = () => { if (!options?.getText || !autoRefreshEnabled) { return; } if (refreshInFlight) { pendingRefresh = true; return; } void refreshText(); }; const requestForcedRefresh = () => { if (!options?.getText) { return; } if (refreshInFlight) { pendingRefresh = true; pendingForcedRefresh = true; return; } void refreshText(true); }; const startRefreshTimer = () => { if (!options?.getText || !autoRefreshEnabled || timer || typeof document === "undefined") { return; } timer = setInterval(() => { requestRefresh(); }, options.refreshMs ?? 3e3); }; const setAutoRefreshEnabled = (nextValue) => { autoRefreshEnabled = nextValue; refreshSessionId += 1; pendingRefresh = false; pendingForcedRefresh = false; if (autoRefreshInput) { autoRefreshInput.checked = nextValue; } if (nextValue) { startRefreshTimer(); requestRefresh(); return; } stopRefreshTimer(); }; const setMaskValuesEnabled = (nextValue) => { maskValuesEnabled = nextValue; refreshSessionId += 1; pendingRefresh = false; pendingForcedRefresh = false; if (maskValuesInput) { maskValuesInput.checked = nextValue; } if (options?.maskText) { updateDisplayedTextFromRaw(); return; } requestForcedRefresh(); }; const footerChildren = [ renderButton({ classNames: ["cbi-button-apply"], text: _("Download"), onClick: () => downloadAsTxt(currentText, name) }), renderButton({ classNames: ["cbi-button-apply"], text: _("Copy"), onClick: () => copyToClipboard(`\`\`\`${name} ${currentText} \`\`\``) }), renderButton({ classNames: ["cbi-button-remove"], text: _("Close"), onClick: () => { destroyLiveRefresh(); ui.hideModal(); } }) ]; if (options?.getText && options?.showAutoRefreshToggle) { autoRefreshInput = document.createElement("input"); autoRefreshInput.type = "checkbox"; autoRefreshInput.className = "cbi-input-checkbox"; autoRefreshInput.checked = autoRefreshEnabled; autoRefreshInput.addEventListener("change", () => { setAutoRefreshEnabled(autoRefreshInput.checked); }); footerChildren.unshift( E("label", { class: "tachyon-partial-modal__checkbox" }, [ autoRefreshInput, E( "span", { class: "tachyon-partial-modal__checkbox-text" }, options.autoRefreshLabel ?? _("Auto refresh") ) ]) ); } if ((options?.getText || options?.maskText) && options?.showMaskValuesToggle) { maskValuesInput = document.createElement("input"); maskValuesInput.type = "checkbox"; maskValuesInput.className = "cbi-input-checkbox"; maskValuesInput.checked = maskValuesEnabled; maskValuesInput.addEventListener("change", () => { setMaskValuesEnabled(maskValuesInput.checked); }); footerChildren.unshift( E("label", { class: "tachyon-partial-modal__checkbox" }, [ maskValuesInput, E( "span", { class: "tachyon-partial-modal__checkbox-text" }, options.maskValuesLabel ?? _("Hide values") ) ]) ); } const body = E("div", { class: "tachyon-partial-modal__body" }, [ E("div", {}, [ contentEl, E("div", { class: "tachyon-partial-modal__footer" }, footerChildren) ]) ]); if ((options?.getText || options?.startAtEnd) && typeof document !== "undefined") { observer = new MutationObserver(() => { if (!body.isConnected) { destroyLiveRefresh(); return; } scheduleInitialScrollToBottom(); }); observer.observe(document.body, { childList: true, subtree: true }); scheduleInitialScrollToBottom(); } if (options?.getText && typeof document !== "undefined") { startRefreshTimer(); requestRefresh(); } updateDisplayedTextFromRaw(); return body; } // src/partials/index.ts var PartialStyles = ` ${styles2} ${styles3} `; // src/tachyon/tabs/diagnostic/partials/renderAvailableActions.ts function renderAvailableActions({ restart, start, stop, enable, disable, globalCheck, doctor, aiDoctor, aiChat, strategyFuzzer, restoreNativeInternet, viewLogs, showSingBoxConfig, generateBugReport, checkServices, testLeaks }) { return E("div", { class: "tachyon_diagnostic-page__right-bar__actions" }, [ E("b", {}, _("Available actions")), ...insertIf(restart.visible, [ renderButton({ classNames: ["cbi-button-apply"], onClick: restart.onClick, icon: renderRotateCcwIcon24, text: _("Restart Tachyon"), loading: restart.loading, disabled: restart.disabled }) ]), ...insertIf(stop.visible, [ renderButton({ classNames: ["cbi-button-remove"], onClick: stop.onClick, icon: renderCircleStopIcon24, text: _("Stop Tachyon"), loading: stop.loading, disabled: stop.disabled }) ]), ...insertIf(start.visible, [ renderButton({ classNames: ["cbi-button-save"], onClick: start.onClick, icon: renderCirclePlayIcon24, text: _("Start Tachyon"), loading: start.loading, disabled: start.disabled }) ]), ...insertIf(disable.visible, [ renderButton({ classNames: ["cbi-button-remove"], onClick: disable.onClick, icon: renderPauseIcon24, text: _("Disable autostart"), loading: disable.loading, disabled: disable.disabled }) ]), ...insertIf(enable.visible, [ renderButton({ classNames: ["cbi-button-save"], onClick: enable.onClick, icon: renderPlayIcon24, text: _("Enable autostart"), loading: enable.loading, disabled: enable.disabled }) ]), ...insertIf(globalCheck.visible, [ renderButton({ onClick: globalCheck.onClick, icon: renderCircleCheckBigIcon24, text: _("Get global check"), loading: globalCheck.loading, disabled: globalCheck.disabled }) ]), ...insertIf(doctor.visible, [ renderButton({ onClick: doctor.onClick, icon: renderRotateCcwIcon24, text: _("Run doctor repair"), loading: doctor.loading, disabled: doctor.disabled }) ]), ...insertIf(aiDoctor.visible, [ renderButton({ classNames: ["cbi-button-action"], onClick: aiDoctor.onClick, icon: renderRotateCcwIcon24, text: _("Run AI Doctor"), loading: aiDoctor.loading, disabled: aiDoctor.disabled }) ]), ...insertIf(!!restoreNativeInternet?.visible, [ renderButton({ classNames: ["cbi-button-reset"], onClick: restoreNativeInternet.onClick, icon: renderCircleStopIcon24, text: _("Restore Native Internet"), loading: restoreNativeInternet.loading, disabled: restoreNativeInternet.disabled }) ]), ...insertIf(!!aiChat?.visible, [ renderButton({ classNames: ["cbi-button-action"], onClick: aiChat.onClick, icon: renderCircleCheckBigIcon24, text: _("AI Chat Assistant"), loading: aiChat.loading, disabled: aiChat.disabled }) ]), ...insertIf(!!strategyFuzzer?.visible, [ renderButton({ classNames: ["cbi-button-action"], onClick: strategyFuzzer.onClick, icon: renderCirclePlayIcon24, text: _("DPI Strategy Fuzzer"), loading: strategyFuzzer.loading, disabled: strategyFuzzer.disabled }) ]), ...insertIf(checkServices.visible, [ renderButton({ classNames: ["cbi-button-action"], onClick: checkServices.onClick, icon: renderSquareChartGanttIcon24, text: _("Check Services"), loading: checkServices.loading, disabled: checkServices.disabled }) ]), ...insertIf(!!testLeaks?.visible, [ renderButton({ classNames: ["cbi-button-action"], onClick: testLeaks.onClick, icon: renderCircleCheckBigIcon24, text: _("IP & DNS Leak Test"), loading: testLeaks.loading, disabled: testLeaks.disabled }) ]), ...insertIf(viewLogs.visible, [ renderButton({ onClick: viewLogs.onClick, icon: renderSquareChartGanttIcon24, text: _("View logs"), loading: viewLogs.loading, disabled: viewLogs.disabled }) ]), ...insertIf(showSingBoxConfig.visible, [ renderButton({ onClick: showSingBoxConfig.onClick, icon: renderCogIcon24, text: _("Show sing-box config"), loading: showSingBoxConfig.loading, disabled: showSingBoxConfig.disabled }) ]), ...insertIf(generateBugReport.visible, [ renderButton({ onClick: generateBugReport.onClick, icon: renderDownloadIcon24, text: _("Generate bug report"), loading: generateBugReport.loading, disabled: generateBugReport.disabled }) ]) ]); } // src/tachyon/tabs/diagnostic/partials/renderCheckSection.ts function renderCheckSummary(items) { if (!items.length) { return E("div", {}, ""); } const renderedItems = items.map((item) => { function getIcon() { const iconWrap = E("span", { class: "tachyon_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: `tachyon_diagnostic_alert__summary__item tachyon_diagnostic_alert__summary__item--${item.state}` }, [getIcon(), E("b", {}, item.key), E("div", {}, item.value)] ); }); return E( "div", { class: "tachyon_diagnostic_alert__summary" }, renderedItems ); } function renderLoadingState3(props) { const iconWrap = E("span", { class: "tachyon_diagnostic_alert__icon" }); iconWrap.appendChild(renderLoaderCircleIcon24()); return E( "div", { class: "tachyon_diagnostic_alert tachyon_diagnostic_alert--loading" }, [ iconWrap, E("div", { class: "tachyon_diagnostic_alert__content" }, [ E("b", { class: "tachyon_diagnostic_alert__title" }, props.title), E( "div", { class: "tachyon_diagnostic_alert__description" }, props.description ) ]), E("div", {}, ""), renderCheckSummary(props.items) ] ); } function renderWarningState(props) { const iconWrap = E("span", { class: "tachyon_diagnostic_alert__icon" }); iconWrap.appendChild(renderCircleAlertIcon24()); return E( "div", { class: "tachyon_diagnostic_alert tachyon_diagnostic_alert--warning" }, [ iconWrap, E("div", { class: "tachyon_diagnostic_alert__content" }, [ E("b", { class: "tachyon_diagnostic_alert__title" }, props.title), E( "div", { class: "tachyon_diagnostic_alert__description" }, props.description ) ]), E("div", {}, ""), renderCheckSummary(props.items) ] ); } function renderErrorState(props) { const iconWrap = E("span", { class: "tachyon_diagnostic_alert__icon" }); iconWrap.appendChild(renderCircleXIcon24()); return E( "div", { class: "tachyon_diagnostic_alert tachyon_diagnostic_alert--error" }, [ iconWrap, E("div", { class: "tachyon_diagnostic_alert__content" }, [ E("b", { class: "tachyon_diagnostic_alert__title" }, props.title), E( "div", { class: "tachyon_diagnostic_alert__description" }, props.description ) ]), E("div", {}, ""), renderCheckSummary(props.items) ] ); } function renderSuccessState(props) { const iconWrap = E("span", { class: "tachyon_diagnostic_alert__icon" }); iconWrap.appendChild(renderCircleCheckIcon24()); return E( "div", { class: "tachyon_diagnostic_alert tachyon_diagnostic_alert--success" }, [ iconWrap, E("div", { class: "tachyon_diagnostic_alert__content" }, [ E("b", { class: "tachyon_diagnostic_alert__title" }, props.title), E( "div", { class: "tachyon_diagnostic_alert__description" }, props.description ) ]), E("div", {}, ""), renderCheckSummary(props.items) ] ); } function renderSkippedState(props) { const iconWrap = E("span", { class: "tachyon_diagnostic_alert__icon" }); iconWrap.appendChild(renderCircleSlashIcon24()); return E( "div", { class: "tachyon_diagnostic_alert tachyon_diagnostic_alert--skipped" }, [ iconWrap, E("div", { class: "tachyon_diagnostic_alert__content" }, [ E("b", { class: "tachyon_diagnostic_alert__title" }, props.title), E( "div", { class: "tachyon_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/tachyon/tabs/diagnostic/partials/renderRunAction.ts function renderRunAction({ loading: loading2, disabled, click }) { return E("div", { class: "tachyon_diagnostic-page__run_check_wrapper" }, [ renderButton({ text: _("Run Diagnostic"), onClick: click, icon: renderSearchIcon24, loading: loading2, disabled, classNames: ["cbi-button-apply"] }) ]); } // src/tachyon/tabs/diagnostic/partials/renderServiceCheckModal.ts function formatRouteType(routeType) { if (!routeType) return _("Direct"); const clean = routeType.trim(); if (clean === "connection" || clean === "proxy" || clean === "outbound") { return _("Proxy"); } if (clean === "direct") { return _("Direct"); } if (clean === "auto") { return _("Auto"); } if (clean.startsWith("zapret2")) { return clean.replace(/^zapret2/, "Zapret 2"); } if (clean.startsWith("zapret")) { return clean.replace(/^zapret/, "Zapret"); } if (clean.startsWith("byedpi")) { return clean.replace(/^byedpi/, "ByeDPI"); } return clean; } function formatSectionName(name) { if (!name) return ""; const clean = name.trim(); const sectionMap = { "Базовая связность": _("Basic connectivity"), "Заблокированные в РФ": _("Blocked resources"), "Свои домены": _("Custom domains"), Custom: _("Custom"), "ChatGPT / OpenAI": "ChatGPT / OpenAI", "Gemini / Google AI": "Gemini / Google AI", "X (Twitter)": "Twitter (X)", "UDP / QUIC": _("UDP / QUIC protocols"), Telegram: "Telegram", Instagram: "Instagram", YouTube: "YouTube", TikTok: "TikTok", Discord: "Discord", Facebook: "Facebook", WhatsApp: "WhatsApp", Netflix: "Netflix", Spotify: "Spotify", Twitch: "Twitch", Steam: "Steam", GitHub: "GitHub" }; return sectionMap[clean] || clean; } function renderStatusBadge(statusClass, isTesting = false, isPending = false) { if (isPending) { return E( "span", { class: "badge cbi-value-title", style: "opacity: 0.6; font-size: 11px; padding: 2px 8px;" }, _("Pending...") ); } if (isTesting) { return E( "span", { class: "badge cbi-button-action", style: "font-size: 11px; padding: 2px 8px;" }, _("Testing...") ); } const cleanStatus = statusClass || ""; const isOk = cleanStatus.toUpperCase().includes("OK") || cleanStatus === "200" || cleanStatus === "OK"; if (isOk) { return E( "span", { class: "badge cbi-button-save", style: "font-weight: bold; font-size: 11px; padding: 2px 8px;" }, _("Available") ); } return E( "span", { class: "badge cbi-button-reset", style: "font-weight: bold; font-size: 11px; padding: 2px 8px;" }, cleanStatus || _("Unavailable") ); } function renderServiceCheckModal() { const container = E( "div", { class: "tachyon-service-check-modal-wrapper", style: "width: 100%; box-sizing: border-box;" }, [ E( "p", { style: "text-align: center; margin-top: 20px;" }, _("Loading service list...") ), E( "div", { class: "spinning", style: "text-align: center; font-size: 24px; color: var(--border-color, #007bff);" }, "⚡" ) ] ); const modalContent = E( "div", { style: "width: 100%; box-sizing: border-box;" }, [ container, E( "div", { id: "tachyon-service-check-footer", style: "margin-top: 15px; display: flex; justify-content: space-between; align-items: center; gap: 10px; border-top: 1px solid var(--border-color, rgba(128,128,128,0.2)); padding-top: 12px; flex-wrap: wrap;" }, [ renderButton({ text: _("Close"), onClick: () => ui.hideModal() }) ] ) ] ); ui.showModal(_("Service availability check"), modalContent); const loadTargets = (targetMode) => { container.innerHTML = ""; container.appendChild( E( "p", { style: "text-align: center; margin-top: 20px;" }, _("Loading service list...") ) ); const args = targetMode === "all" ? ["get-targets", "all"] : ["get-targets"]; callBaseMethod(Tachyon.AvailableMethods.SERVICE_HEALTH_CHECK, args).then((res) => { const response = res; container.innerHTML = ""; if (!response || !response.success) { container.appendChild( E( "p", { style: "color: var(--color-danger, #dc3545);" }, _("Failed to start service check.") ) ); return; } let targets = []; try { if (typeof response.data === "string") { targets = JSON.parse(response.data); } else if (Array.isArray(response.data)) { targets = response.data; } } catch (_e) { container.appendChild( E( "p", { style: "color: var(--color-danger, #dc3545);" }, _("Failed to parse target list.") ) ); return; } if (targets.length === 0) { container.appendChild( E("p", {}, _("No check targets found in the configuration.")) ); return; } const activeSectionsBtn = renderButton({ text: _("Active routes"), classNames: [ targetMode === "active" ? "cbi-button-action" : "cbi-button-neutral" ], onClick: () => loadTargets("active") }); activeSectionsBtn.style.fontSize = "12px"; activeSectionsBtn.style.padding = "4px 12px"; const allProfilesBtn = renderButton({ text: _("All services"), classNames: [ targetMode === "all" ? "cbi-button-action" : "cbi-button-neutral" ], onClick: () => loadTargets("all") }); allProfilesBtn.style.fontSize = "12px"; allProfilesBtn.style.padding = "4px 12px"; const modeSwitcherBar = E( "div", { style: "display: flex; gap: 10px; align-items: center; margin-bottom: 14px; border-bottom: 1px solid var(--border-color, rgba(128,128,128,0.2)); padding-bottom: 10px;" }, [ E( "span", { style: "font-weight: 600; font-size: 13px; opacity: 0.9;" }, _("Check mode:") ), activeSectionsBtn, allProfilesBtn ] ); const sectionNames = Array.from(new Set(targets.map((t) => t.section))); let activeFilter = "ALL"; let searchQuery2 = ""; const totalStatEl = E( "b", { style: "font-size: 18px; display: block; margin-top: 2px;" }, `${targets.length}` ); const passedStatEl = E( "b", { style: "font-size: 18px; color: var(--color-success, #28a745); display: block; margin-top: 2px;" }, "0" ); const failedStatEl = E( "b", { style: "font-size: 18px; color: var(--color-danger, #dc3545); display: block; margin-top: 2px;" }, "0" ); const latencyStatEl = E( "b", { style: "font-size: 18px; color: var(--color-info, #17a2b8); display: block; margin-top: 2px;" }, "-" ); const createStatCard = (title, valueEl) => { return E( "div", { class: "cbi-value", style: "flex: 1 1 110px; min-width: 100px; padding: 8px 10px; border: 1px solid var(--border-color, rgba(128,128,128,0.25)); border-radius: 6px; text-align: center; background: var(--background-color-secondary, rgba(128,128,128,0.05)); margin: 0; box-sizing: border-box;" }, [ E( "small", { style: "display: block; opacity: 0.75; font-size: 11px;" }, title ), valueEl ] ); }; const statsBar = E( "div", { style: "display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; width: 100%; box-sizing: border-box;" }, [ createStatCard(_("Total targets"), totalStatEl), createStatCard(_("Available"), passedStatEl), createStatCard(_("Unavailable"), failedStatEl), createStatCard(_("Avg. latency"), latencyStatEl) ] ); const progressBarInner = E("div", { style: "width: 0%; height: 100%; background: var(--color-success, #28a745); transition: width 0.2s ease;" }); const progressBarContainer = E( "div", { style: "width: 100%; height: 6px; background: var(--border-color, rgba(128,128,128,0.2)); border-radius: 3px; overflow: hidden; margin-bottom: 14px; display: none;" }, [progressBarInner] ); const sectionSelect = E( "select", { class: "cbi-input-select", style: "padding: 4px 8px; font-size: 12px; border-radius: 4px; max-width: 240px;" }, [ E( "option", { value: "ALL" }, `${_("All services")} (${targets.length})` ), ...sectionNames.map((sec) => { const count = targets.filter((t) => t.section === sec).length; return E( "option", { value: sec }, `${formatSectionName(sec)} (${count})` ); }) ] ); sectionSelect.onchange = () => { activeFilter = sectionSelect.value; applyTableFilter(); }; const searchInput = E("input", { type: "text", placeholder: _("Search domain or service..."), class: "cbi-input-text", style: "width: 200px; padding: 4px 10px; font-size: 12px; border-radius: 4px;" }); searchInput.oninput = (e) => { const targetInput = e.target; searchQuery2 = (targetInput?.value || "").toLowerCase().trim(); applyTableFilter(); }; const toolbar = E( "div", { style: "display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 10px; flex-wrap: wrap;" }, [ E( "div", { style: "display: flex; gap: 8px; align-items: center; flex-wrap: wrap;" }, [ E( "span", { style: "font-size: 12px; opacity: 0.8;" }, _("Filter by service:") ), sectionSelect ] ), searchInput ] ); const tableHead = E("tr", { class: "tr cbi-section-table-titles" }, [ E( "th", { class: "th", style: "width: 28%; padding: 8px;" }, _("Service / Target") ), E( "th", { class: "th", style: "width: 17%; padding: 8px;" }, _("Route") ), E( "th", { class: "th", style: "width: 20%; padding: 8px;" }, "IP / DNS" ), E( "th", { class: "th", style: "text-align: right; width: 7%; padding: 8px 4px;" }, "TCP" ), E( "th", { class: "th", style: "text-align: right; width: 7%; padding: 8px 4px;" }, "TLS" ), E( "th", { class: "th", style: "text-align: right; width: 7%; padding: 8px 4px;" }, "HTTP" ), E( "th", { class: "th", style: "text-align: center; width: 14%; padding: 8px 6px;" }, _("Status") ) ]); const tableBody = E("tbody", {}); const table = E( "table", { class: "table cbi-section-table", style: "width: 100%; margin: 0; table-layout: fixed; box-sizing: border-box;" }, [ E( "thead", { style: "position: sticky; top: 0; background: var(--background-color-primary, #ffffff); z-index: 2;" }, [tableHead] ), tableBody ] ); const tableScrollWrapper = E( "div", { style: "max-height: 380px; overflow-y: auto; overflow-x: hidden; border: 1px solid var(--border-color, rgba(128,128,128,0.25)); border-radius: 6px; width: 100%; box-sizing: border-box;" }, [table] ); const rowMap = []; targets.forEach((item, index) => { const rowClass = index % 2 === 0 ? "cbi-rowstyle-1" : "cbi-rowstyle-2"; const badge = renderStatusBadge("", false, true); const tr = E("tr", { class: `tr ${rowClass}` }, [ E( "td", { class: "td", style: "word-break: break-all; padding: 8px;" }, [ E( "span", { style: "font-weight: 600; font-size: 12px;" }, formatSectionName(item.section) ), E("br"), E("small", { style: "opacity: 0.8;" }, item.domain) ] ), E("td", { class: "td", style: "font-size: 12px; padding: 8px;" }, [ E( "span", { class: "badge cbi-value-title", style: "font-size: 11px; padding: 2px 6px;" }, formatRouteType(item.route_type) ) ]), E("td", { class: "td", style: "font-size: 12px; padding: 8px;" }, [ E("span", {}, "?"), E("br"), E("small", { style: "opacity: 0.6;" }, `-`) ]), E( "td", { class: "td", style: "text-align: right; font-size: 12px; padding: 8px 4px;" }, "-" ), E( "td", { class: "td", style: "text-align: right; font-size: 12px; padding: 8px 4px;" }, "-" ), E( "td", { class: "td", style: "text-align: right; font-size: 12px; padding: 8px 4px;" }, "-" ), E( "td", { class: "td", style: "text-align: center; padding: 8px 6px;" }, badge ) ]); rowMap.push({ target: item, tr }); tableBody.appendChild(tr); }); const applyTableFilter = () => { rowMap.forEach(({ target, tr }) => { const matchesSection = activeFilter === "ALL" || target.section === activeFilter; const matchesSearch = !searchQuery2 || target.domain.toLowerCase().includes(searchQuery2) || target.section.toLowerCase().includes(searchQuery2) || formatSectionName(target.section).toLowerCase().includes(searchQuery2); tr.style.display = matchesSection && matchesSearch ? "" : "none"; }); }; const updateSummaryStats = () => { let passed = 0; let failed2 = 0; let totalLat = 0; let countLat = 0; rowMap.forEach(({ result }) => { if (result) { if (result.success) passed++; else failed2++; const lat = result.http_ms || result.tls_ms || result.tcp_ms || 0; if (lat > 0) { totalLat += lat; countLat++; } } }); passedStatEl.textContent = `${passed}`; failedStatEl.textContent = `${failed2}`; latencyStatEl.textContent = countLat > 0 ? `${Math.round(totalLat / countLat)} ms` : "-"; }; container.appendChild(modeSwitcherBar); container.appendChild(statsBar); container.appendChild(progressBarContainer); container.appendChild(toolbar); container.appendChild(tableScrollWrapper); const footer = document.getElementById("tachyon-service-check-footer"); if (footer) { footer.innerHTML = ""; const customDomainInput = E("input", { type: "text", id: "custom-domain-input", placeholder: _( "Enter a domain or IP to check (e.g. example.com)..." ), class: "cbi-input-text", style: "width: 270px; font-size: 12px;" }); const customBtn = renderButton({ text: _("Check domain"), classNames: ["cbi-button-neutral"], onClick: async () => { const domain = customDomainInput.value.trim(); if (!domain) return; customBtn.disabled = true; customBtn.textContent = "..."; try { const cRes = await callBaseMethod( Tachyon.AvailableMethods.SERVICE_HEALTH_CHECK, ["check-custom", domain] ); if (cRes && cRes.success) { const cResults = typeof cRes.data === "string" ? JSON.parse(cRes.data) : cRes.data || []; if (cResults && cResults.length > 0) { const cItem = cResults[0]; const cBadge = renderStatusBadge(cItem.status_class); const customTr = E("tr", { class: "tr cbi-rowstyle-1" }, [ E( "td", { class: "td", style: "word-break: break-all; padding: 8px;" }, [ E( "span", { style: "font-weight: 600;" }, formatSectionName(cItem.section) ), E("br"), E("small", {}, cItem.domain) ] ), E( "td", { class: "td", style: "font-size: 12px; padding: 8px;" }, [ E( "span", { class: "badge cbi-value-title", style: "font-size: 11px; padding: 2px 6px;" }, formatRouteType(cItem.route_type) ) ] ), E( "td", { class: "td", style: "font-size: 12px; padding: 8px;" }, [ cItem.ip || "?", E("br"), E("small", {}, `${cItem.dns_ms}ms`) ] ), E( "td", { class: "td", style: "text-align: right; padding: 8px 4px;" }, cItem.tcp_ms > 0 ? `${cItem.tcp_ms}` : "-" ), E( "td", { class: "td", style: "text-align: right; padding: 8px 4px;" }, cItem.tls_ms > 0 ? `${cItem.tls_ms}` : "-" ), E( "td", { class: "td", style: "text-align: right; padding: 8px 4px;" }, cItem.http_ms > 0 ? `${cItem.http_ms}` : "-" ), E( "td", { class: "td", style: "text-align: center; padding: 8px 6px;" }, cBadge ) ]); tableBody.insertBefore(customTr, tableBody.firstChild); rowMap.unshift({ target: { section: "Custom", route_type: "auto", domain: cItem.domain }, tr: customTr, result: cItem }); updateSummaryStats(); customDomainInput.value = ""; } } } catch (_e) { } finally { customBtn.disabled = false; customBtn.textContent = _("Check domain"); } } }); const leftWrap = E( "div", { style: "display: flex; gap: 8px; align-items: center; flex-wrap: wrap;" }, [customDomainInput, customBtn] ); const checkAllBtn = renderButton({ text: _("Check all"), classNames: ["cbi-button-action"], onClick: async () => { checkAllBtn.disabled = true; checkAllBtn.textContent = _("Testing..."); progressBarContainer.style.display = "block"; progressBarInner.style.width = "0%"; for (let i = 0; i < rowMap.length; i++) { const itemObj = rowMap[i]; const { target, tr } = itemObj; progressBarInner.style.width = `${Math.round((i + 1) / rowMap.length * 100)}%`; const badgeCell = tr.childNodes[6]; badgeCell.innerHTML = ""; badgeCell.appendChild(renderStatusBadge("", true)); try { const cRes = await callBaseMethod( Tachyon.AvailableMethods.SERVICE_HEALTH_CHECK, ["check-domain", JSON.stringify(target)] ); if (cRes && cRes.success) { const cResults = typeof cRes.data === "string" ? JSON.parse(cRes.data) : cRes.data || []; if (cResults && cResults.length > 0) { const cItem = cResults[0]; itemObj.result = cItem; const cBadge = renderStatusBadge(cItem.status_class); tr.childNodes[2].innerHTML = ""; tr.childNodes[2].appendChild( document.createTextNode(cItem.ip || "?") ); tr.childNodes[2].appendChild(E("br")); tr.childNodes[2].appendChild( E( "small", { style: "opacity: 0.6;" }, `${cItem.dns_ms}ms` ) ); tr.childNodes[3].textContent = cItem.tcp_ms > 0 ? `${cItem.tcp_ms}` : "-"; tr.childNodes[4].textContent = cItem.tls_ms > 0 ? `${cItem.tls_ms}` : "-"; tr.childNodes[5].textContent = cItem.http_ms > 0 ? `${cItem.http_ms}` : "-"; badgeCell.innerHTML = ""; badgeCell.appendChild(cBadge); } } } catch (_e) { badgeCell.innerHTML = ""; badgeCell.appendChild(renderStatusBadge("Failed")); } updateSummaryStats(); } checkAllBtn.textContent = _("Check again"); checkAllBtn.disabled = false; } }); const closeBtn = renderButton({ text: _("Close"), onClick: () => ui.hideModal() }); const rightWrap = E("div", { style: "display: flex; gap: 8px;" }, [ checkAllBtn, closeBtn ]); footer.appendChild(leftWrap); footer.appendChild(rightWrap); } }).catch(() => { container.innerHTML = ""; container.appendChild( E( "p", { style: "color: var(--color-danger, #dc3545);" }, _("Failed to start service check.") ) ); }); }; loadTargets("active"); } // src/tachyon/tabs/diagnostic/partials/renderAiChatModal.ts var chatHistory = [ { sender: "assistant", text: _( "Hello! I am Tachyon AI Assistant. How can I help you today? Ask me about domain blocks, diagnostics, or router settings." ), timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) } ]; function renderAiChatModal() { const messageListContainer = E("div", { style: "height: 360px; overflow-y: auto; padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.2)); border: 1px solid var(--border-color, rgba(255,255,255,0.15)); border-radius: 8px; display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; width: 100%; box-sizing: border-box;" }); const renderMessages = () => { messageListContainer.innerHTML = ""; chatHistory.forEach((msg) => { const isUser = msg.sender === "user"; const bubble = E( "div", { style: `max-width: 82%; align-self: ${isUser ? "flex-end" : "flex-start"}; background: ${isUser ? "#007bff" : "var(--background-color-primary, #2a2a2a)"}; color: #fff; padding: 8px 12px; border-radius: 12px; font-size: 13px; border: 1px solid ${isUser ? "#0056b3" : "var(--border-color, rgba(255,255,255,0.15))"}; line-height: 1.4; word-break: break-word;` }, [ E("div", {}, msg.text), E( "small", { style: "display: block; opacity: 0.65; text-align: right; font-size: 10px; margin-top: 4px;" }, msg.timestamp ) ] ); messageListContainer.appendChild(bubble); }); messageListContainer.scrollTop = messageListContainer.scrollHeight; }; renderMessages(); const chatInput = E("input", { type: "text", placeholder: _("Ask Tachyon AI Assistant a question..."), class: "cbi-input-text", style: "flex: 1 1 auto; min-width: 0; font-size: 13px; padding: 6px 10px; border-radius: 6px; box-sizing: border-box;" }); const sendBtn = renderButton({ text: _("Send"), classNames: ["cbi-button-action"], onClick: () => handleSend() }); let sendInFlight = false; const handleSend = async (queryText) => { const text = (queryText || chatInput.value).trim(); if (!text || sendInFlight) return; sendInFlight = true; chatInput.disabled = true; sendBtn.disabled = true; const userMsg = { sender: "user", text, timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }; chatHistory.push(userMsg); chatInput.value = ""; renderMessages(); const typingMsg = { sender: "assistant", text: "🤖 " + _("Thinking..."), timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }; chatHistory.push(typingMsg); renderMessages(); try { const res = await callBaseMethod( Tachyon.AvailableMethods.AI_DOCTOR, [text], "/usr/bin/tachyon", { timeout: 12e4 } ); chatHistory.pop(); let answerText = _("Failed to receive response from AI service."); if (res && res.success) { const d = res.data; if (typeof d === "object" && d !== null) { answerText = String( d.report ?? d.summary ?? d.message ?? d.raw ?? JSON.stringify(d) ); } else if (typeof d === "string") { try { const parsed = JSON.parse(d); answerText = String( parsed.report ?? parsed.summary ?? parsed.message ?? parsed.raw ?? d ); } catch (_e) { answerText = d; } } } chatHistory.push({ sender: "assistant", text: answerText, timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }); } catch (e) { chatHistory.pop(); const errMsg = e instanceof Error ? e.message : _("Server unavailable"); chatHistory.push({ sender: "assistant", text: "❌ " + _("AI service error") + ": " + errMsg, timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }); } renderMessages(); sendInFlight = false; chatInput.disabled = false; sendBtn.disabled = false; }; chatInput.onkeydown = (e) => { if (e.key === "Enter") handleSend(); }; const quickPrompts = [ { label: "🩺 " + _("Check YouTube"), query: "Check YouTube availability" }, { label: "🔍 " + _("Why Discord fails?"), query: "Why Discord fails?" }, { label: "🛠️ " + _("Full System Diagnostic"), query: "Run full system diagnostic" } ]; const quickPromptsBar = E( "div", { style: "display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; width: 100%; box-sizing: border-box;" }, quickPrompts.map((qp) => { const btn = E( "button", { type: "button", class: "cbi-button cbi-button-neutral", style: "font-size: 11px; padding: 2px 8px; border-radius: 10px;" }, qp.label ); btn.onclick = () => handleSend(qp.query); return btn; }) ); const inputToolbar = E( "div", { style: "display: flex; gap: 8px; align-items: center; width: 100%; box-sizing: border-box;" }, [chatInput, sendBtn] ); const modalWrapper = E( "div", { style: "width: 100%; box-sizing: border-box;" }, [quickPromptsBar, messageListContainer, inputToolbar] ); const modalContent = E( "div", { style: "width: 100%; box-sizing: border-box;" }, [ modalWrapper, E( "div", { style: "margin-top: 12px; display: flex; justify-content: flex-end; border-top: 1px solid var(--border-color, rgba(255,255,255,0.15)); padding-top: 10px;" }, [ renderButton({ text: _("Close"), onClick: () => ui.hideModal() }) ] ) ] ); ui.showModal(_("AI Chat & Tachyon Assistant"), modalContent); } // src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts function renderStrategyFuzzerModal(ruleNames = []) { let pollingInterval = null; let isPolling = false; let activeTab2 = "benchmark"; let selectedEngine = "zapret2"; let selectedTarget = "youtube_suite"; let customUrl = ""; let selectedRuleSection = ""; let selectedMode = "presets"; let isRunning = false; let currentState = null; let resultFilter = "all"; let autoApplyEnabled = false; let currentDpiDetection = null; let patternsConfig = { zapret2: { splits: ["1", "2", "3", "midsld", "sniext+4", "1,midsld", "1,sniext+2"], foolings: ["badseq", "md5sig", "badack", "datanoack", "fakeddrop"], ttls: [2, 3, 4, 5, 6, 8], seqovls: ["1", "2"], wsizes: ["1"], payloads: ["tls_client_hello", "http_req", "quic_initial"] }, zapret: { splits: ["1", "2", "midsld", "sniext+4", "1,midsld"], foolings: ["badseq", "md5sig", "badack", "datanoack"], ttls: [2, 3, 4, 6, 8], split_modes: ["split2", "disorder2", "fake,split2", "fake,disorder2"] }, byedpi: { splits: ["1", "2", "1+sniext", "midsld"], disorders: ["1", "2"], ttls: [2, 3, 4, 6, 8], oobs: ["1", "2"], autos: ["t,r,a,s", "r,s", "t,a"], tlsrecs: ["1+sniext"], ipfrags: ["24"] }, custom_strategies: [] }; const modalContainer = E("div", { class: "tachyon_fuzzer_modal", style: "display: flex; flex-direction: column; gap: 14px; width: 100%; box-sizing: border-box;" }); const tabButtons = {}; const createTabButton = (id, icon, label) => { const btn = E( "button", { type: "button", class: `cbi-button ${activeTab2 === id ? "cbi-button-action" : "cbi-button-neutral"}`, style: "display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; font-size: 12px; font-weight: 500; border-radius: 6px; cursor: pointer;" }, [E("span", {}, icon), E("span", {}, label)] ); btn.addEventListener("click", () => { activeTab2 = id; updateTabVisibility(); }); tabButtons[id] = btn; return btn; }; const tabBar = E( "div", { style: "display: flex; gap: 8px; border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.12)); padding-bottom: 10px; flex-wrap: wrap;" }, [ createTabButton("benchmark", "⚡", _("Benchmark & Test")), createTabButton("patterns", "🛠️", _("Pattern Builder")), createTabButton("custom", "➕", _("My Strategies")), createTabButton("ai", "🧠", _("AI Doctor RAG")), createTabButton("history", "📋", _("History")) ] ); const tabContentBenchmark = E("div", { style: "display: flex; flex-direction: column; gap: 14px;" }); const tabContentPatterns = E("div", { style: "display: none; flex-direction: column; gap: 14px;" }); const tabContentCustom = E("div", { style: "display: none; flex-direction: column; gap: 14px;" }); const tabContentAi = E("div", { style: "display: none; flex-direction: column; gap: 14px;" }); const tabContentHistory = E("div", { style: "display: none; flex-direction: column; gap: 14px;" }); const updateTabVisibility = () => { for (const key in tabButtons) { tabButtons[key].className = `cbi-button ${activeTab2 === key ? "cbi-button-action" : "cbi-button-neutral"}`; } tabContentBenchmark.style.display = activeTab2 === "benchmark" ? "flex" : "none"; tabContentPatterns.style.display = activeTab2 === "patterns" ? "flex" : "none"; tabContentCustom.style.display = activeTab2 === "custom" ? "flex" : "none"; tabContentAi.style.display = activeTab2 === "ai" ? "flex" : "none"; tabContentHistory.style.display = activeTab2 === "history" ? "flex" : "none"; if (activeTab2 === "patterns") renderPatternsTab(); if (activeTab2 === "custom") renderCustomTab(); if (activeTab2 === "history") renderHistoryTab(); }; const controlsGrid = E("div", { style: "display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); gap: 10px; align-items: end;" }); const engineSelect = E( "select", { class: "cbi-input-select", style: "width: 100%;" }, [ E("option", { value: "zapret2" }, _("Zapret v2 (nfqws2)")), E("option", { value: "zapret" }, _("Zapret v1 (nfqws)")), E("option", { value: "byedpi" }, _("ByeDPI (ciadpi)")), E("option", { value: "all" }, _("All Engines (Full Matrix)")) ] ); engineSelect.addEventListener("change", () => { selectedEngine = engineSelect.value; }); const engineGroup = E( "div", { style: "display: flex; flex-direction: column; gap: 4px;" }, [ E( "label", { style: "font-size: 11px; font-weight: 600; opacity: 0.85;" }, _("DPI Engine") ), engineSelect ] ); const targetSelect = E( "select", { class: "cbi-input-select", style: "width: 100%;" }, [ E( "option", { value: "youtube_suite", selected: true }, _("🎬 YouTube Full Suite (Web + 4K Stream + CDN)") ), E( "option", { value: "discord_suite" }, _("💬 Discord Suite (Gateway + Voice + CDN)") ), E( "option", { value: "twitch_suite" }, _("🟣 Twitch Suite (Live Stream + HLS CDN)") ), E( "option", { value: "twitter_suite" }, _("🐦 X / Twitter Suite (API + Media CDN)") ), E("option", { value: "chatgpt_suite" }, _("🤖 ChatGPT / OpenAI Suite")), E( "option", { value: "instagram_suite" }, _("📸 Instagram / Meta (Web + CDN)") ), E( "option", { value: "telegram_suite" }, _("✈️ Telegram Suite (Web + Bot API)") ), E("option", { value: "rutracker_suite" }, _("🏴☠️ RuTracker Suite")), E("option", { value: "quic_http3" }, _("⚡ QUIC / HTTP/3 (UDP 443)")), E("option", { value: "custom" }, _("🌐 Custom Target URL...")) ] ); const customUrlInput = E("input", { type: "text", class: "cbi-input-text", placeholder: "https://example.com", style: "width: 100%; display: none; margin-top: 4px;" }); targetSelect.addEventListener("change", () => { selectedTarget = targetSelect.value; customUrlInput.style.display = selectedTarget === "custom" ? "block" : "none"; }); customUrlInput.addEventListener("input", () => { customUrl = customUrlInput.value.trim(); }); const targetGroup = E( "div", { style: "display: flex; flex-direction: column; gap: 4px;" }, [ E( "label", { style: "font-size: 11px; font-weight: 600; opacity: 0.85;" }, _("Target Service / Suite") ), targetSelect, customUrlInput ] ); const modeSelect = E( "select", { class: "cbi-input-select", style: "width: 100%;" }, [ E( "option", { value: "presets", selected: true }, _("⚡ Quick Benchmark (Presets ~30+)") ), E( "option", { value: "combinatorial" }, _("🔍 Combinatorial Deep Fuzzing (~150-300+)") ), E("option", { value: "custom" }, _("🛠️ My Custom Strategies Only")) ] ); modeSelect.addEventListener("change", () => { selectedMode = modeSelect.value; }); const modeGroup = E( "div", { style: "display: flex; flex-direction: column; gap: 4px;" }, [ E( "label", { style: "font-size: 11px; font-weight: 600; opacity: 0.85;" }, _("Search Mode") ), modeSelect ] ); const ruleSelect = E( "select", { class: "cbi-input-select", style: "width: 100%;" }, [ E("option", { value: "", selected: true }, _("Provider Global Default")), ...ruleNames.map((r) => E("option", { value: r }, `${_("Rule:")} ${r}`)) ] ); ruleSelect.addEventListener("change", () => { selectedRuleSection = ruleSelect.value; }); const ruleGroup = E( "div", { style: "display: flex; flex-direction: column; gap: 4px;" }, [ E( "label", { style: "font-size: 11px; font-weight: 600; opacity: 0.85;" }, _("Apply Strategy To") ), ruleSelect ] ); controlsGrid.append(engineGroup, targetGroup, modeGroup, ruleGroup); const autoApplyCheckbox = E("input", { type: "checkbox", id: "tachyon-fuzzer-auto-apply", style: "margin: 0;" }); autoApplyCheckbox.addEventListener("change", () => { autoApplyEnabled = autoApplyCheckbox.checked; }); const autoApplyGroup = E( "div", { style: "display: flex; align-items: end; padding: 4px 0; grid-column: 1 / -1;" }, [ E( "div", { style: "font-size: 11px; cursor: pointer; display: flex; align-items: center; gap: 4px;" }, [autoApplyCheckbox, E("span", {}, _("Auto-apply best strategy"))] ) ] ); controlsGrid.append(autoApplyGroup); const dpiDetectionBanner = E("div", { id: "tachyon-fuzzer-dpi-banner", style: "display: none; padding: 10px 14px; border-radius: 6px; font-size: 12px; line-height: 1.4;" }); const progressContainer = E( "div", { style: "display: none; flex-direction: column; gap: 6px; padding: 10px 14px; background: var(--background-color-secondary, rgba(0,0,0,0.18)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.08));" }, [ E( "div", { style: "display: flex; justify-content: space-between; font-size: 12px; font-weight: 600;" }, [ E( "span", { id: "tachyon-fuzzer-status-text" }, _("Benchmarking in progress...") ), E("span", { id: "tachyon-fuzzer-progress-pct" }, "0%") ] ), E( "div", { style: "width: 100%; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden;" }, [ E("div", { id: "tachyon-fuzzer-progress-bar", style: "height: 100%; width: 0%; background: #007bff; transition: width 0.3s ease;" }) ] ), E( "div", { id: "tachyon-fuzzer-current-strategy", style: "font-size: 11px; opacity: 0.75; font-family: monospace;" }, "" ) ] ); const applyBestBtn = renderButton({ text: _("🏆 Apply Best Match"), classNames: ["cbi-button-save"], disabled: true, onClick: () => handleApplyBest() }); const filterToolbar = E( "div", { style: "display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px;" }, [ E("div", { style: "display: flex; gap: 6px; align-items: center;" }, [ E( "span", { style: "font-size: 11px; opacity: 0.7; margin-right: 4px;" }, _("Filter:") ), (() => { const bAll = E( "button", { type: "button", class: "cbi-button cbi-button-action", style: "padding: 2px 10px; font-size: 11px;" }, _("All") ); const bOk = E( "button", { type: "button", class: "cbi-button cbi-button-neutral", style: "padding: 2px 10px; font-size: 11px;" }, _("✅ Working only") ); const bFast = E( "button", { type: "button", class: "cbi-button cbi-button-neutral", style: "padding: 2px 10px; font-size: 11px;" }, _("⚡ Fast (>1MB/s)") ); const setFilter = (f, btn) => { resultFilter = f; [bAll, bOk, bFast].forEach( (b) => b.className = "cbi-button cbi-button-neutral" ); btn.className = "cbi-button cbi-button-action"; if (currentState) renderResults(currentState); }; bAll.addEventListener("click", () => setFilter("all", bAll)); bOk.addEventListener("click", () => setFilter("success", bOk)); bFast.addEventListener("click", () => setFilter("fast", bFast)); return E("div", { style: "display: flex; gap: 4px;" }, [ bAll, bOk, bFast ]); })() ]), E("div", { style: "display: flex; gap: 8px;" }, [applyBestBtn]) ] ); const resultsContainer = E("div", { style: "max-height: 380px; overflow-y: auto; overflow-x: auto; -webkit-overflow-scrolling: touch; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; width: 100%; box-sizing: border-box;" }); const tableEl = E( "table", { class: "table cbi-section-table", style: "width: 100%; min-width: 620px; margin: 0; font-size: 12px; text-align: left; border-collapse: collapse;" }, [ E( "thead", { class: "cbi-section-table-titles", style: "background: var(--background-color-secondary, rgba(0,0,0,0.25)); position: sticky; top: 0; z-index: 2;" }, [ E("tr", { class: "cbi-section-table-titles" }, [ E( "th", { class: "cbi-section-table-cell", style: "padding: 8px 10px; width: 35px;" }, "#" ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px 10px; width: 75px;" }, _("Engine") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px 10px;" }, _("Strategy & Parameters") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px 10px; width: 95px;" }, _("Status / Suite") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px 10px; width: 65px;" }, _("TTFB") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px 10px; width: 75px;" }, _("Speed") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px 10px; width: 80px;" }, _("Score") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px 10px; width: 85px; text-align: center;" }, _("Actions") ) ]) ] ), E("tbody", { id: "tachyon-fuzzer-results-tbody" }, [ E( "tr", {}, E( "td", { colSpan: 8, style: "padding: 24px; text-align: center; opacity: 0.6;" }, _( 'No benchmark results yet. Configure options and click "Start Benchmark".' ) ) ) ]) ] ); resultsContainer.appendChild(tableEl); const startBtn = renderButton({ text: _("🚀 Start Benchmark"), classNames: ["cbi-button-action"], onClick: () => handleToggleRun() }); const footerActions = E( "div", { style: "display: flex; justify-content: space-between; align-items: center; padding-top: 6px;" }, [ startBtn, renderButton({ text: _("Close"), classNames: ["cbi-button-neutral"], onClick: () => { stopPolling(); ui.hideModal(); } }) ] ); tabContentBenchmark.append( controlsGrid, dpiDetectionBanner, progressContainer, filterToolbar, resultsContainer, footerActions ); const renderChipGroup = (title, items, onAdd, onRemove) => { const input = E("input", { type: "text", class: "cbi-input-text", placeholder: _("+ add"), style: "width: 80px; padding: 2px 6px; font-size: 11px;" }); input.addEventListener("keydown", (e) => { if (e.key === "Enter") { const v = input.value.trim(); if (v) { onAdd(v); input.value = ""; renderPatternsTab(); } } }); const chips = items.map((item, idx) => { const chip = E( "span", { style: "display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: var(--background-color-secondary, rgba(255,255,255,0.08)); border: 1px solid var(--border-color, rgba(255,255,255,0.12)); border-radius: 4px; font-size: 11px; font-family: monospace;" }, [ E("span", {}, String(item)), (() => { const del = E( "span", { style: "cursor: pointer; opacity: 0.6; font-size: 12px; margin-left: 2px;" }, "×" ); del.addEventListener("click", () => { onRemove(idx); renderPatternsTab(); }); return del; })() ] ); return chip; }); return E( "div", { style: "display: flex; flex-direction: column; gap: 4px;" }, [ E( "span", { style: "font-size: 11px; font-weight: 600; opacity: 0.8;" }, title ), E( "div", { style: "display: flex; flex-wrap: wrap; gap: 6px; align-items: center;" }, [...chips, input] ) ] ); }; const renderPatternsTab = () => { tabContentPatterns.replaceChildren(); const z2 = patternsConfig.zapret2; const z1 = patternsConfig.zapret; const bd = patternsConfig.byedpi; const z2Card = E( "div", { style: "padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;" }, [ E( "div", { style: "font-size: 13px; font-weight: bold;" }, "⚡ Zapret v2 (nfqws2) Matrix Patterns" ), renderChipGroup( _("Split Positions (pos)"), z2.splits, (v) => z2.splits.push(v), (i) => z2.splits.splice(i, 1) ), renderChipGroup( _("Fooling Methods (fooling)"), z2.foolings, (v) => z2.foolings.push(v), (i) => z2.foolings.splice(i, 1) ), renderChipGroup( _("TTL Values (ttl)"), z2.ttls, (v) => z2.ttls.push(Number(v) || 4), (i) => z2.ttls.splice(i, 1) ), renderChipGroup( _("Sequence Overlaps (seqovl)"), z2.seqovls, (v) => z2.seqovls.push(v), (i) => z2.seqovls.splice(i, 1) ), renderChipGroup( _("TCP Window Sizes (wsize)"), z2.wsizes, (v) => z2.wsizes.push(v), (i) => z2.wsizes.splice(i, 1) ) ] ); const z1Card = E( "div", { style: "padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;" }, [ E( "div", { style: "font-size: 13px; font-weight: bold;" }, "⚡ Zapret v1 (nfqws) Matrix Patterns" ), renderChipGroup( _("Split Modes (dpi-desync)"), z1.split_modes, (v) => z1.split_modes.push(v), (i) => z1.split_modes.splice(i, 1) ), renderChipGroup( _("Split Positions (dpi-desync-split-pos)"), z1.splits, (v) => z1.splits.push(v), (i) => z1.splits.splice(i, 1) ), renderChipGroup( _("Fooling Methods (dpi-desync-fooling)"), z1.foolings, (v) => z1.foolings.push(v), (i) => z1.foolings.splice(i, 1) ), renderChipGroup( _("TTL Values (dpi-desync-ttl)"), z1.ttls, (v) => z1.ttls.push(Number(v) || 4), (i) => z1.ttls.splice(i, 1) ) ] ); const bdCard = E( "div", { style: "padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;" }, [ E( "div", { style: "font-size: 13px; font-weight: bold;" }, "⚡ ByeDPI (ciadpi) Matrix Patterns" ), renderChipGroup( _("Split Values (-s)"), bd.splits, (v) => bd.splits.push(v), (i) => bd.splits.splice(i, 1) ), renderChipGroup( _("Disorder Values (-d)"), bd.disorders, (v) => bd.disorders.push(v), (i) => bd.disorders.splice(i, 1) ), renderChipGroup( _("OOB Values (-o)"), bd.oobs, (v) => bd.oobs.push(v), (i) => bd.oobs.splice(i, 1) ), renderChipGroup( _("Auto Modes (--auto)"), bd.autos, (v) => bd.autos.push(v), (i) => bd.autos.splice(i, 1) ), renderChipGroup( _("TLS-Rec Bounds (--tlsrec)"), bd.tlsrecs, (v) => bd.tlsrecs.push(v), (i) => bd.tlsrecs.splice(i, 1) ), renderChipGroup( _("IP Frag Bounds (--ip-frag)"), bd.ipfrags, (v) => bd.ipfrags.push(v), (i) => bd.ipfrags.splice(i, 1) ) ] ); const patternsFooter = E( "div", { style: "display: flex; justify-content: space-between; align-items: center; padding-top: 6px;" }, [ renderButton({ text: _("💾 Save Custom Patterns"), classNames: ["cbi-button-save"], onClick: async () => { const res = await TachyonShellMethods.saveFuzzerPatterns(patternsConfig); if (res.success) { showToast(_("Patterns saved successfully!"), "success"); } else { showToast(_("Failed to save patterns"), "error"); } } }), renderButton({ text: _("🔄 Reset to Factory Defaults"), classNames: ["cbi-button-neutral"], onClick: async () => { const res = await TachyonShellMethods.resetFuzzerPatterns(); if (res.success && res.data?.patterns) { patternsConfig = res.data.patterns; renderPatternsTab(); showToast(_("Patterns reset to defaults!"), "success"); } } }) ] ); tabContentPatterns.append(z2Card, z1Card, bdCard, patternsFooter); }; const renderCustomTab = () => { tabContentCustom.replaceChildren(); const nameInput = E("input", { type: "text", class: "cbi-input-text", placeholder: _("Strategy Name"), style: "flex: 1 1 180px; min-width: 140px;" }); const engineSel = E( "select", { class: "cbi-input-select", style: "flex: 0 1 140px; min-width: 120px;" }, [ E("option", { value: "zapret2" }, _("Zapret v2")), E("option", { value: "zapret" }, _("Zapret v1")), E("option", { value: "byedpi" }, _("ByeDPI")) ] ); const argsInput = E("input", { type: "text", class: "cbi-input-text", placeholder: _("Command-line arguments string..."), style: "flex: 2 1 240px; min-width: 180px; font-family: monospace;" }); const addBtn = renderButton({ text: _("+ Add Strategy"), classNames: ["cbi-button-action"], onClick: async () => { const name = nameInput.value.trim(); const eng = engineSel.value; const args = argsInput.value.trim(); if (!name || !args) { showToast(_("Please specify strategy name and arguments"), "error"); return; } patternsConfig.custom_strategies = patternsConfig.custom_strategies || []; patternsConfig.custom_strategies.push({ id: `custom_${Date.now()}`, name, engine: eng, args, description: _("User custom strategy") }); await TachyonShellMethods.saveFuzzerPatterns(patternsConfig); showToast(_("Custom strategy added!"), "success"); nameInput.value = ""; argsInput.value = ""; renderCustomTab(); } }); const addCard = E( "div", { style: "padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 8px;" }, [ E( "div", { style: "font-size: 12px; font-weight: 600;" }, _("Add Custom DPI Strategy") ), E( "div", { style: "display: flex; gap: 8px; flex-wrap: wrap; align-items: center;" }, [nameInput, engineSel, argsInput, addBtn] ) ] ); const customList = patternsConfig.custom_strategies || []; const listCard = E( "div", { style: "border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; overflow: hidden;" }, [ E( "table", { class: "table", style: "width: 100%; margin: 0; font-size: 12px;" }, [ E("thead", {}, [ E("tr", {}, [ E("th", { style: "padding: 8px;" }, _("Name")), E("th", { style: "padding: 8px; width: 90px;" }, _("Engine")), E("th", { style: "padding: 8px;" }, _("Arguments")), E( "th", { style: "padding: 8px; width: 80px; text-align: center;" }, _("Actions") ) ]) ]), E( "tbody", {}, customList.length === 0 ? [ E( "tr", {}, E( "td", { colSpan: 4, style: "padding: 20px; text-align: center; opacity: 0.6;" }, _("No custom strategies added yet.") ) ) ] : customList.map((cs, idx) => { return E("tr", {}, [ E( "td", { style: "padding: 8px; font-weight: 600;" }, cs.name ), E( "td", { style: "padding: 8px; font-family: monospace; font-size: 11px;" }, cs.engine ), E( "td", { style: "padding: 8px; font-family: monospace; font-size: 11px; word-break: break-all;" }, cs.args ), E( "td", { style: "padding: 8px; text-align: center;" }, [ (() => { const delBtn = E( "button", { type: "button", class: "cbi-button cbi-button-reset", style: "padding: 2px 8px; font-size: 11px;" }, _("Delete") ); delBtn.addEventListener("click", async () => { patternsConfig.custom_strategies.splice(idx, 1); await TachyonShellMethods.saveFuzzerPatterns( patternsConfig ); showToast(_("Strategy deleted"), "success"); renderCustomTab(); }); return delBtn; })() ] ) ]); }) ) ] ) ] ); tabContentCustom.append(addCard, listCard); }; const aiPromptInput = E("input", { type: "text", class: "cbi-input-text", placeholder: _( 'Optional context (e.g. "Rostelecom, YouTube 4K buffering is slow")...' ), style: "flex: 1 1 240px; min-width: 180px; font-size: 12px;" }); const aiSynthesizeBtn = renderButton({ text: _("🧠 Synthesize with AI"), classNames: ["cbi-button-action"], onClick: () => handleAiSynthesize() }); const aiAnalysisContainer = E( "div", { id: "tachyon-fuzzer-ai-analysis", style: "display: none; padding: 12px; background: rgba(0, 123, 255, 0.08); border-left: 3px solid #007bff; border-radius: 4px; font-size: 12px; line-height: 1.4;" }, [ E( "div", { style: "font-weight: bold; margin-bottom: 6px; display: flex; align-items: center; gap: 6px;" }, [ E("span", {}, "🧠"), E("span", {}, _("AI Diagnostics & Strategy Rationale")) ] ), E( "div", { id: "tachyon-fuzzer-ai-analysis-text", style: "opacity: 0.9;" }, "" ) ] ); const aiSynthesizerCard = E( "div", { style: "padding: 14px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 12px;" }, [ E( "div", { style: "font-size: 13px; font-weight: bold; display: flex; align-items: center; justify-content: space-between;" }, [ E("span", {}, "🧠 " + _("AI DPI Engineer & Strategy Synthesizer")), E( "span", { style: "font-size: 11px; opacity: 0.6; font-weight: normal;" }, _("RAG Knowledge Base + Live ISP Probe Context") ) ] ), E( "div", { style: "font-size: 12px; opacity: 0.8;" }, _( "Automatically runs a diagnostic probe against the target service, queries the built-in DPI Knowledge Base, and synthesizes 3-5 custom bypass strategies tailored to your ISP." ) ), E("div", { style: "display: flex; gap: 8px; align-items: center;" }, [ aiPromptInput, aiSynthesizeBtn ]), aiAnalysisContainer ] ); tabContentAi.append(aiSynthesizerCard); const renderHistoryTab = async () => { tabContentHistory.replaceChildren(); const historyHeader = E( "div", { style: "display: flex; justify-content: space-between; align-items: center;" }, [ E( "div", { style: "font-size: 13px; font-weight: bold;" }, "📋 " + _("Benchmark History") ), E("div", { style: "display: flex; gap: 8px;" }, [ renderButton({ text: _("🔄 Refresh"), classNames: ["cbi-button-neutral"], onClick: () => renderHistoryTab() }), renderButton({ text: _("🗑️ Clear History"), classNames: ["cbi-button-reset"], onClick: async () => { await TachyonShellMethods.clearFuzzerHistory(); showToast(_("History cleared"), "success"); renderHistoryTab(); } }) ]) ] ); const historyContainer = E("div", { style: "border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; overflow: hidden;" }); try { const res = await TachyonShellMethods.getFuzzerHistory(20); const entries = res.data?.entries || []; if (entries.length === 0) { historyContainer.appendChild( E( "div", { style: "padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;" }, _("No benchmark history yet. Run a benchmark to see results here.") ) ); } else { const table = E( "table", { class: "table cbi-section-table", style: "width: 100%; margin: 0; font-size: 12px;" }, [ E("thead", { class: "cbi-section-table-titles" }, [ E("tr", { class: "cbi-section-table-titles" }, [ E( "th", { class: "cbi-section-table-cell", style: "padding: 8px;" }, _("Date") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px;" }, _("Engine") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px;" }, _("Target") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px;" }, _("DPI Type") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px;" }, _("Best Strategy") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px; width: 60px;" }, _("Score") ), E( "th", { class: "cbi-section-table-cell", style: "padding: 8px; width: 70px;" }, _("Working") ) ]) ]), E( "tbody", {}, entries.reverse().map((entry) => { const date = new Date(entry.timestamp * 1e3); const dateStr = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`; const dpiType = entry.dpi_detection ? `${entry.dpi_detection.type} (${entry.dpi_detection.confidence}%)` : "—"; const bestName = entry.best_strategy ? entry.best_strategy.name : "—"; const bestScore = entry.best_strategy ? String(entry.best_strategy.score) : "0"; const working = `${entry.working_count}/${entry.total_tested}`; return E("tr", { class: "cbi-section-table-row" }, [ E( "td", { class: "cbi-section-table-cell", style: "padding: 8px;" }, dateStr ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px; font-family: monospace;" }, entry.engine ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px;" }, entry.target ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px;" }, dpiType ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px; font-size: 11px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" }, bestName ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px; font-weight: bold;" }, bestScore ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px;" }, working ) ]); }) ) ] ); historyContainer.appendChild(table); } } catch { historyContainer.appendChild( E( "div", { style: "padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;" }, _("Failed to load history") ) ); } tabContentHistory.append(historyHeader, historyContainer); }; const renderResults = (state) => { const tbody = document.getElementById("tachyon-fuzzer-results-tbody"); if (!tbody) return; tbody.replaceChildren(); let list = state.results || []; if (resultFilter === "success") { list = list.filter((r) => r.success); } else if (resultFilter === "fast") { list = list.filter((r) => r.success && r.speed_kbps > 1024); } if (list.length === 0) { tbody.appendChild( E( "tr", {}, E( "td", { colSpan: 8, style: "padding: 24px; text-align: center; opacity: 0.6;" }, state.running ? _("Running benchmarks...") : _("No strategies match the active filter.") ) ) ); return; } list.forEach((item, idx) => { const isBest = state.best_strategy && state.best_strategy.id === item.id; const nameChildren = [E("span", {}, item.name)]; if (item.badge) { nameChildren.push( E( "span", { style: `font-size: 10px; margin-left: 6px; font-weight: bold; color: ${isBest ? "#28a745" : "#17a2b8"};` }, item.badge ) ); } const statusChildren = [ E( "span", { class: `badge ${item.success ? "badge-success" : "badge-danger"}`, style: "font-size: 10px; padding: 2px 6px; display: inline-block;" }, item.success ? `HTTP ${item.http_code}` : "DROP" ) ]; if (item.data_verified) { statusChildren.push( E( "span", { class: "badge badge-success", style: "font-size: 9px; padding: 2px 5px; margin-left: 4px; display: inline-block; font-weight: 500;", title: _( "Data transfer verified: streamed >= 32KB without throttling or disconnect" ) }, _("✓ 32KB Streamed") ) ); } else if (item.dpi_verdict === "throttled_16k") { statusChildren.push( E( "span", { class: "badge badge-danger", style: "font-size: 9px; padding: 2px 5px; margin-left: 4px; display: inline-block; font-weight: 500;", title: _( "DPI throttling: stream cut off after ~16KB payload transfer" ) }, _("✗ Throttled at 16KB") ) ); } else if (item.dpi_verdict === "server_fakes") { statusChildren.push( E( "span", { class: "badge badge-warning", style: "font-size: 9px; padding: 2px 5px; margin-left: 4px; display: inline-block; font-weight: 500;", title: _( "Fake packets reached remote server (HTTP 400 Bad Request)" ) }, _("⚠️ Server received fakes") ) ); } if (item.sub_probes && item.sub_probes.length > 1) { const passedSub = item.sub_probes.filter((p) => p.success).length; const totalSub = item.sub_probes.length; statusChildren.push( E( "div", { style: "font-size: 10px; opacity: 0.75; margin-top: 2px;" }, `${passedSub}/${totalSub} ` + _("endpoints OK") ) ); } if (!item.success && item.error) { statusChildren.push( E( "div", { style: "font-size: 10px; color: var(--error-color-medium, #dc3545); opacity: 0.85; margin-top: 2px; max-width: 220px; word-break: break-word;", title: item.error }, item.error ) ); } const tr = E( "tr", { class: `cbi-section-table-row ${isBest ? "cbi-rowstyle-1" : "cbi-rowstyle-2"}`, style: `border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06)); ${isBest ? "background: var(--background-color-high, rgba(40, 167, 69, 0.08));" : ""}` }, [ E( "td", { class: "cbi-section-table-cell", style: "padding: 8px 10px;" }, String(idx + 1) ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px 10px; font-family: monospace; font-size: 11px;" }, item.engine ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px 10px;" }, [ E("div", { style: "font-weight: 600;" }, nameChildren), E( "div", { style: "font-size: 10px; opacity: 0.7; font-family: monospace; word-break: break-all; margin-top: 2px;" }, item.args ) ] ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px 10px;" }, E("div", {}, statusChildren) ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px 10px;" }, item.success ? `${item.ttfb_ms}ms` : "—" ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px 10px;" }, item.success ? [ E("div", {}, `${(item.speed_kbps / 1024).toFixed(1)}MB/s`), item.data_bytes && item.data_bytes > 0 ? E( "div", { style: "font-size: 10px; opacity: 0.65;" }, `${(item.data_bytes / 1024).toFixed(1)} KB` ) : "" ] : "—" ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px 10px; font-weight: bold;" }, item.success ? String(item.score) : "0" ), E( "td", { class: "cbi-section-table-cell", style: "padding: 8px 10px; text-align: center;" }, E( "div", { style: "display: flex; gap: 4px; justify-content: center;" }, [ renderButton({ text: _("Apply"), classNames: ["cbi-button-action"], disabled: !item.success, onClick: () => handleApplySingle(item) }) ] ) ) ] ); tbody.appendChild(tr); }); }; const updateProgressUI = (state) => { const statusText = document.getElementById("tachyon-fuzzer-status-text"); const pctText = document.getElementById("tachyon-fuzzer-progress-pct"); const progressBar = document.getElementById("tachyon-fuzzer-progress-bar"); const currentStratEl = document.getElementById( "tachyon-fuzzer-current-strategy" ); const applyBestBtn2 = document.getElementById( "tachyon-fuzzer-apply-best-btn" ); if (progressContainer) { progressContainer.style.display = state.running || state.results?.length ? "flex" : "none"; } if (pctText) pctText.innerText = `${state.progress_pct}%`; if (progressBar) progressBar.style.width = `${state.progress_pct}%`; const workingResults = state.results?.filter((r) => r.success) || []; const bestStrategy = state.best_strategy || workingResults[0] || null; if (statusText) { if (state.running) { statusText.innerText = `${_("Testing strategy")} ${state.current_index} / ${state.total_strategies}...`; } else if (state.finished_at > 0 || state.results?.length) { if (bestStrategy || workingResults.length > 0) { if (state.error) { statusText.innerText = `${_("🛑 Benchmark stopped")}. ${_("Found")} ${workingResults.length} ${_("working strategies")}.`; } else { statusText.innerText = _( "✅ Benchmark completed! Optimal strategy identified." ); } } else { statusText.innerText = state.error ? `${_("⚠️ Benchmark stopped")}: ${state.error}` : _( "⚠️ Benchmark completed. No working bypass found for this target." ); } } } if (currentStratEl) { if (state.running && state.current_strategy) { currentStratEl.innerText = `${_("Testing:")} [${state.current_strategy.name}] -> ${state.current_strategy.args}`; } else { currentStratEl.innerText = ""; } } if (applyBestBtn2) { applyBestBtn2.disabled = !bestStrategy; } }; let lastRenderedCount = 0; let lastRenderedFinishedAt = 0; const pollStatus = async () => { if (isPolling) return; isPolling = true; try { const res = await TachyonShellMethods.getFuzzerStatus(); if (res.success && res.data) { currentState = res.data; isRunning = res.data.running; if (isRunning) { startBtn.innerText = _("🛑 Stop Benchmark"); startBtn.disabled = false; startPolling(); } else { stopPolling(); startBtn.innerText = _("🚀 Start Benchmark"); startBtn.disabled = false; } updateProgressUI(res.data); if (res.data.dpi_detection) { currentDpiDetection = res.data.dpi_detection; updateDpiBanner(res.data.dpi_detection); } const resultCount = res.data.results?.length || 0; const finishedAt = res.data.finished_at || 0; if (resultCount !== lastRenderedCount || finishedAt !== lastRenderedFinishedAt) { renderResults(res.data); lastRenderedCount = resultCount; lastRenderedFinishedAt = finishedAt; } if (!isRunning) { if (autoApplyEnabled && currentState?.best_strategy) { try { const applyRes = await TachyonShellMethods.autoApplyFuzzerStrategy( selectedRuleSection || void 0 ); if (applyRes.success) { showToast( `${_("✅ Auto-applied best strategy")}: ${currentState.best_strategy.name}`, "success" ); } else { showToast( `${_("Auto-apply failed")}: ${applyRes.error || _("Unknown error")}`, "error" ); } } catch { showToast(_("Auto-apply failed"), "error"); } } } } } catch { } finally { isPolling = false; } }; const startPolling = () => { stopPolling(); pollingInterval = setInterval(pollStatus, 1200); pollStatus(); }; const stopPolling = () => { if (pollingInterval) { clearInterval(pollingInterval); pollingInterval = null; } }; const updateDpiBanner = (detection) => { const banner = document.getElementById("tachyon-fuzzer-dpi-banner"); if (!banner) return; if (!detection) { banner.style.display = "none"; return; } const typeMeta = { rst: { alertClass: "danger", icon: "🔴" }, throttle: { alertClass: "warning", icon: "🟡" }, dns_block: { alertClass: "info", icon: "🔵" }, ip_block: { alertClass: "danger", icon: "🚫" }, unknown: { alertClass: "notice", icon: "⚪" }, none: { alertClass: "success", icon: "🟢" } }; const typeLabels = { rst: _("TCP Reset Injection"), throttle: _("Throttling / Deep Inspection"), dns_block: _("DNS Blocking"), ip_block: _("IP Block (TCP SYN Timeout)"), unknown: _("Unknown DPI Pattern"), none: _("No Blocking Detected") }; const getLocalizedDpiDetails = (det) => { if (!det) return ""; switch (det.type) { case "ip_block": return _( "TCP connect timed out before TLS handshake — host is blocked at the IP layer. DPI bypass cannot unblock this; route via Sing-box VPN/Proxy outbound instead." ); case "rst": return _( "TCP RST received from DPI — active TCP reset injection detected." ); case "dns_block": return _( "DNS resolution failed — likely DNS-level blocking or hijacking." ); case "throttle": return _( "DPI bandwidth throttling or delay detected — connection throttled during data transfer." ); case "none": return _("Target accessible — no DPI blocking detected."); default: return det.details || _("Analysis inconclusive."); } }; const meta = typeMeta[detection.type] || typeMeta.unknown; const label = typeLabels[detection.type] || detection.type; const ipBlockNotice = detection.type === "ip_block" ? `
` : ""; banner.className = `alert-message ${meta.alertClass}`; banner.style.display = "block"; banner.innerHTML = `