// 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" ? `
⚠️ ${_("IP-level blocking detected! The resource is blocked at the network level (no TCP SYN response). DPI bypass methods (Zapret / ByeDPI) cannot bypass this address — configure proxy/VPN routing (Sing-box) for this domain!")}
` : ""; banner.className = `alert-message ${meta.alertClass}`; banner.style.display = "block"; banner.innerHTML = `
${meta.icon} ${_("DPI Detected")}: ${label} ${detection.confidence}% ${_("confidence")}
${detection.recommended_engines.length > 0 ? `
${_("Recommended")}: ${detection.recommended_engines.join(", ")}
` : ""}
${getLocalizedDpiDetails(detection)}
${ipBlockNotice} `; }; const handleToggleRun = async () => { if (isRunning) { startBtn.disabled = true; startBtn.innerText = _("⏳ Stopping..."); await TachyonShellMethods.stopFuzzer(); showToast(_("Benchmark stopped"), "success"); isRunning = false; stopPolling(); await pollStatus(); return; } startBtn.disabled = true; startBtn.innerText = _("🛑 Stop Benchmark"); progressContainer.style.display = "flex"; const statusTextEl = document.getElementById("tachyon-fuzzer-status-text"); const progressBarEl = document.getElementById( "tachyon-fuzzer-progress-bar" ); const currentStratEl = document.getElementById( "tachyon-fuzzer-current-strategy" ); if (statusTextEl) statusTextEl.innerText = _("🚀 Initializing benchmark in sandbox..."); if (progressBarEl) progressBarEl.style.width = "3%"; if (currentStratEl) currentStratEl.innerText = _( "Preparing strategies and isolated nftables queue..." ); try { const dpiRes = await TachyonShellMethods.detectFuzzerDpi( selectedTarget, selectedTarget === "custom" ? customUrl : void 0 ); if (dpiRes.success && dpiRes.data) { currentDpiDetection = dpiRes.data; updateDpiBanner(dpiRes.data); } } catch { } const tbody = document.getElementById("tachyon-fuzzer-results-tbody"); if (tbody) { tbody.replaceChildren( E( "tr", {}, E( "td", { colSpan: 8, style: "padding: 24px; text-align: center; opacity: 0.85;" }, [ E("span", { style: "margin-right: 8px;" }, "⏳"), E( "span", {}, _("Testing bypass strategies in isolated sandbox...") ) ] ) ) ); } const res = await TachyonShellMethods.startFuzzer( selectedEngine, selectedTarget, customUrl, selectedRuleSection, "", selectedMode ); if (res.success) { showToast(_("Strategy benchmark started"), "success"); startBtn.disabled = false; isRunning = true; lastRenderedCount = 0; lastRenderedFinishedAt = 0; startPolling(); } else { const errMsg = !res.success ? res.error : _("Unknown error"); showToast(`${_("Failed to start benchmark")}: ${errMsg}`, "error"); startBtn.disabled = false; startBtn.innerText = _("🚀 Start Benchmark"); progressContainer.style.display = "none"; } }; const handleAiSynthesize = async () => { const userPrompt = aiPromptInput.value.trim(); aiSynthesizeBtn.disabled = true; aiSynthesizeBtn.innerText = _("🧠 Synthesizing..."); const analysisBox = document.getElementById("tachyon-fuzzer-ai-analysis"); const analysisText = document.getElementById( "tachyon-fuzzer-ai-analysis-text" ); if (analysisBox) analysisBox.style.display = "none"; try { showToast(_("Consulting DPI Knowledge Base & AI..."), "success"); const res = await TachyonShellMethods.fuzzerAiSynthesize( selectedEngine, selectedTarget, customUrl, userPrompt ); if (res.success && res.data) { showToast( _("AI successfully synthesized custom strategies!"), "success" ); if (analysisBox && analysisText && res.data.analysis) { analysisText.innerText = res.data.analysis; analysisBox.style.display = "block"; } activeTab2 = "benchmark"; updateTabVisibility(); startBtn.disabled = true; startBtn.innerText = _("🛑 Stop Benchmark"); const startRes = await TachyonShellMethods.startFuzzer( selectedEngine, selectedTarget, customUrl, selectedRuleSection ); if (startRes.success) { isRunning = true; startPolling(); } } else { const errMsg = !res.success ? res.error : _("AI synthesis failed"); showToast(`${_("AI Synthesis Error")}: ${errMsg}`, "error"); } } catch { showToast(_("Failed to communicate with AI provider"), "error"); } finally { aiSynthesizeBtn.disabled = false; aiSynthesizeBtn.innerText = _("🧠 Synthesize with AI"); } }; const handleApplySingle = async (item) => { const res = await TachyonShellMethods.applyFuzzerStrategy( item.engine, item.args, selectedRuleSection ); if (res.success) { if (typeof ui?.addNotification === "function") { ui.addNotification( _("Tachyon"), E( "p", {}, `${_("Strategy applied successfully and service reloaded!")} (${item.name} -> ${selectedRuleSection || _("Global Default")})` ), "info" ); } else { showToast( `${_("Applied")} "${item.name}" -> ${selectedRuleSection || _("Global Default")}`, "success" ); } if (typeof ui?.hideModal === "function") ui.hideModal(); if (typeof window !== "undefined" && typeof window.location?.reload === "function") { window.location.reload(); } } else { showToast(_("Failed to apply strategy"), "error"); } }; const handleApplyBest = async () => { const best = currentState?.best_strategy || currentState?.results?.find((r) => r.success); if (!best) { showToast(_("No working strategy to apply"), "error"); return; } await handleApplySingle(best); }; TachyonShellMethods.getFuzzerStrategies().then((res) => { if (res.success && res.data?.available_engines) { const av = res.data.available_engines; const opts = engineSelect.options; for (let i = 0; i < opts.length; i++) { const opt = opts[i]; if (opt.value === "zapret2") { opt.text = av.zapret2 ? _("Zapret v2 (nfqws2)") + " — " + _("Installed") : _("Zapret v2 (nfqws2)") + " — " + _("Not installed"); if (!av.zapret2) opt.disabled = true; } else if (opt.value === "zapret") { opt.text = av.zapret ? _("Zapret v1 (nfqws)") + " — " + _("Installed") : _("Zapret v1 (nfqws)") + " — " + _("Not installed"); if (!av.zapret) opt.disabled = true; } else if (opt.value === "byedpi") { opt.text = av.byedpi ? _("ByeDPI (ciadpi)") + " — " + _("Installed") : _("ByeDPI (ciadpi)") + " — " + _("Not installed"); if (!av.byedpi) opt.disabled = true; } } } }).catch(() => { }); TachyonShellMethods.getFuzzerPatterns().then((res) => { if (res.success && res.data?.patterns) { patternsConfig = res.data.patterns; } }).catch(() => { }); modalContainer.append( tabBar, tabContentBenchmark, tabContentPatterns, tabContentCustom, tabContentAi, tabContentHistory ); pollStatus(); ui.showModal(_("⚡ Strategy Fuzzer & Auto-Tuner"), modalContainer); } // src/tachyon/tabs/diagnostic/partials/renderSystemInfo.ts function renderSystemInfo({ items }) { return E( "div", { class: "tachyon_diagnostic-page__right-bar__system-info" }, [ E( "b", { class: "tachyon_diagnostic-page__right-bar__system-info__title" }, _("System information") ), ...items.map((item) => { const tagClass = [ "tachyon_diagnostic-page__right-bar__system-info__row__tag", ...insertIf(item.tag?.kind === "neutral", [ "tachyon_diagnostic-page__right-bar__system-info__row__tag--neutral" ]), ...insertIf(item.tag?.kind === "warning", [ "tachyon_diagnostic-page__right-bar__system-info__row__tag--warning" ]), ...insertIf(item.tag?.kind === "success", [ "tachyon_diagnostic-page__right-bar__system-info__row__tag--success" ]) ].filter(Boolean).join(" "); return E( "div", { class: "tachyon_diagnostic-page__right-bar__system-info__row" }, [ E("b", {}, item.key), E("div", {}, [ E("span", {}, item.value), E("span", { class: tagClass }, item?.tag?.label) ]) ] ); }) ] ); } // src/tachyon/tabs/diagnostic/partials/renderDnsBenchmarkModal.ts function renderDnsBenchmarkModal() { let pollInterval = null; let isRunning = false; const progressBar = E("div", { style: "width: 0%; height: 6px; background: linear-gradient(90deg, #007bff, #28a745); border-radius: 3px; transition: width 0.3s ease;" }); const progressContainer = E( "div", { style: "width: 100%; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden; margin-bottom: 12px; display: none;" }, [progressBar] ); const statusLabel = E( "div", { style: "font-size: 13px; font-weight: 500; margin-bottom: 10px; color: var(--text-color-medium, #6c757d);" }, _("Ready to benchmark DNS providers") ); const resultsTbody = E("tbody", {}); const resultsTable = E( "table", { class: "table cbi-section-table", style: "width: 100%; font-size: 12px; margin-bottom: 16px;" }, [ E("thead", {}, [ E("tr", { class: "tr cbi-section-table-titles" }, [ E("th", { class: "th" }, _("Provider")), E("th", { class: "th" }, _("Protocol")), E("th", { class: "th" }, _("Address")), E("th", { class: "th", style: "text-align: right;" }, _("Latency")), E("th", { class: "th", style: "text-align: center;" }, _("Loss")), E("th", { class: "th", style: "text-align: center;" }, _("Rating")) ]) ]), resultsTbody ] ); const tableContainer = E( "div", { style: "max-height: 280px; overflow-y: auto; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; margin-bottom: 16px;" }, [resultsTable] ); const recommendationContainer = E("div", { style: "display: none; padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.25)); border: 1px solid var(--border-color, #28a745); border-radius: 8px; margin-bottom: 16px;" }); const getRatingBadge = (status, latency) => { let color = "#6c757d"; let text = status.toUpperCase(); if (status === "excellent") { color = "#28a745"; text = _("EXCELLENT"); } else if (status === "good") { color = "#17a2b8"; text = _("GOOD"); } else if (status === "fair") { color = "#ffc107"; text = _("FAIR"); } else if (status === "slow") { color = "#fd7e14"; text = _("SLOW"); } else if (status === "failed" || latency < 0) { color = "#dc3545"; text = _("FAILED"); } return E( "span", { style: `display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; background: ${color}; color: #fff;` }, text ); }; const updateTable = (results) => { resultsTbody.innerHTML = ""; if (!results || results.length === 0) { resultsTbody.appendChild( E("tr", {}, [ E( "td", { colSpan: 6, style: "text-align: center; opacity: 0.6; padding: 20px;" }, _('No benchmark data yet. Click "Start Benchmark" to begin.') ) ]) ); return; } results.forEach((r) => { const latText = r.latency >= 0 ? `${r.latency} ms` : _("Timeout"); let latColor = "inherit"; if (r.latency >= 0 && r.latency < 40) latColor = "#28a745"; else if (r.latency >= 40 && r.latency < 80) latColor = "#17a2b8"; else if (r.latency >= 80 && r.latency < 150) latColor = "#ffc107"; else if (r.latency >= 150 || r.latency < 0) latColor = "#dc3545"; const row = E("tr", { class: "tr cbi-section-table-row" }, [ E("td", { class: "td", style: "font-weight: 600;" }, [ E("span", {}, r.provider), r.tag ? E( "small", { style: "opacity: 0.7; margin-left: 6px; font-weight: normal;" }, `(${r.tag})` ) : "" ]), E("td", { class: "td" }, [ E( "span", { style: `display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 10px; font-weight: 600; text-transform: uppercase; background: ${r.type === "doh" ? "#6f42c1" : "#007bff"}; color: #fff;` }, r.type.toUpperCase() ) ]), E( "td", { class: "td", style: "font-family: monospace; font-size: 11px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;", title: r.address }, r.address ), E( "td", { class: "td", style: `text-align: right; font-weight: 600; color: ${latColor};` }, latText ), E( "td", { class: "td", style: "text-align: center; opacity: 0.85;" }, `${r.lossPct}%` ), E( "td", { class: "td", style: "text-align: center;" }, getRatingBadge(r.status, r.latency) ) ]); resultsTbody.appendChild(row); }); }; const updateRecommendation = (rec) => { if (!rec) { recommendationContainer.style.display = "none"; recommendationContainer.innerHTML = ""; return; } recommendationContainer.style.display = "block"; recommendationContainer.innerHTML = ""; const recContent = E("div", {}, [ E( "div", { style: "font-size: 14px; font-weight: 600; color: #28a745; margin-bottom: 8px; display: flex; align-items: center; gap: 6px;" }, [ E("span", {}, "⚡"), E("span", {}, _("Recommended DNS Configuration")) ] ), E( "div", { style: "display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 8px; font-size: 12px; margin-bottom: 8px;" }, [ E("div", {}, [ E("strong", {}, _("DNS Protocol") + ": "), E( "span", { style: "padding: 1px 6px; border-radius: 4px; font-size: 11px; font-weight: 600; background: #007bff; color: #fff;" }, rec.dns_type.toUpperCase() ) ]), E("div", {}, [ E("strong", {}, _("Upstream Mode") + ": "), E( "span", { style: "font-family: monospace;" }, rec.dns_upstream_mode ) ]), E("div", {}, [ E("strong", {}, _("Primary DNS") + ": "), E( "span", { style: "font-family: monospace;" }, rec.dns_server.join(", ") ) ]), E("div", {}, [ E("strong", {}, _("Bootstrap DNS") + ": "), E( "span", { style: "font-family: monospace;" }, rec.bootstrap_dns_server.join(", ") ) ]), E("div", {}, [ E("strong", {}, _("Fallback DNS") + ": "), E( "span", { style: "font-family: monospace;" }, rec.dns_fallback_server.join(", ") ) ]) ] ), rec.reason ? E( "div", { style: "font-size: 11px; opacity: 0.8; font-style: italic; margin-top: 4px; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 4px;" }, `💡 ${rec.reason}` ) : "" ]); recommendationContainer.appendChild(recContent); }; const startBtn = renderButton({ text: _("Start Benchmark"), classNames: ["cbi-button-action"], onClick: () => startBenchmark() }); const applyBtn = renderButton({ text: `🚀 ${_("Apply Recommended DNS")}`, classNames: ["cbi-button-positive"], disabled: true, onClick: () => applyRecommended() }); const closeBtn = renderButton({ text: _("Close"), classNames: ["cbi-button-neutral"], onClick: () => { if (pollInterval) clearInterval(pollInterval); if (isRunning) TachyonShellMethods.stopDnsBenchmark(); if (ui.hideModal) ui.hideModal(); } }); const pollStatus = async () => { const res = await TachyonShellMethods.getDnsBenchmarkStatus(); if (!res.success || !res.data) return; const data = res.data; isRunning = data.running; if (data.running) { progressContainer.style.display = "block"; progressBar.style.width = `${data.progress}%`; statusLabel.textContent = `⏳ ${data.current_server || _("Testing")} (${data.progress}%)`; if (startBtn) startBtn.disabled = true; if (applyBtn) applyBtn.disabled = true; } else { progressContainer.style.display = "none"; if (data.finished_at) { statusLabel.textContent = `✓ ${_("Benchmark completed successfully")}`; if (startBtn) startBtn.disabled = false; if (applyBtn) applyBtn.disabled = !data.recommendation; } if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } } if (data.results && data.results.length > 0) { updateTable(data.results); } if (data.recommendation) { updateRecommendation(data.recommendation); } }; const startBenchmark = async () => { statusLabel.textContent = `🚀 ${_("Starting DNS benchmark...")}`; progressContainer.style.display = "block"; progressBar.style.width = "0%"; if (startBtn) startBtn.disabled = true; if (applyBtn) applyBtn.disabled = true; const res = await TachyonShellMethods.startDnsBenchmark(); if (!res.success) { statusLabel.textContent = `✗ ${res.error || _("Failed to start benchmark")}`; if (startBtn) startBtn.disabled = false; progressContainer.style.display = "none"; return; } if (pollInterval) clearInterval(pollInterval); pollInterval = window.setInterval(pollStatus, 1e3); pollStatus(); }; const applyRecommended = async () => { if (applyBtn) { applyBtn.disabled = true; applyBtn.textContent = `⏳ ${_("Applying...")}`; } const res = await TachyonShellMethods.applyDnsBenchmark(); if (res.success) { ui.addNotification( _("Tachyon"), E( "p", {}, _( "Recommended DNS settings applied successfully and service restarted!" ) ), "info" ); if (ui.hideModal) ui.hideModal(); window.location.reload(); } else { ui.addNotification( _("Tachyon"), E("p", {}, res.error || _("Failed to apply DNS settings")), "error" ); if (applyBtn) { applyBtn.disabled = false; applyBtn.textContent = `🚀 ${_("Apply Recommended DNS")}`; } } }; const modalContent = E("div", { style: "padding: 8px;" }, [ E( "p", { style: "font-size: 13px; opacity: 0.85; margin-bottom: 12px;" }, _( "Benchmark measures actual latency and resolution reliability of major DNS servers (UDP 53 and encrypted DoH) directly from your router to find the fastest, unblocked upstream for your ISP." ) ), statusLabel, progressContainer, tableContainer, recommendationContainer, E( "div", { style: "display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; border-top: 1px solid var(--border-color, rgba(255,255,255,0.1)); padding-top: 12px;" }, [startBtn, applyBtn, closeBtn] ) ]); ui.showModal(`⚡ ${_("Tachyon DNS Benchmark & Auto-Tuning")}`, modalContent); startBenchmark(); } // src/tachyon/tabs/diagnostic/partials/renderLeakCheckModal.ts function renderLeakCheckModal() { let isRunning = false; const progressBar = E("div", { style: "width: 0%; height: 6px; background: linear-gradient(90deg, #007bff, #28a745); border-radius: 3px; transition: width 0.4s ease;" }); const progressContainer = E( "div", { style: "width: 100%; height: 6px; background: rgba(128,128,128,0.2); border-radius: 3px; overflow: hidden; margin-bottom: 14px;" }, [progressBar] ); const statusLabel = E( "div", { style: "font-size: 13px; font-weight: 500; margin-bottom: 12px; color: var(--text-color-medium, #6c757d);" }, _("Initializing router-level IP & DNS leak test...") ); const resultsContainer = E("div", { style: "display: none; margin-bottom: 16px;" }); const startTest = async () => { if (isRunning) return; isRunning = true; resultsContainer.style.display = "none"; progressContainer.style.display = "block"; progressBar.style.width = "30%"; statusLabel.textContent = _( "Querying direct connection and proxy outbound..." ); if (retryBtn) retryBtn.disabled = true; const timer = setTimeout(() => { progressBar.style.width = "70%"; statusLabel.textContent = _("Testing upstream DNS resolvers..."); }, 1500); try { const response = await TachyonShellMethods.leakCheck( (progress, stage) => { clearTimeout(timer); progressBar.style.width = `${progress}%`; if (stage === "dns") { statusLabel.textContent = _("Testing upstream DNS resolvers..."); } else if (stage === "ip") { statusLabel.textContent = _( "Querying direct connection and proxy outbound..." ); } } ); clearTimeout(timer); progressBar.style.width = "100%"; if (response.success && response.data) { statusLabel.textContent = _("Diagnostic check completed"); setTimeout(() => { progressContainer.style.display = "none"; progressBar.style.width = "0%"; }, 400); renderResults(response.data); } else { const err = !response.success && response.error ? response.error : _("Diagnostic check failed to complete"); progressContainer.style.display = "none"; statusLabel.textContent = err; resultsContainer.innerHTML = ""; resultsContainer.appendChild( E( "div", { class: "alert-message warning" }, err || _( "Could not contact leak test endpoints. Please ensure router has internet access." ) ) ); resultsContainer.style.display = "block"; } } catch (e) { clearTimeout(timer); progressContainer.style.display = "none"; statusLabel.textContent = _("An unexpected error occurred during test"); resultsContainer.innerHTML = ""; resultsContainer.appendChild( E( "div", { class: "alert-message warning" }, e instanceof Error ? e.message : String(e) ) ); resultsContainer.style.display = "block"; } finally { isRunning = false; if (retryBtn) retryBtn.disabled = false; } }; const renderResults = (data) => { resultsContainer.innerHTML = ""; const { ip_leak, dns_leak } = data; const isDirectRouting = ip_leak.proxy_online && (ip_leak.leaked || ip_leak.direct_ip === ip_leak.proxy_ip); const ipAlertClass = !ip_leak.proxy_online ? "alert-message info" : isDirectRouting ? "alert-message warning" : "alert-message success"; const ipAlertText = !ip_leak.proxy_online ? _("Proxy outbound is inactive or not configured for local testing.") : isDirectRouting ? _( "ℹ️ Direct connection (WAN): Public IP matches your ISP. Under selective routing (by domains or blocklists), unblocked resources bypass the proxy — this is standard operation." ) : _("🛡️ SECURE: Public IP is concealed behind the proxy outbound."); const ipTable = E( "table", { class: "table cbi-section-table", style: "width: 100%; margin-bottom: 12px; font-size: 12px;" }, [ E("thead", {}, [ E("tr", { class: "tr cbi-section-table-titles" }, [ E("th", { class: "th" }, _("Connection Path")), E("th", { class: "th" }, _("Observed Public IP")), E("th", { class: "th" }, _("Location")), E("th", { class: "th" }, _("ISP / Organization")), E("th", { class: "th", style: "text-align: center;" }, _("Status")) ]) ]), E("tbody", {}, [ E("tr", { class: "tr cbi-section-table-row" }, [ E("td", { class: "td" }, [ E("b", {}, _("Direct WAN (ISP)")), E( "div", { style: "font-size: 11px; color: var(--text-color-medium, #6c757d);" }, _("Direct connection via ISP") ) ]), E("td", { class: "td" }, [E("code", {}, ip_leak.direct_ip || "—")]), E( "td", { class: "td" }, [ip_leak.direct_country, ip_leak.direct_city].filter(Boolean).join(", ") || "—" ), E("td", { class: "td" }, ip_leak.direct_isp || "—"), E("td", { class: "td", style: "text-align: center;" }, [ E( "span", { class: "badge", style: "background: var(--text-color-medium, #6c757d); color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;" }, _("WAN") ) ]) ]), E("tr", { class: "tr cbi-section-table-row" }, [ E("td", { class: "td" }, [ E("b", {}, _("Proxy Outbound")), E( "div", { style: "font-size: 11px; color: var(--text-color-medium, #6c757d);" }, _("Tachyon proxy routing") ) ]), E("td", { class: "td" }, [E("code", {}, ip_leak.proxy_ip || "—")]), E( "td", { class: "td" }, [ip_leak.proxy_country, ip_leak.proxy_city].filter(Boolean).join(", ") || "—" ), E("td", { class: "td" }, ip_leak.proxy_org || "—"), E("td", { class: "td", style: "text-align: center;" }, [ !ip_leak.proxy_online ? E( "span", { class: "badge", style: "background: #6c757d; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;" }, _("INACTIVE") ) : isDirectRouting ? E( "span", { class: "badge", style: "background: #17a2b8; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;" }, _("DIRECT") ) : E( "span", { class: "badge", style: "background: #28a745; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;" }, _("SECURE") ) ]) ]) ]) ] ); const ipSection = E( "div", { class: "cbi-section", style: "margin-bottom: 20px; border: 1px solid var(--border-color, rgba(128,128,128,0.2)); border-radius: 6px; padding: 12px;" }, [ E("h4", { style: "margin-top: 0; margin-bottom: 8px;" }, [ "🌐 ", _("Public IP Address Isolation") ]), E("div", { class: ipAlertClass, style: "margin-bottom: 12px;" }, [ ipAlertText ]), ipTable ] ); const proxyDnsServers = dns_leak.dns_servers || []; const directDnsServers = dns_leak.direct_dns_servers || []; const hasProxyDns = proxyDnsServers.length > 0; const dnsAlertClass = !hasProxyDns ? "alert-message info" : dns_leak.dns_leaked ? "alert-message warning" : "alert-message success"; const dnsAlertText = !hasProxyDns ? _( "DNS resolvers through proxy are not captured (proxy is offline or test domain is not intercepted)." ) : dns_leak.dns_leaked ? _( "ℹ️ ISP DNS detected: DNS queries are handled by your local Internet Service Provider. If you use selective routing, this is standard behavior for direct connections." ) : _( "🛡️ SECURE: All DNS queries are resolved through independent secure DNS servers." ); const makeDnsRow = (s, pathLabel) => E("tr", { class: "tr cbi-section-table-row" }, [ E("td", { class: "td" }, [E("code", {}, s.ip)]), E("td", { class: "td" }, s.country || "—"), E("td", { class: "td" }, s.isp || "—"), E( "td", { class: "td", style: "text-align: center;" }, s.is_isp ? E( "span", { class: "badge", style: "background: #fd7e14; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;" }, _("ISP DNS") ) : E( "span", { class: "badge", style: "background: #28a745; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 11px;" }, _("SAFE") ) ), E( "td", { class: "td", style: "font-size: 10px; color: var(--text-color-medium, #6c757d);" }, pathLabel ) ]); const proxyDnsRows = proxyDnsServers.map( (s) => makeDnsRow(s, _("via Proxy")) ); const directDnsRows = directDnsServers.map( (s) => makeDnsRow(s, _("via WAN")) ); const allDnsRows = [...proxyDnsRows, ...directDnsRows]; const dnsTable = E( "table", { class: "table cbi-section-table", style: "width: 100%; font-size: 12px; margin-bottom: 12px;" }, [ E("thead", {}, [ E("tr", { class: "tr cbi-section-table-titles" }, [ E("th", { class: "th" }, _("Resolver IP")), E("th", { class: "th" }, _("Country")), E("th", { class: "th" }, _("Upstream Provider / ASN")), E( "th", { class: "th", style: "text-align: center;" }, _("Verdict") ), E("th", { class: "th" }, _("Path")) ]) ]), E( "tbody", {}, allDnsRows.length > 0 ? allDnsRows : [ E("tr", { class: "tr" }, [ E( "td", { class: "td", colSpan: 5, style: "text-align: center; opacity: 0.7;" }, _("No DNS resolvers recorded") ) ]) ] ) ] ); const dnsSection = E( "div", { class: "cbi-section", style: "border: 1px solid var(--border-color, rgba(128,128,128,0.2)); border-radius: 6px; padding: 12px;" }, [ E("h4", { style: "margin-top: 0; margin-bottom: 8px;" }, [ "🔍 ", _("DNS Upstream Resolver Analysis") ]), E("div", { class: dnsAlertClass, style: "margin-bottom: 12px;" }, [ dnsAlertText ]), dnsTable ] ); resultsContainer.appendChild(ipSection); resultsContainer.appendChild(dnsSection); resultsContainer.style.display = "block"; }; const retryBtn = renderButton({ classNames: ["cbi-button-action"], onClick: startTest, icon: renderRotateCcwIcon24, text: _("Re-run Leak Test") }); const closeBtn = renderButton({ classNames: ["cbi-button"], onClick: () => { if (ui.hideModal) ui.hideModal(); }, text: _("Close") }); const modalContent = E("div", { style: "padding: 8px;" }, [ E( "p", { style: "font-size: 13px; color: var(--text-color-medium, #6c757d); margin-bottom: 14px;" }, _( "Simultaneous check of your public IP address and upstream DNS resolvers to verify network visibility through direct connection and proxy." ) ), statusLabel, progressContainer, resultsContainer, E( "div", { style: "display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; border-top: 1px solid var(--border-color, rgba(128,128,128,0.2)); padding-top: 12px;" }, [retryBtn, closeBtn] ) ]); ui.showModal(`🛡️ ${_("Tachyon IP & DNS Leak Detection")}`, modalContent); startTest(); } // src/helpers/normalizeCompiledVersion.ts function normalizeCompiledVersion(version, commitSha) { if (!version || version.includes("COMPILED")) { return "dev"; } if (commitSha && commitSha !== "unknown" && !commitSha.includes("COMPILED")) { const shortSha = commitSha.substring(0, 8); if (shortSha && !version.includes(shortSha)) { return `${version} (${shortSha})`; } } return version; } // src/tachyon/tabs/diagnostic/partials/renderSupportModal.ts var CRYPTO_LIST = [ { name: "Bitcoin", badge: "BTC", address: "bc1q9ehdv7y9g948jejyflkq7xmau3tytgunxgh35h" }, { name: "Ethereum", badge: "ETH / ERC-20", address: "0x6CB7a4547eD62EF64990D5C6B5D9fdA58EB223E6" }, { name: "TON", badge: "TON", address: "UQDPhLRjMz5KltDLACAT3YXXHDVEtIDOHky2i33ZIOtsMEoR" }, { name: "Solana", badge: "SOL", address: "3csTGaNeU9KjhCKHEKAU3XLS5UfVjEeBVhXihpsETZwh" } ]; var CLOUDTIPS_URL = "https://pay.cloudtips.ru/p/48c57581"; var BOOSTY_URL = "https://boosty.to/tachyon"; async function copyTextToClipboard(text) { try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return true; } } catch { } try { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.left = "-9999px"; ta.style.top = "0"; document.body.appendChild(ta); ta.focus(); ta.select(); const successful = document.execCommand("copy"); document.body.removeChild(ta); return successful; } catch { return false; } } function renderSupportModal() { const introBlock = E( "div", { style: "margin-bottom: 16px; padding: 12px 14px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px; font-size: 13px; line-height: 1.5; color: var(--text-color-high, inherit);" }, [ E( "p", { style: "margin: 0;" }, _( "If Tachyon powers your daily networking and keeps your connection fast and secure, consider supporting ongoing development! ☕ 🧀 🌭" ) ) ] ); const boostyCard = E( "div", { style: "margin-bottom: 16px; padding: 14px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid rgba(255, 106, 0, 0.35); border-radius: 6px;" }, [ E( "div", { style: "display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;" }, [ E( "div", { style: "display: flex; flex-direction: column; gap: 2px;" }, [ E("div", { style: "font-weight: bold; font-size: 14px;" }, [ "⭐ Boosty" ]), E( "div", { style: "font-size: 12px; color: var(--text-color-medium, #888);" }, _("Subscriptions, one-time donations and exclusives") ) ] ), renderButton({ classNames: ["cbi-button-action"], icon: renderLinkIcon24, text: _("Support on Boosty"), onClick: () => { window.open(BOOSTY_URL, "_blank", "noopener,noreferrer"); } }) ] ) ] ); const cloudTipsCard = E( "div", { style: "margin-bottom: 16px; padding: 14px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px;" }, [ E( "div", { style: "display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;" }, [ E( "div", { style: "display: flex; flex-direction: column; gap: 2px;" }, [ E("div", { style: "font-weight: bold; font-size: 14px;" }, [ "💳 ", _("Credit Cards / SBP / Tinkoff Pay") ]), E( "div", { style: "font-size: 12px; color: var(--text-color-medium, #888);" }, _("Quick donation via Russian bank cards or SBP") ) ] ), renderButton({ classNames: ["cbi-button-action"], icon: renderLinkIcon24, text: _("Support on CloudTips"), onClick: () => { window.open(CLOUDTIPS_URL, "_blank", "noopener,noreferrer"); } }) ] ) ] ); const cryptoItemsContainer = E("div", { style: "display: flex; flex-direction: column; gap: 10px;" }); CRYPTO_LIST.forEach((item) => { const addressCode = E( "code", { style: "font-family: monospace; font-size: 11px; word-break: break-all; flex: 1; padding: 6px 8px; background: var(--background-color-primary, rgba(0, 0, 0, 0.15)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 4px; user-select: all; line-height: 1.4;" }, item.address ); const copyBtn = renderButton({ classNames: ["cbi-button"], icon: renderCopyIcon24, text: _("Copy Address"), onClick: async () => { const ok = await copyTextToClipboard(item.address); if (ok) { copyBtn.replaceChildren( renderCheckIcon24(), document.createTextNode(" " + _("Copied!")) ); copyBtn.classList.add("cbi-button-apply"); setTimeout(() => { copyBtn.replaceChildren( renderCopyIcon24(), document.createTextNode(" " + _("Copy Address")) ); copyBtn.classList.remove("cbi-button-apply"); }, 2e3); showToast(_("Address copied to clipboard"), "success"); } else { showToast(_("Failed to copy address"), "error"); } } }); copyBtn.style.whiteSpace = "nowrap"; copyBtn.style.flexShrink = "0"; const row = E( "div", { style: "padding: 10px 12px; background: var(--background-color-secondary, rgba(0, 0, 0, 0.05)); border: 1px solid var(--border-color, rgba(128, 128, 128, 0.2)); border-radius: 6px; display: flex; flex-direction: column; gap: 6px;" }, [ E( "div", { style: "display: flex; align-items: center; gap: 8px;" }, [ E( "span", { style: "font-weight: bold; font-size: 13px;" }, item.name ), E( "span", { class: "badge cbi-value-title", style: "font-size: 11px; padding: 2px 6px; opacity: 0.85;" }, item.badge ) ] ), E( "div", { style: "display: flex; align-items: center; gap: 8px; flex-wrap: wrap;" }, [addressCode, copyBtn] ) ] ); cryptoItemsContainer.appendChild(row); }); const cryptoSection = E("div", { style: "margin-bottom: 16px;" }, [ E( "div", { style: "font-weight: bold; font-size: 14px; margin-bottom: 8px; display: flex; align-items: center; gap: 6px;" }, ["🪙 ", _("Cryptocurrency")] ), cryptoItemsContainer ]); const footer = E( "div", { style: "display: flex; justify-content: flex-end; padding-top: 12px; border-top: 1px solid var(--border-color, rgba(128, 128, 128, 0.2));" }, [ renderButton({ classNames: ["cbi-button"], text: _("Close"), onClick: () => { if (ui.hideModal) ui.hideModal(); } }) ] ); const modalContent = E( "div", { style: "max-width: 640px; width: 100%; box-sizing: border-box; padding: 4px;" }, [introBlock, boostyCard, cloudTipsCard, cryptoSection, footer] ); ui.showModal(`💖 ${_("Support Development")}`, modalContent); } // src/tachyon/tabs/diagnostic/partials/renderWikiDisclaimer.ts function renderWikiDisclaimer(kind) { const iconWrap = E("span", { class: "tachyon_diagnostic-page__right-bar__wiki__icon" }); iconWrap.appendChild(renderBookOpenTextIcon24()); const className = [ "tachyon_diagnostic-page__right-bar__wiki", ...insertIf(kind === "error", [ "tachyon_diagnostic-page__right-bar__wiki--error" ]), ...insertIf(kind === "warning", [ "tachyon_diagnostic-page__right-bar__wiki--warning" ]) ].join(" "); return E("div", { class: className }, [ E("div", { class: "tachyon_diagnostic-page__right-bar__wiki__content" }, [ iconWrap, E("div", { class: "tachyon_diagnostic-page__right-bar__wiki__texts" }, [ E("b", {}, _("Troubleshooting")), E("div", {}, _("Do not panic, everything can be fixed, just...")) ]) ]), renderButton({ classNames: ["cbi-button-save"], text: _("Open Project Page"), onClick: () => window.open( "https://github.com/Dushnilin/tachyon#readme", "_blank", "noopener,noreferrer" ) }), renderButton({ classNames: ["cbi-button-save"], icon: renderSendIcon24, text: _("Telegram Channel"), onClick: () => window.open( "https://t.me/tachyon_proxy", "_blank", "noopener,noreferrer" ) }), renderButton({ classNames: ["cbi-button-save"], icon: renderHeartIcon24, text: _("Support Development"), onClick: () => renderSupportModal() }) ]); } // src/tachyon/tabs/diagnostic/checks/runSectionsCheck.ts function getSubscriptionLatencyState(latencyValues) { const hasAvailableLatency = latencyValues.some((item) => Boolean(item)); const hasUnavailableLatency = latencyValues.some((item) => !item); if (!hasAvailableLatency) { return "error"; } if (hasUnavailableLatency) { return "warning"; } return "success"; } async function runSectionsCheck() { const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS; updateCheckStore({ order, code, title, description: _("Checking, please wait"), state: "loading", items: [] }); const sections = await getDashboardSections({ includeSubscriptionCopyState: false }); if (!sections.success) { updateCheckStore({ order, code, title, description: _("Cannot receive checks result"), state: "error", items: [] }); throw new Error("Rule outbounds checks failed"); } const items = []; for (const section of sections.data) { async function getLatency() { if (section.withTagSelect) { const selectedOutbound2 = section.outbounds.find((item) => item.selected) ?? section.outbounds.find( (item) => item.type?.toLowerCase() === "urltest" ) ?? section.outbounds[0]; const isSubscription = section.proxyConfigType === "subscription"; const activeNodeCode = selectedOutbound2?.urlTestInfo?.selectedCode || selectedOutbound2?.priorityInfo?.selectedCode || selectedOutbound2?.code; if (activeNodeCode) { const latencyProxy2 = await TachyonShellMethods.getClashApiProxyLatency( activeNodeCode, section.latencyTestTimeout ); const proxySuccess = latencyProxy2.success && !latencyProxy2.data?.message; if (proxySuccess) { const delay = latencyProxy2.data?.delay; if (typeof delay === "number" && delay > 0) { return { state: "success", latency: `[${selectedOutbound2?.displayName ?? ""}] ${delay}ms` }; } const groupDelays = Object.values(latencyProxy2.data || {}).filter( (v) => typeof v === "number" && v > 0 ); if (groupDelays.length > 0) { const minDelay = Math.min(...groupDelays); return { state: "success", latency: `[${selectedOutbound2?.displayName ?? ""}] ${minDelay}ms` }; } } if (selectedOutbound2?.latency && selectedOutbound2.latency > 0) { return { state: "success", latency: `[${selectedOutbound2.displayName ?? ""}] ${selectedOutbound2.latency}ms` }; } return { state: "error", latency: `[${selectedOutbound2?.displayName ?? ""}] ${_("Not responding")}` }; } const latencyGroup = await TachyonShellMethods.getClashApiGroupLatency( section.code ); const success2 = latencyGroup.success && !latencyGroup.data?.message; if (success2) { const latencyValues = Object.values(latencyGroup.data); const sectionState = isSubscription ? getSubscriptionLatencyState(latencyValues) : "success"; const selectedProxyDelay = latencyGroup.data?.[selectedOutbound2?.code ?? ""]; if (typeof selectedProxyDelay === "number") { return { state: sectionState, latency: `[${selectedOutbound2?.displayName ?? ""}] ${selectedProxyDelay}ms` }; } return { state: "error", latency: `[${selectedOutbound2?.displayName ?? ""}] ${_("Not responding")}` }; } return { state: "error", latency: _("Not responding") }; } const isService = ["zapret", "zapret2", "byedpi"].includes(section.action || "") || Boolean(section.serviceStatus); if (isService) { if (section.serviceStatus) { const s = section.serviceStatus; if (s.ready) { return { state: "success", latency: _("Running") }; } if (s.conflict) { return { state: "error", latency: _("Conflict") }; } if (s.configured) { return { state: "warning", latency: _("Stopped") }; } return { state: "error", latency: _("Not configured") }; } return { state: "warning", latency: _("Unknown") }; } const selectedOutbound = section.outbounds[0]; const latencyProxy = await TachyonShellMethods.getClashApiProxyLatency( section.code, section.latencyTestTimeout ); const success = latencyProxy.success && !latencyProxy.data?.message; if (success) { const delay = latencyProxy.data?.delay; if (typeof delay === "number") { return { state: "success", latency: `${delay} ms` }; } const groupDelays = Object.values(latencyProxy.data || {}).filter( (v) => typeof v === "number" && v > 0 ); if (groupDelays.length > 0) { return { state: "success", latency: `${Math.min(...groupDelays)} ms` }; } } if (section.action === "vpn" && selectedOutbound?.runtimeAvailable) { return { state: "warning", latency: `[${selectedOutbound.displayName || section.code}] ${_("Connectivity probe failed")}` }; } if (selectedOutbound?.latency && selectedOutbound.latency > 0) { return { state: "success", latency: `${selectedOutbound.latency} ms` }; } return { state: "error", latency: _("Not responding") }; } const { latency, state: state2 } = await getLatency(); items.push({ state: state2, key: section.displayName, value: latency }); } 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("Rule outbounds checks failed"); } } // src/tachyon/tabs/diagnostic/serviceTransition.ts function isServiceTransitionStatus(status) { return ["starting", "stopping", "restarting", "reloading"].includes(status); } function getServiceTransition(status) { return { starting: status === "starting", stopping: status === "stopping", restarting: status === "restarting" || status === "reloading" }; } function hasLocalMutatingServiceActionLoading(actions) { return actions.restart.loading || actions.start.loading || actions.stop.loading || actions.enable.loading || actions.disable.loading; } function shouldSkipServicesInfoAutoRefresh({ force, localMutatingActionLoading }) { return !force && localMutatingActionLoading; } function shouldResetDiagnosticsChecks({ resetChecks, diagnosticsRunLoading }) { return resetChecks && !diagnosticsRunLoading; } function shouldDisableDiagnosticRunAction({ providerInfoLoaded, servicesInfoLoading, tachyonRunning, mutatingServiceActionLoading }) { return !providerInfoLoaded || servicesInfoLoading || !tachyonRunning || mutatingServiceActionLoading; } function hasComponentActionLoading(actions) { return Object.values(actions).some((action) => action.loading); } function getAvailableActionsDisabledState({ servicesInfoLoading, mutatingServiceActionLoading, componentActionLoading }) { return { serviceControlsDisabled: servicesInfoLoading || mutatingServiceActionLoading || componentActionLoading, utilityActionsDisabled: mutatingServiceActionLoading || componentActionLoading, viewLogsDisabled: false }; } function shouldShowRestartAction({ tachyonRunning, restartLoading, startLoading, stopLoading }) { return restartLoading || tachyonRunning && !startLoading && !stopLoading; } function shouldShowStartAction({ tachyonRunning, restartLoading, startLoading, stopLoading }) { return startLoading || !restartLoading && !tachyonRunning && !stopLoading; } function shouldShowStopAction({ tachyonRunning, restartLoading, startLoading, stopLoading }) { return stopLoading || restartLoading || tachyonRunning && !startLoading; } // src/tachyon/tabs/diagnostic/diagnosticRunPersistence.ts var DIAGNOSTIC_RUN_STORAGE_KEY = "tachyon:diagnostic-run:v1"; var DIAGNOSTIC_RUN_TTL_MS = 30 * 60 * 1e3; var CHECK_STATES = ["loading", "warning", "success", "error", "skipped"]; var CHECK_ITEM_STATES = ["error", "warning", "success"]; function getSessionStorage3() { if (typeof window === "undefined") { return null; } try { return window.sessionStorage; } catch { return null; } } function isRecord(value) { return Boolean(value) && typeof value === "object"; } function isOptionalBoolean(value) { return value === void 0 || typeof value === "boolean"; } function isDiagnosticsProviderOptions(value) { if (!isRecord(value)) { return false; } return isOptionalBoolean(value.includeZapret) && isOptionalBoolean(value.includeZapret2) && isOptionalBoolean(value.includeByedpi) && isOptionalBoolean(value.includeInbounds); } function isDiagnosticCheckItem(value) { return isRecord(value) && CHECK_ITEM_STATES.includes(String(value.state)) && typeof value.key === "string" && typeof value.value === "string"; } function isDiagnosticCheck(value) { return isRecord(value) && Number.isFinite(value.order) && Object.values(DIAGNOSTICS_CHECKS).includes( value.code ) && typeof value.title === "string" && typeof value.description === "string" && CHECK_STATES.includes(String(value.state)) && Array.isArray(value.items) && value.items.every(isDiagnosticCheckItem); } function isPersistedDiagnosticRun(value) { if (!isRecord(value)) { return false; } return typeof value.nextRunnerIndex === "number" && Number.isInteger(value.nextRunnerIndex) && value.nextRunnerIndex >= 0 && isDiagnosticsProviderOptions(value.providerOptions) && Array.isArray(value.diagnosticsChecks) && value.diagnosticsChecks.every(isDiagnosticCheck) && Number.isFinite(value.updatedAt); } function isExpired(run, now = Date.now()) { return now - run.updatedAt > DIAGNOSTIC_RUN_TTL_MS; } function readPersistedDiagnosticRun(storage = getSessionStorage3()) { if (!storage) { return null; } try { const parsed = JSON.parse( storage.getItem(DIAGNOSTIC_RUN_STORAGE_KEY) || "null" ); if (!isPersistedDiagnosticRun(parsed) || isExpired(parsed)) { storage.removeItem(DIAGNOSTIC_RUN_STORAGE_KEY); return null; } return parsed; } catch { storage.removeItem(DIAGNOSTIC_RUN_STORAGE_KEY); return null; } } function savePersistedDiagnosticRun(run, storage = getSessionStorage3()) { if (!storage) { return; } try { storage.setItem( DIAGNOSTIC_RUN_STORAGE_KEY, JSON.stringify({ ...run, updatedAt: Date.now() }) ); } catch { } } function clearPersistedDiagnosticRun(storage = getSessionStorage3()) { if (!storage) { return; } try { storage.removeItem(DIAGNOSTIC_RUN_STORAGE_KEY); } catch { } } // src/tachyon/tabs/diagnostic/helpers/maskDiagnostics.ts var MASKED_VALUE = "*******"; var SING_BOX_MASKED_KEYS = /* @__PURE__ */ new Set([ "auth_key", "control_url", "exit_node", "hostname", "listen", "listen_port", "username", "uuid", "server", "server_name", "secret", "password", "private_key", "public_key", "short_id", "fingerprint", "server_port", "server_ports", "advertise_routes", "domain", "domain_suffix", "domain_keyword", "domain_regex", "ip_cidr", "source_ip_cidr" ]); var TACHYON_MASK_AFTER_TOKEN = [ "option proxy_string", "option hwid", "option subscription_url", "list subscription_urls", "list urltest_proxy_links", "list selector_proxy_links", "list server_users", "option server_uuid", "option server_username", "option server_password", "option mtproto_secret", "option hysteria2_obfs_password", "option reality_private_key", "option reality_public_key", "option reality_short_id", "list reality_short_id", "option yacd_secret_key" ]; var TACHYON_MASK_AFTER_TOKEN_SPACE = [ "option outbound_json", "list domain", "list domain_suffix", "list domain_keyword", "list domain_regex", "list ip_cidr", "list source_ip_cidr", "list fully_routed_ips", "list excluded_ips", "option dns_server", "option bootstrap_dns_server", "list dns_server", "list bootstrap_dns_server", "option listen", "option listen_port", "option public_host", "option mtproto_faketls", "option mtproto_domain_fronting_ip", "option tls_server_name", "option reality_handshake_server", "option reality_handshake_server_port", "option transport_host", "list transport_hosts", "option tailscale_auth_key", "option tailscale_control_url", "option tailscale_hostname", "list tailscale_advertise_routes", "option tailscale_ephemeral", "option tailscale_exit_node", "option tailscale_exit_node_allow_lan_access", "option mixed_proxy_username", "option mixed_proxy_password", "option ai_doctor_api_key", "option tuic_password", "option ipaddr", "option netmask", "option gateway", "option username", "option password", "option private_key", "option awg_private_key", "option bot_token", "option admin_ids", "option url" ]; function isRecord2(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function isSpaceChar(value) { return value === " " || value === " " || value === "\r" || value === "\n"; } function maskAfterToken(line, token) { const position = line.indexOf(token); return position < 0 ? line : `${line.slice(0, position)}${token} '${MASKED_VALUE}'`; } function maskAfterTokenSpace(line, token) { const position = line.indexOf(token); if (position < 0) { return line; } const spacePosition = position + token.length; if (spacePosition >= line.length || !isSpaceChar(line.slice(spacePosition, spacePosition + 1))) { return line; } return `${line.slice(0, spacePosition + 1)}'${MASKED_VALUE}'`; } function maskOptionPath(line, token) { const position = line.indexOf(token); if (position < 0) { return line; } const slashOffset = line.slice(position + token.length).indexOf("/"); if (slashOffset < 0) { return line; } const slash = slashOffset + position + token.length; const quoteOffset = line.slice(slash + 1).indexOf("'"); if (quoteOffset < 0) { return line; } const quote = quoteOffset + slash + 1; return `${line.slice(0, slash)}/*******'${line.slice(quote + 1)}`; } function maskGlobalCheckLine(line) { let maskedLine = line; for (const token of TACHYON_MASK_AFTER_TOKEN) { maskedLine = maskAfterToken(maskedLine, token); } for (const token of TACHYON_MASK_AFTER_TOKEN_SPACE) { maskedLine = maskAfterTokenSpace(maskedLine, token); } maskedLine = maskOptionPath(maskedLine, "option dns_server '"); maskedLine = maskOptionPath(maskedLine, "list dns_server '"); return maskedLine; } function maskMultilineContinuation(line) { const leadingSpace = line.match(/^\s*/)?.[0] ?? ""; const hasClosingQuote = line.includes("'"); return `${leadingSpace}${MASKED_VALUE}${hasClosingQuote ? "'" : ""}`; } function maskSingBoxConfigValue(value) { if (Array.isArray(value)) { return value.map((item) => maskSingBoxConfigValue(item)); } if (isRecord2(value)) { return Object.fromEntries( Object.entries(value).map(([key, item]) => [ key, SING_BOX_MASKED_KEYS.has(key) ? MASKED_VALUE : maskSingBoxConfigValue(item) ]) ); } return value; } function stringifySingBoxConfig(value) { return typeof value === "string" ? value : JSON.stringify(value, null, 2); } function formatMaskedSingBoxConfig(value) { if (typeof value === "string") { try { return JSON.stringify(maskSingBoxConfigValue(JSON.parse(value)), null, 2); } catch (_error) { return value; } } return JSON.stringify(maskSingBoxConfigValue(value), null, 2); } function maskGlobalCheckText(text = "") { let inMaskedMultiline = false; return `${text}`.split("\n").map((line) => { if (inMaskedMultiline) { if (line.includes("'")) { inMaskedMultiline = false; } return maskMultilineContinuation(line); } const maskedLine = maskGlobalCheckLine(line); if (line.includes("option outbound_json")) { const firstQuote = line.indexOf("'"); if (firstQuote >= 0 && line.slice(firstQuote + 1).indexOf("'") < 0) { inMaskedMultiline = true; } } return maskedLine; }).join("\n"); } // src/tachyon/tabs/diagnostic/initController.ts var SERVICE_STATUS_REFRESH_INTERVAL_MS = 2e3; var SERVICE_ACTION_STATUS_TIMEOUT_MS = 45e3; var latestProviderInfoRequestId = 0; var diagnosticLifecycleRegistered = false; var diagnosticControllerInitialized = false; var diagnosticMounted = false; var diagnosticMountId = 0; var diagnosticCompletedWhileHidden = false; var servicesInfoStateUnsubscribe = null; var servicesInfoRefreshPromise = null; var followedServiceActionJobs = /* @__PURE__ */ new Set(); var handledServiceActionJobs = /* @__PURE__ */ new Set(); function sleep2(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function getDiagnosticsProviderOptions(systemInfo = store.get().diagnosticsSystemInfo) { return { includeZapret: Boolean(systemInfo.zapret_installed), includeZapret2: Boolean(systemInfo.zapret2_installed), includeByedpi: Boolean(systemInfo.byedpi_installed), includeInbounds: systemInfo.server_inbounds_enabled_count > 0 }; } function getNotRunningDiagnosticsChecks() { return getDiagnosticsChecks( _("Not running"), getDiagnosticsProviderOptions() ); } function resetDiagnosticsChecks() { store.set({ diagnosticsChecks: getNotRunningDiagnosticsChecks() }); } function setDiagnosticActionLoading(action, loading2, local = false) { if (local || !loading2) { setLocalServiceAction(action, loading2 && local); } const diagnosticsActions = store.get().diagnosticsActions; store.set({ diagnosticsActions: { ...diagnosticsActions, [action]: { loading: loading2 } } }); } function isDiagnosticMountActive(mountId = diagnosticMountId) { return diagnosticMounted && diagnosticMountId === mountId; } function isLocalMutatingServiceActionLoading() { const actions = store.get().diagnosticsActions; return hasLocalMutatingServiceActionLoading(actions); } function isMutatingServiceActionLoading() { return isLocalMutatingServiceActionLoading() || isServiceTransitionStatus(store.get().servicesInfoWidget.data.tachyonStatus); } function getTachyonStatusText(running, enabled) { if (running) { return enabled ? "running & enabled" : "running but disabled"; } return enabled ? "stopped but enabled" : "stopped & disabled"; } function setDisplayedTachyonRunning(running) { const servicesInfoWidget = store.get().servicesInfoWidget; const enabled = Boolean(servicesInfoWidget.data.tachyonEnabled); store.set({ servicesInfoWidget: { ...servicesInfoWidget, loading: false, data: { ...servicesInfoWidget.data, tachyonRunning: running ? 1 : 0, tachyonStatus: getTachyonStatusText(running, enabled) } } }); } async function refreshDiagnosticServicesInfo({ force = false, mountId = diagnosticMountId, allowInactive = false } = {}) { if (!allowInactive && !isDiagnosticMountActive(mountId)) { return; } if (shouldSkipServicesInfoAutoRefresh({ force, localMutatingActionLoading: isLocalMutatingServiceActionLoading() })) { return; } if (servicesInfoRefreshPromise) { return servicesInfoRefreshPromise; } const promise = fetchServicesInfo().then((uiState) => { followServiceActionsFromUiState(uiState); }).catch((error) => { logger.error( "[DIAGNOSTIC]", "refreshDiagnosticServicesInfo failed", error ); }).finally(() => { if (servicesInfoRefreshPromise === promise) { servicesInfoRefreshPromise = null; } }); servicesInfoRefreshPromise = promise; return promise; } async function waitForTachyonRunningState(expectedRunning) { const startedAt = Date.now(); while (Date.now() - startedAt < SERVICE_ACTION_STATUS_TIMEOUT_MS) { await refreshDiagnosticServicesInfo({ force: true, allowInactive: true }); const tachyonRunning = Boolean( store.get().servicesInfoWidget.data.tachyonRunning ); if (tachyonRunning === expectedRunning) { return true; } await sleep2(SERVICE_STATUS_REFRESH_INTERVAL_MS); } return false; } function startServiceActionStateWatcher() { if (servicesInfoStateUnsubscribe) { return; } servicesInfoStateUnsubscribe = subscribeRuntimeUiState((uiState) => { if (diagnosticMounted) { followServiceActionsFromUiState(uiState); } }); } function stopServiceActionStateWatcher() { if (!servicesInfoStateUnsubscribe) { return; } servicesInfoStateUnsubscribe(); servicesInfoStateUnsubscribe = null; } function isVisibleServiceRuntimeAction(action) { return action === "restart" || action === "start" || action === "stop"; } function setServiceActionStateLoading(state, loading2) { if (!isVisibleServiceRuntimeAction(state.action)) { return; } setDiagnosticActionLoading(state.action, loading2); } async function followServiceActionState(state) { const jobId = state.job_id; if (!jobId || followedServiceActionJobs.has(jobId)) { return; } if (!state.running && handledServiceActionJobs.has(jobId)) { return; } followedServiceActionJobs.add(jobId); if (state.running) { setServiceActionStateLoading(state, true); } try { if (state.running) { await TachyonShellMethods.waitServiceActionJob(jobId); } } catch (error) { logger.error("[DIAGNOSTIC]", "followServiceActionState failed", error); } finally { handledServiceActionJobs.add(jobId); capSetSize(handledServiceActionJobs); setServiceActionStateLoading(state, false); await refreshDiagnosticServicesInfo({ force: true, allowInactive: true }); void TachyonShellMethods.uiActionAck("service", jobId); followedServiceActionJobs.delete(jobId); resetDiagnosticsChecks(); } } function followServiceActionsFromUiState(uiState) { if (!uiState) { return; } for (const action of uiState.actions.service || []) { if (action.job_id) { void followServiceActionState(action); } } } async function fetchSystemInfo() { const systemInfo = await ensureSystemInfo(); if (store.get().diagnosticsRunAction.loading) { return; } store.set({ diagnosticsChecks: getDiagnosticsChecks( _("Not running"), getDiagnosticsProviderOptions(systemInfo) ) }); } async function fetchDiagnosticsProviderInfo({ resetChecks = true } = {}) { const requestId = ++latestProviderInfoRequestId; try { const uiState = await refreshRuntimeUiState({ force: true }); if (requestId !== latestProviderInfoRequestId) { return; } if (uiState) { const currentSystemInfo2 = store.get().diagnosticsSystemInfo; const nextSystemInfo2 = normalizeSingBoxVariantFields({ ...currentSystemInfo2, providerInfoLoaded: true, sing_box_extended: uiState.capabilities.sing_box_extended, sing_box_tiny: uiState.capabilities.sing_box_tiny, sing_box_compressed: uiState.capabilities.sing_box_compressed, sing_box_lx: uiState.capabilities.sing_box_lx, sing_box_tailscale: uiState.capabilities.sing_box_tailscale, zapret_installed: uiState.capabilities.zapret_installed, zapret2_installed: uiState.capabilities.zapret2_installed, byedpi_installed: uiState.capabilities.byedpi_installed, server_inbounds_enabled_count: uiState.capabilities.server_inbounds_enabled_count }); if (!nextSystemInfo2.zapret_installed) { nextSystemInfo2.zapret_version = "not installed"; } if (!nextSystemInfo2.zapret2_installed) { nextSystemInfo2.zapret2_version = "not installed"; } if (!nextSystemInfo2.byedpi_installed) { nextSystemInfo2.byedpi_version = "not installed"; } const nextState2 = { diagnosticsSystemInfo: nextSystemInfo2 }; if (shouldResetDiagnosticsChecks({ resetChecks, diagnosticsRunLoading: store.get().diagnosticsRunAction.loading })) { nextState2.diagnosticsChecks = getDiagnosticsChecks( _("Not running"), getDiagnosticsProviderOptions(nextSystemInfo2) ); } store.set(nextState2); return; } const [zapretRuntime, zapret2Runtime, byedpiRuntime, inboundsConfig] = await Promise.all([ TachyonShellMethods.checkZapretRuntime(), TachyonShellMethods.checkZapret2Runtime(), TachyonShellMethods.checkByedpiRuntime(), TachyonShellMethods.checkInboundsConfig() ]); if (requestId !== latestProviderInfoRequestId) { return; } const currentSystemInfo = store.get().diagnosticsSystemInfo; const nextSystemInfo = { ...currentSystemInfo, providerInfoLoaded: true, zapret_installed: zapretRuntime.success ? zapretRuntime.data.zapret_installed : currentSystemInfo.zapret_installed, zapret2_installed: zapret2Runtime.success ? zapret2Runtime.data.zapret2_installed : currentSystemInfo.zapret2_installed, byedpi_installed: byedpiRuntime.success ? byedpiRuntime.data.byedpi_installed : currentSystemInfo.byedpi_installed, server_inbounds_enabled_count: inboundsConfig.success ? inboundsConfig.data.enabled_count : -1 }; if (!zapretRuntime.success) { logger.error("[DIAGNOSTIC]", "fetchZapretRuntime failed", zapretRuntime); } if (!zapret2Runtime.success) { logger.error( "[DIAGNOSTIC]", "fetchZapret2Runtime failed", zapret2Runtime ); } if (!byedpiRuntime.success) { logger.error("[DIAGNOSTIC]", "fetchByedpiRuntime failed", byedpiRuntime); } if (!inboundsConfig.success) { logger.error( "[DIAGNOSTIC]", "fetchInboundsConfig failed", inboundsConfig ); } if (!nextSystemInfo.zapret_installed) { nextSystemInfo.zapret_version = "not installed"; } if (!nextSystemInfo.zapret2_installed) { nextSystemInfo.zapret2_version = "not installed"; } if (!nextSystemInfo.byedpi_installed) { nextSystemInfo.byedpi_version = "not installed"; } const nextState = { diagnosticsSystemInfo: nextSystemInfo }; if (shouldResetDiagnosticsChecks({ resetChecks, diagnosticsRunLoading: store.get().diagnosticsRunAction.loading })) { nextState.diagnosticsChecks = getDiagnosticsChecks( _("Not running"), getDiagnosticsProviderOptions(nextSystemInfo) ); } store.set(nextState); } catch (error) { logger.error("[DIAGNOSTIC]", "fetchDiagnosticsProviderInfo failed", error); if (requestId === latestProviderInfoRequestId) { const currentSystemInfo = store.get().diagnosticsSystemInfo; store.set({ diagnosticsSystemInfo: { ...currentSystemInfo, providerInfoLoaded: true, server_inbounds_enabled_count: -1 } }); } } } function renderDiagnosticsChecks() { logger.debug("[DIAGNOSTIC]", "renderDiagnosticsChecks"); const diagnosticsChecks = [...store.get().diagnosticsChecks].sort( (a, b) => a.order - b.order ); const container = document.getElementById("tachyon_diagnostic-page-checks"); const renderedDiagnosticsChecks = diagnosticsChecks.map( (check) => renderCheckSection(check) ); return preserveScrollForPage(() => { container.replaceChildren(...renderedDiagnosticsChecks); }); } function renderDiagnosticRunActionWidget() { logger.debug("[DIAGNOSTIC]", "renderDiagnosticRunActionWidget"); const { loading: loading2 } = store.get().diagnosticsRunAction; const providerInfoLoaded = store.get().diagnosticsSystemInfo.providerInfoLoaded; const servicesInfoWidget = store.get().servicesInfoWidget; const tachyonRunning = Boolean(servicesInfoWidget.data.tachyonRunning); const container = document.getElementById( "tachyon_diagnostic-page-run-check" ); const renderedAction = renderRunAction({ loading: loading2, disabled: shouldDisableDiagnosticRunAction({ providerInfoLoaded, servicesInfoLoading: servicesInfoWidget.loading, tachyonRunning, mutatingServiceActionLoading: isMutatingServiceActionLoading() }), click: () => runChecks() }); return preserveScrollForPage(() => { container.replaceChildren(renderedAction); }); } async function handleServiceRuntimeAction({ action, expectedRunning, optimisticRunning }) { setDiagnosticActionLoading(action, true, true); let jobId = ""; let ownsJobFollow = false; let delegatedToWatcher = false; if (optimisticRunning !== void 0) { setDisplayedTachyonRunning(optimisticRunning); } try { const startResponse = await TachyonShellMethods.serviceActionStart(action); if (!startResponse.success) { throw new Error(startResponse.error); } jobId = startResponse.data.job_id; if (followedServiceActionJobs.has(jobId)) { delegatedToWatcher = true; return; } followedServiceActionJobs.add(jobId); ownsJobFollow = true; const result = await TachyonShellMethods.waitServiceActionJob(jobId); if (!result.success) { throw new Error(result.error); } if (!result.data.success) { throw new Error(result.data.message || _("Service action failed")); } await waitForTachyonRunningState(expectedRunning); } catch (e) { logger.error("[DIAGNOSTIC]", `handleServiceRuntimeAction(${action})`, e); } finally { if (!delegatedToWatcher) { if (ownsJobFollow) { followedServiceActionJobs.delete(jobId); } setDiagnosticActionLoading(action, false); await refreshDiagnosticServicesInfo({ force: true, allowInactive: true }); if (jobId) { handledServiceActionJobs.add(jobId); capSetSize(handledServiceActionJobs); capSetSize(handledServiceActionJobs); void TachyonShellMethods.uiActionAck("service", jobId); } resetDiagnosticsChecks(); } } } function handleCheckServicesAction() { renderServiceCheckModal(); } async function handleRestart() { await handleServiceRuntimeAction({ action: "restart", expectedRunning: true, optimisticRunning: false }); } async function handleStart() { await handleServiceRuntimeAction({ action: "start", expectedRunning: true }); } async function handleStop() { await handleServiceRuntimeAction({ action: "stop", expectedRunning: false }); } async function handleEnable() { setDiagnosticActionLoading("enable", true); try { await TachyonShellMethods.enable(); } catch (e) { logger.error("[DIAGNOSTIC]", "handleEnable - e", e); } finally { await refreshDiagnosticServicesInfo({ force: true, allowInactive: true }); setDiagnosticActionLoading("enable", false); } } async function handleDisable() { setDiagnosticActionLoading("disable", true); try { await TachyonShellMethods.disable(); } catch (e) { logger.error("[DIAGNOSTIC]", "handleDisable - e", e); } finally { await refreshDiagnosticServicesInfo({ force: true, allowInactive: true }); setDiagnosticActionLoading("disable", false); } } async function handleShowGlobalCheck() { setDiagnosticActionLoading("globalCheck", true); try { const globalCheck = await TachyonShellMethods.globalCheck(false); if (globalCheck.success) { const rawGlobalCheckText = globalCheck.data ?? ""; const maskedGlobalCheckText = maskGlobalCheckText(rawGlobalCheckText); ui.showModal( _("Global check"), renderModal(rawGlobalCheckText, "global_check", { maskText: () => maskedGlobalCheckText, initialAutoRefresh: false, showMaskValuesToggle: true }) ); } else { notifyActionFailure( "handleShowGlobalCheck", globalCheck, _("Global check failed") ); } } catch (e) { notifyActionFailure("handleShowGlobalCheck", e, _("Global check failed")); } finally { setDiagnosticActionLoading("globalCheck", false); } } async function handleRunDoctor() { setDiagnosticActionLoading("doctor", true); try { const doctorRes = await TachyonShellMethods.doctor(); if (!doctorRes || typeof doctorRes !== "object") { showToast(_("Doctor failed") + ": " + _("Unknown error"), "error"); return; } if (doctorRes.success) { const rawData = doctorRes.data; const data = typeof rawData === "object" && rawData !== null ? rawData : null; const report = data ? String(data.report ?? "") : String(rawData ?? ""); const issues = data ? Number(data.issues ?? 0) : 0; const fixed = data ? Number(data.fixed ?? 0) : 0; const title = issues > 0 ? _("Doctor repair") + " — " + _("Issues") + ": " + issues + ", " + _("Fixed") + ": " + fixed : /safe bypass|аварийного обхода|bypassed|стоковом состоянии|stock (state|internet)/i.test( report ) ? _("Doctor repair") + " — Safe Bypass" : _("Doctor repair") + " — " + _("No issues found"); ui.showModal( title, renderModal(report, "doctor_repair", { initialAutoRefresh: false }) ); } else { const errorMsg = typeof doctorRes.error === "string" ? doctorRes.error : _("Unknown error"); showToast(_("Doctor failed") + ": " + errorMsg, "error"); } } catch (e) { logger.error( "[DIAGNOSTIC]", "handleRunDoctor - e", e instanceof Error ? e.message : String(e) ); showToast(_("Doctor failed"), "error"); } finally { setDiagnosticActionLoading("doctor", false); runChecks(); } } function getAiDoctorHistory() { try { const raw = localStorage.getItem("tachyon_ai_doctor_history"); return raw ? JSON.parse(raw) : []; } catch (_e) { return []; } } function saveAiDoctorHistory(entry) { try { const current = getAiDoctorHistory(); const updated = [entry, ...current].slice(0, 5); localStorage.setItem("tachyon_ai_doctor_history", JSON.stringify(updated)); } catch (_e) { } } async function handleViewLogs() { setDiagnosticActionLoading("viewLogs", true); try { const viewLogs = await TachyonShellMethods.checkLogs(); if (viewLogs.success) { const getLatestLogs = async () => { const latestLogs = await TachyonShellMethods.checkLogs(); if (!latestLogs.success) { throw latestLogs; } return latestLogs.data ?? ""; }; ui.showModal( _("View logs"), renderModal(viewLogs.data, "view_logs", { getText: getLatestLogs, refreshMs: 250, initialAutoRefresh: true, showAutoRefreshToggle: true, startAtEnd: true }) ); } else { notifyActionFailure("handleViewLogs", viewLogs, _("View logs failed")); } } catch (e) { notifyActionFailure("handleViewLogs", e, _("View logs failed")); } finally { setDiagnosticActionLoading("viewLogs", false); } } async function handleRunAiDoctor() { setDiagnosticActionLoading("aiDoctor", true); try { const aiRes = await TachyonShellMethods.aiDoctor(); if (!aiRes || typeof aiRes !== "object") { showToast(_("AI Doctor failed") + ": " + _("Unknown error"), "error"); return; } const rawData = aiRes.data; const data = typeof rawData === "object" && rawData !== null ? rawData : null; if (aiRes.success || data && data.success !== false) { const report = data ? String(data.report ?? data.summary ?? rawData ?? "") : String(rawData ?? ""); const quickFixes = Array.isArray(data?.quick_fixes) ? data.quick_fixes : String(data?.quick_fix ?? "").split(",").map((s) => s.trim()).filter(Boolean); const nowStr = (/* @__PURE__ */ new Date()).toLocaleTimeString(); saveAiDoctorHistory({ timestamp: nowStr, report, quickFixes }); let historyEntries = getAiDoctorHistory(); let activeTab2 = "diagnosis"; let lanClients = []; let loadingClients = false; const loadLanClients = async () => { if (loadingClients) return; loadingClients = true; renderModalLayout(); try { const res = await TachyonShellMethods.getLanClients(); if (res && res.success && res.data && Array.isArray(res.data.clients)) { lanClients = res.data.clients; } } catch (e) { logger.error( "[DIAGNOSTIC]", "getLanClients error", e instanceof Error ? e.message : String(e) ); } finally { loadingClients = false; renderModalLayout(); } }; const copySupportReport = async () => { let currentClients = lanClients; if (currentClients.length === 0) { try { const res = await TachyonShellMethods.getLanClients(); if (res && res.success && res.data?.clients) { lanClients = res.data.clients; currentClients = lanClients; } } catch (_e) { } } const nodeSummary = nodes.map((n) => `${n.name}: ${n.status}`).join(" | "); const fixesSummary = quickFixes.length > 0 ? quickFixes.map((f) => FIX_LABELS[f] || f).join(", ") : _("None"); const clientsSummary = currentClients.length > 0 ? currentClients.map( (c) => `- ${c.hostname} (IP: ${c.ip}, MAC: ${c.mac.slice(0, 8)}**): ${c.mode.toUpperCase()}` ).join("\n") : _("No DHCP clients detected"); const text = [ "# Tachyon AI Doctor Diagnostic Report", `Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`, `Pillars: ${nodeSummary}`, `Recommended Fixes: ${fixesSummary}`, "", "## Diagnosis:", report, "", "## LAN Devices Routing:", clientsSummary ].join("\n"); try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(text); } else { const ta = document.createElement("textarea"); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } showToast( _("Anonymized support report copied to clipboard"), "success" ); } catch { showToast(_("Failed to copy report to clipboard"), "error"); } }; const getDeviceIcon = (hostname) => { const h = hostname.toLowerCase(); if (h.includes("tv") || h.includes("samsung") || h.includes("lg") || h.includes("bravia") || h.includes("roku") || h.includes("appletv")) return "📺"; if (h.includes("phone") || h.includes("iphone") || h.includes("android") || h.includes("pixel") || h.includes("xiaomi") || h.includes("galaxy")) return "📱"; if (h.includes("mac") || h.includes("pc") || h.includes("laptop") || h.includes("desktop") || h.includes("thinkpad")) return "💻"; if (h.includes("playstation") || h.includes("ps4") || h.includes("ps5") || h.includes("xbox") || h.includes("switch") || h.includes("nintendo")) return "🎮"; return "📟"; }; const repLower = report.toLowerCase(); const backendNodes = Array.isArray(data?.nodes) && data.nodes.length === 4 ? data.nodes : null; const nodes = backendNodes ?? [ { name: "WAN", status: repLower.includes("wan interface down") || repLower.includes( "шлюз по умолчанию или внешний интернет недоступен" ) || repLower.includes("wan interface is unreachable") ? "FAIL" : "OK" }, { name: "DNS", status: repLower.includes("сбой разрешения dns") || repLower.includes("dns resolution failed") || repLower.includes("dns failed") || repLower.includes("dnsmasq failed") ? "FAIL" : "OK" }, { name: "sing-box", status: (repLower.includes("sing-box") || repLower.includes("proxy")) && (repLower.includes("остановлен") || repLower.includes("stopped") || repLower.includes("не функционирует") || repLower.includes("error") || repLower.includes("crash")) ? "FAIL" : "OK" }, { name: "nftables", status: (repLower.includes("nftables") || repLower.includes("правила файрвола") || repLower.includes("firewall rules")) && (repLower.includes("нарушены") || repLower.includes("damaged") || repLower.includes("corrupted") || repLower.includes("compromised")) ? "WARN" : "OK" } ]; const FIX_LABELS = { start_singbox: _("Start sing-box"), rebuild_rules: _("Rebuild firewall rules"), fix_dnsmasq: _("Restart dnsmasq"), fix_resolv_symlink: _("Fix resolv.conf"), start_watchdog: _("Start watchdog"), restart_singbox_dns: _("Restart sing-box DNS"), fix_uci_config: _("Restore config backup"), fix_wan_interface: _("Reconnect WAN"), fix_gateway: _("Resolve gateway"), clear_dns_cache: _("Clear DNS cache"), update_subscriptions: _("Update subscriptions"), reset_firewall: _("Restart firewall"), restart_network: _("Restart network"), restart_zapret: _("Restart Zapret/ByeDPI"), optimize_memory: _("Optimize RAM memory"), switch_to_doh: _("Switch DNS to DoH"), heal_network_stack: _("Auto-Heal Network Stack"), enable_safe_bypass: _("Enable Direct WAN Bypass"), restore_native_internet: _("Restore Native Internet (Stop Tachyon)"), fix_system_time: _("Sync System Time (NTP)"), flush_conntrack: _("Flush Conntrack Table"), fix_bootstrap_dns: _("Reset Bootstrap DNS"), optimize_mtu: _("Optimize AWG MTU") }; const renderRootCauseBanner = () => { return E( "div", { class: "cbi-section-node", style: "display: flex; align-items: center; justify-content: space-around; gap: 8px; flex-wrap: wrap; padding: 8px 12px; margin-bottom: 12px; border-radius: 6px; background: var(--background-color-high, rgba(0,0,0,0.03)); border: 1px solid var(--border-color, rgba(0,0,0,0.1));" }, nodes.map((node) => { const labelClass = node.status === "OK" ? "label-success" : node.status === "WARN" ? "label-warning" : "label-danger"; const icon = node.status === "OK" ? "✓" : node.status === "WARN" ? "⚠" : "✕"; return E( "span", { class: `label ${labelClass}`, style: "font-size: 11px; padding: 4px 10px; border-radius: 4px; display: inline-flex; align-items: center; gap: 4px; font-weight: bold;" }, [ E("span", {}, node.name), E("span", {}, `${icon} ${node.status}`) ] ); }) ); }; const renderDiagnosisTabContent = () => { return E("div", { class: "cbi-section-node" }, [ renderRootCauseBanner(), E( "pre", { class: "tachyon-partial-modal__content alert-message notice", style: "white-space: pre-wrap; font-family: inherit; font-size: 12px; line-height: 1.5; max-height: 320px; overflow-y: auto; margin: 0; padding: 12px; border-radius: 6px; border: 1px solid var(--border-color, rgba(0,0,0,0.1));" }, report ), quickFixes.length > 0 ? E( "div", { class: "alert-message warning", style: "margin-top: 12px; padding: 10px 12px; border-radius: 6px;" }, [ E( "div", { style: "font-weight: bold; margin-bottom: 8px; font-size: 12px;" }, "🛠️ " + _("Recommended Quick Fixes:") ), E( "div", { style: "display: flex; gap: 6px; flex-wrap: wrap;" }, quickFixes.map((code) => { let applied = false; const friendlyLabel = FIX_LABELS[code] || code; const btn = renderButton({ classNames: ["cbi-button-apply"], text: `⚡ ${friendlyLabel}`, onClick: async () => { if (applied) return; btn.textContent = "⏳ " + _("Applying...") + " " + friendlyLabel; showToast( _("Applying fix") + ": " + friendlyLabel + "...", "success" ); const fixRes = await TachyonShellMethods.applyQuickFix(code); if (fixRes && typeof fixRes === "object" && fixRes.success) { applied = true; btn.textContent = `✓ ${friendlyLabel} (${_("Fixed")})`; btn.classList.remove("cbi-button-apply"); btn.classList.add("cbi-button-neutral"); showToast( _("Fix applied") + ": " + friendlyLabel, "success" ); } else { btn.textContent = `⚡ ${friendlyLabel}`; showToast( _("Failed to apply fix") + ": " + friendlyLabel, "error" ); } } }); return btn; }) ) ] ) : nodes.some((n) => n.status === "FAIL" || n.status === "WARN") ? E( "div", { class: "alert-message warning", style: "margin-top: 12px; padding: 8px 12px; font-size: 12px; border-radius: 6px;" }, "⚠️ " + _("Issues detected. Review the diagnosis above.") ) : E( "div", { class: "alert-message success", style: "margin-top: 12px; padding: 8px 12px; font-size: 12px; border-radius: 6px;" }, "✓ " + _("No issues detected. System is running normally.") ) ]); }; const renderDevicesTabContent = () => { if (loadingClients) { return E( "div", { class: "cbi-section-node", style: "padding: 20px; text-align: center; font-size: 13px;" }, "⏳ " + _("Loading connected LAN devices...") ); } if (lanClients.length === 0) { return E("div", { class: "cbi-section-node" }, [ E( "div", { class: "alert-message info", style: "margin: 0 0 10px 0; padding: 12px;" }, _("No active DHCP clients found on local network.") ), renderButton({ classNames: ["cbi-button-action"], text: "🔄 " + _("Refresh Device List"), onClick: loadLanClients }) ]); } return E("div", { class: "cbi-section-node" }, [ E( "div", { style: "display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;" }, [ E( "span", { style: "font-weight: bold; font-size: 12px;" }, `📱 ${_("Connected Devices")}: ${lanClients.length}` ), renderButton({ classNames: ["cbi-button-neutral"], text: "🔄 " + _("Refresh"), onClick: loadLanClients }) ] ), E( "div", { style: "display: flex; flex-direction: column; gap: 8px; max-height: 340px; overflow-y: auto;" }, lanClients.map((client) => { const icon = getDeviceIcon(client.hostname); const isDirect = client.mode === "direct"; return E( "div", { class: "cbi-section-node", style: "display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border-color, rgba(0,0,0,0.1)); background: var(--background-color-high, rgba(0,0,0,0.02)); flex-wrap: wrap;" }, [ E( "div", { style: "display: flex; align-items: center; gap: 8px;" }, [ E("span", { style: "font-size: 18px;" }, icon), E("div", {}, [ E( "div", { style: "font-weight: bold; font-size: 12px;" }, client.hostname ), E( "div", { style: "font-size: 11px; opacity: 0.75;" }, `${client.ip} (${client.mac})` ) ]) ] ), E( "div", { style: "display: flex; align-items: center; gap: 8px;" }, [ E( "span", { class: `label ${isDirect ? "label-warning" : "label-success"}`, style: "font-size: 11px; padding: 3px 8px; border-radius: 4px;" }, isDirect ? "🌐 " + _("Direct WAN") : "🛡️ " + _("Proxy / DPI") ), renderButton({ classNames: [ isDirect ? "cbi-button-action" : "cbi-button-apply" ], text: isDirect ? "🛡️ " + _("Route via Proxy") : "⚡ " + _("Direct Bypass"), onClick: async () => { showToast( _("Updating device routing mode..."), "success" ); const res = await TachyonShellMethods.toggleClientBypass( client.ip ); if (res && res.success && res.data) { client.mode = res.data.mode; showToast( _("Device updated") + ": " + client.hostname, "success" ); renderModalLayout(); } else { showToast(_("Failed to update device"), "error"); } } }) ] ) ] ); }) ) ]); }; const renderHistoryTabContent = () => { if (historyEntries.length === 0) { return E( "div", { class: "alert-message info", style: "margin: 0; padding: 12px;" }, _("No diagnostic history available yet.") ); } return E( "div", { style: "display: flex; flex-direction: column; gap: 8px; max-height: 360px; overflow-y: auto;" }, historyEntries.map( (h, i) => E( "div", { class: "cbi-section-node", style: "padding: 10px; border-radius: 6px; border: 1px solid var(--border-color, rgba(0,0,0,0.1)); background: var(--background-color-high, rgba(0,0,0,0.02));" }, [ E( "div", { style: "display: flex; justify-content: space-between; font-weight: bold; font-size: 11px; margin-bottom: 6px; opacity: 0.8;" }, [ E("span", {}, `#${historyEntries.length - i}`), E("span", {}, h.timestamp) ] ), E( "pre", { class: "tachyon-partial-modal__content", style: "margin: 0; white-space: pre-wrap; font-size: 11px; max-height: 120px; overflow-y: auto; padding: 8px; border-radius: 4px; background: rgba(0,0,0,0.05);" }, h.report ) ] ) ) ); }; const mainContainer = E( "div", { class: "tachyon-partial-modal__body", style: "width: 100%; box-sizing: border-box;" }, [] ); const renderModalLayout = () => { mainContainer.replaceChildren( E("div", {}, [ E( "div", { style: "display: flex; gap: 8px; margin-bottom: 12px; border-bottom: 1px solid var(--border-color, rgba(0,0,0,0.1)); padding-bottom: 8px; flex-wrap: wrap;" }, [ renderButton({ classNames: [ activeTab2 === "diagnosis" ? "cbi-button-action" : "cbi-button-neutral" ], text: "🔍 " + _("Current Diagnosis"), onClick: () => { activeTab2 = "diagnosis"; renderModalLayout(); } }), renderButton({ classNames: [ activeTab2 === "devices" ? "cbi-button-action" : "cbi-button-neutral" ], text: `📱 ${_("LAN Devices")} ${lanClients.length > 0 ? `(${lanClients.length})` : ""}`, onClick: () => { activeTab2 = "devices"; if (lanClients.length === 0) { loadLanClients(); } else { renderModalLayout(); } } }), renderButton({ classNames: [ activeTab2 === "history" ? "cbi-button-action" : "cbi-button-neutral" ], text: `🕒 ${_("History")} (${historyEntries.length})`, onClick: () => { historyEntries = getAiDoctorHistory(); activeTab2 = "history"; renderModalLayout(); } }) ] ), activeTab2 === "diagnosis" ? renderDiagnosisTabContent() : activeTab2 === "devices" ? renderDevicesTabContent() : renderHistoryTabContent(), E( "div", { class: "tachyon-partial-modal__footer", style: "margin-top: 15px; display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap;" }, [ E( "div", { style: "display: flex; gap: 8px; flex-wrap: wrap; align-items: center;" }, [ renderButton({ classNames: ["cbi-button-action"], text: "📋 " + _("Copy Support Report"), onClick: copySupportReport }), renderButton({ classNames: ["cbi-button-reset"], text: "🚨 " + _("Restore Native Internet (Stop Tachyon)"), onClick: async () => { showToast( _( "Restoring native direct internet (stopping Tachyon)..." ), "success" ); const fixRes = await TachyonShellMethods.applyQuickFix( "restore_native_internet" ); if (fixRes && typeof fixRes === "object" && fixRes.success) { showToast( _("Native internet restored. Tachyon stopped."), "success" ); ui.hideModal(); await refreshDiagnosticServicesInfo({ force: true, allowInactive: true }); } else { showToast( _("Failed to restore native internet"), "error" ); } } }) ] ), renderButton({ classNames: ["cbi-button-neutral"], text: _("Close"), onClick: () => ui.hideModal() }) ] ) ]) ); }; renderModalLayout(); ui.showModal(_("AI Doctor Diagnosis"), mainContainer); } else { const errorMsg = typeof aiRes.error === "string" ? aiRes.error : _("Unknown error"); showToast(_("AI Doctor failed") + ": " + errorMsg, "error"); } } catch (e) { logger.error( "[DIAGNOSTIC]", "handleRunAiDoctor - e", e instanceof Error ? e.message : String(e) ); showToast(_("AI Doctor failed"), "error"); } finally { setDiagnosticActionLoading("aiDoctor", false); } } function handleOpenAiChat() { renderAiChatModal(); } function handleOpenLeakCheck() { renderLeakCheckModal(); } function handleOpenStrategyFuzzer() { getConfigSections().then((sections) => { const ruleNames = sections.filter((s) => s[".type"] === "section" || s[".type"] === "rule").map((s) => s.name || s[".name"]).filter((n) => Boolean(n)); renderStrategyFuzzerModal(ruleNames); }).catch(() => { renderStrategyFuzzerModal([]); }); } function handleRestoreNativeInternet() { ui.showModal( _("Restore Native Internet"), E("div", {}, [ E( "p", {}, _( "This action will stop Tachyon, restore standard dnsmasq/DNS, remove all proxy/anti-censorship firewall rules and restore direct Internet connection through your ISP." ) ), E( "div", { class: "right", style: "display: flex; justify-content: flex-end; gap: 8px; margin-top: 15px;" }, [ renderButton({ classNames: ["cbi-button-neutral"], text: _("Cancel"), onClick: () => ui.hideModal() }), renderButton({ classNames: ["cbi-button-reset"], text: "🚨 " + _("Restore Native Internet"), onClick: async () => { ui.hideModal(); showToast( _("Restoring native direct internet (stopping Tachyon)..."), "success" ); const res = await TachyonShellMethods.applyQuickFix( "restore_native_internet" ); if (res && typeof res === "object" && res.success) { showToast( _("Native internet restored. Tachyon stopped."), "success" ); await refreshDiagnosticServicesInfo({ force: true, allowInactive: true }); } else { showToast(_("Failed to restore native internet"), "error"); } } }) ] ) ]) ); } async function handleShowSingBoxConfig() { setDiagnosticActionLoading("showSingBoxConfig", true); try { const showSingBoxConfig = await TachyonShellMethods.showSingBoxConfig(false); if (showSingBoxConfig.success) { const rawSingBoxConfigText = stringifySingBoxConfig( showSingBoxConfig.data ); const maskedSingBoxConfigText = formatMaskedSingBoxConfig( showSingBoxConfig.data ); ui.showModal( _("Show sing-box config"), renderModal(rawSingBoxConfigText, "show_sing_box_config", { maskText: () => maskedSingBoxConfigText, initialAutoRefresh: false, showMaskValuesToggle: true }) ); } else { notifyActionFailure( "handleShowSingBoxConfig", showSingBoxConfig, _("Show sing-box config failed") ); } } catch (e) { notifyActionFailure( "handleShowSingBoxConfig", e, _("Show sing-box config failed") ); } finally { setDiagnosticActionLoading("showSingBoxConfig", false); } } async function handleGenerateBugReport() { setDiagnosticActionLoading("generateBugReport", true); try { const configResult = await fs.read("/etc/config/tachyon").catch(() => ""); const logsResult = await executeShellCommand({ command: "/sbin/logread", args: ["-e", "tachyon", "-l", "1000"] }); const singboxLogsResult = await executeShellCommand({ command: "/sbin/logread", args: ["-e", "sing-box", "-l", "1000"] }); const filterUdpErrors = (text) => { if (!text || !text.includes("UDP is not supported by outbound:")) return text; const lines = text.split("\n"); const filtered = []; let noticeEmitted = false; for (const line of lines) { if (line.includes("UDP is not supported by outbound:")) { if (!noticeEmitted) { filtered.push( "UDP traffic through HTTP outbounds is not supported by sing-box; repeated UDP warnings for HTTP outbounds are hidden by Tachyon." ); noticeEmitted = true; } continue; } filtered.push(line); } return filtered.join("\n"); }; const rawReport = [ "--- TACHYON CONFIG ---", configResult || "Failed to fetch config", "", "--- TACHYON LOGS ---", logsResult.code === 0 ? filterUdpErrors(logsResult.stdout) : "Failed to fetch tachyon logs", "", "--- SING-BOX LOGS ---", singboxLogsResult.code === 0 ? filterUdpErrors(singboxLogsResult.stdout) : "Failed to fetch sing-box logs" ].join("\n"); const maskedReport = maskGlobalCheckText(rawReport); const blob = new Blob([maskedReport], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `tachyon-bugreport-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.txt`; a.style.display = "none"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast(_("Bug report downloaded"), "success"); } catch (e) { logger.error("[DIAGNOSTIC]", "handleGenerateBugReport - e", e); showToast(_("Failed to generate bug report"), "error"); } finally { setDiagnosticActionLoading("generateBugReport", false); } } function renderWikiDisclaimerWidget() { const diagnosticsChecks = store.get().diagnosticsChecks; function getWikiKind() { const allResults = diagnosticsChecks.map((check) => check.state); if (allResults.includes("error")) { return "error"; } if (allResults.includes("warning")) { return "warning"; } return "default"; } const container = document.getElementById("tachyon_diagnostic-page-wiki"); return preserveScrollForPage(() => { container.replaceChildren(renderWikiDisclaimer(getWikiKind())); }); } function renderDiagnosticAvailableActionsWidget() { const diagnosticsActions = store.get().diagnosticsActions; const updatesActions = store.get().updatesActions; const servicesInfoWidget = store.get().servicesInfoWidget; logger.debug("[DIAGNOSTIC]", "renderDiagnosticAvailableActionsWidget"); const tachyonEnabled = Boolean(servicesInfoWidget.data.tachyonEnabled); const tachyonRunning = Boolean(servicesInfoWidget.data.tachyonRunning); const serviceTransition = getServiceTransition( servicesInfoWidget.data.tachyonStatus ); const restartLoading = diagnosticsActions.restart.loading || serviceTransition.restarting; const startLoading = diagnosticsActions.start.loading || serviceTransition.starting; const stopLoading = diagnosticsActions.stop.loading || serviceTransition.stopping; const atLeastOneMutatingActionLoading = restartLoading || startLoading || stopLoading || diagnosticsActions.enable.loading || diagnosticsActions.disable.loading; const componentActionLoading = hasComponentActionLoading(updatesActions); const { serviceControlsDisabled, utilityActionsDisabled, viewLogsDisabled } = getAvailableActionsDisabledState({ servicesInfoLoading: servicesInfoWidget.loading, mutatingServiceActionLoading: atLeastOneMutatingActionLoading, componentActionLoading }); const startVisible = shouldShowStartAction({ tachyonRunning, restartLoading, startLoading, stopLoading }); const stopVisible = shouldShowStopAction({ tachyonRunning, restartLoading, startLoading, stopLoading }); const container = document.getElementById("tachyon_diagnostic-page-actions"); const renderedActions = renderAvailableActions({ restart: { loading: restartLoading, visible: shouldShowRestartAction({ tachyonRunning, restartLoading, startLoading, stopLoading }), onClick: handleRestart, disabled: serviceControlsDisabled }, start: { loading: startLoading, visible: startVisible, onClick: handleStart, disabled: serviceControlsDisabled }, stop: { loading: stopLoading, visible: stopVisible, onClick: handleStop, disabled: serviceControlsDisabled }, enable: { loading: diagnosticsActions.enable.loading, visible: !tachyonEnabled, onClick: handleEnable, disabled: serviceControlsDisabled }, disable: { loading: diagnosticsActions.disable.loading, visible: tachyonEnabled, onClick: handleDisable, disabled: serviceControlsDisabled }, globalCheck: { loading: diagnosticsActions.globalCheck.loading, visible: true, onClick: handleShowGlobalCheck, disabled: utilityActionsDisabled }, doctor: { loading: diagnosticsActions.doctor.loading, visible: true, onClick: handleRunDoctor, disabled: utilityActionsDisabled }, aiDoctor: { loading: diagnosticsActions.aiDoctor.loading, visible: true, onClick: handleRunAiDoctor, disabled: diagnosticsActions.aiDoctor.loading }, restoreNativeInternet: { loading: false, visible: true, onClick: handleRestoreNativeInternet, disabled: false }, aiChat: { loading: false, visible: true, onClick: handleOpenAiChat, disabled: false }, strategyFuzzer: { loading: false, visible: true, onClick: handleOpenStrategyFuzzer, disabled: false }, checkServices: { visible: true, loading: diagnosticsActions.checkServices.loading, disabled: utilityActionsDisabled, onClick: handleCheckServicesAction }, testLeaks: { visible: true, loading: false, disabled: false, onClick: handleOpenLeakCheck }, viewLogs: { loading: diagnosticsActions.viewLogs.loading, visible: true, onClick: handleViewLogs, disabled: viewLogsDisabled }, showSingBoxConfig: { loading: diagnosticsActions.showSingBoxConfig.loading, visible: true, onClick: handleShowSingBoxConfig, disabled: utilityActionsDisabled }, generateBugReport: { loading: diagnosticsActions.generateBugReport.loading, visible: true, onClick: handleGenerateBugReport, disabled: utilityActionsDisabled } }); return preserveScrollForPage(() => { container.replaceChildren(renderedActions); }); } function renderDiagnosticSystemInfoWidget() { logger.debug("[DIAGNOSTIC]", "renderDiagnosticSystemInfoWidget"); const diagnosticsSystemInfo = store.get().diagnosticsSystemInfo; const container = document.getElementById( "tachyon_diagnostic-page-system-info" ); const items = [ { key: "Tachyon", value: normalizeCompiledVersion( diagnosticsSystemInfo.tachyon_version, diagnosticsSystemInfo.tachyon_commit_sha ) }, { key: "Luci App", value: normalizeCompiledVersion(TACHYON_LUCI_APP_VERSION) }, { key: "Sing-box", value: formatSingBoxVersion(diagnosticsSystemInfo) } ]; if (diagnosticsSystemInfo.zapret_installed) { items.push({ key: "Zapret", value: diagnosticsSystemInfo.zapret_version }); } if (diagnosticsSystemInfo.zapret2_installed) { items.push({ key: "Zapret2", value: diagnosticsSystemInfo.zapret2_version }); } if (diagnosticsSystemInfo.byedpi_installed) { items.push({ key: "ByeDPI", value: diagnosticsSystemInfo.byedpi_version }); } items.push( { key: "OS", value: diagnosticsSystemInfo.openwrt_version }, { key: "Device", value: diagnosticsSystemInfo.device_model } ); const renderedSystemInfo = renderSystemInfo({ items }); return preserveScrollForPage(() => { container.replaceChildren(renderedSystemInfo); }); } async function onStoreUpdate2(_next, _prev, diff) { if (diff.diagnosticsChecks) { renderDiagnosticsChecks(); renderWikiDisclaimerWidget(); } if (diff.diagnosticsRunAction) { renderDiagnosticRunActionWidget(); } if (diff.diagnosticsActions || diff.servicesInfoWidget || diff.updatesActions) { renderDiagnosticAvailableActionsWidget(); } if (diff.diagnosticsActions || diff.servicesInfoWidget) { renderDiagnosticRunActionWidget(); } if (diff.diagnosticsSystemInfo) { renderDiagnosticSystemInfoWidget(); renderDiagnosticRunActionWidget(); } } function persistDiagnosticRunProgress({ providerOptions, nextRunnerIndex }) { savePersistedDiagnosticRun({ providerOptions, nextRunnerIndex, diagnosticsChecks: store.get().diagnosticsChecks }); } function setDiagnosticCheckLoading(code) { const meta = DIAGNOSTICS_CHECKS_MAP[code]; const diagnosticsChecks = store.get().diagnosticsChecks; const other = diagnosticsChecks.filter((item) => item.code !== code); store.set({ diagnosticsChecks: [ ...other, { order: meta.order, code: meta.code, title: meta.title, description: _("Checking, please wait"), state: "loading", items: [] } ] }); } function getDiagnosticRunners(providerOptions) { return [ { code: "DNS" /* DNS */, run: runDnsCheck }, { code: "SINGBOX" /* SINGBOX */, run: runSingBoxCheck }, ...providerOptions.includeInbounds ? [{ code: "INBOUNDS" /* INBOUNDS */, run: runInboundsCheck }] : [], { code: "NFT" /* NFT */, run: runNftCheck }, ...providerOptions.includeZapret ? [{ code: "ZAPRET" /* ZAPRET */, run: runZapretCheck }] : [], ...providerOptions.includeZapret2 ? [{ code: "ZAPRET2" /* ZAPRET2 */, run: runZapret2Check }] : [], ...providerOptions.includeByedpi ? [{ code: "BYEDPI" /* BYEDPI */, run: runByedpiCheck }] : [], { code: "OUTBOUNDS" /* OUTBOUNDS */, run: runSectionsCheck }, { code: "FAKEIP" /* FAKEIP */, run: runFakeIPCheck } ]; } async function runChecks({ resume } = {}) { if (store.get().diagnosticsRunAction.loading && !resume) { return; } let providerOptions = resume?.providerOptions ?? getDiagnosticsProviderOptions(); let nextRunnerIndex = resume?.nextRunnerIndex ?? 0; store.set({ diagnosticsRunAction: { loading: true }, diagnosticsChecks: resume?.diagnosticsChecks ?? getLoadingDiagnosticsChecks(providerOptions).diagnosticsChecks }); persistDiagnosticRunProgress({ providerOptions, nextRunnerIndex }); try { if (!resume) { await fetchDiagnosticsProviderInfo({ resetChecks: false }); providerOptions = getDiagnosticsProviderOptions(); nextRunnerIndex = 0; store.set({ diagnosticsChecks: getLoadingDiagnosticsChecks(providerOptions).diagnosticsChecks }); persistDiagnosticRunProgress({ providerOptions, nextRunnerIndex }); } const runners = getDiagnosticRunners(providerOptions); for (let index = nextRunnerIndex; index < runners.length; index += 1) { const runner = runners[index]; setDiagnosticCheckLoading(runner.code); persistDiagnosticRunProgress({ providerOptions, nextRunnerIndex: index }); try { await runner.run(); } catch (e) { logger.error( "[DIAGNOSTIC]", `runChecks - ${runner.run.name} failed`, e ); } persistDiagnosticRunProgress({ providerOptions, nextRunnerIndex: index + 1 }); } } catch (e) { logger.error("[DIAGNOSTIC]", "runChecks - e", e); } finally { clearPersistedDiagnosticRun(); store.set({ diagnosticsRunAction: { loading: false } }); if (!diagnosticMounted) { diagnosticCompletedWhileHidden = true; } } } async function loadInitialDiagnosticData() { const diagnosticStatus = document.getElementById("diagnostic-status"); if (diagnosticStatus?.isConnected && diagnosticStatus.offsetParent !== null) { if (store.get().diagnosticsRunAction.loading) { return; } await fetchSystemInfo(); await fetchDiagnosticsProviderInfo(); } } function restorePersistedDiagnosticRun() { const persistedRun = readPersistedDiagnosticRun(); if (!persistedRun) { return false; } store.set({ diagnosticsRunAction: { loading: true }, diagnosticsChecks: persistedRun.diagnosticsChecks }); void runChecks({ resume: persistedRun }); return true; } async function onPageMount2() { const preserveHiddenResult = diagnosticCompletedWhileHidden; onPageUnmount2({ preserveCompletedResult: preserveHiddenResult, preservePersistedRun: true }); diagnosticMounted = true; diagnosticMountId += 1; const mountId = diagnosticMountId; const hasRuntimeSnapshot = Boolean(getCachedRuntimeUiState()); if (!hasRuntimeSnapshot) { const uiState = await refreshRuntimeUiState({ force: true }); if (!diagnosticMounted || mountId !== diagnosticMountId) { return; } if (!uiState) { void refreshDiagnosticServicesInfo({ force: true }); } } const restoredPersistedRun = !preserveHiddenResult && restorePersistedDiagnosticRun(); if (preserveHiddenResult) { diagnosticCompletedWhileHidden = false; } else if (!restoredPersistedRun && !store.get().diagnosticsRunAction.loading) { store.reset(["diagnosticsRunAction"]); resetDiagnosticsChecks(); } store.subscribe(onStoreUpdate2); startServiceActionStateWatcher(); renderDiagnosticsChecks(); renderDiagnosticRunActionWidget(); renderDiagnosticAvailableActionsWidget(); renderDiagnosticSystemInfoWidget(); renderWikiDisclaimerWidget(); if (hasRuntimeSnapshot) { void refreshRuntimeUiState({ force: true }); } if (!preserveHiddenResult && !restoredPersistedRun) { void loadInitialDiagnosticData(); } } function onPageUnmount2({ preserveCompletedResult = false, preservePersistedRun = false } = {}) { diagnosticMounted = false; diagnosticMountId += 1; stopServiceActionStateWatcher(); servicesInfoRefreshPromise = null; store.unsubscribe(onStoreUpdate2); if (!preserveCompletedResult && !store.get().diagnosticsRunAction.loading) { if (!preservePersistedRun) { clearPersistedDiagnosticRun(); } store.reset(["diagnosticsRunAction"]); resetDiagnosticsChecks(); diagnosticCompletedWhileHidden = false; } } function registerLifecycleListeners2() { if (diagnosticLifecycleRegistered) { return; } diagnosticLifecycleRegistered = true; store.subscribe((next, prev, diff) => { if (diff.tabService && next.tabService.current !== prev.tabService.current) { logger.debug( "[DIAGNOSTIC]", "active tab diff event, active tab:", diff.tabService.current ); const isDIAGNOSTICVisible = next.tabService.current === "diagnostic"; if (isDIAGNOSTICVisible) { logger.debug( "[DIAGNOSTIC]", "registerLifecycleListeners", "onPageMount" ); return onPageMount2(); } if (!isDIAGNOSTICVisible) { logger.debug( "[DIAGNOSTIC]", "registerLifecycleListeners", "onPageUnmount" ); return onPageUnmount2(); } } }); } async function initController2() { if (diagnosticControllerInitialized) { return; } diagnosticControllerInitialized = true; onMount("diagnostic-status").then(() => { logger.debug("[DIAGNOSTIC]", "initController", "onMount"); registerLifecycleListeners2(); if (store.get().tabService.current === "diagnostic" || isActiveLuciTab("diagnostic")) { onPageMount2(); } }); } // src/tachyon/tabs/diagnostic/styles.ts var styles4 = ` #cbi-${TACHYON_UCI_PACKAGE}-diagnostic-_mount_node > div { width: 100%; } #cbi-${TACHYON_UCI_PACKAGE}-diagnostic > h3 { display: none; } .tachyon_diagnostic-page { display: grid; grid-template-columns: 2fr 1fr; grid-column-gap: 10px; align-items: start; } @media (max-width: 800px) { .tachyon_diagnostic-page { grid-template-columns: 1fr; } } .tachyon_diagnostic-page__right-bar { display: grid; grid-template-columns: 1fr; grid-row-gap: 10px; } .tachyon_diagnostic-page__right-bar__wiki { border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 10px; display: grid; grid-template-columns: auto; grid-row-gap: 10px; } .tachyon_diagnostic-page__right-bar__wiki--warning { border: 2px var(--warn-color-medium, orange) solid; } .tachyon_diagnostic-page__right-bar__wiki--error { border: 2px var(--error-color-medium, red) solid; } .tachyon_diagnostic-page__right-bar__wiki__content { display: grid; grid-template-columns: 1fr 5fr; grid-column-gap: 10px; } .tachyon_diagnostic-page__right-bar__wiki__texts {} .tachyon_diagnostic-page__right-bar__actions { border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 10px; display: grid; grid-template-columns: auto; grid-row-gap: 10px; } .tachyon_diagnostic-page__right-bar__actions > .tachyon-partial-button { width: 100%; min-width: 0; margin-left: 0; } .tachyon_diagnostic-page__right-bar__system-info { border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 10px; display: grid; grid-template-columns: auto; grid-row-gap: 10px; } .tachyon_diagnostic-page__right-bar__system-info__title { } .tachyon_diagnostic-page__right-bar__system-info__row { display: grid; grid-template-columns: auto 1fr; grid-column-gap: 5px; } .tachyon_diagnostic-page__right-bar__system-info__row__tag { padding: 2px 4px; border: 1px transparent solid; border-radius: 4px; margin-left: 5px; } .tachyon_diagnostic-page__right-bar__system-info__row__tag--neutral { border: 1px var(--background-color-high, gray) solid; color: var(--text-color-medium, gray); } .tachyon_diagnostic-page__right-bar__system-info__row__tag--warning { border: 1px var(--warn-color-medium, orange) solid; color: var(--warn-color-medium, orange); } .tachyon_diagnostic-page__right-bar__system-info__row__tag--success { border: 1px var(--success-color-medium, green) solid; color: var(--success-color-medium, green); } .tachyon_diagnostic-page__left-bar { display: grid; grid-template-columns: 1fr; grid-row-gap: 10px; } .tachyon_diagnostic-page__run_check_wrapper {} .tachyon_diagnostic-page__run_check_wrapper button { width: 100%; } .tachyon_diagnostic-page__checks { display: grid; grid-template-columns: 1fr; grid-row-gap: 10px; } .tachyon_diagnostic_alert { border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; display: grid; grid-template-columns: 24px 1fr; grid-column-gap: 10px; align-items: center; padding: 10px; } .tachyon_diagnostic_alert--loading { border: 2px var(--primary-color-high, dodgerblue) solid; } .tachyon_diagnostic_alert--warning { border: 2px var(--warn-color-medium, orange) solid; color: var(--warn-color-medium, orange); } .tachyon_diagnostic_alert--error { border: 2px var(--error-color-medium, red) solid; color: var(--error-color-medium, red); } .tachyon_diagnostic_alert--success { border: 2px var(--success-color-medium, green) solid; color: var(--success-color-medium, green); } .tachyon_diagnostic_alert--skipped {} .tachyon_diagnostic_alert__icon {} .tachyon_diagnostic_alert__content {} .tachyon_diagnostic_alert__title { display: block; } .tachyon_diagnostic_alert__description {} .tachyon_diagnostic_alert__summary { margin-top: 10px; } .tachyon_diagnostic_alert__summary__item { display: grid; grid-template-columns: 16px auto 1fr; grid-column-gap: 10px; } .tachyon_diagnostic_alert__summary__item--error { color: var(--error-color-medium, red); } .tachyon_diagnostic_alert__summary__item--warning { color: var(--warn-color-medium, orange); } .tachyon_diagnostic_alert__summary__item--success { color: var(--success-color-medium, green); } .tachyon_diagnostic_alert__summary__item__icon { width: 16px; height: 16px; } `; // src/tachyon/tabs/diagnostic/index.ts var DiagnosticTab = { render: render2, initController: initController2, styles: styles4 }; // src/tachyon/tabs/monitoring/render.ts function render3() { return E( "div", { id: "monitoring-status", class: "tachyon_monitoring-page" }, [ E("div", { class: "tachyon_monitoring-page__panel" }, [ E("div", { class: "tachyon_monitoring-page__controls" }, [ E("div", { class: "tachyon_monitoring-page__tabs" }, [ E( "button", { id: "monitoring-tab-active", class: "btn cbi-button tachyon_monitoring-page__tab tachyon_monitoring-page__tab--active", type: "button" }, `${_("Active")} 0` ), E( "button", { id: "monitoring-tab-closed", class: "btn cbi-button tachyon_monitoring-page__tab", type: "button" }, `${_("Closed")} 0` ) ]), E("div", { class: "tachyon_monitoring-page__filters" }, [ E( "select", { id: "monitoring-device-filter", class: "cbi-input-select tachyon_monitoring-page__device-filter" }, [E("option", { value: "all" }, _("All"))] ), E( "select", { id: "monitoring-route-filter", class: "cbi-input-select tachyon_monitoring-page__device-filter tachyon_monitoring-page__route-filter" }, [E("option", { value: "all" }, _("All Routes"))] ), E("label", { class: "tachyon_monitoring-page__search" }, [ E("span", { class: "tachyon_monitoring-page__search-icon" }, []), E("input", { id: "monitoring-search", class: "cbi-input-text tachyon_monitoring-page__search-input", type: "search", placeholder: _("Search"), autocomplete: "off" }) ]) ]), E("div", { class: "tachyon_monitoring-page__actions" }, [ E( "button", { id: "monitoring-close-all", class: "btn cbi-button tachyon_monitoring-page__icon-button", title: _("Close all connections"), "aria-label": _("Close all connections"), type: "button", disabled: true }, [] ), E( "button", { id: "monitoring-pause-toggle", class: "btn cbi-button tachyon_monitoring-page__icon-button", title: _("Pause updates"), "aria-label": _("Pause updates"), type: "button" }, [] ) ]) ]), E( "div", { id: "monitoring-connections", class: "tachyon_monitoring-page__body" }, [ E( "div", { class: "tachyon_monitoring-page__state tachyon_monitoring-page__state--loading" }, _("Loading connections") ) ] ) ]) ] ); } // src/tachyon/tabs/monitoring/initController.ts function normalizeConnectionsPayload(value) { if (!value || typeof value !== "object" || Array.isArray(value)) { return {}; } return value; } var RENDER_INTERVAL_MS = 500; var CONNECTIONS_RPC_POLL_INTERVAL_MS = 3e3; var CLOSED_CONNECTION_LIMIT = 300; var ALL_FILTER_VALUE = "all"; var dependencies = {}; var monitoringMounted = false; var monitoringMountId = 0; var monitoringLifecycleRegistered = false; var monitoringControllerInitialized = false; var serviceStateUnsubscribe = null; var renderTimer = null; var connectionsPollTimer = null; var connectionsSocketUrl = ""; var directConnectionsSocketFailedAt = 0; var DIRECT_SOCKET_COOLDOWN_MS = 6e4; function canUseConnectionsSocket() { if (Date.now() - directConnectionsSocketFailedAt < DIRECT_SOCKET_COOLDOWN_MS) { return false; } return canUseDirectClashApi(); } var connectionsUpdatesId = 0; var renderSkippedForSelection = false; var pendingConnectionsPayload = null; var pollingConnections = false; var activeTab = "active"; var selectedDeviceFilter = ALL_FILTER_VALUE; var selectedRouteFilter = ALL_FILTER_VALUE; var searchQuery = ""; var localDeviceChoices = {}; var routeDisplayNames = {}; var routeSections = []; var serverDisplayNames = {}; var lastDeviceFilterSignature = ""; var lastRouteFilterSignature = ""; var loading = true; var failed = false; var closingAll = false; var monitoringPaused = false; var monitoringPausedAt = null; var serviceAvailability = "loading"; var activeConnections = /* @__PURE__ */ new Map(); var closedConnections = /* @__PURE__ */ new Map(); var closingConnectionIds = /* @__PURE__ */ new Set(); function normalizeString(value) { return value == null ? "" : String(value).trim(); } function getListValues2(value) { if (!value) { return []; } if (Array.isArray(value)) { return value.map((item) => normalizeString(item)).filter(Boolean); } return normalizeString(value).split(/\s+/).map((item) => item.trim()).filter(Boolean); } function getUrlTestIds2(section) { const values = getListValues2(section.urltests); return values.length ? values : section.urltest_enabled === "1" ? ["urltest"] : []; } function getUrlTestTag2(sectionName, id) { return getOutboundTagBySection( id === "urltest" ? `${sectionName}-urltest` : `${sectionName}-urltest-${id}` ); } function formatEndpoint(address, port) { const normalizedAddress = normalizeString(address); const normalizedPort = normalizeString(port); if (!normalizedAddress) { return "-"; } if (!normalizedPort) { return normalizedAddress; } if (normalizedPort === "443") { return normalizedAddress; } if (normalizedAddress.includes(":") && !normalizedAddress.startsWith("[")) { return `[${normalizedAddress}]:${normalizedPort}`; } return `${normalizedAddress}:${normalizedPort}`; } function getDisplayName2(section) { return normalizeString(section.label) || section[".name"]; } function buildRouteDisplayNames(sections) { const map = { "bypass-out": "Bypass", "direct-out": "direct", "tachyon-failover": "Failover" }; const serverMap = {}; const routeSectionItems = []; const urltestsBySection = /* @__PURE__ */ new Map(); sections.filter((section) => section[".type"] === "urltest").forEach((section) => { const owner = normalizeString(section.section); const id = normalizeString(section.id) || section[".name"]; if (!owner || !id) { return; } urltestsBySection.set(owner, [ ...urltestsBySection.get(owner) || [], id ]); }); sections.filter((section) => section[".type"] === "section").filter((section) => section.enabled !== "0").forEach((section) => { const sectionName = section[".name"]; const displayName = getDisplayName2(section); if (!sectionName || !displayName) { return; } routeSectionItems.push({ sectionName, displayName }); map[getOutboundTagBySection(sectionName)] = displayName; const urltestIds = urltestsBySection.get(sectionName) || getUrlTestIds2(section); urltestIds.forEach((id) => { map[getUrlTestTag2(sectionName, id)] = displayName; }); }); sections.filter((section) => section[".type"] === "server").filter((section) => section.enabled !== "0").forEach((section) => { const sectionName = section[".name"]; const displayName = getDisplayName2(section); if (!sectionName || !displayName) { return; } serverMap[`server-${sectionName}-in`] = displayName; }); routeDisplayNames = map; serverDisplayNames = serverMap; routeSections = routeSectionItems.sort( (a, b) => b.sectionName.length - a.sectionName.length ); } function getRouteDisplayNameByTag(tag) { if (!tag) { return ""; } if (routeDisplayNames[tag]) { return routeDisplayNames[tag]; } const manualSection = routeSections.find(({ sectionName }) => { if (tag === sectionName) { return true; } return tag.startsWith(`${sectionName}-`) && tag.endsWith("-out"); }); return manualSection?.displayName || ""; } function getRouteTagFromRule(rule) { const match = normalizeString(rule).match(/=>\s*route\(([^)]+)\)/); return normalizeString(match?.[1]).replace(/^['"]|['"]$/g, ""); } function parseStartedAt(connection) { const startedAt = Date.parse(connection.start || ""); return Number.isFinite(startedAt) ? startedAt : connection.lastSeenAt; } function formatDuration(ms) { const totalSeconds = Math.max(0, Math.floor(ms / 1e3)); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor(totalSeconds % 3600 / 60); const seconds = totalSeconds % 60; const pad = (value) => String(value).padStart(2, "0"); if (hours > 0) { return `${hours}:${pad(minutes)}:${pad(seconds)}`; } return `${minutes}:${pad(seconds)}`; } function formatConnectionDuration(connection) { const startedAt = parseStartedAt(connection); const finishedAt = connection.closedAt || monitoringPausedAt || Date.now(); return formatDuration(finishedAt - startedAt); } function formatBytes2(value) { return prettyBytes(Number.isFinite(value) ? Number(value) : 0); } function getConnectionSourceIp(connection) { return normalizeString(connection.metadata?.sourceIP); } function getConnectionInboundTag(connection) { const metadataType = normalizeString(connection.metadata?.type); const metadataTypeParts = metadataType.split("/"); const metadataTag = normalizeString( metadataTypeParts.length > 1 ? metadataTypeParts[metadataTypeParts.length - 1] : metadataType ); if (metadataTag) { return metadataTag; } const ruleInbound = normalizeString(connection.rule).match( /(?:^|\s)inbound=([^\s]+)/ ); return normalizeString(ruleInbound?.[1]); } function getServerDisplayNameByInboundTag(tag) { return normalizeString(serverDisplayNames[tag]); } function getDeviceName(ip) { const raw = normalizeString(localDeviceChoices[ip]); if (!raw) return ""; const match = raw.match(/^(?:IP|MAC):\s*[^\s—]+\s*—\s*(.+)$/i); if (match && match[1]) { return match[1].trim(); } if (!raw.startsWith("IP:") && !raw.startsWith("MAC:") && raw !== ip) { return raw; } return ""; } function getServerSourceNameByIp(ip) { if (!ip) { return ""; } const connections = [ ...Array.from(activeConnections.values()), ...Array.from(closedConnections.values()) ]; for (const connection of connections) { if (getConnectionSourceIp(connection) !== ip) { continue; } const serverName = getServerDisplayNameByInboundTag( getConnectionInboundTag(connection) ); if (serverName) { return serverName; } } return ""; } function getDeviceFilterLabel(ip) { const serverName = getServerSourceNameByIp(ip); if (serverName) { return serverName; } const deviceName = getDeviceName(ip); return deviceName ? `${deviceName} (${ip})` : ip; } function getSourceCellParts(connection) { const ip = getConnectionSourceIp(connection); const inboundTag = getConnectionInboundTag(connection); const serverName = getServerDisplayNameByInboundTag(inboundTag); if (serverName) { return { primary: serverName, ip: "", copyValue: serverName, searchValue: [serverName, ip, inboundTag].filter(Boolean).join(" ") }; } const deviceName = getDeviceName(ip); if (deviceName) { return { primary: deviceName, ip, copyValue: `${deviceName} (${ip})`, searchValue: `${deviceName} ${ip}` }; } return { primary: ip || "-", ip: "", copyValue: ip || "-", searchValue: ip }; } function getTargetCellParts(connection) { const metadata = connection.metadata || {}; const host = normalizeString(metadata.host); const destinationIp = normalizeString(metadata.destinationIP); const port = metadata.destinationPort; const primaryTarget = host || destinationIp; const primary = primaryTarget ? formatEndpoint(primaryTarget, port) : "-"; return { primary, searchValue: [primary, host, destinationIp].filter(Boolean).join(" ") }; } function getRoute(connection) { const chains = Array.isArray(connection.chains) ? connection.chains : []; const routeTag = [...chains].reverse().find(getRouteDisplayNameByTag); const fallbackRouteTag = getRouteTagFromRule(connection.rule); const route = getRouteDisplayNameByTag(routeTag || "") || getRouteDisplayNameByTag(fallbackRouteTag) || normalizeString(routeTag) || normalizeString(fallbackRouteTag); return route || "-"; } function getNetwork(connection) { return normalizeString(connection.metadata?.network).toLowerCase() || "-"; } function sortConnections(connections, tab) { return [...connections].sort((a, b) => { if (tab === "closed") { return (b.closedAt || 0) - (a.closedAt || 0); } return parseStartedAt(b) - parseStartedAt(a); }); } function getConnectionsForActiveTab() { const source = activeTab === "active" ? Array.from(activeConnections.values()) : Array.from(closedConnections.values()); return sortConnections(source, activeTab); } function normalizeSearchValue(value) { return value.toLowerCase().replace(/\s+/g, " ").trim(); } function getSearchValues(connection) { const target = getTargetCellParts(connection); const source = getSourceCellParts(connection); return [ connection.id, target.primary, getNetwork(connection), getRoute(connection), formatConnectionDuration(connection), formatBytes2(connection.download), formatBytes2(connection.upload), source.primary, source.copyValue, source.searchValue ].filter(Boolean); } function getVisibleConnections() { const normalizedSearch = normalizeSearchValue(searchQuery); return getConnectionsForActiveTab().filter((connection) => { const sourceIp = getConnectionSourceIp(connection); const isMatchingDevice = selectedDeviceFilter === ALL_FILTER_VALUE || sourceIp === selectedDeviceFilter; let isMatchingRoute = selectedRouteFilter === ALL_FILTER_VALUE; if (!isMatchingRoute && getRoute(connection) === selectedRouteFilter) { isMatchingRoute = true; } if (!isMatchingDevice || !isMatchingRoute) { return false; } if (!normalizedSearch) { return true; } return getSearchValues(connection).some( (value) => normalizeSearchValue(value).includes(normalizedSearch) ); }); } function moveConnectionToClosed(connection, now) { closedConnections.set(connection.id, { ...connection, closedAt: now, lastSeenAt: now }); } function trimClosedConnections() { const sorted = sortConnections( Array.from(closedConnections.values()), "closed" ); sorted.slice(CLOSED_CONNECTION_LIMIT).forEach((connection) => { closedConnections.delete(connection.id); }); } function applyConnectionsPayload(payload) { if (monitoringPaused) { pendingConnectionsPayload = payload; return; } const mountId = monitoringMountId; const now = Date.now(); const incomingIds = /* @__PURE__ */ new Set(); const rawConnections = Array.isArray(payload.connections) ? payload.connections : []; rawConnections.forEach((rawConnection) => { const id = normalizeString(rawConnection.id); if (!id) { return; } incomingIds.add(id); closedConnections.delete(id); activeConnections.set(id, { ...rawConnection, id, lastSeenAt: now }); }); Array.from(activeConnections.entries()).forEach(([id, connection]) => { if (!incomingIds.has(id)) { activeConnections.delete(id); moveConnectionToClosed(connection, now); } }); trimClosedConnections(); loading = false; failed = false; if (monitoringMounted && mountId === monitoringMountId) { renderControls(); renderConnections2(); } } function setTab(tab) { if (activeTab === tab) { return; } activeTab = tab; renderControls(); renderConnections2(); } function getKnownSourceIps() { const ips = /* @__PURE__ */ new Set(); activeConnections.forEach((connection) => { const ip = getConnectionSourceIp(connection); if (ip) { ips.add(ip); } }); closedConnections.forEach((connection) => { const ip = getConnectionSourceIp(connection); if (ip) { ips.add(ip); } }); return Array.from(ips).sort((a, b) => { const byLabel = getDeviceFilterLabel(a).localeCompare( getDeviceFilterLabel(b) ); return byLabel || a.localeCompare(b); }); } function renderRouteFilterOptions() { const select = document.getElementById( "monitoring-route-filter" ); if (!select) return; const uniqueRoutes = /* @__PURE__ */ new Set(); Object.values(routeDisplayNames).forEach((name) => { if (name) uniqueRoutes.add(name); }); const routes = Array.from(uniqueRoutes).sort(); if (selectedRouteFilter !== ALL_FILTER_VALUE && !routes.includes(selectedRouteFilter)) { selectedRouteFilter = ALL_FILTER_VALUE; } const signature = [selectedRouteFilter, ...routes].join("|"); if (signature === lastRouteFilterSignature) { select.value = selectedRouteFilter; return; } lastRouteFilterSignature = signature; const options = [ E("option", { value: ALL_FILTER_VALUE }, _("All Routes")), ...routes.map((name) => E("option", { value: name }, name)) ]; select.replaceChildren(...options); select.value = selectedRouteFilter; } function renderDeviceFilterOptions() { const select = document.getElementById( "monitoring-device-filter" ); if (!select) { return; } const sourceIps = getKnownSourceIps(); if (selectedDeviceFilter !== ALL_FILTER_VALUE && !sourceIps.includes(selectedDeviceFilter)) { selectedDeviceFilter = ALL_FILTER_VALUE; } const signature = [ selectedDeviceFilter, ...sourceIps.map((ip) => `${ip}:${getDeviceFilterLabel(ip)}`) ].join("|"); if (signature === lastDeviceFilterSignature) { select.value = selectedDeviceFilter; return; } lastDeviceFilterSignature = signature; const options = [ E("option", { value: ALL_FILTER_VALUE }, _("All")), ...sourceIps.map( (ip) => E("option", { value: ip }, getDeviceFilterLabel(ip)) ) ]; select.replaceChildren(...options); select.value = selectedDeviceFilter; } function setButtonActive(button, active) { if (!button) { return; } button.classList.toggle("tachyon_monitoring-page__tab--active", active); } function renderTabButtonContent(label, count) { return [ E("span", { class: "tachyon_monitoring-page__tab-label" }, label), E("span", { class: "tachyon_monitoring-page__tab-badge" }, String(count)) ]; } function renderControls() { const activeButton = document.getElementById( "monitoring-tab-active" ); const closedButton = document.getElementById( "monitoring-tab-closed" ); const closeAllButton = document.getElementById( "monitoring-close-all" ); const pauseToggleButton = document.getElementById( "monitoring-pause-toggle" ); if (activeButton) { activeButton.replaceChildren( ...renderTabButtonContent(_("Active"), activeConnections.size) ); activeButton.disabled = serviceAvailability === "stopped"; } if (closedButton) { closedButton.replaceChildren( ...renderTabButtonContent(_("Closed"), closedConnections.size) ); closedButton.disabled = serviceAvailability === "stopped"; } setButtonActive(activeButton, activeTab === "active"); setButtonActive(closedButton, activeTab === "closed"); if (closeAllButton) { closeAllButton.replaceChildren(renderXIcon24()); closeAllButton.disabled = serviceAvailability === "stopped" || activeConnections.size === 0 || closingAll; } if (pauseToggleButton) { const title = monitoringPaused ? _("Resume updates") : _("Pause updates"); pauseToggleButton.replaceChildren( monitoringPaused ? renderPlayIcon24() : renderPauseIcon24() ); pauseToggleButton.title = title; pauseToggleButton.setAttribute("aria-label", title); pauseToggleButton.disabled = serviceAvailability === "stopped"; pauseToggleButton.classList.toggle( "tachyon_monitoring-page__icon-button--active", monitoringPaused ); } const searchIcon = document.querySelector( ".tachyon_monitoring-page__search-icon" ); if (searchIcon && searchIcon.childNodes.length === 0) { searchIcon.replaceChildren(renderSearchIcon24()); } renderDeviceFilterOptions(); renderRouteFilterOptions(); const select = document.getElementById( "monitoring-device-filter" ); const searchInput = document.getElementById( "monitoring-search" ); if (select) { select.disabled = serviceAvailability === "stopped"; } if (searchInput) { searchInput.disabled = serviceAvailability === "stopped"; } } function renderValue(value, className = "") { const text = value || "-"; const element = E( "span", { class: ["tachyon_monitoring-page__value", className].filter(Boolean).join(" "), title: text }, text ); element.setAttribute("data-copy-value", text); return element; } function renderSourceValue(source) { const fullText = source.copyValue || source.primary || "-"; if (!source.ip) { const element2 = E( "span", { class: "tachyon_monitoring-page__value tachyon_monitoring-page__source-value tachyon_monitoring-page__source-value--ip-only", title: fullText }, source.primary || "-" ); element2.setAttribute("data-copy-value", fullText); return element2; } const element = E( "span", { class: "tachyon_monitoring-page__value tachyon_monitoring-page__source-value", title: fullText }, [ E( "span", { class: "tachyon_monitoring-page__source-name" }, source.primary ), E("span", { class: "tachyon_monitoring-page__source-ip" }, source.ip) ] ); element.setAttribute("data-copy-value", fullText); return element; } function renderTableCell(label, children) { const cell = E("td", {}, children); cell.setAttribute("data-label", label); return cell; } function renderConnectionRow(connection) { const target = getTargetCellParts(connection); const source = getSourceCellParts(connection); const isClosing = closingConnectionIds.has(connection.id); const closeButton = activeTab === "active" ? E( "button", { class: "btn cbi-button tachyon_monitoring-page__row-action", title: _("Close connection"), "aria-label": _("Close connection"), type: "button", value: connection.id, ...isClosing ? { disabled: true } : {} }, [renderXIcon24()] ) : E("span", {}, "-"); const row = E( "tr", { class: isClosing ? "tachyon_monitoring-page__row--closing" : "" }, [ renderTableCell(_("Host"), [renderValue(target.primary)]), renderTableCell(_("Type"), [ renderValue(getNetwork(connection), "tachyon_monitoring-page__network") ]), renderTableCell(_("Route"), [ renderValue(getRoute(connection), "tachyon_monitoring-page__route") ]), renderTableCell(_("Time"), [ renderValue(formatConnectionDuration(connection)) ]), renderTableCell(_("Downloaded"), [ renderValue(formatBytes2(connection.download)) ]), renderTableCell(_("Uploaded"), [ renderValue(formatBytes2(connection.upload)) ]), renderTableCell(_("Source"), [renderSourceValue(source)]), renderTableCell(_("Close"), [closeButton]) ] ); row.setAttribute("data-connection-id", connection.id); row.setAttribute("data-row-signature", getConnectionRowSignature(connection)); return row; } function renderStateRow(text, className = "") { return E("tr", { class: "tachyon_monitoring-page__state-row" }, [ E( "td", { class: "tachyon_monitoring-page__state-cell", colSpan: 8 }, [ E( "div", { class: ["tachyon_monitoring-page__state", className].filter(Boolean).join(" ") }, text ) ] ) ]); } function renderConnectionsTable(connections, state) { const rows = state ? [renderStateRow(state.text, state.className)] : connections.map(renderConnectionRow); return E("div", { class: "tachyon_monitoring-page__table-wrap" }, [ E( "table", { class: "table cbi-section-table tachyon_monitoring-page__table" }, [ E("thead", {}, [ E("tr", {}, [ E("th", {}, _("Host")), E("th", {}, _("Type")), E("th", {}, _("Route")), E("th", {}, _("Time")), E("th", {}, `↓ ${_("Downloaded")}`), E("th", {}, `↑ ${_("Uploaded")}`), E("th", {}, _("Source")), E("th", {}, _("Close")) ]) ]), E("tbody", {}, rows) ] ) ]); } function isNodeInsideMonitoring(node) { const root = document.getElementById("monitoring-status"); return Boolean(root && node && root.contains(node)); } function isTextSelectionInsideMonitoring() { const selection = window.getSelection?.(); if (!selection || selection.isCollapsed) { return false; } return isNodeInsideMonitoring(selection.anchorNode) || isNodeInsideMonitoring(selection.focusNode); } function renderConnections2(options = {}) { const container = document.getElementById("monitoring-connections"); if (!container) { return; } if (!options.force && isTextSelectionInsideMonitoring()) { renderSkippedForSelection = true; return; } renderSkippedForSelection = false; const previousScrollLeft = container.scrollLeft; if (serviceAvailability === "stopped") { container.replaceChildren( renderConnectionsTable([], { text: _( "Tachyon service is stopped. Start the service to display connections." ) }) ); return; } if (loading) { container.replaceChildren( renderConnectionsTable([], { text: _("Loading connections"), className: "tachyon_monitoring-page__state--loading" }) ); return; } if (failed) { container.replaceChildren( renderConnectionsTable([], { text: _("Connections are unavailable"), className: "tachyon_monitoring-page__state--error" }) ); return; } const visibleConnections = getVisibleConnections(); if (visibleConnections.length === 0) { container.replaceChildren( renderConnectionsTable([], { text: activeTab === "active" ? _("No active connections") : _("No closed connections") }) ); return; } patchConnectionRows(visibleConnections); container.scrollLeft = previousScrollLeft; } function getConnectionRowSignature(connection) { const target = getTargetCellParts(connection); const source = getSourceCellParts(connection); return [ target.primary, getNetwork(connection), getRoute(connection), source.primary, source.ip, closingConnectionIds.has(connection.id) ? "closing" : "open" ].join("|"); } function patchConnectionRows(visibleConnections) { const currentTbody = container_queryConnectionsTbody(); const hasKeyedRows = currentTbody && currentTbody.querySelector("tr[data-connection-id]") != null; if (!currentTbody || !hasKeyedRows) { document.getElementById("monitoring-connections")?.replaceChildren(renderConnectionsTable(visibleConnections)); return; } const tbody = currentTbody; const existingRows = /* @__PURE__ */ new Map(); for (const row of Array.from(tbody.children)) { const id = row.getAttribute("data-connection-id"); if (id) { existingRows.set(id, row); } } const seenIds = /* @__PURE__ */ new Set(); for (const connection of visibleConnections) { seenIds.add(connection.id); const signature = getConnectionRowSignature(connection); const existing = existingRows.get(connection.id); if (!existing) { tbody.appendChild(renderConnectionRow(connection)); continue; } if (existing.getAttribute("data-row-signature") !== signature) { tbody.replaceChild(renderConnectionRow(connection), existing); continue; } patchVolatileCells(existing, connection); tbody.appendChild(existing); } for (const [id, row] of existingRows) { if (!seenIds.has(id)) { row.remove(); } } } function container_queryConnectionsTbody() { return document.getElementById("monitoring-connections")?.querySelector("tbody") ?? null; } function patchVolatileCells(row, connection) { const cells = row.children; if (cells.length < 7) { return; } const volatileByIndex = [ [ 3, () => renderTableCell(_("Time"), [ renderValue(formatConnectionDuration(connection)) ]) ], [ 4, () => renderTableCell(_("Downloaded"), [ renderValue(formatBytes2(connection.download)) ]) ], [ 5, () => renderTableCell(_("Uploaded"), [ renderValue(formatBytes2(connection.upload)) ]) ] ]; for (const [index, makeCell] of volatileByIndex) { const current = cells[index]; const next = makeCell(); if (current) { row.replaceChild(next, current); } else { row.appendChild(next); } } } function flushRenderAfterSelection() { if (!renderSkippedForSelection || isTextSelectionInsideMonitoring()) { return; } renderConnections2({ force: true }); } function setMonitoringPaused(paused) { if (monitoringPaused === paused) { return; } monitoringPaused = paused; monitoringPausedAt = paused ? Date.now() : null; renderSkippedForSelection = false; renderControls(); if (!paused) { const payload = pendingConnectionsPayload; pendingConnectionsPayload = null; if (payload) { applyConnectionsPayload(payload); return; } if (connectionsPollTimer) { void pollConnectionsSnapshot(); return; } } renderConnections2(); } function isElementOverflowing(element) { return element.scrollWidth > element.clientWidth + 1; } function getMonitoringValueOverflowElements(element) { return [ element, ...Array.from(element.querySelectorAll("*")) ].filter(isElementOverflowing); } function getElementCopyText(element, fallback) { return element.getAttribute("data-copy-value") || element.textContent || fallback; } function compactMonitoringText(value) { return value.replace(/\u2026/g, "").trim().replace(/\s+/g, ""); } function getMonitoringValueTextElements(element) { const children = Array.from(element.children).filter( (child) => child instanceof HTMLElement ); if (children.length === 0) { return [element]; } const textElements = children.flatMap(getMonitoringValueTextElements).filter((child) => compactMonitoringText(getElementCopyText(child, ""))); return textElements.length > 0 ? textElements : [element]; } function estimateVisibleMonitoringTextLength(element, fallbackText) { const text = compactMonitoringText(getElementCopyText(element, fallbackText)); if (!text) { return 0; } if (!isElementOverflowing(element)) { return text.length; } return Math.floor( element.clientWidth / Math.max(element.scrollWidth, 1) * text.length ); } function getEstimatedVisibleMonitoringTextLength(element, fallbackText) { const textElements = getMonitoringValueTextElements(element); if (textElements.length === 1 && textElements[0] === element) { return estimateVisibleMonitoringTextLength(element, fallbackText); } return textElements.reduce( (total, textElement) => total + estimateVisibleMonitoringTextLength(textElement, fallbackText), 0 ); } function isCompactTextSubsequence(needle, haystack) { let haystackIndex = 0; for (let needleIndex = 0; needleIndex < needle.length; needleIndex += 1) { haystackIndex = haystack.indexOf(needle[needleIndex], haystackIndex); if (haystackIndex === -1) { return false; } haystackIndex += 1; } return true; } function getSelectionValueElements(selection) { const root = document.getElementById("monitoring-status"); if (!root) { return []; } return Array.from( root.querySelectorAll( ".tachyon_monitoring-page__value[data-copy-value]" ) ).filter((element) => { for (let index = 0; index < selection.rangeCount; index += 1) { try { if (selection.getRangeAt(index).intersectsNode(element)) { return true; } } catch (_error) { return false; } } return false; }); } function shouldCopyFullMonitoringValue(element, selectedText, fullText) { const normalizedSelectedText = selectedText.replace(/\u2026/g, "").trim(); const normalizedFullText = fullText.trim(); const compactSelectedText = compactMonitoringText(selectedText); const compactFullText = compactMonitoringText(fullText); const overflowElements = getMonitoringValueOverflowElements(element); const hasCompositeText = getMonitoringValueTextElements(element).length > 1; if (!normalizedSelectedText || !normalizedFullText) { return false; } if (normalizedSelectedText === normalizedFullText) { return true; } if (overflowElements.length === 0) { return false; } if (hasCompositeText) { const selectedPrefix = compactSelectedText.slice( 0, Math.min(4, compactSelectedText.length) ); if (!compactFullText.startsWith(selectedPrefix) || !isCompactTextSubsequence(compactSelectedText, compactFullText)) { return false; } } else if (!compactFullText.startsWith(compactSelectedText)) { return false; } const estimatedVisibleChars = getEstimatedVisibleMonitoringTextLength( element, normalizedFullText ); return compactSelectedText.length >= Math.max(4, estimatedVisibleChars - 2); } function handleMonitoringValueCopy(event) { const selection = window.getSelection?.(); if (!selection || selection.isCollapsed) { return; } const valueElements = getSelectionValueElements(selection); if (valueElements.length !== 1) { return; } const valueElement = valueElements[0]; const fullText = valueElement.getAttribute("data-copy-value") || valueElement.textContent || ""; const selectedText = selection.toString(); if (!shouldCopyFullMonitoringValue(valueElement, selectedText, fullText)) { return; } event.clipboardData?.setData("text/plain", fullText); event.preventDefault(); } async function closeConnection(connectionId) { if (!connectionId || closingConnectionIds.has(connectionId)) { return; } closingConnectionIds.add(connectionId); renderConnections2(); try { const response = await TachyonShellMethods.closeClashApiConnection(connectionId); if (!response.success) { showToast(_("Failed to close connection"), "error"); return; } const now = Date.now(); const connection = activeConnections.get(connectionId); if (connection) { activeConnections.delete(connectionId); moveConnectionToClosed(connection, now); trimClosedConnections(); pendingConnectionsPayload = null; renderControls(); } } catch (error) { logger.error("[MONITORING]", "closeConnection: failed", error); showToast(_("Failed to close connection"), "error"); } finally { closingConnectionIds.delete(connectionId); renderConnections2(); } } async function closeAllConnections() { if (activeConnections.size === 0 || closingAll) { return; } closingAll = true; renderControls(); try { const response = await TachyonShellMethods.closeAllClashApiConnections(); if (!response.success) { showToast(_("Failed to close connections"), "error"); return; } const now = Date.now(); activeConnections.forEach((connection) => { moveConnectionToClosed(connection, now); }); activeConnections.clear(); pendingConnectionsPayload = null; trimClosedConnections(); } catch (error) { logger.error("[MONITORING]", "closeAllConnections: failed", error); showToast(_("Failed to close connections"), "error"); } finally { closingAll = false; renderControls(); renderConnections2(); } } function bindControls() { const activeButton = document.getElementById("monitoring-tab-active"); const closedButton = document.getElementById("monitoring-tab-closed"); const select = document.getElementById( "monitoring-device-filter" ); const routeSelect = document.getElementById( "monitoring-route-filter" ); const searchInput = document.getElementById( "monitoring-search" ); const closeAllButton = document.getElementById("monitoring-close-all"); const pauseToggleButton = document.getElementById("monitoring-pause-toggle"); const connectionsContainer = document.getElementById( "monitoring-connections" ); if (activeButton) { activeButton.onclick = () => setTab("active"); } if (closedButton) { closedButton.onclick = () => setTab("closed"); } if (closeAllButton) { closeAllButton.onclick = () => { void closeAllConnections(); }; } if (pauseToggleButton) { pauseToggleButton.onclick = () => { setMonitoringPaused(!monitoringPaused); pauseToggleButton.blur(); }; } if (select) { select.onchange = () => { selectedDeviceFilter = select.value || ALL_FILTER_VALUE; renderConnections2(); }; } if (routeSelect) { routeSelect.onchange = () => { selectedRouteFilter = routeSelect.value || ALL_FILTER_VALUE; renderConnections2(); }; } if (searchInput) { searchInput.oninput = () => { searchQuery = searchInput.value; renderConnections2(); }; } if (connectionsContainer) { connectionsContainer.onclick = (event) => { const target = event.target; const button = target?.closest( ".tachyon_monitoring-page__row-action" ); if (button?.value) { void closeConnection(button.value); } }; } } async function loadLocalDevices() { try { localDeviceChoices = await dependencies.loadLocalDeviceChoices?.() || {}; } catch (error) { logger.warn("[MONITORING]", "loadLocalDevices: failed", error); localDeviceChoices = {}; } finally { renderControls(); renderConnections2(); } } async function loadRouteDisplayNames() { try { buildRouteDisplayNames(await CustomTachyonMethods.getConfigSections()); } catch (error) { logger.warn("[MONITORING]", "loadRouteDisplayNames: failed", error); buildRouteDisplayNames([]); } finally { renderControls(); renderConnections2(); } } async function pollConnectionsSnapshot() { if (pollingConnections || !monitoringMounted || monitoringPaused || serviceAvailability !== "running") { return; } const mountId = monitoringMountId; pollingConnections = true; try { const response = await TachyonShellMethods.getClashApiConnections(); if (!monitoringMounted || mountId !== monitoringMountId || serviceAvailability !== "running") { return; } if (!response.success) { failed = true; loading = false; renderConnections2(); return; } applyConnectionsPayload(normalizeConnectionsPayload(response.data)); } catch (error) { if (!monitoringMounted || mountId !== monitoringMountId || serviceAvailability !== "running") { return; } logger.error("[MONITORING]", "connections polling failed", error); failed = true; loading = false; renderConnections2(); } finally { pollingConnections = false; } } function startConnectionsPolling() { if (connectionsPollTimer) { return; } void pollConnectionsSnapshot(); connectionsPollTimer = setInterval(() => { void pollConnectionsSnapshot(); }, CONNECTIONS_RPC_POLL_INTERVAL_MS); } async function connectToConnectionsSocket(updatesId) { const mountId = monitoringMountId; const clashApiSecret = await getClashApiSecret2(); if (!monitoringMounted || mountId !== monitoringMountId || updatesId !== connectionsUpdatesId || serviceAvailability !== "running") { return; } connectionsSocketUrl = `${getClashWsUrl()}/connections?token=${clashApiSecret}`; socket.subscribe( connectionsSocketUrl, (msg) => { if (updatesId !== connectionsUpdatesId || serviceAvailability !== "running") { return; } directConnectionsSocketFailedAt = 0; try { applyConnectionsPayload(JSON.parse(msg)); } catch (error) { logger.error("[MONITORING]", "connections socket parse failed", error); } }, (_err) => { if (!monitoringMounted || mountId !== monitoringMountId || updatesId !== connectionsUpdatesId || serviceAvailability !== "running") { return; } directConnectionsSocketFailedAt = Date.now(); logger.warn( "[MONITORING]", "direct connections socket failed, falling back to polling", _err ); if (connectionsSocketUrl) { socket.disconnect(connectionsSocketUrl); connectionsSocketUrl = ""; } startConnectionsPolling(); } ); } function startConnectionsUpdates() { if (serviceAvailability !== "running") { return; } if (canUseConnectionsSocket()) { const updatesId = ++connectionsUpdatesId; void connectToConnectionsSocket(updatesId); return; } startConnectionsPolling(); } function stopConnectionsUpdates() { connectionsUpdatesId += 1; if (connectionsPollTimer) { clearInterval(connectionsPollTimer); connectionsPollTimer = null; } if (connectionsSocketUrl) { socket.disconnect(connectionsSocketUrl); connectionsSocketUrl = ""; } } function setServiceAvailability(next) { if (serviceAvailability === next) { return; } serviceAvailability = next; if (next === "running") { loading = true; failed = false; startConnectionsUpdates(); } else { stopConnectionsUpdates(); pendingConnectionsPayload = null; if (next === "stopped") { loading = false; failed = false; activeConnections.clear(); closedConnections.clear(); closingConnectionIds.clear(); } else if (next === "unavailable") { loading = false; failed = true; } } renderControls(); renderConnections2(); } function watchServiceState() { serviceStateUnsubscribe?.(); serviceStateUnsubscribe = subscribeRuntimeUiState((uiState) => { if (!monitoringMounted) { return; } setServiceAvailability( getServiceAvailability({ loading: false, failed: false, running: uiState.service.tachyon.running }) ); }); } function resetMonitoringState() { activeTab = "active"; selectedDeviceFilter = ALL_FILTER_VALUE; selectedRouteFilter = ALL_FILTER_VALUE; searchQuery = ""; lastDeviceFilterSignature = ""; lastRouteFilterSignature = ""; loading = true; failed = false; closingAll = false; monitoringPaused = false; monitoringPausedAt = null; serviceAvailability = "loading"; pendingConnectionsPayload = null; activeConnections.clear(); closedConnections.clear(); closingConnectionIds.clear(); const searchInput = document.getElementById( "monitoring-search" ); if (searchInput) { searchInput.value = ""; } } async function onPageMount3() { onPageUnmount3(); monitoringMounted = true; monitoringMountId += 1; const mountId = monitoringMountId; resetMonitoringState(); bindControls(); renderControls(); renderConnections2(); watchServiceState(); void loadLocalDevices(); void loadRouteDisplayNames(); if (getCachedRuntimeUiState()) { void refreshRuntimeUiState({ force: true }); } else { const uiState = await refreshRuntimeUiState({ force: true }); if (!monitoringMounted || mountId !== monitoringMountId) { return; } if (!uiState && serviceAvailability === "loading") { setServiceAvailability("unavailable"); } } document.addEventListener("selectionchange", flushRenderAfterSelection); document.addEventListener("copy", handleMonitoringValueCopy); renderTimer = setInterval(() => { if (monitoringPaused) { return; } renderConnections2(); }, RENDER_INTERVAL_MS); } var monitoringVisibilityPaused = false; function pauseMonitoringUpdates() { if (!monitoringMounted || monitoringVisibilityPaused) return; monitoringVisibilityPaused = true; if (renderTimer) { clearInterval(renderTimer); renderTimer = null; } stopConnectionsUpdates(); } function resumeMonitoringUpdates() { if (!monitoringMounted || !monitoringVisibilityPaused) return; monitoringVisibilityPaused = false; if (serviceAvailability === "running") { startConnectionsUpdates(); } if (!renderTimer) { renderTimer = setInterval(() => { if (monitoringPaused) { return; } renderConnections2(); }, RENDER_INTERVAL_MS); } } if (typeof document !== "undefined" && typeof document.addEventListener === "function") { document.addEventListener("visibilitychange", () => { if (document.hidden) { pauseMonitoringUpdates(); } else { resumeMonitoringUpdates(); } }); } function onPageUnmount3() { monitoringMounted = false; monitoringMountId += 1; monitoringVisibilityPaused = false; if (renderTimer) { clearInterval(renderTimer); renderTimer = null; } stopConnectionsUpdates(); serviceStateUnsubscribe?.(); serviceStateUnsubscribe = null; document.removeEventListener("selectionchange", flushRenderAfterSelection); document.removeEventListener("copy", handleMonitoringValueCopy); } function registerLifecycleListeners3() { if (monitoringLifecycleRegistered) { return; } monitoringLifecycleRegistered = true; store.subscribe( (next, prev, diff) => { if (diff.tabService && next.tabService.current !== prev.tabService.current) { const isMonitoringVisible = next.tabService.current === "monitoring"; if (isMonitoringVisible) { return onPageMount3(); } if (!isMonitoringVisible) { return onPageUnmount3(); } } } ); } async function initController3(controllerDependencies = {}) { dependencies = { ...dependencies, ...controllerDependencies }; if (monitoringControllerInitialized) { return; } monitoringControllerInitialized = true; onMount("monitoring-status").then(() => { registerLifecycleListeners3(); if (store.get().tabService.current === "monitoring") { onPageMount3(); } }); } // src/tachyon/tabs/monitoring/styles.ts var styles5 = ` .tachyon_monitoring-page { --tachyon-monitoring-danger-color: #d9534f; } #cbi-${TACHYON_UCI_PACKAGE}-monitoring-_mount_node { margin: 16px 0 22px; padding: 0; } #cbi-${TACHYON_UCI_PACKAGE}-monitoring-_mount_node > .cbi-value-title { display: none; } #cbi-${TACHYON_UCI_PACKAGE}-monitoring-_mount_node > .cbi-value-field { margin-left: 0; width: 100%; } #cbi-${TACHYON_UCI_PACKAGE}-monitoring-_mount_node > div { width: 100%; } #cbi-${TACHYON_UCI_PACKAGE}-monitoring > h3 { display: none; } .tachyon_monitoring-page { --tachyon-monitoring-control-height: 34px; --tachyon-monitoring-row-action-size: 24px; --tachyon-monitoring-divider-color: rgba(127, 127, 127, 0.22); --tachyon-monitoring-soft-bg: rgba(127, 127, 127, 0.08); --tachyon-monitoring-soft-bg-hover: rgba(127, 127, 127, 0.14); --tachyon-monitoring-danger-color: var(--error-color-medium, #d32f2f); --tachyon-monitoring-success-color: var(--success-color-medium, #2e7d32); --tachyon-monitoring-paused-color: var(--primary-color-high, #1976d2); width: 100%; min-width: 0; } .tachyon_monitoring-page__panel { margin-top: 0; border: 0; border-radius: 0; padding: 0; background: transparent; box-sizing: border-box; width: 100%; min-width: 0; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__icon-button { width: 32px; height: 32px; min-width: 32px; min-height: 32px; padding: 0; box-sizing: border-box; display: flex; align-items: center; justify-content: center; flex: 0 0 auto; line-height: 1; margin: 0; border: 1px solid var(--tachyon-monitoring-divider-color) !important; border-radius: 6px; background: var(--tachyon-monitoring-soft-bg) !important; color: var(--text-color-medium) !important; box-shadow: none; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__icon-button:hover:not(:disabled) { background: var(--tachyon-monitoring-soft-bg-hover) !important; color: var(--text-color-high) !important; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__icon-button--active { background: rgba(25, 118, 210, 0.16) !important; color: var(--primary-color-high, #1976d2) !important; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__icon-button:disabled { opacity: 0.45; cursor: not-allowed; } .tachyon_monitoring-page #monitoring-close-all.btn.tachyon_monitoring-page__icon-button { order: 2; border-color: rgba(217, 83, 79, 0.4) !important; background: transparent !important; color: var(--tachyon-monitoring-danger-color) !important; } .tachyon_monitoring-page #monitoring-close-all.btn.tachyon_monitoring-page__icon-button:hover:not(:disabled) { border-color: rgba(217, 83, 79, 0.6) !important; background: transparent !important; color: color-mix(in srgb, var(--tachyon-monitoring-danger-color) 70%, white) !important; } .tachyon_monitoring-page #monitoring-pause-toggle.btn.tachyon_monitoring-page__icon-button, .tachyon_monitoring-page #monitoring-pause-toggle.btn.tachyon_monitoring-page__icon-button--active { order: 1; border-color: rgba(128, 128, 128, 0.3) !important; background: transparent !important; color: var(--text-color-medium, #888) !important; } .tachyon_monitoring-page #monitoring-pause-toggle.btn.tachyon_monitoring-page__icon-button:hover:not(:disabled), .tachyon_monitoring-page #monitoring-pause-toggle.btn.tachyon_monitoring-page__icon-button--active:hover:not(:disabled) { border-color: rgba(128, 128, 128, 0.6) !important; background: transparent !important; color: var(--text-color-high, #eee) !important; } .tachyon_monitoring-page__icon-button svg, .tachyon_monitoring-page__row-action svg { width: 16px; height: 16px; display: block; flex: 0 0 auto; } .tachyon_monitoring-page__controls { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 12px; width: 100%; min-width: 0; } .tachyon_monitoring-page__actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 0; } .tachyon_monitoring-page__tabs { display: inline-flex; align-items: center; gap: 2px; width: max-content; padding: 2px; border: 1px solid var(--tachyon-monitoring-divider-color); border-radius: 6px; background: var(--tachyon-monitoring-soft-bg); box-sizing: border-box; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__tab { height: calc(var(--tachyon-monitoring-control-height) - 6px); min-height: calc(var(--tachyon-monitoring-control-height) - 6px); margin: 0; padding: 0 12px; border: 0 !important; border-radius: 4px; background: transparent !important; color: var(--text-color-medium) !important; box-shadow: none; display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-weight: 600; line-height: 1; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__tab:hover { background: var(--tachyon-monitoring-soft-bg-hover) !important; color: var(--text-color-high) !important; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__tab--active { background: rgba(25, 118, 210, 0.16) !important; color: var(--primary-color-high, #1976d2) !important; font-weight: 700; } .tachyon_monitoring-page__tab-label { display: inline-block; } .tachyon_monitoring-page__tab-badge { min-width: 18px; height: 18px; padding: 0 6px; border-radius: 999px; background: rgba(127, 127, 127, 0.22); color: var(--text-color-medium); display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; font-size: 12px; font-weight: 700; line-height: 1; } .tachyon_monitoring-page__tab--active .tachyon_monitoring-page__tab-badge { background: rgba(25, 118, 210, 0.22); color: var(--primary-color-high, #1976d2); } .tachyon_monitoring-page__filters { display: flex; flex: 1 1 auto; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: 12px; min-width: 0; } .tachyon_monitoring-page__device-filter { width: min(220px, 100%); min-width: 0; height: var(--tachyon-monitoring-control-height) !important; min-height: var(--tachyon-monitoring-control-height) !important; padding-top: 0 !important; padding-bottom: 0 !important; margin: 0 !important; box-sizing: border-box; line-height: calc(var(--tachyon-monitoring-control-height) - 2px) !important; } .tachyon_monitoring-page__search { position: relative; display: flex; align-items: center; width: min(320px, 100%); min-width: 0; height: var(--tachyon-monitoring-control-height); margin: 0; } .tachyon_monitoring-page__search-icon { position: absolute; left: 8px; width: 16px; height: 16px; color: var(--text-color-medium); pointer-events: none; } .tachyon_monitoring-page__search-icon svg { width: 16px; height: 16px; display: block; } .tachyon_monitoring-page__search-input { width: 100%; height: var(--tachyon-monitoring-control-height) !important; min-height: var(--tachyon-monitoring-control-height) !important; padding-left: 30px !important; padding-top: 0 !important; padding-bottom: 0 !important; margin: 0 !important; box-sizing: border-box; line-height: calc(var(--tachyon-monitoring-control-height) - 2px) !important; } .tachyon_monitoring-page__body { margin-top: 0; width: 100%; min-width: 0; } .tachyon_monitoring-page__table-wrap { width: 100%; overflow-x: auto; margin-bottom: 0; } .tachyon_monitoring-page__table { width: 100%; min-width: 840px; table-layout: fixed; border-collapse: collapse; border-spacing: 0; margin-bottom: 0; } .tachyon_monitoring-page__table th, .tachyon_monitoring-page__table td { padding: 8px 6px; border-bottom: 1px solid var(--tachyon-monitoring-divider-color); box-sizing: border-box; text-align: left; vertical-align: middle; overflow: hidden; white-space: nowrap; } .tachyon_monitoring-page__table th { color: var(--text-color-medium); font-size: 11px; font-weight: 600; text-transform: uppercase; white-space: nowrap; border-bottom-color: rgba(127, 127, 127, 0.32); } .tachyon_monitoring-page__table th:nth-child(1) { width: 25%; } .tachyon_monitoring-page__table th:nth-child(2) { width: 5%; } .tachyon_monitoring-page__table th:nth-child(3) { width: 9%; } .tachyon_monitoring-page__table th:nth-child(4) { width: 7%; } .tachyon_monitoring-page__table th:nth-child(5) { width: 9%; } .tachyon_monitoring-page__table th:nth-child(6) { width: 8%; } .tachyon_monitoring-page__table th:nth-child(7) { width: 33%; } .tachyon_monitoring-page__table th:nth-child(8) { width: 4%; } .tachyon_monitoring-page__table tbody tr:last-child td { border-bottom: 0; } .tachyon_monitoring-page__table td:last-child { padding-top: 0; padding-bottom: 0; } .tachyon_monitoring-page__table th:nth-child(4), .tachyon_monitoring-page__table td:nth-child(4), .tachyon_monitoring-page__table th:nth-child(5), .tachyon_monitoring-page__table td:nth-child(5), .tachyon_monitoring-page__table th:nth-child(6), .tachyon_monitoring-page__table td:nth-child(6) { text-align: right; } .tachyon_monitoring-page__table th:nth-child(7), .tachyon_monitoring-page__table td:nth-child(7) { text-align: left; } .tachyon_monitoring-page__table th:last-child, .tachyon_monitoring-page__table td:last-child { text-align: center; } .tachyon_monitoring-page__value { display: block; max-width: 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; line-height: 1.3; color: var(--text-color-high); font-size: 13px; user-select: text; } .tachyon_monitoring-page__source-value { display: flex; align-items: baseline; justify-content: flex-start; gap: 5px; } .tachyon_monitoring-page__source-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tachyon_monitoring-page__source-ip { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-color-medium); font-size: 12px; } .tachyon_monitoring-page__source-value--ip-only { color: var(--text-color-high); } .tachyon_monitoring-page__cell-main { color: var(--text-color-high); font-weight: 600; line-height: 1.25; } .tachyon_monitoring-page__cell-secondary { margin-top: 2px; color: var(--text-color-medium); font-size: 12px; line-height: 1.25; } .tachyon_monitoring-page__route { display: inline-block; width: auto; padding: 2px 6px; border-radius: 4px; background: rgba(128, 128, 128, 0.15); color: var(--text-color-high, #eee); font-size: 11px; font-weight: 500; } .tachyon_monitoring-page__network { background: transparent; border: 0; padding: 0; color: var(--text-color-medium, #bbb); font-family: inherit; font-size: 13px; text-transform: lowercase; } .tachyon_monitoring-page__table td:nth-child(4) .tachyon_monitoring-page__value, .tachyon_monitoring-page__table td:nth-child(5) .tachyon_monitoring-page__value, .tachyon_monitoring-page__table td:nth-child(6) .tachyon_monitoring-page__value { color: var(--text-color-medium, #bbb); font-family: inherit; text-align: right; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__row-action { width: var(--tachyon-monitoring-row-action-size); height: var(--tachyon-monitoring-row-action-size); min-width: var(--tachyon-monitoring-row-action-size); min-height: var(--tachyon-monitoring-row-action-size); padding: 0; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; line-height: 1; margin: 0; border: 0 !important; border-radius: 999px; background: transparent !important; color: var(--tachyon-monitoring-danger-color) !important; box-shadow: none; cursor: pointer; } .tachyon_monitoring-page__row-action svg { width: 14px; height: 14px; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__row-action:hover:not(:disabled) { background: var(--tachyon-monitoring-soft-bg-hover) !important; color: var(--tachyon-monitoring-danger-color) !important; } .tachyon_monitoring-page .btn.tachyon_monitoring-page__row-action:disabled { opacity: 0.45; cursor: wait; } .tachyon_monitoring-page__row--closing { opacity: 0.55; } .tachyon_monitoring-page__state { min-height: 90px; width: 100%; display: flex; align-items: center; justify-content: center; color: var(--text-color-medium); text-align: center; box-sizing: border-box; } .tachyon_monitoring-page__state-cell { padding: 0 !important; } .tachyon_monitoring-page__state--error { color: var(--error-color-medium, #d32f2f); } @media (max-width: 900px) { .tachyon_monitoring-page__controls { align-items: center; } .tachyon_monitoring-page__tabs { flex: 1 0 100%; } .tachyon_monitoring-page__filters { flex: 1 1 0; } .tachyon_monitoring-page__device-filter, .tachyon_monitoring-page__search { max-width: none; } .tachyon_monitoring-page__table { min-width: 0; } .tachyon_monitoring-page__table thead { display: none; } .tachyon_monitoring-page__table, .tachyon_monitoring-page__table tbody, .tachyon_monitoring-page__table tr, .tachyon_monitoring-page__table td { display: block; width: 100%; } .tachyon_monitoring-page__table tr { border: 1px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 8px; box-sizing: border-box; margin-bottom: 8px; } .tachyon_monitoring-page__table td { display: grid; grid-template-columns: minmax(92px, 34%) minmax(0, 1fr); gap: 8px; border: 0; border-bottom: 1px solid var(--tachyon-monitoring-divider-color); padding: 4px 0; box-sizing: border-box; text-align: left; } .tachyon_monitoring-page__table td::before { content: attr(data-label); color: var(--text-color-medium); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tachyon_monitoring-page__table td:last-child { grid-template-columns: minmax(92px, 34%) minmax(0, 1fr); align-items: center; border-bottom: 0; min-height: var(--tachyon-monitoring-row-action-size); padding: 0; } .tachyon_monitoring-page__value { text-align: right; } .tachyon_monitoring-page__source-value { justify-content: flex-end; } .tachyon_monitoring-page__state-row td::before { display: none; } } @media (max-width: 520px) { .tachyon_monitoring-page__controls, .tachyon_monitoring-page__filters { align-items: stretch; } .tachyon_monitoring-page__tabs, .tachyon_monitoring-page__filters, .tachyon_monitoring-page__device-filter, .tachyon_monitoring-page__search { width: 100%; } .tachyon_monitoring-page__controls, .tachyon_monitoring-page__filters { flex-direction: column; } .tachyon_monitoring-page__actions { align-self: flex-end; } .tachyon_monitoring-page__tabs { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); width: 100%; } .tachyon_monitoring-page__table td { grid-template-columns: 1fr; gap: 2px; } .tachyon_monitoring-page__value { text-align: left; } .tachyon_monitoring-page__source-value { justify-content: flex-start; } } `; // src/tachyon/tabs/monitoring/index.ts var MonitoringTab = { render: render3, initController: initController3, styles: styles5 }; // src/tachyon/tabs/updates/render.ts function render4() { return E("div", { id: "updates-status", class: "tachyon_updates-page" }, [ E("div", { id: "tachyon_updates-components", class: "tachyon_updates-page__components" }) ]); } // src/tachyon/tabs/updates/componentActionCompletion.ts function shouldApplyCompletedComponentActionResult(result, notify) { return result.action !== "check_update" || notify; } // src/tachyon/tabs/updates/checkResultLifecycle.ts function shouldPreserveCompletedCheckResultOnNextMount({ action, mounted }) { return action === "check_update" && !mounted; } function shouldResetCheckResultsOnMount({ anyActionLoading, preserveCheckResultsOnNextMount: preserveCheckResultsOnNextMount2, persistentCacheEnabled = false }) { return !persistentCacheEnabled && !anyActionLoading && !preserveCheckResultsOnNextMount2; } function shouldRefreshComponentStateBeforeRender(uiState) { return Boolean(uiState?.actions.component.some((state) => state.running)); } function shouldExposeCheckResults({ mounted, cacheResolved }) { return mounted && cacheResolved; } // src/tachyon/tabs/updates/sameReleaseBuild.ts function describeSameReleaseBuild({ currentSha, latestSha }) { const current = (currentSha || "").substring(0, 8); const latest = (latestSha || "").substring(0, 8); if (current && latest) { return { kind: "sha", text: `${current} → ${latest}` }; } if (current || latest) { return { kind: "sha", text: current || latest }; } return { kind: "fallback", text: "" }; } // src/tachyon/tabs/updates/sessionJobs.ts var HANDLED_JOBS_STORAGE_KEY = "tachyon_handled_component_jobs"; var LEGACY_JOB_STORAGE_KEY = "tachyon_post_update_job"; var isPageReloading = false; function safeReloadPage() { if (isPageReloading) { return; } isPageReloading = true; if (typeof window !== "undefined" && window.location) { window.location.reload(); } } function isReloadInProgress() { return isPageReloading; } function saveHandledJobToSession(jobId) { if (!jobId || typeof sessionStorage === "undefined") { return; } try { const raw = sessionStorage.getItem(HANDLED_JOBS_STORAGE_KEY); const list = raw ? JSON.parse(raw) : []; if (!list.includes(jobId)) { list.push(jobId); if (list.length > 30) { list.shift(); } sessionStorage.setItem(HANDLED_JOBS_STORAGE_KEY, JSON.stringify(list)); } sessionStorage.setItem(LEGACY_JOB_STORAGE_KEY, jobId); } catch (_e) { } } function loadHandledJobsFromSession() { if (typeof sessionStorage === "undefined") { return []; } const result = /* @__PURE__ */ new Set(); try { const raw = sessionStorage.getItem(HANDLED_JOBS_STORAGE_KEY); if (raw) { const parsed = JSON.parse(raw); if (Array.isArray(parsed)) { for (const id of parsed) { if (typeof id === "string" && id) { result.add(id); } } } } const legacy = sessionStorage.getItem(LEGACY_JOB_STORAGE_KEY); if (legacy) { result.add(legacy); } } catch (_e) { } return Array.from(result); } // src/tachyon/tabs/updates/partials/renderUpdateProgressModal.ts function formatSha(sha) { if (!sha) return ""; const clean = sha.trim(); return clean.length >= 7 ? clean.slice(0, 7) : clean; } function renderVersionBadgeText(opts) { const cVer = opts.currentVersion || ""; const tVer = opts.targetVersion || ""; const cSha = formatSha(opts.currentSha); const tSha = formatSha(opts.targetSha); if (tVer) { if (cVer === tVer) { if (cSha && tSha && cSha !== tSha) { return `${cVer} (${cSha} → ${tSha})`; } if (tSha) { return `${cVer} (${tSha})`; } if (cSha) { return `${cVer} (${cSha} → ${_("new build")})`; } return `${cVer} (${_("new build")})`; } const currentPart = cVer ? cSha ? `${cVer} (${cSha})` : cVer : ""; const targetPart = tSha ? `${tVer} (${tSha})` : tVer; return currentPart ? `${currentPart} → ${targetPart}` : targetPart; } if (cVer) { return cSha ? `${cSha}` : cVer; } return opts.action; } var activeModalController = null; var activeModalJobId = null; function getActiveProgressModalController() { return activeModalController; } function setActiveProgressModalJobId(jobId) { activeModalJobId = jobId; } function showUpdateProgressModal(options) { if (activeModalController) { activeModalController.close(); } const isCheckAction = options.action === "check_update"; let modalTitleText = _("Operation in progress..."); if (isCheckAction) { modalTitleText = `${_("Checking for updates...")}: ${options.componentTitle}`; } else if (options.action === "remove") { modalTitleText = `${_("Removing")} ${options.componentTitle}...`; } else { modalTitleText = `${_("Updating")} ${options.componentTitle}...`; } let elapsedSeconds = 0; let timerInterval = null; let currentModalVersions = { ...options }; const timerBadgeEl = E( "div", { class: "tachyon-update-modal__timer-badge" }, "⏱️ 00:00" ); const titleBadgeEl = E( "span", { class: "tachyon-update-modal__version-badge" }, renderVersionBadgeText(options) ); const headerEl = E("div", { class: "tachyon-update-modal__header" }, [ E("div", { class: "tachyon-update-modal__header-info" }, [ E( "b", { class: "tachyon-update-modal__component-name" }, options.componentTitle ), titleBadgeEl ]), timerBadgeEl ]); const logPreEl = E( "pre", { class: "tachyon-update-modal__log" }, _("Operation started. Waiting for log output...") ); const logPanelEl = E("div", { class: "tachyon-update-modal__log-panel" }, [ E("div", { class: "tachyon-update-modal__log-header" }, [ E("b", {}, _("Operation log")), renderButton({ classNames: ["cbi-button-action", "tachyon-update-modal__log-copy"], text: _("Copy log"), onClick: copyLog }) ]), logPreEl ]); const actionButtonContainer = E( "div", { class: "tachyon-update-modal__actions" }, [ renderButton({ text: _("Operation in progress..."), disabled: true, loading: true, onClick: () => { } }) ] ); const modalBodyEl = E("div", { class: "tachyon-update-modal__body" }, [ headerEl, logPanelEl, actionButtonContainer ]); const updateTimerDisplay = () => { const mins = Math.floor(elapsedSeconds / 60).toString().padStart(2, "0"); const secs = (elapsedSeconds % 60).toString().padStart(2, "0"); timerBadgeEl.textContent = `⏱️ ${mins}:${secs}`; }; timerInterval = setInterval(() => { elapsedSeconds += 1; updateTimerDisplay(); }, 1e3); function cleanupTimers() { if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } } let logTrackingJobId = null; let logTrackingOffset = 0; let logFullText = ""; let logPollTimer = null; let logPollStopped = true; let reloadProbeActive = false; function appendLogText(text) { if (!text) { return; } logFullText += text; logPreEl.textContent = logFullText; logPreEl.scrollTop = logPreEl.scrollHeight; } async function pollLog() { if (logPollStopped || !logTrackingJobId) { return; } const jobId = logTrackingJobId; const offset = logTrackingOffset; try { const response = await TachyonShellMethods.componentActionLog( jobId, offset ); if (logPollStopped || logTrackingJobId !== jobId) { return; } if (response.success && response.data) { logTrackingOffset = response.data.offset; appendLogText(response.data.log); } } catch (_error) { } finally { if (!logPollStopped && logTrackingJobId === jobId) { logPollTimer = setTimeout(pollLog, 1500); } } } function stopLogTracking() { logPollStopped = true; if (logPollTimer) { clearTimeout(logPollTimer); logPollTimer = null; } logTrackingJobId = null; } function finishLogTracking() { if (logPollStopped || !logTrackingJobId) { stopLogTracking(); return; } const jobId = logTrackingJobId; const offset = logTrackingOffset; logPollStopped = true; if (logPollTimer) { clearTimeout(logPollTimer); logPollTimer = null; } void TachyonShellMethods.componentActionLog(jobId, offset).then((response) => { if (response.success && response.data) { logTrackingOffset = response.data.offset; appendLogText(response.data.log); } }).catch(() => { }).finally(() => { logTrackingJobId = null; }); } function copyLog() { if (!logFullText) { return; } copyToClipboard(logFullText); } const controller = { updateStep: (_stepIndex, _statusText) => { }, updateStatus: (_statusText) => { }, updateVersions: (opts) => { currentModalVersions = { ...currentModalVersions, ...opts }; titleBadgeEl.textContent = renderVersionBadgeText(currentModalVersions); }, completeSuccess: (message, opts) => { cleanupTimers(); finishLogTracking(); const successMsg = message || (isCheckAction ? _("Check completed!") : options.action === "remove" ? _("Removal completed successfully!") : _("Operation completed successfully!")); const bannerEl = E( "div", { class: "tachyon-update-modal__success-banner" }, [renderCheckIcon24(), E("span", {}, successMsg)] ); if (opts?.reloadPage) { reloadProbeActive = true; let attempt = 0; const maxAttempts = 30; const statusEl = E( "div", { class: "tachyon-update-modal__reload-status", style: "margin: 10px 0; font-size: 0.95em; color: var(--cbi-color-neutral, #666);" }, _("Waiting for router services to restart...") ); const reloadBtn = renderButton({ classNames: ["cbi-button-save"], text: _("Reload now"), onClick: () => { reloadProbeActive = false; if (activeModalJobId) { saveHandledJobToSession(activeModalJobId); } safeReloadPage(); } }); actionButtonContainer.replaceChildren( bannerEl, statusEl, E("div", { class: "tachyon-update-modal__action-buttons" }, [ reloadBtn ]) ); const probeAndReload = async () => { await new Promise((resolve) => setTimeout(resolve, 1500)); while (reloadProbeActive && attempt < maxAttempts && !isReloadInProgress()) { attempt += 1; statusEl.textContent = `${_("Verifying router and service readiness...")} (${attempt}/${maxAttempts})`; try { const res = await TachyonShellMethods.getSystemInfo(); if (res && res.success) { statusEl.textContent = _( "Services are online and ready! Reloading page..." ); reloadBtn.textContent = _("Reloading..."); if (activeModalJobId) { saveHandledJobToSession(activeModalJobId); } await new Promise((resolve) => setTimeout(resolve, 800)); safeReloadPage(); return; } } catch (_err) { } await new Promise((resolve) => setTimeout(resolve, 1500)); } if (reloadProbeActive && !isReloadInProgress()) { statusEl.textContent = _( "Services restarted. Click to reload page." ); } }; void probeAndReload(); } else { const actionButtons = []; if (opts?.onInstall) { const installBtn = renderButton({ classNames: ["cbi-button-save"], text: opts.installText || _("Install"), onClick: () => { controller.close(); opts.onInstall(); } }); actionButtons.push(installBtn); } const closeBtn = renderButton({ classNames: ["cbi-button-apply"], text: _("Close"), onClick: () => { controller.close(); } }); actionButtons.push(closeBtn); actionButtonContainer.replaceChildren( bannerEl, E( "div", { class: "tachyon-update-modal__action-buttons" }, actionButtons ) ); const defaultAutoCloseMs = opts?.onInstall ? 0 : isCheckAction ? 1200 : 0; const autoCloseMs = opts?.autoCloseMs ?? defaultAutoCloseMs; if (autoCloseMs > 0) { setTimeout(() => { if (activeModalController === controller || activeModalController === null) { controller.close(); } }, autoCloseMs); } } }, completeError: (errorMessage) => { cleanupTimers(); finishLogTracking(); let cleanError = (errorMessage || "").trim(); if (cleanError.includes("Near here") || /^[-=~_^]{3,}$/m.test(cleanError)) { cleanError = cleanError.split("\n").filter( (line) => !line.includes("Near here") && !/^[\s\-_^]+$/.test(line) && line.trim() !== "" ).join(" ").trim(); } const bannerEl = E( "div", { class: "tachyon-update-modal__error-banner" }, [renderXIcon24(), E("span", {}, cleanError || _("Operation failed"))] ); const closeBtn = renderButton({ classNames: ["cbi-button-remove"], text: _("Close"), onClick: () => { controller.close(); } }); actionButtonContainer.replaceChildren( bannerEl, E("div", { class: "tachyon-update-modal__action-buttons" }, [closeBtn]) ); }, startLogTracking: (jobId) => { if (!jobId || logTrackingJobId === jobId) { return; } logTrackingJobId = jobId; logTrackingOffset = 0; logPollStopped = false; if (logPollTimer) { clearTimeout(logPollTimer); logPollTimer = null; } void pollLog(); }, stopLogTracking: () => { stopLogTracking(); }, getLogText: () => logFullText, close: () => { cleanupTimers(); stopLogTracking(); reloadProbeActive = false; if (activeModalController === controller) { activeModalController = null; activeModalJobId = null; } ui.hideModal(); } }; activeModalController = controller; ui.showModal(modalTitleText, modalBodyEl); return controller; } // src/tachyon/tabs/updates/initController.ts function getComponentCardTitle(component) { switch (component) { case "tachyon": return "Tachyon"; case "sing_box": return "Sing-box"; case "zapret": return "Zapret"; case "zapret2": return "Zapret2"; case "byedpi": return "ByeDPI"; case "wdtt": return "WDTT"; case "olcrtc": return "OlcRTC"; case "fptn": return "FPTN"; case "tailscale": return "Tailscale"; default: return String(component); } } function getComponentCurrentVersion(component) { const sys = store.get().diagnosticsSystemInfo; switch (component) { case "tachyon": return sys.tachyon_version; case "sing_box": return sys.sing_box_version; case "zapret": return sys.zapret_version; case "zapret2": return sys.zapret2_version; case "byedpi": return sys.byedpi_version; case "wdtt": return sys.wdtt_version; case "olcrtc": return sys.olcrtc_version; case "fptn": return sys.fptn_version; case "tailscale": return sys.tailscale_version; default: return void 0; } } var updatesLifecycleRegistered = false; var updatesControllerInitialized = false; var updatesMounted = false; var updatesMountId = 0; var pageUnloading2 = false; var preserveCheckResultsOnNextMount = false; var componentUpdateCheckCacheResolved = false; var componentUpdateCheckCacheSnapshot = null; var componentUpdateCheckCachePromise = null; var componentActionStateUnsubscribe = null; var componentActionStateRefreshPromise = null; var followedComponentJobs = /* @__PURE__ */ new Set(); var handledComponentJobs = /* @__PURE__ */ new Set(); var activeVersionPickerComponent = null; var versionPickerLoading = false; var versionPickerError = null; var versionPickerReleases = []; var versionPickerReleasesCache = {}; for (const savedJob of loadHandledJobsFromSession()) { handledComponentJobs.add(savedJob); followedComponentJobs.add(savedJob); } capSetSize(handledComponentJobs); if (typeof window !== "undefined") { window.addEventListener("pagehide", () => { pageUnloading2 = true; }); window.addEventListener("pageshow", () => { pageUnloading2 = false; }); } function isNotInstalled(version) { return !version || version === "not installed"; } function shouldShowInstallAfterCheck(component) { const status = getVisibleCheckResult(component)?.status; return status === "outdated" || status === "dev" || status === "outdated_same_release"; } function getVisibleCheckResult(component) { if (!shouldExposeCheckResults({ mounted: updatesMounted, cacheResolved: componentUpdateCheckCacheResolved })) { return null; } return store.get().updatesChecks[component]; } function getLatestVersion(component) { const checkResult = getVisibleCheckResult(component); if (!checkResult || !shouldShowInstallAfterCheck(component)) { return void 0; } return checkResult.latest_version || void 0; } function getGitHubReleaseUrl(component) { const checkResult = getVisibleCheckResult(component); if (!checkResult || !shouldShowInstallAfterCheck(component) || !checkResult.release_url) { return void 0; } return checkResult.release_url; } function isAnyActionLoading() { return Object.values(store.get().updatesActions).some((item) => item.loading); } function isSystemInfoLoading() { const systemInfo = store.get().diagnosticsSystemInfo; return systemInfo.loading || !systemInfo.loaded; } function setActionLoading(action, loading2, local = false) { if (local || !loading2) { setLocalComponentAction(action, loading2 && local); } const updatesActions = store.get().updatesActions; store.set({ updatesActions: { ...updatesActions, [action]: { loading: loading2 } } }); } function beginComponentAction(button) { if (isAnyActionLoading()) { return false; } setActionLoading(button.key, true, true); return true; } function setCheckResult(component, status, latestVersion, releaseUrl = "", currentSha = "", latestSha = "") { const updatesChecks = store.get().updatesChecks; store.set({ updatesChecks: { ...updatesChecks, [component]: { status, latest_version: latestVersion, release_url: releaseUrl, ...currentSha ? { current_sha: currentSha } : {}, ...latestSha ? { latest_sha: latestSha } : {} } } }); } function resetCheckResult(component) { setCheckResult(component, null, ""); } function applyCachedCheckResults(results) { results.forEach((result) => { const status = result.status || null; if (status === "latest" || status === "outdated" || status === "dev" || status === "outdated_same_release") { setCheckResult( result.component, status, result.latest_version || "", result.release_url || "", result.current_sha || "", result.latest_sha || "" ); } }); } function loadComponentUpdateCheckCache({ force = false } = {}) { if (!force && componentUpdateCheckCacheSnapshot) { return Promise.resolve(componentUpdateCheckCacheSnapshot); } if (componentUpdateCheckCachePromise) { return componentUpdateCheckCachePromise; } const promise = TachyonShellMethods.componentUpdateCheckCache().then( (response) => response.success ? response.data : { enabled: false, results: [] } ).then((cache) => { componentUpdateCheckCacheSnapshot = cache; return cache; }).finally(() => { if (componentUpdateCheckCachePromise === promise) { componentUpdateCheckCachePromise = null; } }); componentUpdateCheckCachePromise = promise; return promise; } function getErrorMessage(error, fallback) { return error instanceof Error && error.message ? error.message : fallback; } async function ackComponentActionJob(jobId) { try { const response = await TachyonShellMethods.uiActionAck("component", jobId); if (!response.success) { logger.debug("[UPDATES]", "component action ack failed", response.error); } } catch (error) { logger.debug("[UPDATES]", "component action ack failed", error); } } function getExpectedLatestVersionForAction(button) { if (button.targetVersion) { return button.targetVersion; } if (button.component !== "tachyon" || button.action !== "install" && button.action !== "reinstall") { return void 0; } return store.get().updatesChecks[button.component].latest_version || void 0; } function getCheckToastMessage(status) { if (status === "outdated" || status === "outdated_same_release") { return _("Update is available"); } if (status === "dev") { return _("Installed version is newer than release"); } return _("Latest version is installed"); } async function refreshSystemInfoAfterMutation() { await ensureSystemInfo({ force: true, silent: true }); } function notifyActionProvidersAvailabilityChanged(systemInfo) { if (typeof window === "undefined" || typeof CustomEvent === "undefined") { return; } window.dispatchEvent( new CustomEvent(TACHYON_ACTION_PROVIDERS_AVAILABILITY_EVENT, { detail: { zapretInstalled: Boolean(systemInfo.zapret_installed), zapret2Installed: Boolean(systemInfo.zapret2_installed), byedpiInstalled: Boolean(systemInfo.byedpi_installed), wdttInstalled: Boolean(systemInfo.wdtt_installed), olcrtcInstalled: Boolean(systemInfo.olcrtc_installed) } }) ); } var RELOAD_POLL_INTERVAL_MS = 1e3; var RELOAD_POLL_MAX_WAIT_MS = 3e4; async function waitForTachyonResponsive() { const deadline = Date.now() + RELOAD_POLL_MAX_WAIT_MS; while (Date.now() < deadline) { try { const response = await TachyonShellMethods.getUiState(); if (response.success) { return true; } } catch { } await new Promise( (resolve) => setTimeout(resolve, RELOAD_POLL_INTERVAL_MS) ); } return false; } function reloadPageAfterTachyonUpdate(jobId) { if (jobId) { saveHandledJobToSession(jobId); } void waitForTachyonResponsive().finally(() => { safeReloadPage(); }); } function patchSystemInfoAfterMutation(result) { const systemInfo = store.get().diagnosticsSystemInfo; const nextSystemInfo = { ...systemInfo, loading: false, loaded: true }; const version = result.current_version || result.latest_version || _("unknown"); if (result.component === "tachyon" && (result.action === "install" || result.action === "reinstall")) { nextSystemInfo.tachyon_version = version; } if (result.component === "sing_box") { nextSystemInfo.sing_box_version = version; if (result.action === "install_extended") { nextSystemInfo.sing_box_extended = 1; nextSystemInfo.sing_box_tiny = 0; nextSystemInfo.sing_box_compressed = 0; nextSystemInfo.sing_box_lx = 0; nextSystemInfo.sing_box_tailscale = 1; } if (result.action === "install_extended_compressed") { nextSystemInfo.sing_box_extended = 1; nextSystemInfo.sing_box_tiny = 0; nextSystemInfo.sing_box_compressed = 1; nextSystemInfo.sing_box_lx = 0; nextSystemInfo.sing_box_tailscale = 1; } if (result.action === "install_lx") { nextSystemInfo.sing_box_extended = 1; nextSystemInfo.sing_box_tiny = 0; nextSystemInfo.sing_box_compressed = 0; nextSystemInfo.sing_box_lx = 1; nextSystemInfo.sing_box_tailscale = 1; } if (result.action === "install_stable") { nextSystemInfo.sing_box_extended = 0; nextSystemInfo.sing_box_tiny = 0; nextSystemInfo.sing_box_compressed = 0; nextSystemInfo.sing_box_lx = 0; nextSystemInfo.sing_box_tailscale = 1; } if (result.action === "install_tiny") { nextSystemInfo.sing_box_extended = 0; nextSystemInfo.sing_box_tiny = 1; nextSystemInfo.sing_box_compressed = 0; nextSystemInfo.sing_box_lx = 0; nextSystemInfo.sing_box_tailscale = 0; } } if (result.component === "zapret") { nextSystemInfo.providerInfoLoaded = true; if (result.action === "remove") { nextSystemInfo.zapret_installed = 0; nextSystemInfo.zapret_version = "not installed"; } else { nextSystemInfo.zapret_installed = 1; nextSystemInfo.zapret_version = version; } } if (result.component === "zapret2") { nextSystemInfo.providerInfoLoaded = true; if (result.action === "remove") { nextSystemInfo.zapret2_installed = 0; nextSystemInfo.zapret2_version = "not installed"; } else { nextSystemInfo.zapret2_installed = 1; nextSystemInfo.zapret2_version = version; } } if (result.component === "byedpi") { nextSystemInfo.providerInfoLoaded = true; if (result.action === "remove") { nextSystemInfo.byedpi_installed = 0; nextSystemInfo.byedpi_version = "not installed"; } else { nextSystemInfo.byedpi_installed = 1; nextSystemInfo.byedpi_version = version; } } if (result.component === "wdtt") { nextSystemInfo.providerInfoLoaded = true; if (result.action === "remove") { nextSystemInfo.wdtt_installed = 0; nextSystemInfo.wdtt_version = "not installed"; } else { nextSystemInfo.wdtt_installed = 1; nextSystemInfo.wdtt_version = version; } } if (result.component === "olcrtc") { nextSystemInfo.providerInfoLoaded = true; if (result.action === "remove") { nextSystemInfo.olcrtc_installed = 0; nextSystemInfo.olcrtc_version = "not installed"; } else { nextSystemInfo.olcrtc_installed = 1; nextSystemInfo.olcrtc_version = version; } } if (result.component === "fptn") { nextSystemInfo.providerInfoLoaded = true; if (result.action === "remove") { nextSystemInfo.fptn_installed = 0; nextSystemInfo.fptn_version = "not installed"; } else { nextSystemInfo.fptn_installed = 1; nextSystemInfo.fptn_version = version; } } if (result.component === "direct_bypass") { nextSystemInfo.direct_bypass_enabled = result.action === "enable" ? 1 : 0; } if (result.component === "torrserver_direct") { nextSystemInfo.torrserver_direct_enabled = result.action === "enable" ? 1 : 0; nextSystemInfo.torrserver_direct_active = result.action === "enable" ? 1 : 0; } const normalizedSystemInfo = normalizeSingBoxVariantFields(nextSystemInfo); store.set({ diagnosticsSystemInfo: normalizedSystemInfo }); if (result.component === "zapret" || result.component === "zapret2" || result.component === "byedpi" || result.component === "wdtt" || result.component === "olcrtc" || result.component === "fptn") { notifyActionProvidersAvailabilityChanged(normalizedSystemInfo); } } async function applyCompletedComponentAction({ key, result, notify }) { const modalController = getActiveProgressModalController(); if (result.action === "check_update") { setActionLoading(key, false); if (!shouldApplyCompletedComponentActionResult(result, notify)) { modalController?.completeSuccess(_("Check completed!")); return; } if (shouldPreserveCompletedCheckResultOnNextMount({ action: result.action, mounted: updatesMounted })) { preserveCheckResultsOnNextMount = true; } const status = result.status || null; const hasUpdate = status === "outdated" || status === "outdated_same_release"; if (status === "latest" || status === "outdated" || status === "dev" || status === "outdated_same_release") { setCheckResult( result.component, status, result.latest_version || "", result.release_url || "", result.current_sha || "", result.latest_sha || "" ); modalController?.updateVersions({ currentVersion: getComponentCurrentVersion(result.component), targetVersion: result.latest_version || "", currentSha: result.current_sha || "", targetSha: result.latest_sha || "" }); } if (notify) { showToast(getCheckToastMessage(status), "success"); } if (hasUpdate) { const installButton = getComponentInstallAction(result.component); modalController?.completeSuccess(getCheckToastMessage(status), { autoCloseMs: 0, installText: installButton.text, onInstall: () => { void handleComponentAction(installButton); } }); } else { modalController?.completeSuccess(getCheckToastMessage(status)); } return; } if (result.action === "install" || result.action === "reinstall" || result.action.startsWith("install_")) { setCheckResult(result.component, "latest", result.latest_version || ""); } else { resetCheckResult(result.component); } patchSystemInfoAfterMutation(result); setActionLoading(key, false); if (result.component === "tachyon" && (result.action === "install" || result.action === "reinstall" || result.action === "install_version")) { if (notify && result.message) { showToast(result.message, "success", 1200); } if (notify) { if (modalController) { modalController.completeSuccess(result.message, { reloadPage: true }); } else { reloadPageAfterTachyonUpdate(result.job_id); } } else { modalController?.completeSuccess(result.message); } return; } if (notify && result.message) { showToast(result.message, "success"); } modalController?.completeSuccess(result.message); void refreshSystemInfoAfterMutation(); } async function completeComponentActionJob(key, jobId, response) { if (pageUnloading2) { setActionLoading(key, false); return; } const alreadyHandled = handledComponentJobs.has(jobId); if (alreadyHandled) { setActionLoading(key, false); return; } const shouldNotify = shouldNotifyOwnedUiAction("component", jobId); const modalController = getActiveProgressModalController(); if (!response.success || !response.data.success) { const message = response.success ? response.data.message || _("Failed to execute") : response.error || _("Failed to execute"); if (isTransientRpcError(message)) { setActionLoading(key, false); void refreshComponentActionState(); return; } handledComponentJobs.add(jobId); capSetSize(handledComponentJobs); saveHandledJobToSession(jobId); setActionLoading(key, false); if (shouldNotify) { showToast(message, "error"); } modalController?.completeError(message); await ackComponentActionJob(jobId); return; } handledComponentJobs.add(jobId); capSetSize(handledComponentJobs); saveHandledJobToSession(jobId); await ackComponentActionJob(jobId); await applyCompletedComponentAction({ key, result: response.data, notify: shouldNotify }); } async function followComponentActionState(state) { const jobId = state.job_id; const key = getComponentActionKey(state.component, state.action); if (!jobId || !key || followedComponentJobs.has(jobId) || handledComponentJobs.has(jobId)) { return; } followedComponentJobs.add(jobId); if (shouldShowLoadingForRestoredAction(state)) { setActionLoading(key, true); if (!getActiveProgressModalController()) { setActiveProgressModalJobId(jobId); showUpdateProgressModal({ component: state.component, action: state.action, componentTitle: getComponentCardTitle(state.component), currentVersion: state.current_version, targetVersion: state.latest_version }); } getActiveProgressModalController()?.startLogTracking(jobId); } try { const response = state.running ? await TachyonShellMethods.waitComponentActionJob( jobId, state.component, state.action, state.latest_version || void 0 ) : { success: true, data: state }; await completeComponentActionJob(key, jobId, response); } catch (error) { logger.error("[UPDATES]", "followComponentActionState failed", error); if (!pageUnloading2) { const message = getErrorMessage(error, _("Failed to execute")); setActionLoading(key, false); if (!isTransientRpcError(message)) { showToast(message, "error"); getActiveProgressModalController()?.completeError(message); } } } finally { followedComponentJobs.delete(jobId); } } async function followAlreadyRunningComponentAction(button) { const uiState = await refreshRuntimeUiState({ force: true }); if (!uiState) { return false; } let state = uiState.actions.component.find( (item) => item.running && item.component === button.component && item.action === button.action && (!item.job_id || !handledComponentJobs.has(item.job_id)) ); if (!state) { state = uiState.actions.component.find( (item) => item.running && item.component === button.component && (!item.job_id || !handledComponentJobs.has(item.job_id)) ); } if (!state) { return false; } if (state.job_id) { markUiActionOwned("component", state.job_id); } await followComponentActionState(state); return true; } function isComponentActionAlreadyRunningError(message) { return Boolean( message && message.includes("Another component action is already running") ); } function handleComponentUiState(uiState) { for (const state of uiState.actions.component || []) { const jobId = state.job_id; if (!jobId) { continue; } if (handledComponentJobs.has(jobId) || followedComponentJobs.has(jobId)) { continue; } if (state.running) { void followComponentActionState(state); } else { handledComponentJobs.add(jobId); capSetSize(handledComponentJobs); saveHandledJobToSession(jobId); void ackComponentActionJob(jobId); } } } async function refreshComponentActionState() { if (componentActionStateRefreshPromise) { return componentActionStateRefreshPromise; } componentActionStateRefreshPromise = (async () => { if (!updatesMounted) { return; } const state = await refreshRuntimeUiState({ force: true }); if (!state) { return; } handleComponentUiState(state); })().finally(() => { componentActionStateRefreshPromise = null; }); return componentActionStateRefreshPromise; } function startComponentActionStateWatcher() { if (componentActionStateUnsubscribe) { return; } componentActionStateUnsubscribe = subscribeRuntimeUiState((uiState) => { if (updatesMounted) { handleComponentUiState(uiState); } }); } function stopComponentActionStateWatcher() { if (!componentActionStateUnsubscribe) { return; } componentActionStateUnsubscribe(); componentActionStateUnsubscribe = null; } async function handleComponentAction(button) { if (!beginComponentAction(button)) { return; } const cardTitle = getComponentCardTitle(button.component); const currentVersion = getComponentCurrentVersion(button.component); const targetVersion = getExpectedLatestVersionForAction(button); const checkResult = getVisibleCheckResult(button.component); const currentSha = checkResult?.current_sha; const targetSha = checkResult?.latest_sha; let modalController = getActiveProgressModalController(); if (!modalController) { modalController = showUpdateProgressModal({ component: button.component, action: button.action, componentTitle: cardTitle, currentVersion, targetVersion, currentSha, targetSha }); } let jobId = ""; let ownsJobFollow = false; try { const startResponse = await TachyonShellMethods.componentActionStart( button.component, button.action, button.targetVersion ); if (!startResponse.success) { if (isComponentActionAlreadyRunningError(startResponse.error)) { if (await followAlreadyRunningComponentAction(button)) { return; } await new Promise((resolve) => setTimeout(resolve, 800)); if (await followAlreadyRunningComponentAction(button)) { return; } setActionLoading(button.key, false); modalController.completeError( _("Another component action is already running") ); await refreshComponentActionState(); return; } if (isTransientRpcError(startResponse.error)) { if (await followAlreadyRunningComponentAction(button)) { return; } await new Promise((resolve) => setTimeout(resolve, 800)); if (await followAlreadyRunningComponentAction(button)) { return; } setActionLoading(button.key, false); modalController.completeError( startResponse.error || _("Transient connection error") ); await refreshComponentActionState(); return; } throw new Error(startResponse.error); } jobId = startResponse.data.job_id; if (followedComponentJobs.has(jobId) || handledComponentJobs.has(jobId)) { return; } followedComponentJobs.add(jobId); ownsJobFollow = true; setActiveProgressModalJobId(jobId); markUiActionOwned("component", jobId); modalController.startLogTracking(jobId); const response = await TachyonShellMethods.waitComponentActionJob( jobId, button.component, button.action, getExpectedLatestVersionForAction(button) ); await completeComponentActionJob(button.key, jobId, response); } catch (error) { logger.error("[UPDATES]", "handleComponentAction failed", error); if (!pageUnloading2) { const message = getErrorMessage(error, _("Failed to execute")); setActionLoading(button.key, false); if (!isTransientRpcError(message)) { showToast(message, "error"); } modalController.completeError(message); void refreshComponentActionState(); } } finally { if (ownsJobFollow) { followedComponentJobs.delete(jobId); } } } function getCheckAction(component, key) { return { key, text: _("Check update"), icon: renderSearchIcon24, component, action: "check_update" }; } function getInstallAction(component, key, installed) { return { key, text: installed ? _("Update") : _("Install"), icon: installed ? renderRotateCcwIcon24 : renderDownloadIcon24, component, action: "install" }; } function getComponentInstallKey(component) { switch (component) { case "tachyon": return "tachyonInstall"; case "sing_box": return "singBoxInstall"; case "zapret": return "zapretInstall"; case "zapret2": return "zapret2Install"; case "byedpi": return "byedpiInstall"; case "wdtt": return "wdttInstall"; case "olcrtc": return "olcrtcInstall"; case "tailscale": return "tailscaleInstall"; default: return "tachyonInstall"; } } function getComponentInstallAction(component) { const isInstalled = !isNotInstalled(getComponentCurrentVersion(component)); const key = getComponentInstallKey(component); return getInstallAction(component, key, isInstalled); } function getInstalledUpdateActions(component, checkKey, installKey, installed = true) { if (!installed) { return []; } const actions = [getCheckAction(component, checkKey)]; if (shouldShowInstallAfterCheck(component)) { actions.push(getInstallAction(component, installKey, true)); } return actions; } function getComponentBackupVersion(component) { const sys = store.get().diagnosticsSystemInfo; switch (component) { case "sing_box": return sys.sing_box_backup_version || ""; case "zapret": return sys.zapret_backup_version || ""; case "zapret2": return sys.zapret2_backup_version || ""; case "byedpi": return sys.byedpi_backup_version || ""; case "wdtt": return sys.wdtt_backup_version || ""; case "olcrtc": return sys.olcrtc_backup_version || ""; case "fptn": return sys.fptn_backup_version || ""; case "tailscale": return sys.tailscale_backup_version || ""; default: return ""; } } function getRollbackAction(component, key, backupVersion) { return { key, text: backupVersion ? `${_("Rollback")} (${backupVersion})` : _("Rollback"), icon: renderRotateCcwIcon24, component, action: "rollback" }; } function getOptionalComponentActions({ component, installed, checkKey, installKey, removeKey, rollbackKey }) { if (!installed) { return [getInstallAction(component, installKey, false)]; } const actions = [ ...getInstalledUpdateActions(component, checkKey, installKey), { key: removeKey, text: _("Remove"), icon: renderXIcon24, component, action: "remove" } ]; const backupVersion = getComponentBackupVersion(component); if (backupVersion) { actions.push(getRollbackAction(component, rollbackKey, backupVersion)); } return actions; } var COMPONENT_REPO_URLS = { tachyon: "https://github.com/Dushnilin/tachyon", sing_box: "https://github.com/SagerNet/sing-box", zapret: "https://github.com/remittor/zapret-openwrt", zapret2: "https://github.com/Dushnilin/zapret2-openwrt", byedpi: "https://github.com/DPITrickster/ByeDPI-OpenWrt", wdtt: "https://github.com/SpaceNeuroX/qwdtt-openwrt", olcrtc: "https://github.com/alekvol/openwrt-olcrtc", fptn: "https://github.com/fptn-project/fptn", tailscale: "https://openwrt.org/packages/pkgdata/tailscale", direct_bypass: "", torrserver_direct: "" }; function getComponentCards() { const systemInfo = normalizeSingBoxVariantFields( store.get().diagnosticsSystemInfo ); const systemInfoLoading = isSystemInfoLoading(); const zapretInstalled = Boolean(systemInfo.zapret_installed); const zapret2Installed = Boolean(systemInfo.zapret2_installed); const byedpiInstalled = Boolean(systemInfo.byedpi_installed); const wdttInstalled = Boolean(systemInfo.wdtt_installed); const olcrtcInstalled = Boolean(systemInfo.olcrtc_installed); const fptnInstalled = Boolean(systemInfo.fptn_installed); const tailscaleInstalled = Boolean(systemInfo.tailscale_installed); const singBoxInstalled = !isNotInstalled(systemInfo.sing_box_version); const singBoxStable = singBoxInstalled && !systemInfo.sing_box_extended && !systemInfo.sing_box_tiny; const singBoxExtended = Boolean(systemInfo.sing_box_extended) && !systemInfo.sing_box_compressed && !systemInfo.sing_box_lx; const singBoxExtendedCompressed = Boolean(systemInfo.sing_box_extended) && Boolean(systemInfo.sing_box_compressed); const singBoxLx = Boolean(systemInfo.sing_box_extended) && Boolean(systemInfo.sing_box_lx); const singBoxTiny = Boolean(systemInfo.sing_box_tiny); const tachyonActions = [ ...getInstalledUpdateActions("tachyon", "tachyonCheck", "tachyonInstall"), { key: "tachyonReinstall", text: _("Reinstall"), icon: renderRotateCcwIcon24, component: "tachyon", action: "reinstall" } ]; const singBoxActions = getInstalledUpdateActions( "sing_box", "singBoxCheck", "singBoxInstall", singBoxInstalled ); const singBoxBackup = getComponentBackupVersion("sing_box"); if (singBoxBackup && singBoxInstalled) { singBoxActions.push( getRollbackAction("sing_box", "singBoxRollback", singBoxBackup) ); } if (!singBoxStable) { singBoxActions.push({ key: "singBoxInstallStable", text: "Stable", icon: renderDownloadIcon24, component: "sing_box", action: "install_stable" }); } if (!singBoxTiny) { singBoxActions.push({ key: "singBoxInstallTiny", text: "Tiny", icon: renderDownloadIcon24, component: "sing_box", action: "install_tiny" }); } if (!singBoxExtended) { singBoxActions.push({ key: "singBoxInstallExtended", text: "Extended", icon: renderDownloadIcon24, component: "sing_box", action: "install_extended" }); } if (!singBoxExtendedCompressed) { singBoxActions.push({ key: "singBoxInstallExtendedCompressed", text: "Extended compressed", icon: renderDownloadIcon24, component: "sing_box", action: "install_extended_compressed" }); } if (!singBoxLx) { singBoxActions.push({ key: "singBoxInstallLx", text: "Leadaxe (lx)", icon: renderDownloadIcon24, component: "sing_box", action: "install_lx" }); } const zapretActions = getOptionalComponentActions({ component: "zapret", installed: zapretInstalled, checkKey: "zapretCheck", installKey: "zapretInstall", removeKey: "zapretRemove", rollbackKey: "zapretRollback" }); const zapret2Actions = getOptionalComponentActions({ component: "zapret2", installed: zapret2Installed, checkKey: "zapret2Check", installKey: "zapret2Install", removeKey: "zapret2Remove", rollbackKey: "zapret2Rollback" }); const byedpiActions = getOptionalComponentActions({ component: "byedpi", installed: byedpiInstalled, checkKey: "byedpiCheck", installKey: "byedpiInstall", removeKey: "byedpiRemove", rollbackKey: "byedpiRollback" }); const wdttActions = getOptionalComponentActions({ component: "wdtt", installed: wdttInstalled, checkKey: "wdttCheck", installKey: "wdttInstall", removeKey: "wdttRemove", rollbackKey: "wdttRollback" }); const olcrtcActions = getOptionalComponentActions({ component: "olcrtc", installed: olcrtcInstalled, checkKey: "olcrtcCheck", installKey: "olcrtcInstall", removeKey: "olcrtcRemove", rollbackKey: "olcrtcRollback" }); const fptnActions = getOptionalComponentActions({ component: "fptn", installed: fptnInstalled, checkKey: "fptnCheck", installKey: "fptnInstall", removeKey: "fptnRemove", rollbackKey: "fptnRollback" }); const tailscaleActions = getOptionalComponentActions({ component: "tailscale", installed: tailscaleInstalled, checkKey: "tailscaleCheck", installKey: "tailscaleInstall", removeKey: "tailscaleRemove", rollbackKey: "tailscaleRollback" }); const directBypassEnabled = Boolean(systemInfo.direct_bypass_enabled); const directBypassEndpoint = systemInfo.direct_bypass_address ? `${systemInfo.direct_bypass_address}:${systemInfo.direct_bypass_port || "2080"}` : ""; const torrserverRunning = Boolean(systemInfo.torrserver_running); const torrserverDirectAvailable = Boolean( systemInfo.torrserver_direct_available ); const torrserverDirectEnabled = Boolean(systemInfo.torrserver_direct_enabled); const torrserverDirectActive = Boolean(systemInfo.torrserver_direct_active); const directBypassActions = [ directBypassEnabled ? { key: "directBypassDisable", text: _("Disable"), icon: renderXIcon24, component: "direct_bypass", action: "disable" } : { key: "directBypassEnable", text: _("Enable"), icon: renderRotateCcwIcon24, component: "direct_bypass", action: "enable" } ]; const torrserverDirectActions = [ torrserverDirectEnabled ? { key: "torrserverDirectDisable", text: _("Disable"), icon: renderXIcon24, component: "torrserver_direct", action: "disable" } : { key: "torrserverDirectEnable", text: _("Enable"), icon: renderRotateCcwIcon24, component: "torrserver_direct", action: "enable", disabled: !torrserverDirectAvailable } ]; return [ { component: "tachyon", column: 0, title: "Tachyon", version: systemInfoLoading ? _("Loading...") : normalizeCompiledVersion( systemInfo.tachyon_version, systemInfo.tachyon_commit_sha ), latestVersion: getLatestVersion("tachyon"), releaseUrl: getGitHubReleaseUrl("tachyon"), repoUrl: COMPONENT_REPO_URLS.tachyon, actions: tachyonActions, supportsVersions: true }, { component: "sing_box", column: 0, title: "Sing-box", version: systemInfoLoading ? _("Loading...") : formatSingBoxVersion(systemInfo), badgeNode: systemInfoLoading ? null : renderSingBoxVariantBadge(systemInfo), latestVersion: getLatestVersion("sing_box"), releaseUrl: getGitHubReleaseUrl("sing_box"), repoUrl: systemInfo.sing_box_repo_url || (singBoxLx ? "https://github.com/Leadaxe/sing-box-lx" : singBoxExtended || singBoxExtendedCompressed ? "https://github.com/shtorm-7/sing-box-extended" : COMPONENT_REPO_URLS.sing_box), actions: singBoxActions, supportsVersions: true }, { component: "direct_bypass", column: 0, title: _("Direct Proxy"), version: directBypassEnabled ? `HTTP/SOCKS5 · ${directBypassEndpoint || _("Enabled")}` : _("Disabled"), copyValue: directBypassEnabled && directBypassEndpoint ? directBypassEndpoint : void 0, actions: directBypassActions }, { component: "torrserver_direct", column: 0, title: _("TorrServer Direct"), version: !torrserverRunning ? _("TorrServer not found") : !torrserverDirectAvailable ? _("Dedicated cgroup unavailable") : torrserverDirectEnabled && torrserverDirectActive ? _("Enabled") : torrserverDirectEnabled ? _("Waiting for TorrServer") : _("Disabled"), actions: torrserverDirectActions }, { component: "zapret", column: 1, title: "Zapret", version: systemInfoLoading ? _("Loading...") : zapretInstalled ? systemInfo.zapret_version : _("Not installed"), latestVersion: getLatestVersion("zapret"), releaseUrl: getGitHubReleaseUrl("zapret"), repoUrl: COMPONENT_REPO_URLS.zapret, actions: zapretActions, supportsVersions: true }, { component: "zapret2", column: 1, title: "Zapret2", version: systemInfoLoading ? _("Loading...") : zapret2Installed ? systemInfo.zapret2_version : _("Not installed"), latestVersion: getLatestVersion("zapret2"), releaseUrl: getGitHubReleaseUrl("zapret2"), repoUrl: COMPONENT_REPO_URLS.zapret2, actions: zapret2Actions, supportsVersions: true }, { component: "byedpi", column: 1, title: "ByeDPI", version: systemInfoLoading ? _("Loading...") : byedpiInstalled ? systemInfo.byedpi_version : _("Not installed"), latestVersion: getLatestVersion("byedpi"), releaseUrl: getGitHubReleaseUrl("byedpi"), repoUrl: COMPONENT_REPO_URLS.byedpi, actions: byedpiActions }, { component: "wdtt", column: 2, title: "WDTT", version: systemInfoLoading ? _("Loading...") : wdttInstalled ? systemInfo.wdtt_version : _("Not installed"), latestVersion: getLatestVersion("wdtt"), releaseUrl: getGitHubReleaseUrl("wdtt"), repoUrl: COMPONENT_REPO_URLS.wdtt, actions: wdttActions, supportsVersions: true }, { component: "olcrtc", column: 2, title: "OlcRTC", version: systemInfoLoading ? _("Loading...") : olcrtcInstalled ? systemInfo.olcrtc_version : _("Not installed"), latestVersion: getLatestVersion("olcrtc"), releaseUrl: getGitHubReleaseUrl("olcrtc"), repoUrl: COMPONENT_REPO_URLS.olcrtc, actions: olcrtcActions, supportsVersions: true }, { component: "fptn", column: 2, title: "FPTN", version: systemInfoLoading ? _("Loading...") : fptnInstalled ? systemInfo.fptn_version : _("Not installed"), latestVersion: getLatestVersion("fptn"), releaseUrl: getGitHubReleaseUrl("fptn"), repoUrl: COMPONENT_REPO_URLS.fptn, actions: fptnActions, supportsVersions: true }, { component: "tailscale", column: 2, title: "Tailscale", version: systemInfoLoading ? _("Loading...") : tailscaleInstalled ? systemInfo.tailscale_version : _("Not installed"), latestVersion: getLatestVersion("tailscale"), releaseUrl: getGitHubReleaseUrl("tailscale"), repoUrl: COMPONENT_REPO_URLS.tailscale, actions: tailscaleActions } ]; } async function toggleVersionPicker(component) { if (activeVersionPickerComponent === component) { activeVersionPickerComponent = null; versionPickerLoading = false; versionPickerError = null; renderUpdatesComponents(); return; } activeVersionPickerComponent = component; versionPickerError = null; if (versionPickerReleasesCache[component]) { versionPickerReleases = versionPickerReleasesCache[component]; versionPickerLoading = false; renderUpdatesComponents(); return; } versionPickerLoading = true; versionPickerReleases = []; renderUpdatesComponents(); try { const response = await TachyonShellMethods.componentListReleases( component, 5 ); if (activeVersionPickerComponent !== component) { return; } if (!response.success) { versionPickerError = response.error || _("No versions found"); versionPickerReleases = []; } else if (!response.data || response.data.length === 0) { versionPickerError = _("No versions found"); versionPickerReleases = []; } else { versionPickerReleases = response.data; versionPickerReleasesCache[component] = response.data; versionPickerError = null; } } catch (_error) { if (activeVersionPickerComponent !== component) { return; } versionPickerError = _("Failed to load versions"); versionPickerReleases = []; } finally { if (activeVersionPickerComponent === component) { versionPickerLoading = false; renderUpdatesComponents(); } } } async function handleInstallVersion(component, tag) { activeVersionPickerComponent = null; renderUpdatesComponents(); const key = getComponentInstallKey(component); const button = { key, text: _("Install"), icon: renderDownloadIcon24, component, action: "install_version", targetVersion: tag }; await handleComponentAction(button); } function renderVersionPickerDropdown(component) { const container = E("div", { class: "tachyon-version-picker" }); if (versionPickerLoading) { container.appendChild( E( "div", { class: "tachyon-version-picker__loading" }, _("Loading versions...") ) ); return container; } if (versionPickerError) { container.appendChild( E("div", { class: "tachyon-version-picker__error" }, versionPickerError) ); return container; } const list = E("div", { class: "tachyon-version-picker__list" }); for (const release of versionPickerReleases) { const children = [ E("span", { class: "tachyon-version-picker__tag" }, release.tag) ]; if (release.published) { children.push( E("span", { class: "tachyon-version-picker__date" }, release.published) ); } if (release.prerelease) { children.push( E( "span", { class: "tachyon-version-picker__prerelease" }, _("pre-release") ) ); } children.push( renderButton({ text: _("Install"), loading: false, disabled: isAnyActionLoading(), onClick: () => void handleInstallVersion(component, release.tag) }) ); list.appendChild( E("div", { class: "tachyon-version-picker__item" }, children) ); } container.appendChild(list); return container; } function renderComponentCard(card) { const updatesActions = store.get().updatesActions; const anyActionLoading = isAnyActionLoading(); const systemInfoLoading = isSystemInfoLoading(); const checkResult = getVisibleCheckResult(card.component); const headerChildren = [ E("b", { class: "tachyon_updates-page__component__title" }, card.title) ]; if (card.badgeNode) { headerChildren.push(card.badgeNode); } headerChildren.push( E( "span", { class: "tachyon_updates-page__component__header-version" }, card.version ) ); if (card.repoUrl) { headerChildren.push( E( "a", { class: "tachyon_updates-page__component__repo-link", href: card.repoUrl, target: "_blank", rel: "noopener noreferrer", title: card.repoUrl }, renderGlobeIcon24() ) ); } const header = E( "div", { class: "tachyon_updates-page__component__header" }, headerChildren ); const detailsChildren = []; if (checkResult && checkResult.status) { let labelText = ""; const latestValueNodes = []; if (checkResult.status === "outdated") { labelText = _("Update is available:"); const versionToShow = checkResult.latest_version || card.latestVersion || card.version; if (checkResult.release_url) { latestValueNodes.push( E( "a", { class: "tachyon_updates-page__component__release-version-link", href: checkResult.release_url, target: "_blank", rel: "noopener noreferrer" }, versionToShow || _("Open") ) ); } else if (versionToShow) { latestValueNodes.push(document.createTextNode(versionToShow)); } } else if (checkResult.status === "outdated_same_release") { labelText = _("Update is available for current release"); const build = describeSameReleaseBuild({ currentSha: checkResult.current_sha, latestSha: checkResult.latest_sha }); latestValueNodes.push( E( "span", { class: "tachyon_updates-page__component__sha-info", title: _("Installed build → Available build") }, build.kind === "sha" ? build.text : _("Rebuilt release") ) ); } else if (checkResult.status === "latest") { labelText = _("Latest version is installed"); } else if (checkResult.status === "dev") { labelText = `${_("Installed version is newer than release")}. ${_("Latest version:")}`; const versionToShow = checkResult.latest_version || card.latestVersion; if (checkResult.release_url) { latestValueNodes.push( E( "a", { class: "tachyon_updates-page__component__release-version-link", href: checkResult.release_url, target: "_blank", rel: "noopener noreferrer" }, versionToShow || _("Open") ) ); } else if (versionToShow) { latestValueNodes.push(document.createTextNode(versionToShow)); } } if (labelText) { const rowChildren = [ E( "span", { class: "tachyon_updates-page__component__info-label" }, labelText ) ]; if (latestValueNodes.length > 0) { rowChildren.push( E( "span", { class: "tachyon_updates-page__component__info-value tachyon_updates-page__component__info-value--latest" }, latestValueNodes ) ); } detailsChildren.push( E( "div", { class: "tachyon_updates-page__component__info-row" }, rowChildren ) ); } } const detailsContainer = detailsChildren.length > 0 ? E( "div", { class: "tachyon_updates-page__component__details" }, detailsChildren ) : null; const primaryActions = []; const dangerActions = []; const variantActions = []; card.actions.forEach((action) => { if (action.action === "remove") { dangerActions.push(action); } else if (action.action.startsWith("install_")) { variantActions.push(action); } else { primaryActions.push(action); } }); const actionElements = []; const primaryButtons = primaryActions.map((action) => { const loading2 = updatesActions[action.key].loading; const isUpdateOrInstall = action.action === "install" || action.action === "reinstall"; return renderButton({ classNames: isUpdateOrInstall ? ["cbi-button-save"] : [], text: action.text, icon: action.icon, loading: loading2, disabled: action.disabled || systemInfoLoading || anyActionLoading && !loading2, onClick: () => void handleComponentAction(action) }); }); const dangerButtons = dangerActions.map((action) => { const loading2 = updatesActions[action.key].loading; return renderButton({ classNames: ["cbi-button-remove"], text: action.text, icon: action.icon, loading: loading2, disabled: action.disabled || systemInfoLoading || anyActionLoading && !loading2, onClick: () => void handleComponentAction(action) }); }); if (primaryButtons.length > 0 || dangerButtons.length > 0) { actionElements.push( E("div", { class: "tachyon_updates-page__component__actions-main" }, [ ...primaryButtons, ...dangerButtons ]) ); } if (card.copyValue) { actionElements.push( E("div", { class: "tachyon_updates-page__component__actions-main" }, [ renderButton({ text: _("Copy address"), icon: renderCopyIcon24, disabled: anyActionLoading, onClick: () => copyToClipboard(card.copyValue || "") }) ]) ); } if (variantActions.length > 0) { const variantButtons = variantActions.map((action) => { const loading2 = updatesActions[action.key].loading; return renderButton({ text: action.text, icon: action.icon, loading: loading2, disabled: systemInfoLoading || anyActionLoading && !loading2, onClick: () => void handleComponentAction(action) }); }); actionElements.push( E("div", { class: "tachyon_updates-page__component__variants" }, [ E( "div", { class: "tachyon_updates-page__component__variants-title" }, _("Install another build:") ), E( "div", { class: "tachyon_updates-page__component__variants-buttons" }, variantButtons ) ]) ); } if (card.supportsVersions) { const isPickerOpen = activeVersionPickerComponent === card.component; const versionsButton = renderButton({ text: isPickerOpen ? _("Hide versions") : _("Versions"), loading: isPickerOpen && versionPickerLoading, disabled: systemInfoLoading || anyActionLoading, onClick: () => void toggleVersionPicker(card.component) }); actionElements.push( E("div", { class: "tachyon_updates-page__component__versions" }, [ versionsButton ]) ); if (isPickerOpen) { actionElements.push(renderVersionPickerDropdown(card.component)); } } const actionsContainer = E( "div", { class: [ "tachyon_updates-page__component__actions", detailsContainer ? "tachyon_updates-page__component__actions--with-details" : "" ].filter(Boolean).join(" ") }, actionElements ); const cardChildren = [header]; if (detailsContainer) { cardChildren.push(detailsContainer); } cardChildren.push(actionsContainer); return E("div", { class: "tachyon_updates-page__component" }, cardChildren); } function renderUpdatesComponents() { const container = document.getElementById("tachyon_updates-components"); if (!container) { return; } const columns = [[], [], []]; getComponentCards().forEach((card) => { columns[card.column]?.push(renderComponentCard(card)); }); return preserveScrollForPage(() => { container.replaceChildren( ...columns.map( (columnNodes) => E( "div", { class: "tachyon_updates-page__components-column" }, columnNodes ) ) ); }); } function onStoreUpdate3(_next, _prev, diff) { if (diff.diagnosticsSystemInfo || diff.updatesActions || diff.updatesChecks || diff.diagnosticsActions || diff.servicesInfoWidget) { renderUpdatesComponents(); } } function applyComponentUpdateCheckCache(componentUpdateCheckCache) { componentUpdateCheckCacheResolved = true; if (componentUpdateCheckCache.enabled) { store.reset(["updatesChecks"]); applyCachedCheckResults(componentUpdateCheckCache.results); } if (shouldResetCheckResultsOnMount({ anyActionLoading: isAnyActionLoading(), preserveCheckResultsOnNextMount, persistentCacheEnabled: componentUpdateCheckCache.enabled })) { store.reset(["updatesChecks"]); } } async function onPageMount4() { onPageUnmount4(); updatesMounted = true; updatesMountId += 1; const mountId = updatesMountId; const cachedRuntimeState = getCachedRuntimeUiState(); const hasRuntimeSnapshot = Boolean(cachedRuntimeState); const needsFreshStateBeforeRender = shouldRefreshComponentStateBeforeRender(cachedRuntimeState); const runtimeStateRefreshPromise = !hasRuntimeSnapshot || needsFreshStateBeforeRender ? refreshRuntimeUiState({ force: true }) : null; const prefetchedComponentUpdateCheckCache = componentUpdateCheckCacheSnapshot; if (prefetchedComponentUpdateCheckCache) { applyComponentUpdateCheckCache(prefetchedComponentUpdateCheckCache); } renderUpdatesComponents(); const componentUpdateCheckCache = await loadComponentUpdateCheckCache({ force: Boolean(prefetchedComponentUpdateCheckCache) }); if (!updatesMounted || mountId !== updatesMountId) { return; } applyComponentUpdateCheckCache(componentUpdateCheckCache); preserveCheckResultsOnNextMount = false; renderUpdatesComponents(); if (runtimeStateRefreshPromise) { await runtimeStateRefreshPromise; if (!updatesMounted || mountId !== updatesMountId) { return; } } store.subscribe(onStoreUpdate3); startComponentActionStateWatcher(); renderUpdatesComponents(); void ensureSystemInfo(); if (hasRuntimeSnapshot) { void refreshRuntimeUiState({ force: true }); } } function onPageUnmount4() { updatesMounted = false; updatesMountId += 1; activeVersionPickerComponent = null; stopComponentActionStateWatcher(); store.unsubscribe(onStoreUpdate3); } function registerLifecycleListeners4() { if (updatesLifecycleRegistered) { return; } updatesLifecycleRegistered = true; store.subscribe((next, prev, diff) => { if (diff.tabService && next.tabService.current !== prev.tabService.current) { const isUpdatesVisible = next.tabService.current === "updates"; if (isUpdatesVisible) { return onPageMount4(); } if (updatesMounted) { return onPageUnmount4(); } } }); } async function initController4() { if (updatesControllerInitialized) { return; } updatesControllerInitialized = true; void loadComponentUpdateCheckCache(); onMount("updates-status").then(() => { logger.debug("[UPDATES]", "initController", "onMount"); registerLifecycleListeners4(); if (store.get().tabService.current === "updates" || isActiveLuciTab("updates")) { onPageMount4(); } }); } // src/tachyon/tabs/updates/styles.ts var styles6 = ` #cbi-${TACHYON_UCI_PACKAGE}-updates-_mount_node > div { width: 100%; } #cbi-${TACHYON_UCI_PACKAGE}-updates > h3 { display: none; } .tachyon_updates-page { width: 100%; } .tachyon_updates-page__components { display: flex; align-items: flex-start; gap: 10px; width: 100%; flex-wrap: wrap; } .tachyon_updates-page__components-column { display: flex; flex: 1 1 0; flex-direction: column; gap: 10px; min-width: 280px; } @media (max-width: 760px) { .tachyon_updates-page__components { flex-direction: column; } .tachyon_updates-page__components-column { width: 100%; min-width: 0; } .tachyon_updates-page__component { width: 100%; min-width: 0; box-sizing: border-box; } } .tachyon_updates-page__component { border: 2px var(--background-color-low, lightgray) solid; border-radius: 4px; padding: 10px; display: flex; flex-direction: column; gap: 10px; min-width: 0; box-sizing: border-box; } .tachyon_updates-page__component__header { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; border-bottom: 1px var(--background-color-low, lightgray) solid; padding-bottom: 8px; margin-bottom: 2px; } .tachyon_updates-page__component__title { color: var(--text-color-high); font-size: 16px; font-weight: bold; line-height: 1.2; } .tachyon_updates-page__component__header-version { color: var(--text-color-medium, #888); font-size: 13px; font-weight: normal; } .tachyon_updates-page__component__repo-link { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 3px; color: var(--text-color-medium, #888); text-decoration: none; transition: color 0.15s, background-color 0.15s; flex-shrink: 0; } .tachyon_updates-page__component__repo-link:hover { color: var(--link-color, #3498db); background-color: var(--background-color-low, rgba(0, 0, 0, 0.05)); } .tachyon_updates-page__component__details { display: flex; flex-direction: column; gap: 6px; } .tachyon_updates-page__component__info-row { display: flex; justify-content: flex-start; align-items: center; min-height: 24px; gap: 8px; white-space: nowrap; } .tachyon_updates-page__component__info-label { color: var(--text-color-medium, #888); font-size: 12px; } .tachyon_updates-page__component__info-value { color: var(--text-color-high, #000); font-weight: 500; font-size: 13px; text-align: left; display: flex; align-items: center; gap: 6px; min-width: 0; overflow-wrap: anywhere; } .tachyon_updates-page__component__info-value--latest { flex-wrap: wrap; justify-content: flex-start; } .tachyon_updates-page__component__release-version-link { color: var(--link-color, #3498db) !important; text-decoration: underline; font-weight: bold; } .tachyon_updates-page__component__release-version-link:hover { color: var(--link-color-dark, #2980b9) !important; } .tachyon_updates-page__component__sha-info { font-family: monospace; font-size: 11px; color: var(--text-color-medium, #888); background: var(--background-color-low, rgba(0,0,0,0.06)); border-radius: 3px; padding: 1px 5px; letter-spacing: 0.02em; } .tachyon_updates-page__component__actions { display: flex; flex-direction: column; gap: 10px; margin-top: auto; } .tachyon_updates-page__component__actions--with-details { border-top: 1px var(--background-color-low, lightgray) solid; padding-top: 10px; } .tachyon_updates-page__component__actions-main { display: flex; justify-content: flex-start; align-items: center; flex-wrap: wrap; gap: 6px; } .tachyon_updates-page__component__variants { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; } .tachyon_updates-page__component__variants-title { font-size: 11px; font-weight: bold; color: var(--text-color-medium, gray); } .tachyon_updates-page__component__variants-buttons { display: flex; flex-wrap: wrap; gap: 6px; } .tachyon-update-modal__body { display: flex; flex-direction: column; gap: 10px; width: 100%; box-sizing: border-box; padding: 6px 0; } .tachyon-update-modal__header { display: flex; justify-content: space-between; align-items: center; gap: 10px; border-bottom: 1px solid var(--border-color-low, rgba(128,128,128,0.2)); padding-bottom: 10px; } .tachyon-update-modal__header-info { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .tachyon-update-modal__component-name { font-size: 16px; font-weight: bold; color: var(--text-color-high, inherit); } .tachyon-update-modal__version-badge { font-size: 12px; font-family: monospace; padding: 2px 6px; border-radius: 4px; background: var(--background-color-low, rgba(0, 0, 0, 0.05)); color: var(--text-color-high, inherit); border: 1px solid var(--border-color-low, #dee2e6); } .tachyon-update-modal__timer-badge { font-size: 13px; font-family: monospace; color: var(--text-color-medium, #6c757d); } .tachyon-update-modal__success-banner { display: flex; align-items: center; gap: 8px; color: var(--color-green-base, #2ecc71); font-weight: bold; } .tachyon-update-modal__error-banner { display: flex; align-items: center; gap: 8px; color: var(--color-red-base, #e74c3c); font-weight: bold; } .tachyon-update-modal__actions { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-top: 6px; border-top: 1px solid var(--border-color-low, #dee2e6); padding-top: 10px; flex-wrap: wrap; } .tachyon-update-modal__action-buttons { display: flex; align-items: center; gap: 8px; margin-left: auto; } .tachyon-update-modal__log-panel { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; min-height: 0; } .tachyon-update-modal__log-header { display: flex; justify-content: space-between; align-items: center; gap: 8px; color: var(--text-color-high, inherit); } .tachyon-update-modal__log-copy { padding: 2px 8px; font-size: 12px; } .tachyon-update-modal__log { margin: 0; height: 350px; min-height: 200px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; font-family: monospace; font-size: 11px; line-height: 1.5; color: var(--text-color-high, #212529); background: var(--background-color-low, #f8f9fa); border: 1px solid var(--border-color-low, #dee2e6); border-radius: 4px; padding: 8px; flex: 1 1 auto; min-height: 0; } .tachyon-version-picker { margin-top: 8px; padding: 8px; border: 1px solid var(--border-color-low, #dee2e6); border-radius: 4px; background: var(--background-color-low, #f8f9fa); } .tachyon-version-picker__loading, .tachyon-version-picker__error { padding: 6px 0; font-size: 12px; color: var(--text-color-medium, #6c757d); } .tachyon-version-picker__error { color: var(--color-red-base, #e74c3c); } .tachyon-version-picker__list { display: flex; flex-direction: column; gap: 6px; } .tachyon-version-picker__item { display: flex; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--border-color-low, #e9ecef); } .tachyon-version-picker__item:last-child { border-bottom: none; } .tachyon-version-picker__tag { font-weight: bold; font-size: 13px; color: var(--text-color-high, inherit); } .tachyon-version-picker__date { font-size: 11px; color: var(--text-color-medium, #6c757d); } .tachyon-version-picker__prerelease { font-size: 10px; color: var(--color-yellow-base, #f39c12); border: 1px solid var(--color-yellow-base, #f39c12); border-radius: 3px; padding: 1px 4px; } `; // src/tachyon/tabs/updates/index.ts var UpdatesTab = { render: render4, initController: initController4, styles: styles6 }; // src/styles.ts var GlobalStyles = ` ${DashboardTab.styles} ${DiagnosticTab.styles} ${MonitoringTab.styles} ${UpdatesTab.styles} ${PartialStyles} /* Hide extra H3 for settings tab */ #cbi-${TACHYON_UCI_PACKAGE}-settings > h3 { display: none; } /* Hide extra H3 for rules tab */ #cbi-${TACHYON_UCI_PACKAGE}-section > h3:nth-child(1) { display: none; } /* Vertical align for remove rule action button */ #cbi-${TACHYON_UCI_PACKAGE}-section > .cbi-section-remove { margin-bottom: -32px; } #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-actions > div { display: inline-flex; align-items: center; gap: 4px; } #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-actions { text-align: right; } /* Rule reorder visuals */ #cbi-${TACHYON_UCI_PACKAGE}-section { position: relative; } #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-table-row { position: relative; } #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-table-row.placeholder { opacity: 1; } #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-table-row.placeholder em { font-style: italic; } #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-table-row.drag-over-above::after, #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-table-row.drag-over-below::after { content: ''; position: absolute; left: 10px; right: 10px; height: 2px; border-radius: 2px; background: var(--primary-color-high, #1976d2); pointer-events: none; z-index: 2; } #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-table-row.drag-over-above::after { top: -1px; } #cbi-${TACHYON_UCI_PACKAGE}-section .cbi-section-table-row.drag-over-below::after { bottom: -1px; } /* Centered class helper */ .centered { display: flex; align-items: center; justify-content: center; } /* Rotate class helper */ .rotate { animation: spin 1s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Skeleton styles*/ .skeleton { background-color: var(--background-color-low, #e0e0e0); border-radius: 4px; position: relative; overflow: hidden; } .skeleton::after { content: ''; position: absolute; top: 0; left: -150%; width: 150%; height: 100%; background: linear-gradient( 90deg, transparent, rgba(255, 255, 255, 0.4), transparent ); animation: skeleton-shimmer 1.6s infinite; } @keyframes skeleton-shimmer { 100% { left: 150%; } } /* Toast */ .toast-container { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 10px; z-index: 9999; font-family: system-ui, sans-serif; } .toast { opacity: 0; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease; padding: 10px 16px; border-radius: 6px; color: #fff; font-size: 14px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); min-width: 220px; max-width: 340px; text-align: center; } .toast-success { background-color: #28a745; } .toast-error { background-color: #dc3545; } .toast.visible { opacity: 1; transform: translateY(0); } /* Tachyon Mobile & Responsive Layout Enhancements */ @media (max-width: 768px) { [id^="cbi-${TACHYON_UCI_PACKAGE}"] .cbi-section-table, .tachyon-view .cbi-section-table { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; border-collapse: collapse; } [id^="cbi-${TACHYON_UCI_PACKAGE}"] .cbi-section-table-cell, .tachyon-view .cbi-section-table-cell { min-width: 0; word-break: break-word; overflow-wrap: anywhere; } [id^="cbi-${TACHYON_UCI_PACKAGE}"] .cbi-section-actions, .tachyon-view .cbi-section-actions { display: flex; flex-wrap: wrap; gap: 6px; } [id^="cbi-${TACHYON_UCI_PACKAGE}"] .cbi-dropdown, .tachyon-view .cbi-dropdown { max-width: 100%; } [id^="cbi-${TACHYON_UCI_PACKAGE}"] .cbi-input-textarea, [id^="cbi-${TACHYON_UCI_PACKAGE}"] .cbi-input-text, [id^="cbi-${TACHYON_UCI_PACKAGE}"] .cbi-input-select, .tachyon-view .cbi-input-textarea, .tachyon-view .cbi-input-text, .tachyon-view .cbi-input-select { max-width: 100%; box-sizing: border-box; } /* Modal responsiveness scoped to Tachyon */ .modal .fkp-server-info-modal, .cbi-modal .fkp-server-info-modal, .modal:has([class*="tachyon"]), .cbi-modal:has([class*="tachyon"]) { max-width: min(96vw, 1100px) !important; box-sizing: border-box; margin: 10px auto; } } `; // src/helpers/injectGlobalStyles.ts var TACHYON_GLOBAL_STYLES_ID = "tachyon-global-styles"; function injectGlobalStyles() { if (document.getElementById(TACHYON_GLOBAL_STYLES_ID)) { return; } document.head.insertAdjacentHTML( "beforeend", ` ` ); } // src/main.ts if (typeof structuredClone !== "function") globalThis.structuredClone = (obj) => JSON.parse(JSON.stringify(obj)); return baseclass.extend({ BOOTSTRAP_DNS_SERVER_OPTIONS, DEFAULT_LATENCY_TEST_URL, DNS_SERVERS_BY_PROTOCOL, DOMAIN_LIST_OPTIONS, DashboardTab, DiagnosticTab, LATENCY_TEST_URL_OPTIONS, MonitoringTab, TACHYON_ACTION_PROVIDERS_AVAILABILITY_EVENT, TACHYON_UCI_PACKAGE, TachyonShellMethods, UpdatesTab, applyUiStateToStore, bulkValidate, coreService, formatOutboundType, getClashUIUrl, getProxyUrlName, getProxyUrlTransport, injectGlobalStyles, parseValueList, renderDnsBenchmarkModal, showToast, store, validateDNS, validateDomain, validateIP, validateOutboundJson, validatePath, validateProxyUrl, validateSubnet, validateUrl });