Files
NeoTachyon/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js
T

22162 lines
643 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/icons/renderSendIcon24.ts
function renderSendIcon24() {
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-send-icon lucide-send",
},
[
svgEl("path", {
d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z",
}),
svgEl("path", {
d: "m21.854 2.147-10.94 10.939",
}),
],
);
}
// 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-widget-tailscale" },
renderWidget({
loading: true,
failed: false,
title: "",
items: [],
}),
),
]),
E("div", { id: "dashboard-connections-grid" }, []),
E(
"div",
{ id: "dashboard-sections-grid" },
renderSections({
loading: true,
failed: false,
section: {
code: "",
sectionName: "",
displayName: "",
outbounds: [],
withTagSelect: false,
},
onTestLatency: () => {},
onChooseOutbound: () => {},
onCopyOutbound: () => {},
onShowUrlTestInfo: () => {},
onShowPriorityInfo: () => {},
onUpdateSubscription: () => {},
latencyFetching: false,
latencyProgress: void 0,
subscriptionUpdating: false,
selectorSwitchingTag: void 0,
}),
),
]),
],
);
}
// src/helpers/showToast.ts
function showToast(message, type, duration = 3e3) {
let container = document.querySelector(".toast-container");
if (!container) {
container = document.createElement("div");
container.className = "toast-container";
document.body.appendChild(container);
}
const toast = document.createElement("div");
toast.className = `toast toast-${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => toast.classList.add("visible"), 100);
setTimeout(() => {
toast.classList.remove("visible");
setTimeout(() => toast.remove(), 300);
}, duration);
}
// src/helpers/copyToClipboard.ts
function copyToClipboard(text) {
const textarea = document.createElement("textarea");
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand("copy");
showToast(_("Copied"), "success");
} catch (_err) {
showToast(_("Failed to copy!"), "error");
console.error("copyToClipboard - e", _err);
}
document.body.removeChild(textarea);
}
// src/tachyon/methods/custom/getConfigSections.ts
async function getConfigSections() {
return uci
.load(TACHYON_UCI_PACKAGE)
.then(() => uci.sections(TACHYON_UCI_PACKAGE));
}
// src/tachyon/runtimeTags.ts
var RESERVED_RUNTIME_TAGS = /* @__PURE__ */ new Set([
"dns-server",
"fakeip-server",
"bootstrap-dns-server",
"fakeip-dns-rule-tag",
"fakeip-ruleset-dns-rule-tag",
"service-fakeip-dns-rule-tag",
"tproxy-in",
"tproxy6-in",
"dns-in",
"service-mixed-in",
"direct-out",
"bypass-out",
]);
function allocateRuntimeTag(base, postfix) {
let suffix = 1;
let candidate = `${base}-${postfix}`;
while (RESERVED_RUNTIME_TAGS.has(candidate)) {
candidate = `${base}-${postfix}-${suffix}`;
suffix += 1;
}
return candidate;
}
function getOutboundTagBySection(sectionName) {
return allocateRuntimeTag(sectionName, "out");
}
// src/tachyon/methods/shell/callBaseMethod.ts
async function callBaseMethod(
method,
args = [],
command = "/usr/bin/tachyon",
options = {},
) {
try {
const response = await executeShellCommand({
command,
args: [method, ...args],
timeout: options.timeout ?? 15e3,
});
const exitCode = response.code ?? 0;
if (
exitCode !== 0 &&
!(options.allowNonZeroWithStdout && response.stdout)
) {
return {
success: false,
error: response.stderr || response.stdout || "",
};
}
if (response.stdout) {
try {
return {
success: true,
data: JSON.parse(response.stdout),
};
} catch (_e) {
return {
success: true,
data: response.stdout,
};
}
}
return {
success: false,
error: response.stderr || "",
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "",
};
}
}
// src/tachyon/types.ts
var Tachyon;
((Tachyon2) => {
let AvailableMethods;
((AvailableMethods2) => {
AvailableMethods2["CHECK_DNS_AVAILABLE"] = "check_dns_available";
AvailableMethods2["CHECK_FAKEIP"] = "check_fakeip";
AvailableMethods2["CHECK_NFT_RULES"] = "check_nft_rules";
AvailableMethods2["CHECK_ZAPRET_RUNTIME"] = "check_zapret_runtime";
AvailableMethods2["CHECK_ZAPRET2_RUNTIME"] = "check_zapret2_runtime";
AvailableMethods2["CHECK_BYEDPI_RUNTIME"] = "check_byedpi_runtime";
AvailableMethods2["CHECK_INBOUNDS_CONFIG"] = "check_inbounds_config";
AvailableMethods2["GET_STATUS"] = "get_status";
AvailableMethods2["GET_OUTBOUND_METADATA"] = "get_outbound_metadata";
AvailableMethods2["GET_SUBSCRIPTION_METADATA"] =
"get_subscription_metadata";
AvailableMethods2["CHECK_SING_BOX"] = "check_sing_box";
AvailableMethods2["CHECK_INBOUNDS"] = "check_inbounds";
AvailableMethods2["GET_SING_BOX_STATUS"] = "get_sing_box_status";
AvailableMethods2["GET_ZAPRET_STATUS"] = "get_zapret_status";
AvailableMethods2["GET_TAILSCALE_PEERS"] = "get_tailscale_peers";
AvailableMethods2["GET_ZAPRET2_STATUS"] = "get_zapret2_status";
AvailableMethods2["GET_BYEDPI_STATUS"] = "get_byedpi_status";
AvailableMethods2["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";
AvailableMethods2["FUZZER_START"] = "fuzzer_start";
AvailableMethods2["FUZZER_STATUS"] = "fuzzer_status";
AvailableMethods2["FUZZER_STOP"] = "fuzzer_stop";
AvailableMethods2["FUZZER_APPLY"] = "fuzzer_apply";
AvailableMethods2["FUZZER_STRATEGIES"] = "fuzzer_strategies";
AvailableMethods2["FUZZER_AI_SYNTHESIZE"] = "fuzzer_ai_synthesize";
AvailableMethods2["FUZZER_GET_PATTERNS"] = "fuzzer_get_patterns";
AvailableMethods2["FUZZER_SAVE_PATTERNS"] = "fuzzer_save_patterns";
AvailableMethods2["FUZZER_RESET_PATTERNS"] = "fuzzer_reset_patterns";
AvailableMethods2["FUZZER_DETECT_DPI"] = "fuzzer_detect_dpi";
AvailableMethods2["FUZZER_AUTO_APPLY"] = "fuzzer_auto_apply";
AvailableMethods2["FUZZER_HISTORY"] = "fuzzer_history";
AvailableMethods2["FUZZER_CLEAR_HISTORY"] = "fuzzer_clear_history";
})(
(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 },
),
getTailscalePeers: async () =>
callBaseMethod(
Tachyon.AvailableMethods.GET_TAILSCALE_PEERS,
[],
"/usr/bin/tachyon",
{ allowNonZeroWithStdout: true },
),
getZapretStatus: async () =>
callBaseMethod(
Tachyon.AvailableMethods.GET_ZAPRET_STATUS,
[],
"/usr/bin/tachyon",
{ allowNonZeroWithStdout: true },
),
getZapret2Status: async () =>
callBaseMethod(
Tachyon.AvailableMethods.GET_ZAPRET2_STATUS,
[],
"/usr/bin/tachyon",
{ allowNonZeroWithStdout: true },
),
getByedpiStatus: async () =>
callBaseMethod(
Tachyon.AvailableMethods.GET_BYEDPI_STATUS,
[],
"/usr/bin/tachyon",
{ allowNonZeroWithStdout: true },
),
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 isDifferentVersion = Boolean(
targetVersion && !versionsMatch(targetVersion, baselineVersion),
);
const confirmedByVersion = async () => {
if (!isSelfUpdate) return "";
if (
Date.now() - jobStartedAt <
COMPONENT_ACTION_MIN_ELAPSED_FOR_SELF_UPDATE_MS
) {
return "";
}
const version = await readTachyonVersion();
if (!version) return "";
if (isDifferentVersion && versionsMatch(version, targetVersion)) {
return version;
}
if (
!targetVersion &&
baselineVersion &&
!versionsMatch(version, baselineVersion)
) {
return version;
}
return "";
};
const confirmedSameVersionReinstall = async () => {
if (!isSelfUpdate || action !== "reinstall" || !baselineVersion) {
return "";
}
if (targetVersion && !versionsMatch(targetVersion, baselineVersion)) {
return "";
}
const version = await readTachyonVersion();
if (versionsMatch(version, baselineVersion)) {
return version;
}
return "";
};
const settleVersion = async (version) => {
if (!selfUpdateVersionMatchedAt) {
selfUpdateVersionMatchedAt = Date.now();
return false;
}
if (
Date.now() - selfUpdateVersionMatchedAt <
COMPONENT_ACTION_SELF_UPDATE_SETTLE_MS
) {
return false;
}
if (isDifferentVersion) {
if ((await confirmedByVersion()) === version) {
return true;
}
} else if (action === "reinstall") {
if ((await confirmedSameVersionReinstall()) === version) {
return true;
}
}
selfUpdateVersionMatchedAt = 0;
return false;
};
const selfUpdateResult = (installedVersion) => ({
success: true,
data: {
success: true,
component,
action,
message: translate("Tachyon has been installed"),
current_version: installedVersion,
latest_version: expectedLatestVersion,
changed: true,
status: "latest",
},
});
const jobDoneResult = (data) => ({
success: true,
data,
});
while (true) {
await sleep(COMPONENT_ACTION_POLL_INTERVAL_MS);
const stateResponse = await readComponentActionState(jobId);
if (
isSelfUpdate &&
Date.now() - jobStartedAt >=
COMPONENT_ACTION_SELF_UPDATE_HARD_TIMEOUT_MS
) {
if (stateResponse && !stateResponse.running) {
return jobDoneResult(stateResponse);
}
const version = await readTachyonVersion();
if (targetVersion && version && version !== targetVersion) {
return {
success: false,
error: _("Tachyon update did not complete within the timeout"),
};
}
return selfUpdateResult(version || baselineVersion);
}
if (stateResponse && !stateResponse.running) {
if (
isSelfUpdate &&
stateResponse.success === false &&
(isDifferentVersion || action === "reinstall")
) {
const version =
(await confirmedByVersion()) ||
(await confirmedSameVersionReinstall());
if (version) {
if (await settleVersion(version)) {
return selfUpdateResult(version);
}
continue;
}
}
return jobDoneResult(stateResponse);
}
const statusResponse = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.COMPONENT_ACTION_STATUS, jobId],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS,
});
const parsedResponse = parseComponentActionResult(statusResponse);
if ((statusResponse.code ?? 0) !== 0 || !parsedResponse) {
if (isSelfUpdate) {
const version =
(await confirmedByVersion()) ||
(await confirmedSameVersionReinstall());
if (version) {
if (await settleVersion(version)) {
return selfUpdateResult(version);
}
continue;
}
transientRpc.reset();
continue;
}
if (stateResponse?.running) {
transientRpc.reset();
continue;
}
if (await isComponentActionStillRunning(jobId, component, action)) {
transientRpc.reset();
continue;
}
const failure = componentActionFailure(statusResponse, parsedResponse);
if (transientRpc.shouldContinue(failure.error)) {
continue;
}
return failure;
}
transientRpc.reset();
if (parsedResponse.running) {
continue;
}
if (
isSelfUpdate &&
parsedResponse.success === false &&
(isDifferentVersion || action === "reinstall")
) {
const version =
(await confirmedByVersion()) ||
(await confirmedSameVersionReinstall());
if (version) {
if (await settleVersion(version)) {
return selfUpdateResult(version);
}
continue;
}
}
return jobDoneResult(parsedResponse);
}
},
subscriptionUpdateStart: async (section, sourceIndex) => {
const startArgs = [
Tachyon.AvailableMethods.SUBSCRIPTION_UPDATE_ASYNC,
...(section ? [section] : []),
...(section && sourceIndex !== void 0 ? [String(sourceIndex)] : []),
];
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: startArgs,
timeout: SUBSCRIPTION_UPDATE_RPC_TIMEOUT_MS,
});
const parsedResponse = parseSubscriptionUpdateStartResult(response);
if (
(response.code ?? 0) !== 0 ||
!parsedResponse?.success ||
!parsedResponse.job_id
) {
return {
success: false,
error:
parsedResponse?.message ||
response.stderr ||
_("Subscription update failed"),
};
}
return {
success: true,
data: parsedResponse,
};
},
subscriptionUpdateStatus: async (jobId) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.SUBSCRIPTION_UPDATE_STATUS, jobId],
timeout: SUBSCRIPTION_UPDATE_RPC_TIMEOUT_MS,
});
const parsedResponse = parseSubscriptionUpdateJobState(response);
if ((response.code ?? 0) !== 0 || !parsedResponse) {
return {
success: false,
error: response.stderr || _("Subscription update failed"),
};
}
return {
success: true,
data: parsedResponse,
};
},
waitSubscriptionUpdateJob: async (jobId) => {
const transientRpc = createTransientRpcGraceTracker(
UI_ACTION_TRANSIENT_RPC_GRACE_MS,
);
while (true) {
await sleep(SUBSCRIPTION_UPDATE_POLL_INTERVAL_MS);
const response =
await TachyonShellMethods.subscriptionUpdateStatus(jobId);
if (!response.success) {
if (transientRpc.shouldContinue(response.error)) {
continue;
}
return response;
}
transientRpc.reset();
if (response.data.running) {
continue;
}
return response;
}
},
getWatchdogStatus: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: ["watchdog", "status"],
timeout: 5e3,
});
return {
success: true,
data: { running: (response.code ?? 1) === 0 },
};
},
watchdogStart: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: ["watchdog_start"],
timeout: 8e3,
});
return {
success: (response.code ?? 1) === 0,
};
},
watchdogStop: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: ["watchdog_stop"],
timeout: 8e3,
});
return {
success: (response.code ?? 1) === 0,
};
},
/**
* Run an arbitrary UCI command via shell — used for saving Smart Detect
* settings and per-device routing IPs from the Advanced Settings panel.
*/
uciRunCommand: async (args) => {
const response = await executeShellCommand({
command: "/sbin/uci",
args,
timeout: 5e3,
});
return {
success: (response.code ?? 1) === 0,
};
},
startFuzzer: async (
engine = "zapret2",
target = "youtube",
customUrl,
ruleSection,
customFile,
mode,
) => {
const args = [Tachyon.AvailableMethods.FUZZER_START, engine, target];
if (customUrl) args.push(customUrl);
else args.push("");
if (ruleSection) args.push(ruleSection);
else args.push("");
if (customFile) args.push(customFile);
else args.push("");
if (mode) args.push(mode);
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args,
timeout: 1e4,
});
let parsed = null;
try {
parsed = JSON.parse(response.stdout?.trim() || "{}");
} catch {
parsed = null;
}
if ((response.code ?? 1) === 0 && parsed && parsed.success) {
return {
success: true,
data: parsed,
};
}
return {
success: false,
error: parsed?.error || response.stderr || _("Failed to start fuzzer"),
};
},
getFuzzerStatus: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.FUZZER_STATUS],
timeout: 8e3,
});
let parsed = null;
try {
parsed = JSON.parse(response.stdout?.trim() || "{}");
} catch {
parsed = null;
}
if ((response.code ?? 1) === 0 && parsed) {
return {
success: true,
data: parsed,
};
}
return {
success: false,
error: response.stderr || _("Failed to get fuzzer status"),
};
},
stopFuzzer: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.FUZZER_STOP],
timeout: 8e3,
});
if ((response.code ?? 1) === 0) {
return { success: true, data: void 0 };
}
return {
success: false,
error: response.stderr || _("Failed to stop fuzzer"),
};
},
applyFuzzerStrategy: async (engine, args, targetRuleOrGlobal) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [
Tachyon.AvailableMethods.FUZZER_APPLY,
engine,
args,
targetRuleOrGlobal || "global",
],
timeout: 1e4,
});
let parsed = null;
try {
parsed = JSON.parse(response.stdout?.trim() || "{}");
} catch {
parsed = null;
}
if ((response.code ?? 1) === 0 && parsed && parsed.success) {
return {
success: true,
data: parsed,
};
}
return {
success: false,
error: parsed?.error || response.stderr || _("Failed to apply strategy"),
};
},
getFuzzerStrategies: async (mode) => {
const args = [Tachyon.AvailableMethods.FUZZER_STRATEGIES];
if (mode) args.push(mode);
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args,
timeout: 8e3,
});
let parsed = null;
try {
parsed = JSON.parse(response.stdout?.trim() || "{}");
} catch {
parsed = null;
}
if ((response.code ?? 1) === 0 && parsed) {
return {
success: true,
data: parsed,
};
}
return {
success: false,
error: response.stderr || _("Failed to get fuzzer strategies"),
};
},
fuzzerAiSynthesize: async (engine, target, customUrl, userPrompt) => {
const args = [
Tachyon.AvailableMethods.FUZZER_AI_SYNTHESIZE,
engine,
target,
];
if (customUrl) args.push(customUrl);
else args.push("");
if (userPrompt) args.push(userPrompt);
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args,
timeout: 65e3,
});
let parsed = null;
try {
parsed = JSON.parse(response.stdout?.trim() || "{}");
} catch {
parsed = null;
}
if ((response.code ?? 1) === 0 && parsed && parsed.success) {
return {
success: true,
data: parsed,
};
}
return {
success: false,
error:
parsed?.error ||
response.stderr ||
_("Failed to synthesize AI strategies"),
};
},
getFuzzerPatterns: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.FUZZER_GET_PATTERNS],
timeout: 8e3,
});
let parsed = null;
try {
parsed = JSON.parse(response.stdout?.trim() || "{}");
} catch {
parsed = null;
}
if ((response.code ?? 1) === 0 && parsed && parsed.success) {
return {
success: true,
data: parsed,
};
}
return {
success: false,
error: response.stderr || _("Failed to get fuzzer patterns"),
};
},
saveFuzzerPatterns: async (patterns) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [
Tachyon.AvailableMethods.FUZZER_SAVE_PATTERNS,
JSON.stringify(patterns),
],
timeout: 1e4,
});
let parsed = null;
try {
parsed = JSON.parse(response.stdout?.trim() || "{}");
} catch {
parsed = null;
}
if ((response.code ?? 1) === 0 && parsed && parsed.success) {
return {
success: true,
data: { message: parsed.message || _("Patterns saved successfully") },
};
}
return {
success: false,
error:
parsed?.error || response.stderr || _("Failed to save fuzzer patterns"),
};
},
resetFuzzerPatterns: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.FUZZER_RESET_PATTERNS],
timeout: 8e3,
});
let parsed = null;
try {
parsed = JSON.parse(response.stdout?.trim() || "{}");
} catch {
parsed = null;
}
if ((response.code ?? 1) === 0 && parsed && parsed.success) {
return {
success: true,
data: {
message: parsed.message || _("Patterns reset to factory defaults"),
patterns: parsed.patterns,
},
};
}
return {
success: false,
error:
parsed?.error ||
response.stderr ||
_("Failed to reset fuzzer patterns"),
};
},
detectFuzzerDpi: async (target, customUrl) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [
Tachyon.AvailableMethods.FUZZER_DETECT_DPI,
target,
...(customUrl ? [customUrl] : []),
],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS,
});
const parsed = parseJsonObjectOutput(response.stdout);
if ((response.code ?? 0) !== 0 || !parsed) {
return {
success: false,
error: response.stderr || _("Failed to detect DPI type"),
};
}
return {
success: true,
data: parsed,
};
},
autoApplyFuzzerStrategy: async (targetRule) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [
Tachyon.AvailableMethods.FUZZER_AUTO_APPLY,
...(targetRule ? [targetRule] : []),
],
timeout: UI_ACTION_RPC_TIMEOUT_MS,
});
const parsed = parseJsonObjectOutput(response.stdout);
if ((response.code ?? 0) !== 0 || !parsed?.success) {
return {
success: false,
error:
parsed?.error ||
response.stderr ||
_("Failed to auto-apply strategy"),
};
}
return {
success: true,
data: parsed,
};
},
getFuzzerHistory: async (limit) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.FUZZER_HISTORY, String(limit || 20)],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS,
});
const parsed = parseJsonObjectOutput(response.stdout);
if ((response.code ?? 0) !== 0 || !parsed) {
return {
success: false,
error: response.stderr || _("Failed to get fuzzer history"),
};
}
return {
success: true,
data: parsed,
};
},
clearFuzzerHistory: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.FUZZER_CLEAR_HISTORY],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS,
});
const parsed = parseJsonObjectOutput(response.stdout);
if ((response.code ?? 0) !== 0 || !parsed?.success) {
return {
success: false,
error: response.stderr || _("Failed to clear fuzzer history"),
};
}
return {
success: true,
data: { message: _("History cleared") },
};
},
};
// 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: "",
sing_box_backup_version: "",
sing_box_backup_time: 0,
zapret_version: "loading",
zapret_installed: 0,
zapret_backup_version: "",
zapret_backup_time: 0,
zapret2_version: "loading",
zapret2_installed: 0,
zapret2_backup_version: "",
zapret2_backup_time: 0,
byedpi_version: "loading",
byedpi_installed: 0,
byedpi_backup_version: "",
byedpi_backup_time: 0,
tailscale_version: "loading",
tailscale_installed: 0,
tailscale_backup_version: "",
tailscale_backup_time: 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 },
tachyonRollback: { loading: false },
singBoxCheck: { loading: false },
singBoxInstall: { loading: false },
singBoxRollback: { loading: false },
singBoxInstallExtended: { loading: false },
singBoxInstallExtendedCompressed: { loading: false },
singBoxInstallLx: { loading: false },
singBoxInstallTiny: { loading: false },
singBoxInstallStable: { loading: false },
zapretCheck: { loading: false },
zapretInstall: { loading: false },
zapretRemove: { loading: false },
zapretRollback: { loading: false },
zapret2Check: { loading: false },
zapret2Install: { loading: false },
zapret2Remove: { loading: false },
zapret2Rollback: { loading: false },
byedpiCheck: { loading: false },
byedpiInstall: { loading: false },
byedpiRemove: { loading: false },
byedpiRollback: { loading: false },
tailscaleCheck: { loading: false },
tailscaleInstall: { loading: false },
tailscaleRemove: { loading: false },
tailscaleRollback: { 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: "" },
tailscale: { status: null, latest_version: "", release_url: "" },
},
};
// src/tachyon/services/store.service.ts
function jsonStableStringify(obj) {
return JSON.stringify(obj, (_2, value) => {
if (value && typeof value === "object" && !Array.isArray(value)) {
return Object.keys(value)
.sort()
.reduce((acc, key) => {
acc[key] = value[key];
return acc;
}, {});
}
return value;
});
}
function jsonEqual(a, b) {
try {
return jsonStableStringify(a) === jsonStableStringify(b);
} catch {
return false;
}
}
var StoreService = class {
constructor(initial) {
this.listeners = /* @__PURE__ */ new Set();
this.value = initial;
this.initial = structuredClone(initial);
}
get() {
return this.value;
}
set(next) {
const prev = this.value;
const diff = {};
for (const key in next) {
if (next[key] === prev[key]) continue;
if (!jsonEqual(next[key], prev[key])) diff[key] = next[key];
}
if (Object.keys(diff).length === 0) {
return;
}
const merged = { ...prev, ...next };
this.value = merged;
this.listeners.forEach((cb) => cb(this.value, prev, diff));
}
reset(keys) {
const prev = this.value;
const next = structuredClone(this.value);
if (keys && keys.length > 0) {
keys.forEach((key) => {
next[key] = structuredClone(this.initial[key]);
});
} else {
Object.assign(next, structuredClone(this.initial));
}
if (jsonEqual(prev, next)) return;
this.value = next;
const diff = {};
for (const key in next) {
if (!jsonEqual(next[key], prev[key])) diff[key] = next[key];
}
this.listeners.forEach((cb) => cb(this.value, prev, diff));
}
subscribe(cb) {
this.listeners.add(cb);
cb(this.value, this.value, {});
return () => this.listeners.delete(cb);
}
unsubscribe(cb) {
this.listeners.delete(cb);
}
patch(key, value) {
this.set({ [key]: value });
}
getKey(key) {
return this.value[key];
}
subscribeKey(key, cb) {
let prev = this.value[key];
const wrapper = (val) => {
if (!jsonEqual(val[key], prev)) {
prev = val[key];
cb(val[key]);
}
};
this.listeners.add(wrapper);
return () => this.listeners.delete(wrapper);
}
};
var initialStore = {
tabService: {
current: "",
all: [],
},
bandwidthWidget: {
loading: true,
failed: false,
data: { up: 0, down: 0 },
},
trafficTotalWidget: {
loading: true,
failed: false,
data: { downloadTotal: 0, uploadTotal: 0 },
},
systemInfoWidget: {
loading: true,
failed: false,
data: { connections: 0, memory: 0 },
},
servicesInfoWidget: {
loading: true,
failed: false,
data: {
singbox: 0,
tachyonRunning: 0,
tachyonEnabled: 0,
tachyonStatus: "",
watchdogRunning: 0,
},
},
tailscaleWidget: {
loading: true,
failed: false,
data: null,
},
sectionsWidget: {
loading: true,
failed: false,
latencyFetchingSections: {},
latencyProgressSections: {},
selectorSwitchingSections: {},
subscriptionUpdatingSections: {},
data: [],
},
...initialDiagnosticStore,
};
var store = new StoreService(initialStore);
// src/tachyon/services/tachyonLogWatcher.service.ts
var TachyonLogWatcher = class _TachyonLogWatcher {
constructor() {
this.intervalMs = 5e3;
this.lastLines = /* @__PURE__ */ new Set();
this.maxTrackedLines = 500;
this.running = false;
this.paused = false;
this.checking = false;
if (typeof document !== "undefined") {
document.addEventListener("visibilitychange", () => {
if (document.hidden) this.pause();
else this.resume();
});
}
}
static getInstance() {
if (!_TachyonLogWatcher.instance) {
_TachyonLogWatcher.instance = new _TachyonLogWatcher();
}
return _TachyonLogWatcher.instance;
}
init(fetcher, options) {
this.fetcher = fetcher;
this.onNewLog = options?.onNewLog;
this.intervalMs = options?.intervalMs ?? 5e3;
this.maxTrackedLines = options?.maxTrackedLines ?? 500;
this.lastLines = /* @__PURE__ */ new Set();
logger.info(
"[TachyonLogWatcher]",
`initialized (interval: ${this.intervalMs}ms)`,
);
}
normalizeLines(raw) {
return raw.split("\n").filter(Boolean).slice(-this.maxTrackedLines);
}
async checkOnce() {
if (!this.fetcher) {
logger.warn("[TachyonLogWatcher]", "fetcher not found");
return;
}
if (this.paused) {
logger.debug(
"[TachyonLogWatcher]",
"skipped check \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",
"tachyon:rollback": "tachyonRollback",
"sing_box:check_update": "singBoxCheck",
"sing_box:install": "singBoxInstall",
"sing_box:rollback": "singBoxRollback",
"sing_box:install_extended": "singBoxInstallExtended",
"sing_box:install_extended_compressed": "singBoxInstallExtendedCompressed",
"sing_box:install_lx": "singBoxInstallLx",
"sing_box:install_tiny": "singBoxInstallTiny",
"sing_box:install_stable": "singBoxInstallStable",
"zapret:check_update": "zapretCheck",
"zapret:install": "zapretInstall",
"zapret:remove": "zapretRemove",
"zapret:rollback": "zapretRollback",
"zapret2:check_update": "zapret2Check",
"zapret2:install": "zapret2Install",
"zapret2:remove": "zapret2Remove",
"zapret2:rollback": "zapret2Rollback",
"byedpi:check_update": "byedpiCheck",
"byedpi:install": "byedpiInstall",
"byedpi:remove": "byedpiRemove",
"byedpi:rollback": "byedpiRollback",
"tailscale:check_update": "tailscaleCheck",
"tailscale:install": "tailscaleInstall",
"tailscale:remove": "tailscaleRemove",
"tailscale:rollback": "tailscaleRollback",
};
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 },
tachyonRollback: { loading: false },
singBoxCheck: { loading: false },
singBoxInstall: { loading: false },
singBoxRollback: { loading: false },
singBoxInstallExtended: { loading: false },
singBoxInstallExtendedCompressed: { loading: false },
singBoxInstallLx: { loading: false },
singBoxInstallTiny: { loading: false },
singBoxInstallStable: { loading: false },
zapretCheck: { loading: false },
zapretInstall: { loading: false },
zapretRemove: { loading: false },
zapretRollback: { loading: false },
zapret2Check: { loading: false },
zapret2Install: { loading: false },
zapret2Remove: { loading: false },
zapret2Rollback: { loading: false },
byedpiCheck: { loading: false },
byedpiInstall: { loading: false },
byedpiRemove: { loading: false },
byedpiRollback: { loading: false },
tailscaleCheck: { loading: false },
tailscaleInstall: { loading: false },
tailscaleRemove: { loading: false },
tailscaleRollback: { 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();
void fetchTailscalePeers();
}
async function followSubscriptionUpdateState(state) {
const jobId = state.job_id;
const sectionName = state.section || "";
if (!jobId || !sectionName || followedSubscriptionJobs.has(jobId)) {
return;
}
if (!state.running && handledSubscriptionJobs.has(jobId)) {
return;
}
followedSubscriptionJobs.add(jobId);
if (shouldShowLoadingForRestoredAction(state)) {
setSubscriptionUpdating(sectionName, true);
}
try {
const response = state.running
? await TachyonShellMethods.waitSubscriptionUpdateJob(jobId)
: {
success: true,
data: state,
};
await completeSubscriptionUpdateJob(jobId, sectionName, response);
} catch (error) {
logger.error("[DASHBOARD]", "followSubscriptionUpdateState failed", error);
if (!pageUnloading) {
const message =
error instanceof Error
? error.message
: _("Failed to update subscriptions");
setSubscriptionUpdating(sectionName, false);
if (!isTransientRpcError(message)) {
showToast(_("Failed to update subscriptions"), "error");
}
}
} finally {
followedSubscriptionJobs.delete(jobId);
}
}
async function completeLatencyTestJob(jobId, sectionName) {
setLatencyFetching(sectionName, false);
if (pageUnloading) {
return;
}
if (jobId && handledLatencyJobs.has(jobId)) {
return;
}
if (jobId) {
handledLatencyJobs.add(jobId);
capSetSize(handledLatencyJobs);
}
if (jobId) {
void TachyonShellMethods.uiActionAck("latency", jobId);
}
void fetchDashboardSections({ force: true });
}
async function followLatencyTestState(state) {
const jobId = state.job_id;
const sectionName = state.section || "";
if (!jobId || !sectionName || followedLatencyJobs.has(jobId)) {
return;
}
if (!state.running && handledLatencyJobs.has(jobId)) {
return;
}
followedLatencyJobs.add(jobId);
if (shouldShowLoadingForRestoredAction(state)) {
setLatencyFetching(sectionName, true);
}
try {
if (state.running) {
await TachyonShellMethods.waitLatencyTestJob(jobId);
}
await completeLatencyTestJob(jobId, sectionName);
} catch (error) {
logger.error("[DASHBOARD]", "followLatencyTestState failed", error);
if (!pageUnloading) {
setLatencyFetching(sectionName, false);
}
} finally {
followedLatencyJobs.delete(jobId);
}
}
function followDashboardActionsFromUiState(uiState) {
for (const state of uiState.actions.subscription || []) {
if (state.running || (state.job_id && state.section)) {
void followSubscriptionUpdateState(state);
} else if (state.job_id && !handledSubscriptionJobs.has(state.job_id)) {
handledSubscriptionJobs.add(state.job_id);
void TachyonShellMethods.uiActionAck("subscription", state.job_id);
}
}
for (const state of uiState.actions.latency || []) {
if (state.running || (state.job_id && state.section)) {
void followLatencyTestState(state);
} else if (state.job_id && !handledLatencyJobs.has(state.job_id)) {
handledLatencyJobs.add(state.job_id);
void TachyonShellMethods.uiActionAck("latency", state.job_id);
}
}
}
function startActionStateWatcher() {
if (actionStateUnsubscribe) {
return;
}
actionStateUnsubscribe = subscribeRuntimeUiState((uiState) => {
if (dashboardMounted) {
followDashboardActionsFromUiState(uiState);
}
});
}
function stopActionStateWatcher() {
if (!actionStateUnsubscribe) {
return;
}
actionStateUnsubscribe();
actionStateUnsubscribe = null;
}
async function connectToClashSockets(dataUpdatesId) {
const mountId = dashboardMountId;
const clashApiSecret = await getClashApiSecret2();
if (
!dashboardMounted ||
mountId !== dashboardMountId ||
dataUpdatesId !== dashboardDataUpdatesId ||
getDashboardServiceAvailability() === "stopped"
) {
return;
}
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",
);
}
var latestTailscalePeersRequestId = 0;
async function fetchTailscalePeers() {
const requestId = ++latestTailscalePeersRequestId;
try {
const response = await TachyonShellMethods.getTailscalePeers();
if (requestId !== latestTailscalePeersRequestId) return;
store.set({
tailscaleWidget: {
loading: false,
failed: !response.success,
data: response.success ? response.data : null,
},
});
} catch {
if (requestId !== latestTailscalePeersRequestId) return;
store.set({
tailscaleWidget: {
loading: false,
failed: true,
data: store.get().tailscaleWidget.data,
},
});
}
}
function peerRow(name, online) {
return {
key: name,
value: online ? _("\u2714 Online") : _("\u2718 Offline"),
attributes: {
class: online
? "tachyon_dashboard-page__widgets-section__item__row--success"
: "tachyon_dashboard-page__widgets-section__item__row--error",
},
};
}
async function renderTailscaleWidget() {
renderStoreWidget(
"dashboard-widget-tailscale",
"tailscaleWidget",
_("Tailscale"),
(data) => {
if (!data || !data.configured) return [];
const items = [
{
key: _("State"),
value: data.backend_state || "\u2014",
attributes: {
class:
data.backend_state === "Running"
? "tachyon_dashboard-page__widgets-section__item__row--success"
: "tachyon_dashboard-page__widgets-section__item__row--error",
},
},
];
if (data.self && data.self.ips.length) {
items.push({ key: _("This router"), value: data.self.ips[0] });
}
for (const peer of data.peers.slice(0, 5)) {
if (!peer.online && !peer.name) continue;
items.push(
peerRow(peer.name || peer.dns_name || peer.ips[0], peer.online),
);
}
if (data.peers.length > 5) {
items.push({
key: _("Other peers"),
value: `+${data.peers.length - 5}`,
});
}
return items;
},
"renderTailscaleWidget",
);
}
async function onStoreUpdate(next, prev, diff) {
if (diff.sectionsWidget) {
const inlineUpdated =
canUpdateLatencyProgressInline(
prev.sectionsWidget,
next.sectionsWidget,
) && updateLatencyProgressInline(next.sectionsWidget);
if (!inlineUpdated) {
renderSectionsWidget();
}
}
if (diff.bandwidthWidget) {
renderBandwidthWidget();
}
if (diff.trafficTotalWidget) {
renderTrafficTotalWidget();
}
if (diff.systemInfoWidget) {
renderSystemInfoWidget();
}
if (diff.servicesInfoWidget) {
syncDashboardServiceAvailability();
renderServicesInfoWidget();
}
if (diff.tailscaleWidget) {
renderTailscaleWidget();
}
}
async function onPageMount() {
onPageUnmount();
dashboardMounted = true;
dashboardMountId += 1;
const mountId = dashboardMountId;
const hasRuntimeSnapshot = Boolean(getCachedRuntimeUiState());
if (!hasRuntimeSnapshot) {
const uiState = await refreshRuntimeUiState({ force: true });
if (!dashboardMounted || mountId !== dashboardMountId) {
return;
}
if (!uiState) {
void fetchServicesInfo();
void fetchTailscalePeers();
}
}
store.subscribe(onStoreUpdate);
startActionStateWatcher();
void renderSectionsWidget();
void renderConnectionsWidget();
void renderBandwidthWidget();
void renderTrafficTotalWidget();
void renderSystemInfoWidget();
void renderServicesInfoWidget();
void renderTailscaleWidget();
syncDashboardServiceAvailability();
void fetchTailscalePeers();
if (hasRuntimeSnapshot) {
void refreshRuntimeUiState({ force: true });
}
}
function onPageUnmount() {
dashboardMounted = false;
dashboardMountId += 1;
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,
tailscale_version: _("unknown"),
tailscale_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 {
width: 100%;
box-sizing: border-box;
}
.tachyon-partial-modal__content {
max-height: 75vh;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
border-radius: 4px;
box-sizing: border-box;
width: 100%;
}
.tachyon-partial-modal__footer {
display: flex;
justify-content: flex-end;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.tachyon-partial-modal__footer button {
margin-left: 0;
}
.tachyon-partial-modal__checkbox {
display: inline-flex;
align-items: center;
gap: 8px;
margin-right: auto;
cursor: pointer;
user-select: none;
}
.tachyon-partial-modal__checkbox-text {
line-height: 1.2;
}
@media (max-width: 640px) {
.tachyon-partial-modal__content {
max-height: 82vh;
}
.tachyon-partial-modal__footer {
flex-direction: column-reverse;
align-items: stretch;
}
.tachyon-partial-modal__footer button {
width: 100%;
min-height: 38px;
}
.tachyon-partial-modal__checkbox {
margin-right: 0;
margin-bottom: 6px;
width: 100%;
}
}
`;
// src/partials/button/renderButton.ts
function renderButton({
classNames = [],
disabled,
loading: loading2,
onClick,
text,
icon,
}) {
const hasIcon = !!loading2 || !!icon;
function getWrappedIcon() {
const iconWrap = E("span", {
class: "tachyon-partial-button__icon",
});
if (loading2) {
iconWrap.appendChild(renderLoaderCircleIcon24());
return iconWrap;
}
if (icon) {
iconWrap.appendChild(icon());
return iconWrap;
}
return iconWrap;
}
function getClass() {
return [
"btn",
"tachyon-partial-button",
...insertIf(Boolean(disabled), ["tachyon-partial-button--disabled"]),
...insertIf(Boolean(loading2), ["tachyon-partial-button--loading"]),
...insertIf(Boolean(hasIcon), ["tachyon-partial-button--with-icon"]),
...classNames,
]
.filter(Boolean)
.join(" ");
}
function getDisabled() {
if (loading2 || disabled) {
return true;
}
return void 0;
}
return E(
"button",
{
type: "button",
class: getClass(),
disabled: getDisabled(),
click: onClick,
},
[...insertIf(hasIcon, [getWrappedIcon()]), E("span", {}, text)],
);
}
// src/partials/modal/renderModal.ts
function renderModal(text, name, options) {
let rawText = text ?? "";
let currentText = "";
let refreshInFlight = false;
let pendingRefresh = false;
let pendingForcedRefresh = false;
let refreshSessionId = 0;
let timer;
let observer;
let autoRefreshEnabled =
options?.initialAutoRefresh ?? Boolean(options?.getText);
let maskValuesEnabled = options?.initialMaskValues ?? true;
let shouldScrollToBottomOnMount = Boolean(options?.startAtEnd);
let autoRefreshInput;
let maskValuesInput;
const getDisplayText = (value) => {
if (maskValuesEnabled && options?.maskText) {
return options.maskText(value);
}
return value;
};
const codeEl = E("code", {}, "");
const contentEl = E(
"pre",
{ class: "tachyon-partial-modal__content" },
codeEl,
);
const stopRefreshTimer = () => {
if (timer) {
clearInterval(timer);
timer = void 0;
}
};
const destroyLiveRefresh = () => {
refreshSessionId += 1;
pendingRefresh = false;
pendingForcedRefresh = false;
stopRefreshTimer();
observer?.disconnect();
observer = void 0;
};
const scrollToBottom = () => {
contentEl.scrollTop = contentEl.scrollHeight;
};
const scheduleInitialScrollToBottom = () => {
if (!shouldScrollToBottomOnMount || !body.isConnected) {
return;
}
shouldScrollToBottomOnMount = false;
requestAnimationFrame(() => {
scrollToBottom();
});
};
const updateText = (nextText) => {
const normalizedText = nextText ?? "";
const shouldStickToBottom =
shouldScrollToBottomOnMount ||
contentEl.scrollTop + contentEl.clientHeight >=
contentEl.scrollHeight - 16;
if (normalizedText === currentText) {
if (shouldStickToBottom) {
requestAnimationFrame(() => {
scrollToBottom();
});
}
return;
}
currentText = normalizedText;
codeEl.textContent = currentText;
if (shouldStickToBottom) {
requestAnimationFrame(() => {
scrollToBottom();
});
}
};
const updateDisplayedTextFromRaw = () => {
updateText(getDisplayText(rawText));
};
const refreshText = async (force = false) => {
if (
!options?.getText ||
(!force && !autoRefreshEnabled) ||
refreshInFlight
) {
return;
}
if (!body.isConnected) {
return;
}
refreshInFlight = true;
const sessionId = refreshSessionId;
try {
const nextText = await options.getText({
maskValues: options.maskText ? false : maskValuesEnabled,
});
if (!body.isConnected || (!force && !autoRefreshEnabled)) {
return;
}
if (sessionId !== refreshSessionId) {
return;
}
const normalizedText = nextText ?? "";
rawText = normalizedText;
updateText(getDisplayText(normalizedText));
} catch (error) {
console.warn("[renderModal] failed to refresh modal content", error);
} finally {
refreshInFlight = false;
if (pendingRefresh) {
const shouldForceRefresh = pendingForcedRefresh;
pendingRefresh = false;
pendingForcedRefresh = false;
if ((shouldForceRefresh || autoRefreshEnabled) && body.isConnected) {
void refreshText(shouldForceRefresh);
}
}
}
};
const requestRefresh = () => {
if (!options?.getText || !autoRefreshEnabled) {
return;
}
if (refreshInFlight) {
pendingRefresh = true;
return;
}
void refreshText();
};
const requestForcedRefresh = () => {
if (!options?.getText) {
return;
}
if (refreshInFlight) {
pendingRefresh = true;
pendingForcedRefresh = true;
return;
}
void refreshText(true);
};
const startRefreshTimer = () => {
if (
!options?.getText ||
!autoRefreshEnabled ||
timer ||
typeof document === "undefined"
) {
return;
}
timer = setInterval(() => {
requestRefresh();
}, options.refreshMs ?? 3e3);
};
const setAutoRefreshEnabled = (nextValue) => {
autoRefreshEnabled = nextValue;
refreshSessionId += 1;
pendingRefresh = false;
pendingForcedRefresh = false;
if (autoRefreshInput) {
autoRefreshInput.checked = nextValue;
}
if (nextValue) {
startRefreshTimer();
requestRefresh();
return;
}
stopRefreshTimer();
};
const setMaskValuesEnabled = (nextValue) => {
maskValuesEnabled = nextValue;
refreshSessionId += 1;
pendingRefresh = false;
pendingForcedRefresh = false;
if (maskValuesInput) {
maskValuesInput.checked = nextValue;
}
if (options?.maskText) {
updateDisplayedTextFromRaw();
return;
}
requestForcedRefresh();
};
const footerChildren = [
renderButton({
classNames: ["cbi-button-apply"],
text: _("Download"),
onClick: () => downloadAsTxt(currentText, name),
}),
renderButton({
classNames: ["cbi-button-apply"],
text: _("Copy"),
onClick: () =>
copyToClipboard(`\`\`\`${name}
${currentText}
\`\`\``),
}),
renderButton({
classNames: ["cbi-button-remove"],
text: _("Close"),
onClick: () => {
destroyLiveRefresh();
ui.hideModal();
},
}),
];
if (options?.getText && options?.showAutoRefreshToggle) {
autoRefreshInput = document.createElement("input");
autoRefreshInput.type = "checkbox";
autoRefreshInput.className = "cbi-input-checkbox";
autoRefreshInput.checked = autoRefreshEnabled;
autoRefreshInput.addEventListener("change", () => {
setAutoRefreshEnabled(autoRefreshInput.checked);
});
footerChildren.unshift(
E("label", { class: "tachyon-partial-modal__checkbox" }, [
autoRefreshInput,
E(
"span",
{ class: "tachyon-partial-modal__checkbox-text" },
options.autoRefreshLabel ?? _("Auto refresh"),
),
]),
);
}
if (
(options?.getText || options?.maskText) &&
options?.showMaskValuesToggle
) {
maskValuesInput = document.createElement("input");
maskValuesInput.type = "checkbox";
maskValuesInput.className = "cbi-input-checkbox";
maskValuesInput.checked = maskValuesEnabled;
maskValuesInput.addEventListener("change", () => {
setMaskValuesEnabled(maskValuesInput.checked);
});
footerChildren.unshift(
E("label", { class: "tachyon-partial-modal__checkbox" }, [
maskValuesInput,
E(
"span",
{ class: "tachyon-partial-modal__checkbox-text" },
options.maskValuesLabel ?? _("Hide values"),
),
]),
);
}
const body = E("div", { class: "tachyon-partial-modal__body" }, [
E("div", {}, [
contentEl,
E("div", { class: "tachyon-partial-modal__footer" }, footerChildren),
]),
]);
if (
(options?.getText || options?.startAtEnd) &&
typeof document !== "undefined"
) {
observer = new MutationObserver(() => {
if (!body.isConnected) {
destroyLiveRefresh();
return;
}
scheduleInitialScrollToBottom();
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
scheduleInitialScrollToBottom();
}
if (options?.getText && typeof document !== "undefined") {
startRefreshTimer();
requestRefresh();
}
updateDisplayedTextFromRaw();
return body;
}
// src/partials/index.ts
var PartialStyles = `
${styles2}
${styles3}
`;
// src/tachyon/tabs/diagnostic/partials/renderAvailableActions.ts
function renderAvailableActions({
restart,
start,
stop,
enable,
disable,
globalCheck,
doctor,
aiDoctor,
aiChat,
strategyFuzzer,
restoreNativeInternet,
viewLogs,
showSingBoxConfig,
generateBugReport,
checkServices,
}) {
return E("div", { class: "tachyon_diagnostic-page__right-bar__actions" }, [
E("b", {}, _("Available actions")),
...insertIf(restart.visible, [
renderButton({
classNames: ["cbi-button-apply"],
onClick: restart.onClick,
icon: renderRotateCcwIcon24,
text: _("Restart Tachyon"),
loading: restart.loading,
disabled: restart.disabled,
}),
]),
...insertIf(stop.visible, [
renderButton({
classNames: ["cbi-button-remove"],
onClick: stop.onClick,
icon: renderCircleStopIcon24,
text: _("Stop Tachyon"),
loading: stop.loading,
disabled: stop.disabled,
}),
]),
...insertIf(start.visible, [
renderButton({
classNames: ["cbi-button-save"],
onClick: start.onClick,
icon: renderCirclePlayIcon24,
text: _("Start Tachyon"),
loading: start.loading,
disabled: start.disabled,
}),
]),
...insertIf(disable.visible, [
renderButton({
classNames: ["cbi-button-remove"],
onClick: disable.onClick,
icon: renderPauseIcon24,
text: _("Disable autostart"),
loading: disable.loading,
disabled: disable.disabled,
}),
]),
...insertIf(enable.visible, [
renderButton({
classNames: ["cbi-button-save"],
onClick: enable.onClick,
icon: renderPlayIcon24,
text: _("Enable autostart"),
loading: enable.loading,
disabled: enable.disabled,
}),
]),
...insertIf(globalCheck.visible, [
renderButton({
onClick: globalCheck.onClick,
icon: renderCircleCheckBigIcon24,
text: _("Get global check"),
loading: globalCheck.loading,
disabled: globalCheck.disabled,
}),
]),
...insertIf(doctor.visible, [
renderButton({
onClick: doctor.onClick,
icon: renderRotateCcwIcon24,
text: _("Run doctor repair"),
loading: doctor.loading,
disabled: doctor.disabled,
}),
]),
...insertIf(aiDoctor.visible, [
renderButton({
classNames: ["cbi-button-action"],
onClick: aiDoctor.onClick,
icon: renderRotateCcwIcon24,
text: _("Run AI Doctor"),
loading: aiDoctor.loading,
disabled: aiDoctor.disabled,
}),
]),
...insertIf(!!restoreNativeInternet?.visible, [
renderButton({
classNames: ["cbi-button-reset"],
onClick: restoreNativeInternet.onClick,
icon: renderCircleStopIcon24,
text: _("Restore Native Internet"),
loading: restoreNativeInternet.loading,
disabled: restoreNativeInternet.disabled,
}),
]),
...insertIf(!!aiChat?.visible, [
renderButton({
classNames: ["cbi-button-action"],
onClick: aiChat.onClick,
icon: renderCircleCheckBigIcon24,
text: _("AI Chat Assistant"),
loading: aiChat.loading,
disabled: aiChat.disabled,
}),
]),
...insertIf(!!strategyFuzzer?.visible, [
renderButton({
classNames: ["cbi-button-action"],
onClick: strategyFuzzer.onClick,
icon: renderCirclePlayIcon24,
text: _("\u26A1 DPI Strategy Fuzzer"),
loading: strategyFuzzer.loading,
disabled: strategyFuzzer.disabled,
}),
]),
...insertIf(checkServices.visible, [
renderButton({
classNames: ["cbi-button-action"],
onClick: checkServices.onClick,
icon: renderSquareChartGanttIcon24,
text: _("Check Services"),
loading: checkServices.loading,
disabled: checkServices.disabled,
}),
]),
...insertIf(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/renderStrategyFuzzerModal.ts
function renderStrategyFuzzerModal(ruleNames = []) {
let pollingInterval = null;
let isPolling = false;
let activeTab2 = "benchmark";
let selectedEngine = "zapret2";
let selectedTarget = "youtube_suite";
let customUrl = "";
let selectedRuleSection = "";
let selectedMode = "presets";
let isRunning = false;
let currentState = null;
let resultFilter = "all";
let autoApplyEnabled = false;
let currentDpiDetection = null;
let patternsConfig = {
zapret2: {
splits: ["1", "2", "3", "midsld", "sniext+4", "1,midsld", "1,sniext+2"],
foolings: ["badseq", "md5sig", "badack", "datanoack", "fakeddrop"],
ttls: [2, 3, 4, 5, 6, 8],
seqovls: ["1", "2"],
wsizes: ["1"],
payloads: ["tls_client_hello", "http_req", "quic_initial"],
},
zapret: {
splits: ["1", "2", "midsld", "sniext+4", "1,midsld"],
foolings: ["badseq", "md5sig", "badack", "datanoack"],
ttls: [2, 3, 4, 6, 8],
split_modes: ["split2", "disorder2", "fake,split2", "fake,disorder2"],
},
byedpi: {
splits: ["1", "2", "1+sniext", "midsld"],
disorders: ["1", "2"],
ttls: [2, 3, 4, 6, 8],
oobs: ["1", "2"],
autos: ["t,r,a,s", "r,s", "t,a"],
tlsrecs: ["1+sniext"],
ipfrags: ["24"],
},
custom_strategies: [],
};
const modalContainer = E("div", {
class: "tachyon_fuzzer_modal",
style:
"display: flex; flex-direction: column; gap: 14px; width: 100%; max-width: 920px; box-sizing: border-box;",
});
const tabButtons = {};
const createTabButton = (id, icon, label) => {
const btn = E(
"button",
{
type: "button",
class: `cbi-button ${activeTab2 === id ? "cbi-button-action" : "cbi-button-neutral"}`,
style:
"display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; font-size: 12px; font-weight: 500; border-radius: 6px; cursor: pointer;",
},
[E("span", {}, icon), E("span", {}, label)],
);
btn.addEventListener("click", () => {
activeTab2 = id;
updateTabVisibility();
});
tabButtons[id] = btn;
return btn;
};
const tabBar = E(
"div",
{
style:
"display: flex; gap: 8px; border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.12)); padding-bottom: 10px; flex-wrap: wrap;",
},
[
createTabButton("benchmark", "\u26A1", _("Benchmark & Test")),
createTabButton("patterns", "\u{1F6E0}\uFE0F", _("Pattern Builder")),
createTabButton("custom", "\u2795", _("My Strategies")),
createTabButton("ai", "\u{1F9E0}", _("AI Doctor RAG")),
createTabButton("history", "\u{1F4CB}", _("History")),
],
);
const tabContentBenchmark = E("div", {
style: "display: flex; flex-direction: column; gap: 14px;",
});
const tabContentPatterns = E("div", {
style: "display: none; flex-direction: column; gap: 14px;",
});
const tabContentCustom = E("div", {
style: "display: none; flex-direction: column; gap: 14px;",
});
const tabContentAi = E("div", {
style: "display: none; flex-direction: column; gap: 14px;",
});
const tabContentHistory = E("div", {
style: "display: none; flex-direction: column; gap: 14px;",
});
const updateTabVisibility = () => {
for (const key in tabButtons) {
tabButtons[key].className =
`cbi-button ${activeTab2 === key ? "cbi-button-action" : "cbi-button-neutral"}`;
}
tabContentBenchmark.style.display =
activeTab2 === "benchmark" ? "flex" : "none";
tabContentPatterns.style.display =
activeTab2 === "patterns" ? "flex" : "none";
tabContentCustom.style.display = activeTab2 === "custom" ? "flex" : "none";
tabContentAi.style.display = activeTab2 === "ai" ? "flex" : "none";
tabContentHistory.style.display =
activeTab2 === "history" ? "flex" : "none";
if (activeTab2 === "patterns") renderPatternsTab();
if (activeTab2 === "custom") renderCustomTab();
if (activeTab2 === "history") renderHistoryTab();
};
const controlsGrid = E("div", {
style:
"display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; align-items: end;",
});
const engineSelect = E(
"select",
{ class: "cbi-input-select", style: "width: 100%;" },
[
E("option", { value: "zapret2" }, _("Zapret v2 (nfqws2)")),
E("option", { value: "zapret" }, _("Zapret v1 (nfqws)")),
E("option", { value: "byedpi" }, _("ByeDPI (ciadpi)")),
E("option", { value: "all" }, _("All Engines (Full Matrix)")),
],
);
engineSelect.addEventListener("change", () => {
selectedEngine = engineSelect.value;
});
const engineGroup = E(
"div",
{ style: "display: flex; flex-direction: column; gap: 4px;" },
[
E(
"label",
{ style: "font-size: 11px; font-weight: 600; opacity: 0.85;" },
_("DPI Engine"),
),
engineSelect,
],
);
const targetSelect = E(
"select",
{ class: "cbi-input-select", style: "width: 100%;" },
[
E(
"option",
{ value: "youtube_suite", selected: true },
_("\u{1F3AC} YouTube Suite (Web + 4K Stream)"),
),
E(
"option",
{ value: "discord_suite" },
_("\u{1F4AC} Discord Suite (API + WSS + CDN)"),
),
E(
"option",
{ value: "instagram_suite" },
_("\u{1F4F8} Instagram / Meta (Web + CDN)"),
),
E(
"option",
{ value: "telegram_suite" },
_("\u2708\uFE0F Telegram Suite (Web + Bot API)"),
),
E(
"option",
{ value: "rutracker_suite" },
_("\u{1F3F4}\u200D\u2620\uFE0F RuTracker Suite"),
),
E("option", { value: "quic_http3" }, _("\u26A1 QUIC / HTTP/3 (UDP 443)")),
E("option", { value: "custom" }, _("\u{1F310} Custom Target URL...")),
],
);
const customUrlInput = E("input", {
type: "text",
class: "cbi-input-text",
placeholder: "https://example.com",
style: "width: 100%; display: none; margin-top: 4px;",
});
targetSelect.addEventListener("change", () => {
selectedTarget = targetSelect.value;
customUrlInput.style.display =
selectedTarget === "custom" ? "block" : "none";
});
customUrlInput.addEventListener("input", () => {
customUrl = customUrlInput.value.trim();
});
const targetGroup = E(
"div",
{ style: "display: flex; flex-direction: column; gap: 4px;" },
[
E(
"label",
{ style: "font-size: 11px; font-weight: 600; opacity: 0.85;" },
_("Target Service / Suite"),
),
targetSelect,
customUrlInput,
],
);
const modeSelect = E(
"select",
{ class: "cbi-input-select", style: "width: 100%;" },
[
E(
"option",
{ value: "presets", selected: true },
_("\u26A1 Quick Benchmark (Presets ~12-14)"),
),
E(
"option",
{ value: "combinatorial" },
_("\u{1F50D} Combinatorial Deep Fuzzing (~40-80+)"),
),
E(
"option",
{ value: "custom" },
_("\u{1F6E0}\uFE0F My Custom Strategies Only"),
),
],
);
modeSelect.addEventListener("change", () => {
selectedMode = modeSelect.value;
});
const modeGroup = E(
"div",
{ style: "display: flex; flex-direction: column; gap: 4px;" },
[
E(
"label",
{ style: "font-size: 11px; font-weight: 600; opacity: 0.85;" },
_("Search Mode"),
),
modeSelect,
],
);
const ruleSelect = E(
"select",
{ class: "cbi-input-select", style: "width: 100%;" },
[
E("option", { value: "", selected: true }, _("Provider Global Default")),
...ruleNames.map((r) => E("option", { value: r }, `${_("Rule:")} ${r}`)),
],
);
ruleSelect.addEventListener("change", () => {
selectedRuleSection = ruleSelect.value;
});
const ruleGroup = E(
"div",
{ style: "display: flex; flex-direction: column; gap: 4px;" },
[
E(
"label",
{ style: "font-size: 11px; font-weight: 600; opacity: 0.85;" },
_("Apply Strategy To"),
),
ruleSelect,
],
);
controlsGrid.append(engineGroup, targetGroup, modeGroup, ruleGroup);
const autoApplyCheckbox = E("input", {
type: "checkbox",
id: "tachyon-fuzzer-auto-apply",
style: "margin: 0;",
});
autoApplyCheckbox.addEventListener("change", () => {
autoApplyEnabled = autoApplyCheckbox.checked;
});
const autoApplyGroup = E(
"div",
{
style:
"display: flex; align-items: end; padding: 4px 0; grid-column: 1 / -1;",
},
[
E(
"div",
{
style:
"font-size: 11px; cursor: pointer; display: flex; align-items: center; gap: 4px;",
},
[autoApplyCheckbox, E("span", {}, _("Auto-apply best strategy"))],
),
],
);
controlsGrid.append(autoApplyGroup);
const dpiDetectionBanner = E("div", {
id: "tachyon-fuzzer-dpi-banner",
style:
"display: none; padding: 10px 14px; border-radius: 6px; font-size: 12px; line-height: 1.4;",
});
const progressContainer = E(
"div",
{
style:
"display: none; flex-direction: column; gap: 6px; padding: 10px 14px; background: var(--background-color-secondary, rgba(0,0,0,0.18)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.08));",
},
[
E(
"div",
{
style:
"display: flex; justify-content: space-between; font-size: 12px; font-weight: 600;",
},
[
E(
"span",
{ id: "tachyon-fuzzer-status-text" },
_("Benchmarking in progress..."),
),
E("span", { id: "tachyon-fuzzer-progress-pct" }, "0%"),
],
),
E(
"div",
{
style:
"width: 100%; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden;",
},
[
E("div", {
id: "tachyon-fuzzer-progress-bar",
style:
"height: 100%; width: 0%; background: #007bff; transition: width 0.3s ease;",
}),
],
),
E(
"div",
{
id: "tachyon-fuzzer-current-strategy",
style: "font-size: 11px; opacity: 0.75; font-family: monospace;",
},
"",
),
],
);
const applyBestBtn = renderButton({
text: _("\u{1F3C6} Apply Best Match"),
classNames: ["cbi-button-save"],
disabled: true,
onClick: () => handleApplyBest(),
});
const filterToolbar = E(
"div",
{
style:
"display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px;",
},
[
E("div", { style: "display: flex; gap: 6px; align-items: center;" }, [
E(
"span",
{ style: "font-size: 11px; opacity: 0.7; margin-right: 4px;" },
_("Filter:"),
),
(() => {
const bAll = E(
"button",
{
type: "button",
class: "cbi-button cbi-button-action",
style: "padding: 2px 10px; font-size: 11px;",
},
_("All"),
);
const bOk = E(
"button",
{
type: "button",
class: "cbi-button cbi-button-neutral",
style: "padding: 2px 10px; font-size: 11px;",
},
_("\u2705 Working only"),
);
const bFast = E(
"button",
{
type: "button",
class: "cbi-button cbi-button-neutral",
style: "padding: 2px 10px; font-size: 11px;",
},
_("\u26A1 Fast (>1MB/s)"),
);
const setFilter = (f, btn) => {
resultFilter = f;
[bAll, bOk, bFast].forEach(
(b) => (b.className = "cbi-button cbi-button-neutral"),
);
btn.className = "cbi-button cbi-button-action";
if (currentState) renderResults(currentState);
};
bAll.addEventListener("click", () => setFilter("all", bAll));
bOk.addEventListener("click", () => setFilter("success", bOk));
bFast.addEventListener("click", () => setFilter("fast", bFast));
return E("div", { style: "display: flex; gap: 4px;" }, [
bAll,
bOk,
bFast,
]);
})(),
]),
E("div", { style: "display: flex; gap: 8px;" }, [applyBestBtn]),
],
);
const resultsContainer = E("div", {
style:
"max-height: 380px; overflow-y: auto; overflow-x: auto; -webkit-overflow-scrolling: touch; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; width: 100%; box-sizing: border-box;",
});
const tableEl = E(
"table",
{
class: "table",
style:
"width: 100%; min-width: 620px; margin: 0; font-size: 12px; text-align: left; border-collapse: collapse;",
},
[
E(
"thead",
{
style:
"background: var(--background-color-secondary, rgba(0,0,0,0.25)); position: sticky; top: 0; z-index: 2;",
},
[
E("tr", {}, [
E("th", { style: "padding: 8px 10px; width: 35px;" }, "#"),
E("th", { style: "padding: 8px 10px; width: 75px;" }, _("Engine")),
E(
"th",
{ style: "padding: 8px 10px;" },
_("Strategy & Parameters"),
),
E(
"th",
{ style: "padding: 8px 10px; width: 95px;" },
_("Status / Suite"),
),
E("th", { style: "padding: 8px 10px; width: 65px;" }, _("TTFB")),
E("th", { style: "padding: 8px 10px; width: 75px;" }, _("Speed")),
E("th", { style: "padding: 8px 10px; width: 80px;" }, _("Score")),
E(
"th",
{ style: "padding: 8px 10px; width: 85px; text-align: center;" },
_("Actions"),
),
]),
],
),
E("tbody", { id: "tachyon-fuzzer-results-tbody" }, [
E(
"tr",
{},
E(
"td",
{
colSpan: 8,
style: "padding: 24px; text-align: center; opacity: 0.6;",
},
_(
'No benchmark results yet. Configure options and click "Start Benchmark".',
),
),
),
]),
],
);
resultsContainer.appendChild(tableEl);
const startBtn = renderButton({
text: _("\u{1F680} Start Benchmark"),
classNames: ["cbi-button-action"],
onClick: () => handleToggleRun(),
});
const footerActions = E(
"div",
{
style:
"display: flex; justify-content: space-between; align-items: center; padding-top: 6px;",
},
[
startBtn,
renderButton({
text: _("Close"),
classNames: ["cbi-button-neutral"],
onClick: () => {
stopPolling();
ui.hideModal();
},
}),
],
);
tabContentBenchmark.append(
controlsGrid,
dpiDetectionBanner,
progressContainer,
filterToolbar,
resultsContainer,
footerActions,
);
const renderChipGroup = (title, items, onAdd, onRemove) => {
const input = E("input", {
type: "text",
class: "cbi-input-text",
placeholder: "+ add",
style: "width: 80px; padding: 2px 6px; font-size: 11px;",
});
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const v = input.value.trim();
if (v) {
onAdd(v);
input.value = "";
renderPatternsTab();
}
}
});
const chips = items.map((item, idx) => {
const chip = E(
"span",
{
style:
"display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: var(--background-color-secondary, rgba(255,255,255,0.08)); border: 1px solid var(--border-color, rgba(255,255,255,0.12)); border-radius: 4px; font-size: 11px; font-family: monospace;",
},
[
E("span", {}, String(item)),
(() => {
const del = E(
"span",
{
style:
"cursor: pointer; opacity: 0.6; font-size: 12px; margin-left: 2px;",
},
"\xD7",
);
del.addEventListener("click", () => {
onRemove(idx);
renderPatternsTab();
});
return del;
})(),
],
);
return chip;
});
return E(
"div",
{ style: "display: flex; flex-direction: column; gap: 4px;" },
[
E(
"span",
{ style: "font-size: 11px; font-weight: 600; opacity: 0.8;" },
title,
),
E(
"div",
{
style:
"display: flex; flex-wrap: wrap; gap: 6px; align-items: center;",
},
[...chips, input],
),
],
);
};
const renderPatternsTab = () => {
tabContentPatterns.replaceChildren();
const z2 = patternsConfig.zapret2;
const z1 = patternsConfig.zapret;
const bd = patternsConfig.byedpi;
const z2Card = E(
"div",
{
style:
"padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;",
},
[
E(
"div",
{ style: "font-size: 13px; font-weight: bold;" },
"\u26A1 Zapret v2 (nfqws2) Matrix Patterns",
),
renderChipGroup(
_("Split Positions (pos)"),
z2.splits,
(v) => z2.splits.push(v),
(i) => z2.splits.splice(i, 1),
),
renderChipGroup(
_("Fooling Methods (fooling)"),
z2.foolings,
(v) => z2.foolings.push(v),
(i) => z2.foolings.splice(i, 1),
),
renderChipGroup(
_("TTL Values (ttl)"),
z2.ttls,
(v) => z2.ttls.push(Number(v) || 4),
(i) => z2.ttls.splice(i, 1),
),
renderChipGroup(
_("Sequence Overlaps (seqovl)"),
z2.seqovls,
(v) => z2.seqovls.push(v),
(i) => z2.seqovls.splice(i, 1),
),
renderChipGroup(
_("TCP Window Sizes (wsize)"),
z2.wsizes,
(v) => z2.wsizes.push(v),
(i) => z2.wsizes.splice(i, 1),
),
],
);
const z1Card = E(
"div",
{
style:
"padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;",
},
[
E(
"div",
{ style: "font-size: 13px; font-weight: bold;" },
"\u26A1 Zapret v1 (nfqws) Matrix Patterns",
),
renderChipGroup(
_("Split Modes (dpi-desync)"),
z1.split_modes,
(v) => z1.split_modes.push(v),
(i) => z1.split_modes.splice(i, 1),
),
renderChipGroup(
_("Split Positions (dpi-desync-split-pos)"),
z1.splits,
(v) => z1.splits.push(v),
(i) => z1.splits.splice(i, 1),
),
renderChipGroup(
_("Fooling Methods (dpi-desync-fooling)"),
z1.foolings,
(v) => z1.foolings.push(v),
(i) => z1.foolings.splice(i, 1),
),
renderChipGroup(
_("TTL Values (dpi-desync-ttl)"),
z1.ttls,
(v) => z1.ttls.push(Number(v) || 4),
(i) => z1.ttls.splice(i, 1),
),
],
);
const bdCard = E(
"div",
{
style:
"padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 10px;",
},
[
E(
"div",
{ style: "font-size: 13px; font-weight: bold;" },
"\u26A1 ByeDPI (ciadpi) Matrix Patterns",
),
renderChipGroup(
_("Split Values (-s)"),
bd.splits,
(v) => bd.splits.push(v),
(i) => bd.splits.splice(i, 1),
),
renderChipGroup(
_("Disorder Values (-d)"),
bd.disorders,
(v) => bd.disorders.push(v),
(i) => bd.disorders.splice(i, 1),
),
renderChipGroup(
_("OOB Values (-o)"),
bd.oobs,
(v) => bd.oobs.push(v),
(i) => bd.oobs.splice(i, 1),
),
renderChipGroup(
_("Auto Modes (--auto)"),
bd.autos,
(v) => bd.autos.push(v),
(i) => bd.autos.splice(i, 1),
),
renderChipGroup(
_("TLS-Rec Bounds (--tlsrec)"),
bd.tlsrecs,
(v) => bd.tlsrecs.push(v),
(i) => bd.tlsrecs.splice(i, 1),
),
renderChipGroup(
_("IP Frag Bounds (--ip-frag)"),
bd.ipfrags,
(v) => bd.ipfrags.push(v),
(i) => bd.ipfrags.splice(i, 1),
),
],
);
const patternsFooter = E(
"div",
{
style:
"display: flex; justify-content: space-between; align-items: center; padding-top: 6px;",
},
[
renderButton({
text: _("\u{1F4BE} Save Custom Patterns"),
classNames: ["cbi-button-save"],
onClick: async () => {
const res =
await TachyonShellMethods.saveFuzzerPatterns(patternsConfig);
if (res.success) {
showToast(_("Patterns saved successfully!"), "success");
} else {
showToast(_("Failed to save patterns"), "error");
}
},
}),
renderButton({
text: _("\u{1F504} Reset to Factory Defaults"),
classNames: ["cbi-button-neutral"],
onClick: async () => {
const res = await TachyonShellMethods.resetFuzzerPatterns();
if (res.success && res.data?.patterns) {
patternsConfig = res.data.patterns;
renderPatternsTab();
showToast(_("Patterns reset to defaults!"), "success");
}
},
}),
],
);
tabContentPatterns.append(z2Card, z1Card, bdCard, patternsFooter);
};
const renderCustomTab = () => {
tabContentCustom.replaceChildren();
const nameInput = E("input", {
type: "text",
class: "cbi-input-text",
placeholder: _("Strategy Name"),
style: "flex: 1 1 180px; min-width: 140px;",
});
const engineSel = E(
"select",
{
class: "cbi-input-select",
style: "flex: 0 1 140px; min-width: 120px;",
},
[
E("option", { value: "zapret2" }, "Zapret v2"),
E("option", { value: "zapret" }, "Zapret v1"),
E("option", { value: "byedpi" }, "ByeDPI"),
],
);
const argsInput = E("input", {
type: "text",
class: "cbi-input-text",
placeholder: _("Command-line arguments string..."),
style: "flex: 2 1 240px; min-width: 180px; font-family: monospace;",
});
const addBtn = renderButton({
text: _("+ Add Strategy"),
classNames: ["cbi-button-action"],
onClick: async () => {
const name = nameInput.value.trim();
const eng = engineSel.value;
const args = argsInput.value.trim();
if (!name || !args) {
showToast(_("Please specify strategy name and arguments"), "error");
return;
}
patternsConfig.custom_strategies =
patternsConfig.custom_strategies || [];
patternsConfig.custom_strategies.push({
id: `custom_${Date.now()}`,
name,
engine: eng,
args,
description: _("User custom strategy"),
});
await TachyonShellMethods.saveFuzzerPatterns(patternsConfig);
showToast(_("Custom strategy added!"), "success");
nameInput.value = "";
argsInput.value = "";
renderCustomTab();
},
});
const addCard = E(
"div",
{
style:
"padding: 12px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 8px;",
},
[
E(
"div",
{ style: "font-size: 12px; font-weight: 600;" },
_("Add Custom DPI Strategy"),
),
E(
"div",
{
style:
"display: flex; gap: 8px; flex-wrap: wrap; align-items: center;",
},
[nameInput, engineSel, argsInput, addBtn],
),
],
);
const customList = patternsConfig.custom_strategies || [];
const listCard = E(
"div",
{
style:
"border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; overflow: hidden;",
},
[
E(
"table",
{ class: "table", style: "width: 100%; margin: 0; font-size: 12px;" },
[
E("thead", {}, [
E("tr", {}, [
E("th", { style: "padding: 8px;" }, _("Name")),
E("th", { style: "padding: 8px; width: 90px;" }, _("Engine")),
E("th", { style: "padding: 8px;" }, _("Arguments")),
E(
"th",
{
style: "padding: 8px; width: 80px; text-align: center;",
},
_("Actions"),
),
]),
]),
E(
"tbody",
{},
customList.length === 0
? [
E(
"tr",
{},
E(
"td",
{
colSpan: 4,
style:
"padding: 20px; text-align: center; opacity: 0.6;",
},
_("No custom strategies added yet."),
),
),
]
: customList.map((cs, idx) => {
return E("tr", {}, [
E(
"td",
{ style: "padding: 8px; font-weight: 600;" },
cs.name,
),
E(
"td",
{
style:
"padding: 8px; font-family: monospace; font-size: 11px;",
},
cs.engine,
),
E(
"td",
{
style:
"padding: 8px; font-family: monospace; font-size: 11px; word-break: break-all;",
},
cs.args,
),
E(
"td",
{
style: "padding: 8px; text-align: center;",
},
[
(() => {
const delBtn = E(
"button",
{
type: "button",
class: "cbi-button cbi-button-reset",
style: "padding: 2px 8px; font-size: 11px;",
},
_("Delete"),
);
delBtn.addEventListener("click", async () => {
patternsConfig.custom_strategies.splice(idx, 1);
await TachyonShellMethods.saveFuzzerPatterns(
patternsConfig,
);
showToast(_("Strategy deleted"), "success");
renderCustomTab();
});
return delBtn;
})(),
],
),
]);
}),
),
],
),
],
);
tabContentCustom.append(addCard, listCard);
};
const aiPromptInput = E("input", {
type: "text",
class: "cbi-input-text",
placeholder: _(
'Optional context (e.g. "Rostelecom, YouTube 4K buffering is slow")...',
),
style: "flex: 1 1 240px; min-width: 180px; font-size: 12px;",
});
const aiSynthesizeBtn = renderButton({
text: _("\u{1F9E0} Synthesize with AI"),
classNames: ["cbi-button-action"],
onClick: () => handleAiSynthesize(),
});
const aiAnalysisContainer = E(
"div",
{
id: "tachyon-fuzzer-ai-analysis",
style:
"display: none; padding: 12px; background: rgba(0, 123, 255, 0.08); border-left: 3px solid #007bff; border-radius: 4px; font-size: 12px; line-height: 1.4;",
},
[
E(
"div",
{
style:
"font-weight: bold; margin-bottom: 6px; display: flex; align-items: center; gap: 6px;",
},
[
E("span", {}, "\u{1F9E0}"),
E("span", {}, _("AI Diagnostics & Strategy Rationale")),
],
),
E(
"div",
{ id: "tachyon-fuzzer-ai-analysis-text", style: "opacity: 0.9;" },
"",
),
],
);
const aiSynthesizerCard = E(
"div",
{
style:
"padding: 14px; background: var(--background-color-secondary, rgba(0,0,0,0.15)); border-radius: 6px; border: 1px solid var(--border-color, rgba(255,255,255,0.1)); display: flex; flex-direction: column; gap: 12px;",
},
[
E(
"div",
{
style:
"font-size: 13px; font-weight: bold; display: flex; align-items: center; justify-content: space-between;",
},
[
E(
"span",
{},
"\u{1F9E0} " + _("AI DPI Engineer & Strategy Synthesizer"),
),
E(
"span",
{ style: "font-size: 11px; opacity: 0.6; font-weight: normal;" },
_("RAG Knowledge Base + Live ISP Probe Context"),
),
],
),
E(
"div",
{ style: "font-size: 12px; opacity: 0.8;" },
_(
"Automatically runs a diagnostic probe against the target service, queries the built-in DPI Knowledge Base, and synthesizes 3-5 custom bypass strategies tailored to your ISP.",
),
),
E("div", { style: "display: flex; gap: 8px; align-items: center;" }, [
aiPromptInput,
aiSynthesizeBtn,
]),
aiAnalysisContainer,
],
);
tabContentAi.append(aiSynthesizerCard);
const renderHistoryTab = async () => {
tabContentHistory.replaceChildren();
const historyHeader = E(
"div",
{
style:
"display: flex; justify-content: space-between; align-items: center;",
},
[
E(
"div",
{ style: "font-size: 13px; font-weight: bold;" },
"\u{1F4CB} " + _("Benchmark History"),
),
E("div", { style: "display: flex; gap: 8px;" }, [
renderButton({
text: _("\u{1F504} Refresh"),
classNames: ["cbi-button-neutral"],
onClick: () => renderHistoryTab(),
}),
renderButton({
text: _("\u{1F5D1}\uFE0F Clear History"),
classNames: ["cbi-button-reset"],
onClick: async () => {
await TachyonShellMethods.clearFuzzerHistory();
showToast(_("History cleared"), "success");
renderHistoryTab();
},
}),
]),
],
);
const historyContainer = E("div", {
style:
"border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; overflow: hidden;",
});
try {
const res = await TachyonShellMethods.getFuzzerHistory(20);
const entries = res.data?.entries || [];
if (entries.length === 0) {
historyContainer.appendChild(
E(
"div",
{
style:
"padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;",
},
_("No benchmark history yet. Run a benchmark to see results here."),
),
);
} else {
const table = E(
"table",
{
class: "table",
style: "width: 100%; margin: 0; font-size: 12px;",
},
[
E("thead", {}, [
E("tr", {}, [
E("th", { style: "padding: 8px;" }, _("Date")),
E("th", { style: "padding: 8px;" }, _("Engine")),
E("th", { style: "padding: 8px;" }, _("Target")),
E("th", { style: "padding: 8px;" }, _("DPI Type")),
E("th", { style: "padding: 8px;" }, _("Best Strategy")),
E("th", { style: "padding: 8px; width: 60px;" }, _("Score")),
E("th", { style: "padding: 8px; width: 70px;" }, _("Working")),
]),
]),
E(
"tbody",
{},
entries.reverse().map((entry) => {
const date = new Date(entry.timestamp * 1e3);
const dateStr = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
const dpiType = entry.dpi_detection
? `${entry.dpi_detection.type} (${entry.dpi_detection.confidence}%)`
: "\u2014";
const bestName = entry.best_strategy
? entry.best_strategy.name
: "\u2014";
const bestScore = entry.best_strategy
? String(entry.best_strategy.score)
: "0";
const working = `${entry.working_count}/${entry.total_tested}`;
return E("tr", {}, [
E("td", { style: "padding: 8px;" }, dateStr),
E(
"td",
{
style: "padding: 8px; font-family: monospace;",
},
entry.engine,
),
E("td", { style: "padding: 8px;" }, entry.target),
E("td", { style: "padding: 8px;" }, dpiType),
E(
"td",
{
style:
"padding: 8px; font-size: 11px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;",
},
bestName,
),
E(
"td",
{ style: "padding: 8px; font-weight: bold;" },
bestScore,
),
E("td", { style: "padding: 8px;" }, working),
]);
}),
),
],
);
historyContainer.appendChild(table);
}
} catch {
historyContainer.appendChild(
E(
"div",
{
style:
"padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;",
},
_("Failed to load history"),
),
);
}
tabContentHistory.append(historyHeader, historyContainer);
};
const renderResults = (state) => {
const tbody = document.getElementById("tachyon-fuzzer-results-tbody");
if (!tbody) return;
tbody.replaceChildren();
let list = state.results || [];
if (resultFilter === "success") {
list = list.filter((r) => r.success);
} else if (resultFilter === "fast") {
list = list.filter((r) => r.success && r.speed_kbps > 1024);
}
if (list.length === 0) {
tbody.appendChild(
E(
"tr",
{},
E(
"td",
{
colSpan: 8,
style: "padding: 24px; text-align: center; opacity: 0.6;",
},
state.running
? _("Running benchmarks...")
: _("No strategies match the active filter."),
),
),
);
return;
}
list.forEach((item, idx) => {
const isBest = state.best_strategy && state.best_strategy.id === item.id;
const statusBadge = `<span class="label ${item.success ? "badge-success" : "badge-danger"}" style="font-size: 10px; padding: 2px 6px; border-radius: 3px;">${item.success ? `HTTP ${item.http_code}` : "DROP"}</span>`;
let subProbesHtml = "";
if (item.sub_probes && item.sub_probes.length > 1) {
const passedSub = item.sub_probes.filter((p) => p.success).length;
const totalSub = item.sub_probes.length;
subProbesHtml = `<div style="font-size: 10px; opacity: 0.75; margin-top: 2px;">${passedSub}/${totalSub} endpoints OK</div>`;
}
let errorHtml = "";
if (!item.success && item.error) {
errorHtml = `<div style="font-size: 10px; color: #dc3545; opacity: 0.85; margin-top: 2px; max-width: 140px; word-break: break-word;" title="${item.error}">${item.error}</div>`;
}
let badgeHtml = "";
if (item.badge) {
badgeHtml = `<span style="font-size: 10px; margin-left: 6px; font-weight: bold; color: ${isBest ? "#28a745" : "#17a2b8"};">${item.badge}</span>`;
}
const tr = E(
"tr",
{
style: `border-bottom: 1px solid var(--border-color, rgba(255,255,255,0.06)); ${isBest ? "background: rgba(40, 167, 69, 0.08);" : ""}`,
},
[
E("td", { style: "padding: 8px 10px;" }, String(idx + 1)),
E(
"td",
{
style:
"padding: 8px 10px; font-family: monospace; font-size: 11px;",
},
item.engine,
),
E("td", { style: "padding: 8px 10px;" }, [
E("div", { style: "font-weight: 600;" }, [
E("span", {}, item.name),
E("span", { innerHTML: badgeHtml }),
]),
E(
"div",
{
style:
"font-size: 10px; opacity: 0.7; font-family: monospace; word-break: break-all; margin-top: 2px;",
},
item.args,
),
]),
E(
"td",
{ style: "padding: 8px 10px;" },
E("div", { innerHTML: statusBadge + subProbesHtml + errorHtml }),
),
E(
"td",
{ style: "padding: 8px 10px;" },
item.success ? `${item.ttfb_ms}ms` : "\u2014",
),
E(
"td",
{ style: "padding: 8px 10px;" },
item.success
? `${(item.speed_kbps / 1024).toFixed(1)}MB/s`
: "\u2014",
),
E(
"td",
{ style: "padding: 8px 10px; font-weight: bold;" },
item.success ? String(item.score) : "0",
),
E(
"td",
{ style: "padding: 8px 10px; text-align: center;" },
E(
"div",
{ style: "display: flex; gap: 4px; justify-content: center;" },
[
renderButton({
text: _("Apply"),
classNames: ["cbi-button-action"],
disabled: !item.success,
onClick: () => handleApplySingle(item),
}),
],
),
),
],
);
tbody.appendChild(tr);
});
};
const updateProgressUI = (state) => {
const statusText = document.getElementById("tachyon-fuzzer-status-text");
const pctText = document.getElementById("tachyon-fuzzer-progress-pct");
const progressBar = document.getElementById("tachyon-fuzzer-progress-bar");
const currentStratEl = document.getElementById(
"tachyon-fuzzer-current-strategy",
);
const applyBestBtn2 = document.getElementById(
"tachyon-fuzzer-apply-best-btn",
);
if (progressContainer) {
progressContainer.style.display =
state.running || state.results?.length ? "flex" : "none";
}
if (pctText) pctText.innerText = `${state.progress_pct}%`;
if (progressBar) progressBar.style.width = `${state.progress_pct}%`;
if (statusText) {
if (state.running) {
statusText.innerText = `${_("Testing strategy")} ${state.current_index} / ${state.total_strategies}...`;
} else if (state.finished_at > 0) {
statusText.innerText = state.best_strategy
? _("\u2705 Benchmark completed! Optimal strategy identified.")
: _(
"\u26A0\uFE0F Benchmark completed. No working bypass found for this target.",
);
}
}
if (currentStratEl && state.current_strategy) {
currentStratEl.innerText = `Testing: [${state.current_strategy.name}] -> ${state.current_strategy.args}`;
} else if (currentStratEl && !state.running) {
currentStratEl.innerText = "";
}
if (state.best_strategy && applyBestBtn2) {
applyBestBtn2.disabled = false;
}
};
let lastRenderedCount = 0;
let lastRenderedFinishedAt = 0;
const pollStatus = async () => {
if (isPolling) return;
isPolling = true;
try {
const res = await TachyonShellMethods.getFuzzerStatus();
if (res.success && res.data) {
currentState = res.data;
isRunning = res.data.running;
updateProgressUI(res.data);
const resultCount = res.data.results?.length || 0;
const finishedAt = res.data.finished_at || 0;
if (
resultCount !== lastRenderedCount ||
finishedAt !== lastRenderedFinishedAt
) {
renderResults(res.data);
lastRenderedCount = resultCount;
lastRenderedFinishedAt = finishedAt;
}
if (!isRunning) {
stopPolling();
startBtn.innerText = _("\u{1F680} Start Benchmark");
startBtn.disabled = false;
if (autoApplyEnabled && currentState?.best_strategy) {
try {
const applyRes =
await TachyonShellMethods.autoApplyFuzzerStrategy(
selectedRuleSection || void 0,
);
if (applyRes.success) {
showToast(
`${_("\u2705 Auto-applied best strategy")}: ${currentState.best_strategy.name}`,
"success",
);
} else {
showToast(
`${_("Auto-apply failed")}: ${applyRes.error || _("Unknown error")}`,
"error",
);
}
} catch {
showToast(_("Auto-apply failed"), "error");
}
}
}
}
} catch {
} finally {
isPolling = false;
}
};
const startPolling = () => {
stopPolling();
pollingInterval = setInterval(pollStatus, 600);
pollStatus();
};
const stopPolling = () => {
if (pollingInterval) {
clearInterval(pollingInterval);
pollingInterval = null;
}
};
const updateDpiBanner = (detection) => {
const banner = document.getElementById("tachyon-fuzzer-dpi-banner");
if (!banner) return;
if (!detection) {
banner.style.display = "none";
return;
}
const typeColors = {
rst: {
bg: "rgba(220, 53, 69, 0.12)",
border: "#dc3545",
icon: "\u{1F534}",
},
throttle: {
bg: "rgba(255, 193, 7, 0.12)",
border: "#ffc107",
icon: "\u{1F7E1}",
},
dns_block: {
bg: "rgba(23, 162, 184, 0.12)",
border: "#17a2b8",
icon: "\u{1F535}",
},
unknown: {
bg: "rgba(108, 117, 125, 0.12)",
border: "#6c757d",
icon: "\u26AA",
},
none: {
bg: "rgba(40, 167, 69, 0.12)",
border: "#28a745",
icon: "\u{1F7E2}",
},
};
const typeLabels = {
rst: "TCP Reset Injection",
throttle: "Throttling / Deep Inspection",
dns_block: "DNS Blocking",
unknown: "Unknown DPI Pattern",
none: "No Blocking Detected",
};
const colors = typeColors[detection.type] || typeColors.unknown;
const label = typeLabels[detection.type] || detection.type;
banner.style.display = "block";
banner.style.background = colors.bg;
banner.style.borderLeft = `3px solid ${colors.border}`;
banner.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: start;">
<div>
<span style="font-weight: bold;">${colors.icon} ${_("DPI Detected")}: ${label}</span>
<span style="opacity: 0.7; margin-left: 8px;">${detection.confidence}% ${_("confidence")}</span>
</div>
${detection.recommended_engines.length > 0 ? `<div style="font-size: 11px; opacity: 0.7;">${_("Recommended")}: ${detection.recommended_engines.join(", ")}</div>` : ""}
</div>
<div style="margin-top: 4px; opacity: 0.8; font-size: 11px;">${detection.details}</div>
`;
};
const handleToggleRun = async () => {
if (isRunning) {
await TachyonShellMethods.stopFuzzer();
showToast(_("Benchmark stopped"), "success");
stopPolling();
pollStatus();
return;
}
startBtn.disabled = true;
startBtn.innerText = _("\u{1F6D1} Stop Benchmark");
progressContainer.style.display = "flex";
const statusTextEl = document.getElementById("tachyon-fuzzer-status-text");
const progressBarEl = document.getElementById(
"tachyon-fuzzer-progress-bar",
);
const currentStratEl = document.getElementById(
"tachyon-fuzzer-current-strategy",
);
if (statusTextEl)
statusTextEl.innerText = _(
"\u{1F680} Initializing benchmark in sandbox...",
);
if (progressBarEl) progressBarEl.style.width = "3%";
if (currentStratEl)
currentStratEl.innerText = _(
"Preparing strategies and isolated nftables queue...",
);
try {
const dpiRes = await TachyonShellMethods.detectFuzzerDpi(
selectedTarget,
selectedTarget === "custom" ? customUrl : void 0,
);
if (dpiRes.success && dpiRes.data) {
currentDpiDetection = dpiRes.data;
updateDpiBanner(dpiRes.data);
}
} catch {}
const tbody = document.getElementById("tachyon-fuzzer-results-tbody");
if (tbody) {
tbody.replaceChildren(
E(
"tr",
{},
E(
"td",
{
colSpan: 8,
style: "padding: 24px; text-align: center; opacity: 0.85;",
},
[
E("span", { style: "margin-right: 8px;" }, "\u23F3"),
E(
"span",
{},
_("Testing bypass strategies in isolated sandbox..."),
),
],
),
),
);
}
const res = await TachyonShellMethods.startFuzzer(
selectedEngine,
selectedTarget,
customUrl,
selectedRuleSection,
"",
selectedMode,
);
if (res.success) {
showToast(_("Strategy benchmark started"), "success");
startBtn.disabled = false;
isRunning = true;
lastRenderedCount = 0;
lastRenderedFinishedAt = 0;
startPolling();
} else {
const errMsg = !res.success ? res.error : _("Unknown error");
showToast(`${_("Failed to start benchmark")}: ${errMsg}`, "error");
startBtn.disabled = false;
startBtn.innerText = _("\u{1F680} Start Benchmark");
progressContainer.style.display = "none";
}
};
const handleAiSynthesize = async () => {
const userPrompt = aiPromptInput.value.trim();
aiSynthesizeBtn.disabled = true;
aiSynthesizeBtn.innerText = _("\u{1F9E0} Synthesizing...");
const analysisBox = document.getElementById("tachyon-fuzzer-ai-analysis");
const analysisText = document.getElementById(
"tachyon-fuzzer-ai-analysis-text",
);
if (analysisBox) analysisBox.style.display = "none";
try {
showToast(_("Consulting DPI Knowledge Base & AI..."), "success");
const res = await TachyonShellMethods.fuzzerAiSynthesize(
selectedEngine,
selectedTarget,
customUrl,
userPrompt,
);
if (res.success && res.data) {
showToast(
_("AI successfully synthesized custom strategies!"),
"success",
);
if (analysisBox && analysisText && res.data.analysis) {
analysisText.innerText = res.data.analysis;
analysisBox.style.display = "block";
}
activeTab2 = "benchmark";
updateTabVisibility();
startBtn.disabled = true;
startBtn.innerText = _("\u{1F6D1} Stop Benchmark");
const startRes = await TachyonShellMethods.startFuzzer(
selectedEngine,
selectedTarget,
customUrl,
selectedRuleSection,
);
if (startRes.success) {
isRunning = true;
startPolling();
}
} else {
const errMsg = !res.success ? res.error : _("AI synthesis failed");
showToast(`${_("AI Synthesis Error")}: ${errMsg}`, "error");
}
} catch {
showToast(_("Failed to communicate with AI provider"), "error");
} finally {
aiSynthesizeBtn.disabled = false;
aiSynthesizeBtn.innerText = _("\u{1F9E0} Synthesize with AI");
}
};
const handleApplySingle = async (item) => {
const res = await TachyonShellMethods.applyFuzzerStrategy(
item.engine,
item.args,
selectedRuleSection,
);
if (res.success) {
showToast(
`${_("Applied")} "${item.name}" -> ${selectedRuleSection || _("Global Default")}`,
"success",
);
} else {
showToast(_("Failed to apply strategy"), "error");
}
};
const handleApplyBest = async () => {
if (!currentState?.best_strategy) return;
await handleApplySingle(currentState.best_strategy);
};
TachyonShellMethods.getFuzzerStrategies()
.then((res) => {
if (res.success && res.data?.available_engines) {
const av = res.data.available_engines;
const opts = engineSelect.options;
for (let i = 0; i < opts.length; i++) {
const opt = opts[i];
if (opt.value === "zapret2") {
opt.text = av.zapret2
? _("Zapret v2 (nfqws2)") + " \u2014 " + _("Installed")
: _("Zapret v2 (nfqws2)") + " \u2014 " + _("Not installed");
if (!av.zapret2) opt.disabled = true;
} else if (opt.value === "zapret") {
opt.text = av.zapret
? _("Zapret v1 (nfqws)") + " \u2014 " + _("Installed")
: _("Zapret v1 (nfqws)") + " \u2014 " + _("Not installed");
if (!av.zapret) opt.disabled = true;
} else if (opt.value === "byedpi") {
opt.text = av.byedpi
? _("ByeDPI (ciadpi)") + " \u2014 " + _("Installed")
: _("ByeDPI (ciadpi)") + " \u2014 " + _("Not installed");
if (!av.byedpi) opt.disabled = true;
}
}
}
})
.catch(() => {});
TachyonShellMethods.getFuzzerPatterns()
.then((res) => {
if (res.success && res.data?.patterns) {
patternsConfig = res.data.patterns;
}
})
.catch(() => {});
modalContainer.append(
tabBar,
tabContentBenchmark,
tabContentPatterns,
tabContentCustom,
tabContentAi,
tabContentHistory,
);
pollStatus();
ui.showModal(_("\u26A1 Strategy Fuzzer & Auto-Tuner"), modalContainer);
}
// src/tachyon/tabs/diagnostic/partials/renderSystemInfo.ts
function renderSystemInfo({ items }) {
return E(
"div",
{ class: "tachyon_diagnostic-page__right-bar__system-info" },
[
E(
"b",
{ class: "tachyon_diagnostic-page__right-bar__system-info__title" },
_("System information"),
),
...items.map((item) => {
const tagClass = [
"tachyon_diagnostic-page__right-bar__system-info__row__tag",
...insertIf(item.tag?.kind === "neutral", [
"tachyon_diagnostic-page__right-bar__system-info__row__tag--neutral",
]),
...insertIf(item.tag?.kind === "warning", [
"tachyon_diagnostic-page__right-bar__system-info__row__tag--warning",
]),
...insertIf(item.tag?.kind === "success", [
"tachyon_diagnostic-page__right-bar__system-info__row__tag--success",
]),
]
.filter(Boolean)
.join(" ");
return E(
"div",
{ class: "tachyon_diagnostic-page__right-bar__system-info__row" },
[
E("b", {}, item.key),
E("div", {}, [
E("span", {}, item.value),
E("span", { class: tagClass }, item?.tag?.label),
]),
],
);
}),
],
);
}
// src/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",
),
}),
renderButton({
classNames: ["cbi-button-save"],
icon: renderSendIcon24,
text: _("Telegram Channel"),
onClick: () =>
window.open(
"https://t.me/tachyon_proxy",
"_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 handleOpenStrategyFuzzer() {
getConfigSections()
.then((sections) => {
const ruleNames = sections
.filter((s) => s[".type"] === "section" || s[".type"] === "rule")
.map((s) => s.name || s[".name"])
.filter((n) => Boolean(n));
renderStrategyFuzzerModal(ruleNames);
})
.catch(() => {
renderStrategyFuzzerModal([]);
});
}
function handleRestoreNativeInternet() {
ui.showModal(
_("Restore Native Internet"),
E("div", {}, [
E(
"p",
{},
_(
"This action will stop Tachyon, restore standard dnsmasq/DNS, remove all proxy/anti-censorship firewall rules and restore direct Internet connection through your ISP.",
),
),
E(
"div",
{
class: "right",
style:
"display: flex; justify-content: flex-end; gap: 8px; margin-top: 15px;",
},
[
renderButton({
classNames: ["cbi-button-neutral"],
text: _("Cancel"),
onClick: () => ui.hideModal(),
}),
renderButton({
classNames: ["cbi-button-reset"],
text: "\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,
},
strategyFuzzer: {
loading: false,
visible: true,
onClick: handleOpenStrategyFuzzer,
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";
case "tailscale":
return "Tailscale";
default:
return String(component);
}
}
function getComponentCurrentVersion(component) {
const sys = store.get().diagnosticsSystemInfo;
switch (component) {
case "tachyon":
return sys.tachyon_version;
case "sing_box":
return sys.sing_box_version;
case "zapret":
return sys.zapret_version;
case "zapret2":
return sys.zapret2_version;
case "byedpi":
return sys.byedpi_version;
case "tailscale":
return sys.tailscale_version;
default:
return void 0;
}
}
var updatesLifecycleRegistered = false;
var updatesControllerInitialized = false;
var updatesMounted = false;
var updatesMountId = 0;
var pageUnloading2 = false;
var preserveCheckResultsOnNextMount = false;
var componentUpdateCheckCacheResolved = false;
var componentUpdateCheckCacheSnapshot = null;
var componentUpdateCheckCachePromise = null;
var componentActionStateUnsubscribe = null;
var componentActionStateRefreshPromise = null;
var followedComponentJobs = /* @__PURE__ */ new Set();
var handledComponentJobs = /* @__PURE__ */ new Set();
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";
case "tailscale":
return "tailscaleInstall";
}
}
function getComponentInstallAction(component) {
const isInstalled = !isNotInstalled(getComponentCurrentVersion(component));
const key = getComponentInstallKey(component);
return getInstallAction(component, key, isInstalled);
}
function getInstalledUpdateActions(
component,
checkKey,
installKey,
installed = true,
) {
if (!installed) {
return [];
}
const actions = [getCheckAction(component, checkKey)];
if (shouldShowInstallAfterCheck(component)) {
actions.push(getInstallAction(component, installKey, true));
}
return actions;
}
function getComponentBackupVersion(component) {
const sys = store.get().diagnosticsSystemInfo;
switch (component) {
case "sing_box":
return sys.sing_box_backup_version || "";
case "zapret":
return sys.zapret_backup_version || "";
case "zapret2":
return sys.zapret2_backup_version || "";
case "byedpi":
return sys.byedpi_backup_version || "";
case "tailscale":
return sys.tailscale_backup_version || "";
default:
return "";
}
}
function getRollbackAction(component, key, backupVersion) {
return {
key,
text: backupVersion ? `${_("Rollback")} (${backupVersion})` : _("Rollback"),
icon: renderRotateCcwIcon24,
component,
action: "rollback",
};
}
function getOptionalComponentActions({
component,
installed,
checkKey,
installKey,
removeKey,
rollbackKey,
}) {
if (!installed) {
return [getInstallAction(component, installKey, false)];
}
const actions = [
...getInstalledUpdateActions(component, checkKey, installKey),
{
key: removeKey,
text: _("Remove"),
icon: renderXIcon24,
component,
action: "remove",
},
];
const backupVersion = getComponentBackupVersion(component);
if (backupVersion) {
actions.push(getRollbackAction(component, rollbackKey, backupVersion));
}
return actions;
}
var COMPONENT_REPO_URLS = {
tachyon: "https://github.com/Dushnilin/tachyon",
sing_box: "https://github.com/SagerNet/sing-box",
zapret: "https://github.com/remittor/zapret-openwrt",
zapret2: "https://github.com/Dushnilin/zapret2-openwrt",
byedpi: "https://github.com/DPITrickster/ByeDPI-OpenWrt",
tailscale: "https://openwrt.org/packages/pkgdata/tailscale",
};
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 tailscaleInstalled = Boolean(systemInfo.tailscale_installed);
const singBoxInstalled = !isNotInstalled(systemInfo.sing_box_version);
const singBoxStable =
singBoxInstalled &&
!systemInfo.sing_box_extended &&
!systemInfo.sing_box_tiny;
const singBoxExtended =
Boolean(systemInfo.sing_box_extended) &&
!systemInfo.sing_box_compressed &&
!systemInfo.sing_box_lx;
const singBoxExtendedCompressed =
Boolean(systemInfo.sing_box_extended) &&
Boolean(systemInfo.sing_box_compressed);
const singBoxLx =
Boolean(systemInfo.sing_box_extended) && Boolean(systemInfo.sing_box_lx);
const singBoxTiny = Boolean(systemInfo.sing_box_tiny);
const tachyonActions = [
...getInstalledUpdateActions("tachyon", "tachyonCheck", "tachyonInstall"),
{
key: "tachyonReinstall",
text: _("Reinstall"),
icon: renderRotateCcwIcon24,
component: "tachyon",
action: "reinstall",
},
];
const singBoxActions = getInstalledUpdateActions(
"sing_box",
"singBoxCheck",
"singBoxInstall",
singBoxInstalled,
);
const singBoxBackup = getComponentBackupVersion("sing_box");
if (singBoxBackup && singBoxInstalled) {
singBoxActions.push(
getRollbackAction("sing_box", "singBoxRollback", singBoxBackup),
);
}
if (!singBoxStable) {
singBoxActions.push({
key: "singBoxInstallStable",
text: "Stable",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_stable",
});
}
if (!singBoxTiny) {
singBoxActions.push({
key: "singBoxInstallTiny",
text: "Tiny",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_tiny",
});
}
if (!singBoxExtended) {
singBoxActions.push({
key: "singBoxInstallExtended",
text: "Extended",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_extended",
});
}
if (!singBoxExtendedCompressed) {
singBoxActions.push({
key: "singBoxInstallExtendedCompressed",
text: "Extended compressed",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_extended_compressed",
});
}
if (!singBoxLx) {
singBoxActions.push({
key: "singBoxInstallLx",
text: "Leadaxe (lx)",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_lx",
});
}
const zapretActions = getOptionalComponentActions({
component: "zapret",
installed: zapretInstalled,
checkKey: "zapretCheck",
installKey: "zapretInstall",
removeKey: "zapretRemove",
rollbackKey: "zapretRollback",
});
const zapret2Actions = getOptionalComponentActions({
component: "zapret2",
installed: zapret2Installed,
checkKey: "zapret2Check",
installKey: "zapret2Install",
removeKey: "zapret2Remove",
rollbackKey: "zapret2Rollback",
});
const byedpiActions = getOptionalComponentActions({
component: "byedpi",
installed: byedpiInstalled,
checkKey: "byedpiCheck",
installKey: "byedpiInstall",
removeKey: "byedpiRemove",
rollbackKey: "byedpiRollback",
});
const tailscaleActions = getOptionalComponentActions({
component: "tailscale",
installed: tailscaleInstalled,
checkKey: "tailscaleCheck",
installKey: "tailscaleInstall",
removeKey: "tailscaleRemove",
rollbackKey: "tailscaleRollback",
});
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,
},
{
component: "tailscale",
column: 1,
title: "Tailscale",
version: systemInfoLoading
? _("Loading...")
: tailscaleInstalled
? systemInfo.tailscale_version
: _("Not installed"),
latestVersion: getLatestVersion("tailscale"),
releaseUrl: getGitHubReleaseUrl("tailscale"),
repoUrl: COMPONENT_REPO_URLS.tailscale,
actions: tailscaleActions,
},
];
}
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 {
width: 100%;
min-width: 0;
box-sizing: border-box;
}
}
.tachyon_updates-page__component {
border: 2px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
min-width: 0;
box-sizing: border-box;
}
.tachyon_updates-page__component__header {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
border-bottom: 1px var(--background-color-low, lightgray) solid;
padding-bottom: 8px;
margin-bottom: 2px;
}
.tachyon_updates-page__component__title {
color: var(--text-color-high);
font-size: 16px;
font-weight: bold;
line-height: 1.2;
}
.tachyon_updates-page__component__header-version {
color: var(--text-color-medium, #888);
font-size: 13px;
font-weight: normal;
}
.tachyon_updates-page__component__repo-link {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 3px;
color: var(--text-color-medium, #888);
text-decoration: none;
transition: color 0.15s, background-color 0.15s;
flex-shrink: 0;
}
.tachyon_updates-page__component__repo-link:hover {
color: var(--link-color, #3498db);
background-color: var(--background-color-low, rgba(0, 0, 0, 0.05));
}
.tachyon_updates-page__component__details {
display: flex;
flex-direction: column;
gap: 6px;
}
.tachyon_updates-page__component__info-row {
display: flex;
justify-content: flex-start;
align-items: center;
min-height: 24px;
gap: 8px;
white-space: nowrap;
}
.tachyon_updates-page__component__info-label {
color: var(--text-color-medium, #888);
font-size: 12px;
}
.tachyon_updates-page__component__info-value {
color: var(--text-color-high, #000);
font-weight: 500;
font-size: 13px;
text-align: left;
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
overflow-wrap: anywhere;
}
.tachyon_updates-page__component__info-value--latest {
flex-wrap: wrap;
justify-content: flex-start;
}
.tachyon_updates-page__component__release-version-link {
color: var(--link-color, #3498db) !important;
text-decoration: underline;
font-weight: bold;
}
.tachyon_updates-page__component__release-version-link:hover {
color: var(--link-color-dark, #2980b9) !important;
}
.tachyon_updates-page__component__sha-info {
font-family: monospace;
font-size: 11px;
color: var(--text-color-medium, #888);
background: var(--background-color-low, rgba(0,0,0,0.06));
border-radius: 3px;
padding: 1px 5px;
letter-spacing: 0.02em;
}
.tachyon_updates-page__component__actions {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: auto;
}
.tachyon_updates-page__component__actions--with-details {
border-top: 1px var(--background-color-low, lightgray) solid;
padding-top: 10px;
}
.tachyon_updates-page__component__actions-main {
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: 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);
}
/* Global Mobile & Responsive Layout Enhancements */
@media (max-width: 768px) {
.cbi-section-table {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-collapse: collapse;
}
.cbi-section-table-cell {
min-width: 0;
word-break: break-word;
overflow-wrap: anywhere;
}
.cbi-section-actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.cbi-dropdown {
max-width: 100%;
}
.cbi-input-textarea,
.cbi-input-text,
.cbi-input-select {
max-width: 100%;
box-sizing: border-box;
}
/* Modal responsiveness */
.modal,
.cbi-modal {
max-width: 96vw !important;
box-sizing: border-box;
margin: 10px auto;
}
}
`;
// src/helpers/injectGlobalStyles.ts
var TACHYON_GLOBAL_STYLES_ID = "tachyon-global-styles";
function injectGlobalStyles() {
if (document.getElementById(TACHYON_GLOBAL_STYLES_ID)) {
return;
}
document.head.insertAdjacentHTML(
"beforeend",
`
<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,
});