Files
NeoTachyon/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js
T
Dushnilin 8f99c40f2e fix(frontend): dashboard status и latency для сервисных секций
- getZapret2Status/getZapretStatus/getSingBoxStatus/getByedpiStatus:
  добавлен allowNonZeroWithStdout — rpcd может вернуть не-zero exit при
  наличии полезного stdout (модуль-провайдер не найден, но JSON-статус
  всё равно отдан). Без этого флага callBaseMethod считал вызов
  провалившимся → serviceStatus undefined → «Неизвестный»
- customProxyLatencies: сервисные секции (SING_BOX, ZAPRET, ZAPRET2,
  BYEDPI) больше не перезаписываются кешем latency-теста. Ранее
  неудачный тест кешировал -1, перезаписывая latency:0 из секции →
  отображалось «-1ms» вместо статуса
2026-08-24 14:39:33 +03:00

18278 lines
551 KiB
JavaScript

// 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 [ip, cidr, extra] = value.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/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 (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://, hysteria2://, hy2://, or tuic://"
)
};
}
// src/helpers/parseValueList.ts
function parseValueList(value) {
return value.split(/\n/).map((line) => line.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/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() \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435");
return;
}
downloadAsTxt(this.getLogs(), filename);
}
};
var logger = new Logger();
// src/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: "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",
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)",
"8.8.8.8": "8.8.8.8 (Google)",
"9.9.9.9": "9.9.9.9 (Quad9)",
"77.88.8.8": "77.88.8.8 (Yandex)",
"94.140.14.14": "94.140.14.14 (AdGuard)",
"185.222.222.222": "185.222.222.222 (DNS.SB)",
"194.242.2.2": "194.242.2.2 (Mullvad)"
},
doh: {
"https://cloudflare-dns.com/dns-query": "Cloudflare",
"https://dns.google/dns-query": "Google",
"https://dns.quad9.net/dns-query": "Quad9",
"https://dns.adguard.com/dns-query": "AdGuard",
"https://dns.mullvad.net/dns-query": "Mullvad",
"https://dns.nextdns.io/dns-query": "NextDNS",
"https://doh.cleanbrowsing.org/doh/family-filter/": "CleanBrowsing Family"
},
dot: {
"1.1.1.1": "1.1.1.1 (Cloudflare)",
"dns.google": "Google",
"dns.quad9.net": "Quad9",
"dns.adguard.com": "AdGuard",
"dns.mullvad.net": "Mullvad",
"dns.nextdns.io": "NextDNS",
"common.dot.dns.yandex.net": "Yandex"
},
doq: {
"1.1.1.1:784": "Cloudflare",
"dns.google:784": "Google",
"dns.adguard.com:785": "AdGuard",
"dns.mullvad.net:784": "Mullvad",
"dns.nextdns.io:784": "NextDNS"
}
};
var BOOTSTRAP_DNS_SERVER_OPTIONS = {
"77.88.8.8": "77.88.8.8 (Yandex DNS)",
"77.88.8.1": "77.88.8.1 (Yandex DNS)",
"1.1.1.1": "1.1.1.1 (Cloudflare DNS)",
"1.0.0.1": "1.0.0.1 (Cloudflare DNS)",
"8.8.8.8": "8.8.8.8 (Google DNS)",
"8.8.4.4": "8.8.4.4 (Google DNS)",
"9.9.9.9": "9.9.9.9 (Quad9 DNS)",
"9.9.9.11": "9.9.9.11 (Quad9 DNS)"
};
var 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):\/\//i;
function isCopyableProxyLink(link) {
return COPYABLE_PROXY_URI_RE.test((link || "").trim());
}
// src/icons/renderLoaderCircleIcon24.ts
function renderLoaderCircleIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-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"
})
]
);
}
// src/icons/renderCircleAlertIcon24.ts
function renderCircleAlertIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-circle-alert-icon lucide-circle-alert"
},
[
svgEl("circle", {
cx: "12",
cy: "12",
r: "10"
}),
svgEl("line", {
x1: "12",
y1: "8",
x2: "12",
y2: "12"
}),
svgEl("line", {
x1: "12",
y1: "16",
x2: "12.01",
y2: "16"
})
]
);
}
// src/icons/renderCircleCheckIcon24.ts
function renderCircleCheckIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-circle-check-icon lucide-circle-check"
},
[
svgEl("circle", {
cx: "12",
cy: "12",
r: "10"
}),
svgEl("path", {
d: "M9 12l2 2 4-4"
})
]
);
}
// src/icons/renderCircleSlashIcon24.ts
function renderCircleSlashIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-circle-slash-icon lucide-circle-slash"
},
[
svgEl("circle", {
cx: "12",
cy: "12",
r: "10"
}),
svgEl("line", {
x1: "9",
y1: "15",
x2: "15",
y2: "9"
})
]
);
}
// src/icons/renderCircleXIcon24.ts
function renderCircleXIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-circle-x-icon lucide-circle-x"
},
[
svgEl("circle", {
cx: "12",
cy: "12",
r: "10"
}),
svgEl("path", {
d: "M15 9L9 15"
}),
svgEl("path", {
d: "M9 9L15 15"
})
]
);
}
// src/icons/renderCheckIcon24.ts
function renderCheckIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-check-icon lucide-check"
},
[
svgEl("path", {
d: "M20 6 9 17l-5-5"
})
]
);
}
// src/icons/renderXIcon24.ts
function renderXIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-x-icon lucide-x"
},
[svgEl("path", { d: "M18 6 6 18" }), svgEl("path", { d: "m6 6 12 12" })]
);
}
// src/icons/renderTriangleAlertIcon24.ts
function renderTriangleAlertIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-triangle-alert-icon lucide-triangle-alert"
},
[
svgEl("path", {
d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"
}),
svgEl("path", { d: "M12 9v4" }),
svgEl("path", { d: "M12 17h.01" })
]
);
}
// src/icons/renderPauseIcon24.ts
function renderPauseIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-pause-icon lucide-pause"
},
[
svgEl("rect", {
x: "14",
y: "3",
width: "5",
height: "18",
rx: "1"
}),
svgEl("rect", {
x: "5",
y: "3",
width: "5",
height: "18",
rx: "1"
})
]
);
}
// src/icons/renderPlayIcon24.ts
function renderPlayIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-play-icon lucide-play"
},
[
svgEl("path", {
d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"
})
]
);
}
// src/icons/renderRotateCcwIcon24.ts
function renderRotateCcwIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-rotate-ccw-icon lucide-rotate-ccw"
},
[
svgEl("path", {
d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"
}),
svgEl("path", {
d: "M3 3v5h5"
})
]
);
}
// src/icons/renderCircleStopIcon24.ts
function renderCircleStopIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-circle-stop-icon lucide-circle-stop"
},
[
svgEl("circle", {
cx: "12",
cy: "12",
r: "10"
}),
svgEl("rect", {
x: "9",
y: "9",
width: "6",
height: "6",
rx: "1"
})
]
);
}
// src/icons/renderCirclePlayIcon24.ts
function renderCirclePlayIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-circle-play-icon lucide-circle-play"
},
[
svgEl("path", {
d: "M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z"
}),
svgEl("circle", {
cx: "12",
cy: "12",
r: "10"
})
]
);
}
// src/icons/renderCircleCheckBigIcon24.ts
function renderCircleCheckBigIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-circle-check-big-icon lucide-circle-check-big"
},
[
svgEl("path", {
d: "M21.801 10A10 10 0 1 1 17 3.335"
}),
svgEl("path", {
d: "m9 11 3 3L22 4"
})
]
);
}
// src/icons/renderSquareChartGanttIcon24.ts
function renderSquareChartGanttIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-square-chart-gantt-icon lucide-square-chart-gantt"
},
[
svgEl("rect", {
width: "18",
height: "18",
x: "3",
y: "3",
rx: "2"
}),
svgEl("path", { d: "M9 8h7" }),
svgEl("path", { d: "M8 12h6" }),
svgEl("path", { d: "M11 16h5" })
]
);
}
// src/icons/renderCogIcon24.ts
function renderCogIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-cog-icon lucide-cog"
},
[
svgEl("path", { d: "M11 10.27 7 3.34" }),
svgEl("path", { d: "m11 13.73-4 6.93" }),
svgEl("path", { d: "M12 22v-2" }),
svgEl("path", { d: "M12 2v2" }),
svgEl("path", { d: "M14 12h8" }),
svgEl("path", { d: "m17 20.66-1-1.73" }),
svgEl("path", { d: "m17 3.34-1 1.73" }),
svgEl("path", { d: "M2 12h2" }),
svgEl("path", { d: "m20.66 17-1.73-1" }),
svgEl("path", { d: "m20.66 7-1.73 1" }),
svgEl("path", { d: "m3.34 17 1.73-1" }),
svgEl("path", { d: "m3.34 7 1.73 1" }),
svgEl("circle", { cx: "12", cy: "12", r: "2" }),
svgEl("circle", { cx: "12", cy: "12", r: "8" })
]
);
}
// src/icons/renderSearchIcon24.ts
function renderSearchIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-search-icon lucide-search"
},
[
svgEl("path", { d: "m21 21-4.34-4.34" }),
svgEl("circle", { cx: "11", cy: "11", r: "8" })
]
);
}
// src/icons/renderBookOpenTextIcon24.ts
function renderBookOpenTextIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-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" })
]
);
}
// src/icons/renderCopyIcon24.ts
function renderCopyIcon24() {
return svgEl(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-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"
})
]
);
}
// src/icons/renderLinkIcon24.ts
function renderLinkIcon24() {
return svgEl(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-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"
})
]
);
}
// src/icons/renderDownloadIcon24.ts
function renderDownloadIcon24() {
const NS = "http://www.w3.org/2000/svg";
return svgEl(
"svg",
{
xmlns: NS,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-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"
})
]
);
}
// src/icons/renderInfoIcon24.ts
function renderInfoIcon24() {
return svgEl(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "lucide lucide-info-icon lucide-info"
},
[
svgEl("circle", {
cx: "12",
cy: "12",
r: "10"
}),
svgEl("path", {
d: "M12 16v-4"
}),
svgEl("path", {
d: "M12 8h.01"
})
]
);
}
// src/icons/renderGlobeIcon24.ts
function renderGlobeIcon24() {
return svgEl(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "14",
height: "14",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "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" })
]
);
}
// 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/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 ? "\u221E" : 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,
isCollapsed,
onToggleCollapse
}) {
const isConnectionNode = ["vpn", "awg", "warp"].includes(
section.action || ""
);
const isServiceNode = ["zapret", "zapret2", "byedpi"].includes(
section.action || ""
);
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) {
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 < 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 ? `\u25CF ${_("Checking...")}` : outbound.latency && outbound.latency > 0 ? `\u25CF ${outbound.latency} ms` : outbound.latency === -1 ? `\u25CF ${_("Not responding")}` : outbound.runtimeAvailable ? `\u25CF ${_("Connected")}` : `\u25CF ${_("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)
),
E(
"span",
{
style: "opacity: 0.7; font-size: 13px; white-space: nowrap; flex-shrink: 0;"
},
[outbound.type].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 typeLabel = isManualUrlTest || isManualPriority ? `${outbound.type} (${_("Manual")})` : outbound.urlTestInfo || outbound.priorityInfo ? `${outbound.type} (${_("Auto")})` : outbound.type;
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("b", {}, renderFlagEmojis(outbound.displayName)),
...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 ? connectionStatusText : outbound.latency ? `${outbound.latency}ms` : "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))]
)
]
);
}
if (isServiceNode && section.serviceStatus) {
const ss = section.serviceStatus;
const statusColor = 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)";
const statusText = ss.ready ? _("Running") : ss.conflict ? _("Conflict") : ss.configured ? _("Stopped") : _("Not configured");
const typeLabel = ss.serviceType === "zapret" ? "Zapret" : ss.serviceType === "zapret2" ? "Zapret2" : "ByeDPI";
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.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.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;"
},
ss.statusMessage
) : ""
]);
}
const metadataNodes = (section.subscriptionMetadata || []).map((metadata) => renderSubscriptionMetadata(metadata)).filter(Boolean);
const subscriptionUpdateAction = renderSubscriptionUpdateAction(
section,
subscriptionUpdating,
onUpdateSubscription
);
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"].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 < 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 ? _("Not responding") : selectedOutbound.runtimeAvailable ? _("Connected") : _("Not connected");
} else {
latencyText = selectedOutbound.latency ? `${selectedOutbound.latency}ms` : "";
}
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] : [],
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;" },
`\u25B2 ${prettyBytes(c.upload)} | \u25BC ${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-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_ZAPRET2_STATUS"] = "get_zapret2_status";
AvailableMethods2["GET_BYEDPI_STATUS"] = "get_byedpi_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["SUBSCRIPTION_UPDATE_ASYNC"] = "subscription_update_async";
AvailableMethods2["SUBSCRIPTION_UPDATE_STATUS"] = "subscription_update_status";
AvailableMethods2["SERVICE_HEALTH_CHECK"] = "service_health_check";
})(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"
];
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 = 1e4;
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_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 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 }
),
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 }
),
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) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [
Tachyon.AvailableMethods.COMPONENT_ACTION_ASYNC,
component,
action
],
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
),
waitComponentActionJob: async (jobId, component, action, expectedLatestVersion) => {
const jobStartedAt = Date.now();
const isSelfUpdate = component === "tachyon" && (action === "install" || action === "reinstall");
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 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 (targetVersion && versionsMatch(version, targetVersion)) {
return version;
}
if (!targetVersion && baselineVersion && !versionsMatch(version, baselineVersion)) {
return version;
}
return "";
};
const confirmedSameVersionReinstall = async () => {
if (!isSelfUpdate || action !== "reinstall" || !baselineVersion) {
return "";
}
const version = await readTachyonVersion();
if (versionsMatch(version, baselineVersion) && (!targetVersion || versionsMatch(version, targetVersion))) {
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 (await confirmedByVersion() === version) {
return true;
}
if (version === baselineVersion && 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 (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;
}
if (!stateResponse?.running) {
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
};
}
};
// src/tachyon/methods/custom/getDashboardSections.ts
var DASHBOARD_SECTION_CACHE_DIR = "/var/run/tachyon/section-cache";
var CLASH_API_FETCH_TIMEOUT_MS = 5e3;
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() {
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) {
return {
success: true,
data: await response.json()
};
}
} catch (_error) {
} 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);
}
function isConnectionAction(action) {
return Boolean(
action && ["connection", "proxy", "outbound", "vpn", "awg", "warp"].includes(
action
)
);
}
function isServiceAction(action) {
return Boolean(action && ["zapret", "zapret2", "byedpi"].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 getOutboundDisplayName(code, entry, link, outboundMetadata, preferMetadata = false) {
const metadataName = outboundMetadata?.names?.[code];
return (preferMetadata ? metadataName : getProxyUrlName(link)) || (preferMetadata ? getProxyUrlName(link) : metadataName) || entry?.value?.name || code;
}
function buildUrlTestInfo({
code,
displayName,
entry,
groupCache,
proxyByCode,
manualLinkByCode,
cachedProxyLinks,
outboundMetadata,
showDetectedCountries,
selectorNow,
selectorCodes = []
}) {
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);
return [
{
code: childCode,
displayName: getOutboundDisplayName(
childCode,
childEntry,
link,
outboundMetadata,
cachedProxyLinks.has(childCode)
),
latency: childEntry?.value?.history?.[0]?.delay || 0,
type: childEntry?.value?.type || "",
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 = []
}) {
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);
return {
code: childCode,
displayName: getOutboundDisplayName(
childCode,
childEntry,
link,
outboundMetadata,
cachedProxyLinks.has(childCode)
),
latency: childEntry?.value?.history?.[0]?.delay || 0,
type: childEntry?.value?.type || "",
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 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 displayName = priorityConfig?.displayName || urlTestConfig?.displayName || getOutboundDisplayName(
code,
item,
link,
outboundMetadata,
cachedProxyLinks.has(code)
);
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
}) : void 0;
const priorityInfo = priorityConfig ? buildPriorityInfo({
config: priorityConfig,
entry: item,
groupCache: priorityGroups[code],
proxyByCode,
manualLinkByCode,
cachedProxyLinks,
outboundMetadata,
showDetectedCountries: priorityConfig.showDetectedCountries,
selectorNow,
selectorCodes
}) : 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 latency = item?.value.history?.[0]?.delay || activeMemberLatency || 0;
return [
{
code,
displayName,
latency,
type: priorityConfig ? "Priority" : item?.value.type || "URLTest",
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)
};
}
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 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 (sectionAction === "vpn" || sectionAction === "awg" || sectionAction === "warp") {
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: section.interface || outbound?.value?.name || (sectionAction === "awg" ? "AmneziaWG" : sectionAction.toUpperCase()),
latency: outbound?.value?.history?.length ? outbound.value.history[0].delay > 0 ? outbound.value.history[0].delay : -1 : 0,
type: sectionAction === "awg" ? "AmneziaWG" : sectionAction === "warp" ? "WARP" : outbound?.value?.type || "",
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;
let serviceStatus;
try {
if (serviceType === "zapret") {
const result = await TachyonShellMethods.getZapretStatus();
if (result.success) {
const s = result.data;
serviceStatus = {
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) {
const s = result.data;
serviceStatus = {
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) {
const s = result.data;
serviceStatus = {
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
};
}
}
} catch (_error) {
}
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();
}
getTabsInfo() {
const tabs = Array.from(
document.querySelectorAll(".cbi-tab, .cbi-tab-disabled")
);
return tabs.map((el) => ({
el,
id: el.dataset.tab || "",
active: el.classList.contains("cbi-tab") && !el.classList.contains("cbi-tab-disabled")
}));
}
getActiveTabId() {
const active = document.querySelector(
".cbi-tab:not(.cbi-tab-disabled)"
);
return active?.dataset.tab || null;
}
notify() {
const tabs = this.getTabsInfo();
const activeId = this.getActiveTabId();
if (activeId !== this.lastActiveId) {
this.lastActiveId = activeId;
this.callback?.(activeId, tabs);
}
}
onChange(callback) {
this.callback = callback;
this.notify();
}
};
var TabServiceInstance = TabService.getInstance();
// src/tachyon/tabs/diagnostic/helpers/getCheckTitle.ts
function getCheckTitle(name) {
return `${name} ${_("checks")}`;
}
// src/tachyon/tabs/diagnostic/checks/contstants.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: "",
zapret_version: "loading",
zapret_installed: 0,
zapret2_version: "loading",
zapret2_installed: 0,
byedpi_version: "loading",
byedpi_installed: 0,
server_inbounds_enabled_count: -1,
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 },
singBoxCheck: { loading: false },
singBoxInstall: { 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 },
zapret2Check: { loading: false },
zapret2Install: { loading: false },
zapret2Remove: { loading: false },
byedpiCheck: { loading: false },
byedpiInstall: { loading: false },
byedpiRemove: { 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: "" }
}
};
// 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
}
},
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 \u2014 tab not visible");
return;
}
if (this.checking) {
logger.debug(
"[TachyonLogWatcher]",
"skipped check \u2014 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)\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:reinstall": "tachyonReinstall",
"sing_box:check_update": "singBoxCheck",
"sing_box:install": "singBoxInstall",
"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:remove": "zapretRemove",
"zapret2:check_update": "zapret2Check",
"zapret2:install": "zapret2Install",
"zapret2:remove": "zapret2Remove",
"byedpi:check_update": "byedpiCheck",
"byedpi:install": "byedpiInstall",
"byedpi:remove": "byedpiRemove"
};
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_tiny) {
variant = _("tiny");
}
return variant ? `${version} (${variant})` : version;
}
function normalizeSingBoxVariantFields(value) {
const versionExtended = isExtendedSingBoxVersion(value.sing_box_version);
const singBoxExtended = Boolean(value.sing_box_extended) || versionExtended;
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: singBoxExtended && value.sing_box_lx ? 1 : 0,
sing_box_tailscale: singBoxExtended || value.sing_box_tailscale ? 1 : 0
};
}
// 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 },
singBoxCheck: { loading: false },
singBoxInstall: { 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 },
zapret2Check: { loading: false },
zapret2Install: { loading: false },
zapret2Remove: { loading: false },
byedpiCheck: { loading: false },
byedpiInstall: { loading: false },
byedpiRemove: { 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,
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,
tachyonRunning: uiState.service.tachyon.running,
tachyonEnabled: uiState.service.tachyon.enabled,
tachyonStatus: uiState.service.tachyon.status,
watchdogRunning: store.get().servicesInfoWidget.data.watchdogRunning
}
},
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 = 1e3;
var RUNTIME_UI_STATE_ACTIVE_POLL_INTERVAL_MS = 500;
var RUNTIME_UI_STATE_HIDDEN_POLL_INTERVAL_MS = 3e4;
var runtimeUiStateRefreshPromise = null;
var lastRuntimeUiStateRefreshAt = 0;
var lastRuntimeUiState;
var runtimeStateResumeRefreshRegistered = false;
var runtimeStatePollTimer = null;
var runtimeStatePollingStarted = false;
var runtimeStateHasRunningAction = false;
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;
}
return runtimeStateHasRunningAction ? RUNTIME_UI_STATE_ACTIVE_POLL_INTERVAL_MS : RUNTIME_UI_STATE_IDLE_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;
const promise = TachyonShellMethods.getUiState().then((response) => {
if (!response.success) {
return void 0;
}
applyUiStateToStore(response.data);
lastRuntimeUiState = response.data;
runtimeStateHasRunningAction = hasRunningAction(response.data);
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 = 1e4;
var LOG_WATCHER_START_DELAY_MS = 5e3;
function componentDisplayName(component) {
const names = {
tachyon: "Tachyon",
sing_box: "sing-box",
zapret: "Zapret",
zapret2: "Zapret2",
byedpi: "ByeDPI"
};
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,
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
}
}
});
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)`
)
);
}
// 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);
}
localStorage.setItem(
DASHBOARD_EXPANDED_SECTIONS_KEY,
JSON.stringify(Array.from(expandedSections))
);
void renderSectionsWidget();
void renderConnectionsWidget();
}
var SECTIONS_REFRESH_INTERVAL_MS = 1e4;
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 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();
if (typeof window !== "undefined") {
window.addEventListener("pagehide", () => {
pageUnloading = true;
});
window.addEventListener("pageshow", () => {
pageUnloading = false;
});
}
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();
}
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;
}
socket.subscribe(
`${getClashWsUrl()}/traffic?token=${clashApiSecret}`,
(msg) => {
if (dataUpdatesId !== dashboardDataUpdatesId || getDashboardServiceAvailability() === "stopped") {
return;
}
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;
}
logger.error(
"[DASHBOARD]",
"connectToClashSockets - traffic: failed to connect to",
getClashWsUrl()
);
store.set({
bandwidthWidget: {
loading: false,
failed: true,
data: { up: 0, down: 0 }
}
});
}
);
socket.subscribe(
`${getClashWsUrl()}/connections?token=${clashApiSecret}`,
(msg) => {
if (dataUpdatesId !== dashboardDataUpdatesId || getDashboardServiceAvailability() === "stopped") {
return;
}
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;
}
logger.error(
"[DASHBOARD]",
"connectToClashSockets - connections: failed to connect to",
getClashWsUrl()
);
store.set({
trafficTotalWidget: {
loading: false,
failed: true,
data: { downloadTotal: 0, uploadTotal: 0 }
},
systemInfoWidget: {
loading: false,
failed: true,
data: {
connections: 0,
memory: 0
}
}
});
}
);
}
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;
}
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) {
customProxyLatencies.set(tag, response.data.delay || -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);
}
}
}
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);
if (!countryFlag) {
return renderFlagEmojis(member.displayName);
}
return [
E(
"span",
{ class: "tachyon_dashboard-page__urltest-details__country-badge" },
countryFlag
),
...renderFlagEmojis(member.displayName)
];
}
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"
},
member.type
)
] : []
] : [
E("b", {}, renderMemberName(member)),
...member.type ? [
E(
"span",
{
class: "tachyon_dashboard-page__urltest-details__row-type"
},
member.type
)
] : []
]
),
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"].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) => ({
...outbound,
latency: SERVICE_TYPES.has(outbound.type) || !customProxyLatencies.has(outbound.code) ? outbound.latency : customProxyLatencies.get(outbound.code)
}))
}));
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 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],
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(...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() {
try {
const [res, hostnames] = await Promise.all([
TachyonShellMethods.getClashApiConnections(),
fetchHostnames()
]);
if (res.success && res.data && typeof res.data === "object" && Array.isArray(res.data.connections)) {
const connectionsList = res.data.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) => [
{
key: "Tachyon",
value: data.tachyonRunning ? _("\u2714 Running") : _("\u2718 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 ? _("\u2714 Running") : _("\u2718 Stopped"),
attributes: {
class: data.singbox ? "tachyon_dashboard-page__widgets-section__item__row--success" : "tachyon_dashboard-page__widgets-section__item__row--error"
}
}
],
"renderServicesInfoWidget"
);
}
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();
}
}
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();
}
}
store.subscribe(onStoreUpdate);
startActionStateWatcher();
void renderSectionsWidget();
void renderConnectionsWidget();
void renderBandwidthWidget();
void renderTrafficTotalWidget();
void renderSystemInfoWidget();
void renderServicesInfoWidget();
syncDashboardServiceAvailability();
if (hasRuntimeSnapshot) {
void refreshRuntimeUiState({ force: true });
}
}
function onPageUnmount() {
dashboardMounted = false;
dashboardMountId += 1;
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: table;
table-layout: fixed;
width: 100%;
--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 0 auto;
}
.tachyon_dashboard-page .btn.tachyon_dashboard-page__outbound-section__subscription-update {
min-width: 130px;
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: 99px;
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__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__header b {
min-width: 0;
line-height: 1.25;
overflow-wrap: anywhere;
}
.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__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
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;
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 { state, description } = fakeIPWorks ? checks.differentIP ? { 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 ? "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: ""
}
])
]
});
}
// 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 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: 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,
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,
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 {}
.tachyon-partial-modal__content {
max-height: 70vh;
overflow: scroll;
border-radius: 4px;
}
.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;
}
`;
// 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,
restoreNativeInternet,
viewLogs,
showSingBoxConfig,
generateBugReport,
checkServices
}) {
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(checkServices.visible, [
renderButton({
classNames: ["cbi-button-action"],
onClick: checkServices.onClick,
icon: renderSquareChartGanttIcon24,
text: _("Check Services"),
loading: checkServices.loading,
disabled: checkServices.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 = {
"\u0411\u0430\u0437\u043E\u0432\u0430\u044F \u0441\u0432\u044F\u0437\u043D\u043E\u0441\u0442\u044C": _("Basic connectivity"),
"\u0417\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0432 \u0420\u0424": _("Blocked resources"),
"\u0421\u0432\u043E\u0438 \u0434\u043E\u043C\u0435\u043D\u044B": _("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);"
},
"\u26A1"
)
]
);
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: "\u{1F916} " + _("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: "\u274C " + _("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: "\u{1FA7A} " + _("Check YouTube"), query: "Check YouTube availability" },
{ label: "\u{1F50D} " + _("Why Discord fails?"), query: "Why Discord fails?" },
{
label: "\u{1F6E0}\uFE0F " + _("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%; max-width: 680px; box-sizing: border-box;" },
[quickPromptsBar, messageListContainer, inputToolbar]
);
const modalContent = E(
"div",
{ style: "width: 100%; max-width: 680px; 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/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/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/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"
)
})
]);
}
// 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";
if (selectedOutbound2?.code) {
const latencyProxy2 = await TachyonShellMethods.getClashApiProxyLatency(
selectedOutbound2.code,
section.latencyTestTimeout
);
const proxySuccess = latencyProxy2.success && !latencyProxy2.data.message;
if (proxySuccess) {
return {
state: "success",
latency: `[${selectedOutbound2.displayName ?? ""}] ${latencyProxy2.data.delay}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 (selectedProxyDelay) {
return {
state: sectionState,
latency: `[${selectedOutbound2?.displayName ?? ""}] ${selectedProxyDelay}ms`
};
}
return {
state: "error",
latency: `[${selectedOutbound2?.displayName ?? ""}] ${_("Not responding")}`
};
}
return {
state: "error",
latency: _("Not responding")
};
}
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")
};
}
const selectedOutbound = section.outbounds[0];
const latencyProxy = await TachyonShellMethods.getClashApiProxyLatency(
section.code,
section.latencyTestTimeout
);
const success = latencyProxy.success && !latencyProxy.data.message;
if (success) {
return {
state: "success",
latency: `${latencyProxy.data.delay} ms`
};
}
if (section.action === "vpn" && selectedOutbound?.runtimeAvailable) {
return {
state: "warning",
latency: `[${selectedOutbound.displayName || section.code}] ${_("Connectivity probe failed")}`
};
}
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") + " \u2014 " + _("Issues") + ": " + issues + ", " + _("Fixed") + ": " + fixed : /safe bypass|аварийного обхода|bypassed|стоковом состоянии|stock (state|internet)/i.test(
report
) ? _("Doctor repair") + " \u2014 Safe Bypass" : _("Doctor repair") + " \u2014 " + _("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 "\u{1F4FA}";
if (h.includes("phone") || h.includes("iphone") || h.includes("android") || h.includes("pixel") || h.includes("xiaomi") || h.includes("galaxy"))
return "\u{1F4F1}";
if (h.includes("mac") || h.includes("pc") || h.includes("laptop") || h.includes("desktop") || h.includes("thinkpad"))
return "\u{1F4BB}";
if (h.includes("playstation") || h.includes("ps4") || h.includes("ps5") || h.includes("xbox") || h.includes("switch") || h.includes("nintendo"))
return "\u{1F3AE}";
return "\u{1F4DF}";
};
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(
"\u0448\u043B\u044E\u0437 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0438\u043B\u0438 \u0432\u043D\u0435\u0448\u043D\u0438\u0439 \u0438\u043D\u0442\u0435\u0440\u043D\u0435\u0442 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D"
) || repLower.includes("wan interface is unreachable") ? "FAIL" : "OK"
},
{
name: "DNS",
status: repLower.includes("\u0441\u0431\u043E\u0439 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u044F 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("\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D") || repLower.includes("stopped") || repLower.includes("\u043D\u0435 \u0444\u0443\u043D\u043A\u0446\u0438\u043E\u043D\u0438\u0440\u0443\u0435\u0442") || repLower.includes("error") || repLower.includes("crash")) ? "FAIL" : "OK"
},
{
name: "nftables",
status: (repLower.includes("nftables") || repLower.includes("\u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u0444\u0430\u0439\u0440\u0432\u043E\u043B\u0430") || repLower.includes("firewall rules")) && (repLower.includes("\u043D\u0430\u0440\u0443\u0448\u0435\u043D\u044B") || 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" ? "\u2713" : node.status === "WARN" ? "\u26A0" : "\u2715";
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;"
},
"\u{1F6E0}\uFE0F " + _("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: `\u26A1 ${friendlyLabel}`,
onClick: async () => {
if (applied) return;
btn.textContent = "\u23F3 " + _("Applying...") + " " + friendlyLabel;
showToast(
_("Applying fix") + ": " + friendlyLabel + "...",
"success"
);
const fixRes = await TachyonShellMethods.applyQuickFix(code);
if (fixRes && typeof fixRes === "object" && fixRes.success) {
applied = true;
btn.textContent = `\u2713 ${friendlyLabel} (${_("Fixed")})`;
btn.classList.remove("cbi-button-apply");
btn.classList.add("cbi-button-neutral");
showToast(
_("Fix applied") + ": " + friendlyLabel,
"success"
);
} else {
btn.textContent = `\u26A1 ${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;"
},
"\u26A0\uFE0F " + _("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;"
},
"\u2713 " + _("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;"
},
"\u23F3 " + _("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: "\u{1F504} " + _("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;" },
`\u{1F4F1} ${_("Connected Devices")}: ${lanClients.length}`
),
renderButton({
classNames: ["cbi-button-neutral"],
text: "\u{1F504} " + _("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 ? "\u{1F310} " + _("Direct WAN") : "\u{1F6E1}\uFE0F " + _("Proxy / DPI")
),
renderButton({
classNames: [
isDirect ? "cbi-button-action" : "cbi-button-apply"
],
text: isDirect ? "\u{1F6E1}\uFE0F " + _("Route via Proxy") : "\u26A1 " + _("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%; max-width: 680px; 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: "\u{1F50D} " + _("Current Diagnosis"),
onClick: () => {
activeTab2 = "diagnosis";
renderModalLayout();
}
}),
renderButton({
classNames: [
activeTab2 === "devices" ? "cbi-button-action" : "cbi-button-neutral"
],
text: `\u{1F4F1} ${_("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: `\u{1F552} ${_("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: "\u{1F4CB} " + _("Copy Support Report"),
onClick: copySupportReport
}),
renderButton({
classNames: ["cbi-button-reset"],
text: "\u{1F6A8} " + _("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 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: "\u{1F6A8} " + _("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 rawReport = [
"--- TACHYON CONFIG ---",
configResult || "Failed to fetch config",
"",
"--- TACHYON LOGS ---",
logsResult.code === 0 ? logsResult.stdout : "Failed to fetch tachyon logs",
"",
"--- SING-BOX LOGS ---",
singboxLogsResult.code === 0 ? 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
},
checkServices: {
visible: true,
loading: diagnosticsActions.checkServices.loading,
disabled: utilityActionsDisabled,
onClick: handleCheckServicesAction
},
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 = 1500;
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 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"
};
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.startsWith(`${sectionName}-`) || !tag.endsWith("-out")) {
return false;
}
const middle = tag.slice(sectionName.length + 1, -4);
return /^\d+(?:-\d+)?$/.test(middle);
});
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", {}, `\u2193 ${_("Downloaded")}`),
E("th", {}, `\u2191 ${_("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;
}
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;
}
failed = true;
loading = false;
renderConnections2();
}
);
}
function startConnectionsUpdates() {
if (serviceAvailability !== "running") {
return;
}
if (canUseDirectClashApi()) {
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);
}
function onPageUnmount3() {
monitoringMounted = false;
monitoringMountId += 1;
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} \u2192 ${latest}` };
}
if (current || latest) {
return { kind: "sha", text: current || latest };
}
return { kind: "fallback", text: "" };
}
// 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} \u2192 ${tSha})`;
}
if (tSha) {
return `${cVer} (${tSha})`;
}
if (cSha) {
return `${cVer} (${cSha})`;
}
return `${cVer} \u2192 ${tVer}`;
}
const currentPart = cVer ? cSha ? `${cVer} (${cSha})` : cVer : "";
const targetPart = tSha ? `${tVer} (${tSha})` : tVer;
return currentPart ? `${currentPart} \u2192 ${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" },
"\u23F1\uFE0F 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 = `\u23F1\uFE0F ${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;
window.location.reload();
}
});
actionButtonContainer.replaceChildren(
bannerEl,
statusEl,
E("div", { class: "tachyon-update-modal__action-buttons" }, [
reloadBtn
])
);
const probeAndReload = async () => {
await new Promise((resolve) => setTimeout(resolve, 2e3));
while (reloadProbeActive && attempt < maxAttempts) {
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, waiting for full initialization..."
);
await new Promise((resolve) => setTimeout(resolve, 5e3));
if (!reloadProbeActive) return;
try {
const confirm = await TachyonShellMethods.getSystemInfo();
if (confirm && confirm.success) {
statusEl.textContent = _(
"Services are online and ready! Reloading page..."
);
reloadBtn.textContent = _("Reloading...");
await new Promise((resolve) => setTimeout(resolve, 600));
if (activeModalJobId) {
try {
sessionStorage.setItem(
"tachyon_post_update_job",
activeModalJobId
);
} catch (_e) {
}
}
window.location.reload();
return;
}
} catch (_confirmErr) {
}
}
} catch (_err) {
}
await new Promise((resolve) => setTimeout(resolve, 1500));
}
if (reloadProbeActive) {
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();
const bannerEl = E(
"div",
{ class: "tachyon-update-modal__error-banner" },
[renderXIcon24(), E("span", {}, errorMessage || _("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";
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;
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();
try {
const savedJob = sessionStorage.getItem("tachyon_post_update_job");
if (savedJob) {
handledComponentJobs.add(savedJob);
capSetSize(handledComponentJobs);
followedComponentJobs.add(savedJob);
sessionStorage.removeItem("tachyon_post_update_job");
}
} catch (_e) {
}
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.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)
}
})
);
}
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() {
void waitForTachyonResponsive().finally(() => {
window.location.reload();
});
}
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;
}
}
const normalizedSystemInfo = normalizeSingBoxVariantFields(nextSystemInfo);
store.set({
diagnosticsSystemInfo: normalizedSystemInfo
});
if (result.component === "zapret" || result.component === "zapret2" || result.component === "byedpi") {
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")) {
if (notify && result.message) {
showToast(result.message, "success", 1200);
}
if (notify) {
if (modalController) {
modalController.completeSuccess(result.message, { reloadPage: true });
} else {
reloadPageAfterTachyonUpdate();
}
} 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);
setActionLoading(key, false);
if (shouldNotify) {
showToast(message, "error");
}
modalController?.completeError(message);
await ackComponentActionJob(jobId);
return;
}
handledComponentJobs.add(jobId);
capSetSize(handledComponentJobs);
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);
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
);
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";
}
}
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 getOptionalComponentActions({
component,
installed,
checkKey,
installKey,
removeKey
}) {
if (!installed) {
return [getInstallAction(component, installKey, false)];
}
return [
...getInstalledUpdateActions(component, checkKey, installKey),
{
key: removeKey,
text: _("Remove"),
icon: renderXIcon24,
component,
action: "remove"
}
];
}
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"
};
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 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
);
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"
});
const zapret2Actions = getOptionalComponentActions({
component: "zapret2",
installed: zapret2Installed,
checkKey: "zapret2Check",
installKey: "zapret2Install",
removeKey: "zapret2Remove"
});
const byedpiActions = getOptionalComponentActions({
component: "byedpi",
installed: byedpiInstalled,
checkKey: "byedpiCheck",
installKey: "byedpiInstall",
removeKey: "byedpiRemove"
});
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
},
{
component: "sing_box",
column: 0,
title: "Sing-box",
version: systemInfoLoading ? _("Loading...") : formatSingBoxVersion(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
},
{
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
},
{
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
},
{
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
}
];
}
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),
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 \u2192 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: 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: 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 (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
)
])
);
}
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(
E(
"div",
{ class: "tachyon_updates-page__components-column" },
columns[0]
),
E(
"div",
{ class: "tachyon_updates-page__components-column" },
columns[1]
)
);
});
}
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;
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 auto;
flex-direction: column;
gap: 10px;
min-width: max-content;
}
@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 {
border: 2px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
min-width: max-content;
}
.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: nowrap;
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: nowrap;
gap: 6px;
}
.tachyon-update-modal__body {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-width: 640px;
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, rgba(255,255,255,0.12));
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;
color: var(--text-color-high, #fff);
}
.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.1));
color: var(--text-color-medium, #aaa);
border: 1px solid var(--border-color, rgba(255,255,255,0.1));
}
.tachyon-update-modal__timer-badge {
font-size: 13px;
font-family: monospace;
color: var(--text-color-medium, #888);
}
.tachyon-update-modal__success-banner {
display: flex;
align-items: center;
gap: 8px;
color: #2ecc71;
font-weight: bold;
}
.tachyon-update-modal__error-banner {
display: flex;
align-items: center;
gap: 8px;
color: #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, rgba(255,255,255,0.12));
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;
}
.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, #ddd);
background: rgba(0, 0, 0, 0.35);
border: 1px solid var(--border-color, rgba(255,255,255,0.12));
border-radius: 4px;
padding: 8px;
flex: 1 1 auto;
min-height: 0;
}
`;
// 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);
}
`;
// 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",
`
<style id="${TACHYON_GLOBAL_STYLES_ID}">
${GlobalStyles}
</style>
`
);
}
// 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,
getClashUIUrl,
getProxyUrlName,
injectGlobalStyles,
parseValueList,
showToast,
store,
validateDNS,
validateDomain,
validateIP,
validateOutboundJson,
validatePath,
validateProxyUrl,
validateSubnet,
validateUrl
});