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

9306 lines
261 KiB
JavaScript

// This file is autogenerated, please don't change manually
"use strict";
"require baseclass";
"require fs";
"require uci";
"require ui";
// src/validators/validateIp.ts
function validateIPV4(ip) {
const ipRegex = /^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/;
if (ipRegex.test(ip)) {
return { valid: true, message: _("Valid") };
}
return { valid: false, message: _("Invalid IP address") };
}
// src/validators/validateDomain.ts
function validateDomain(domain, allowDotTLD = false) {
const domainRegex = /^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/;
if (allowDotTLD) {
const dotTLD = /^\.[a-zA-Z]{2,}$/;
if (dotTLD.test(domain)) {
return { valid: true, message: _("Valid") };
}
}
if (!domainRegex.test(domain)) {
return { valid: false, message: _("Invalid domain address") };
}
const hostname = domain.split("/")[0];
const parts = hostname.split(".");
const atLeastOneInvalidPart = parts.some((part) => part.length > 63);
if (atLeastOneInvalidPart) {
return { valid: false, message: _("Invalid domain address") };
}
return { valid: true, message: _("Valid") };
}
// src/validators/validateDns.ts
function validateDNS(value) {
if (!value) {
return { valid: false, message: _("DNS server address cannot be empty") };
}
const cleanedValueWithoutPort = value.replace(/:(\d+)(?=\/|$)/, "");
const cleanedIpWithoutPath = cleanedValueWithoutPort.split("/")[0];
if (validateIPV4(cleanedIpWithoutPath).valid) {
return { valid: true, message: _("Valid") };
}
if (validateDomain(cleanedValueWithoutPort).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(", ")
};
const regex = new RegExp(
`^(?:${protocols.map((p) => p.replace(":", "")).join("|")})://(?:(?:[A-Za-z0-9-]+\\.)+[A-Za-z]{2,}|(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}|localhost)(?::\\d+)?(?:/[^\\s]*)?$`
);
if (regex.test(url)) {
return { valid: true, message: _("Valid") };
}
return { valid: false, message: _("Invalid URL format") };
}
// src/validators/validatePath.ts
function validatePath(value) {
if (!value) {
return {
valid: false,
message: _("Path cannot be empty")
};
}
const pathRegex = /^\/[a-zA-Z0-9_\-/.]+$/;
if (pathRegex.test(value)) {
return {
valid: true,
message: _("Valid")
};
}
return {
valid: false,
message: _(
'Invalid path format. Path must start with "/" and contain valid characters'
)
};
}
// src/validators/validateSubnet.ts
function validateSubnet(value) {
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(?:\/\d{1,2})?$/;
if (!subnetRegex.test(value)) {
return {
valid: false,
message: _("Invalid format. Use X.X.X.X or X.X.X.X/Y")
};
}
const [ip, cidr] = value.split("/");
if (ip === "0.0.0.0" && cidr == null) {
return { valid: false, message: _("IP address 0.0.0.0 is not allowed") };
}
const ipCheck = validateIPV4(ip);
if (!ipCheck.valid) {
return ipCheck;
}
if (cidr) {
const cidrNum = parseInt(cidr, 10);
if (cidrNum < 0 || cidrNum > 32) {
return {
valid: false,
message: _("CIDR must be between 0 and 32")
};
}
}
return { valid: true, message: _("Valid") };
}
// src/validators/bulkValidate.ts
function bulkValidate(values, validate) {
const results = values.map((value) => ({ ...validate(value), value }));
return {
valid: results.every((r) => r.valid),
results
};
}
// src/validators/validateOutboundJson.ts
function validateOutboundJson(value) {
try {
JSON.parse(value);
return { valid: true, message: _("Valid") };
} catch {
return { valid: false, message: _("Invalid JSON format") };
}
}
// src/validators/validateShadowsocksUrl.ts
function validateShadowsocksUrl(url) {
if (!url.startsWith("ss://")) {
return {
valid: false,
message: _("Invalid Shadowsocks URL: must start with ss://")
};
}
try {
if (!url || /\s/.test(url)) {
return {
valid: false,
message: _("Invalid Shadowsocks URL: must not contain spaces")
};
}
const mainPart = url.includes("?") ? url.split("?")[0] : url.split("#")[0];
const encryptedPart = mainPart.split("/")[2]?.split("@")[0];
if (!encryptedPart) {
return {
valid: false,
message: _("Invalid Shadowsocks URL: missing credentials")
};
}
try {
const decoded = atob(encryptedPart);
if (!decoded.includes(":")) {
return {
valid: false,
message: _(
"Invalid Shadowsocks URL: decoded credentials must contain method:password"
)
};
}
} catch (_e) {
if (!encryptedPart.includes(":") && !encryptedPart.includes("-")) {
return {
valid: false,
message: _(
'Invalid Shadowsocks URL: missing method and password separator ":"'
)
};
}
}
const serverPart = url.split("@")[1];
if (!serverPart) {
return {
valid: false,
message: _("Invalid Shadowsocks URL: missing server address")
};
}
const [server, portAndRest] = serverPart.split(":");
if (!server) {
return {
valid: false,
message: _("Invalid Shadowsocks URL: missing server")
};
}
const port = portAndRest ? portAndRest.split(/[?#]/)[0] : null;
if (!port) {
return {
valid: false,
message: _("Invalid Shadowsocks URL: missing port")
};
}
const portNum = parseInt(port, 10);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
return {
valid: false,
message: _("Invalid port number. Must be between 1 and 65535")
};
}
} catch (_e) {
return {
valid: false,
message: _("Invalid Shadowsocks URL: parsing failed")
};
}
return { valid: true, message: _("Valid") };
}
// src/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 [host, port] = hostPortPart.split(":");
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("/", "");
const portNum = Number(cleanedPort);
if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535)
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/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 [host, port] = hostPortPart.split(":");
if (!host)
return { valid: false, message: "Invalid Trojan URL: missing hostname" };
if (!port)
return { valid: false, message: "Invalid Trojan URL: missing port" };
const portNum = Number(port);
if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535)
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 [host, port] = hostPortPart.split(":");
if (!host) {
return {
valid: false,
message: _("Invalid SOCKS URL: missing hostname or IP")
};
}
if (!port) {
return { valid: false, message: _("Invalid SOCKS URL: missing port") };
}
const portNum = Number(port);
if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535) {
return {
valid: false,
message: _("Invalid SOCKS URL: invalid port number")
};
}
const ipv4Result = validateIPV4(host);
const domainResult = validateDomain(host);
if (!ipv4Result.valid && !domainResult.valid) {
return {
valid: false,
message: _("Invalid SOCKS URL: invalid host format")
};
}
} 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 [host, port] = hostPortPart.split(":");
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 isValidPortNumber = (value) => {
if (!/^\d+$/.test(value)) {
return false;
}
const portNum = Number(value);
return Number.isInteger(portNum) && portNum >= 1 && portNum <= 65535;
};
const isValidPortEntry = (entry) => {
if (!entry) {
return false;
}
if (!entry.includes("-")) {
return isValidPortNumber(entry);
}
const rangeParts = entry.split("-");
if (rangeParts.length !== 2) {
return false;
}
const [start, end] = rangeParts;
return isValidPortNumber(start) && isValidPortNumber(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")
};
}
}
return { valid: true, message: _("Valid") };
} catch (_e) {
return { valid: false, message: _("Invalid HY2 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("trojan://")) {
return validateTrojanUrl(trimmedUrl);
}
if (/^socks(4|4a|5):\/\//.test(trimmedUrl)) {
return validateSocksUrl(trimmedUrl);
}
if (trimmedUrl.startsWith("hysteria2://") || trimmedUrl.startsWith("hy2://")) {
return validateHysteria2Url(trimmedUrl);
}
return {
valid: false,
message: _(
"URL must start with vless://, ss://, trojan://, socks4://, socks4a://, socks5://, hysteria2://, or hy2://"
)
};
}
// 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/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/podkop/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);
switch (level) {
case "error":
console.error(message);
break;
case "warn":
console.warn(message);
break;
case "info":
console.info(message);
break;
default:
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.info("[SHELL]", `[${operationName}] took ${elapsed.toFixed(2)} ms`);
}
}
// src/constants.ts
var PODKOP_UCI_PACKAGE = "podkop-plus";
var PODKOP_LUCI_APP_VERSION = "__COMPILED_VERSION_VARIABLE__";
var PODKOP_ACTION_PROVIDERS_AVAILABILITY_EVENT = "podkop:action-providers-availability";
var FAKEIP_CHECK_DOMAIN = "fakeip.podkop.fyi";
var IP_CHECK_DOMAIN = "ip.podkop.fyi";
var REGIONAL_OPTIONS = [
"russia_inside",
"russia_outside",
"ukraine_inside"
];
var ALLOWED_WITH_RUSSIA_INSIDE = [
"russia_inside",
"meta",
"twitter",
"discord",
"telegram",
"cloudflare",
"google_ai",
"google_play",
"hetzner",
"ovh",
"hodca",
"roblox",
"digitalocean",
"cloudfront"
];
var DOMAIN_LIST_OPTIONS = {
russia_inside: "Russia inside",
russia_outside: "Russia outside",
ukraine_inside: "Ukraine",
geoblock: "Geo Block",
block: "Block",
porn: "Porn",
news: "News",
anime: "Anime",
youtube: "Youtube",
discord: "Discord",
meta: "Meta",
twitter: "Twitter (X)",
hdrezka: "HDRezka",
tiktok: "Tik-Tok",
telegram: "Telegram",
cloudflare: "Cloudflare",
google_ai: "Google AI",
google_play: "Google Play",
hodca: "H.O.D.C.A",
roblox: "Roblox",
ads_hagezi_pro: "Ads (Hagezi Pro)",
supercell: "Supercell",
hetzner: "Hetzner ASN",
ovh: "OVH ASN",
digitalocean: "Digital Ocean ASN",
cloudfront: "CloudFront ASN"
};
var DNS_SERVER_OPTIONS = {
"1.1.1.1": "1.1.1.1 (Cloudflare)",
"8.8.8.8": "8.8.8.8 (Google)",
"9.9.9.9": "9.9.9.9 (Quad9)",
"dns.adguard-dns.com": "dns.adguard-dns.com (AdGuard Default)",
"unfiltered.adguard-dns.com": "unfiltered.adguard-dns.com (AdGuard Unfiltered)",
"family.adguard-dns.com": "family.adguard-dns.com (AdGuard Family)"
};
var BOOTSTRAP_DNS_SERVER_OPTIONS = {
"77.88.8.8": "77.88.8.8 (Yandex DNS)",
"77.88.8.1": "77.88.8.1 (Yandex DNS)",
"1.1.1.1": "1.1.1.1 (Cloudflare DNS)",
"1.0.0.1": "1.0.0.1 (Cloudflare DNS)",
"8.8.8.8": "8.8.8.8 (Google DNS)",
"8.8.4.4": "8.8.4.4 (Google DNS)",
"9.9.9.9": "9.9.9.9 (Quad9 DNS)",
"9.9.9.11": "9.9.9.11 (Quad9 DNS)"
};
var 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/getProxyUrlName.ts
function getProxyUrlName(url) {
try {
const [_link, hash] = url.split("#");
if (!hash) {
return "";
}
return decodeURIComponent(hash);
} catch {
return "";
}
}
// 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 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/helpers/prettyBytes.ts
function prettyBytes(n) {
const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
if (n < 1e3) {
return n + " B";
}
const exponent = Math.min(Math.floor(Math.log10(n) / 3), UNITS.length - 1);
n = Number((n / Math.pow(1e3, exponent)).toPrecision(3));
const unit = UNITS[exponent];
return n + " " + unit;
}
// src/podkop/tabs/dashboard/partials/renderSections.ts
var REGION_NAME_FALLBACKS = {
XK: "Kosovo"
};
var regionDisplayNamesCache = {};
function getLuciLanguage() {
const luci = globalThis.L;
if (luci?.env?.lang) {
return `${luci.env.lang}`.replace("_", "-");
}
if (document.documentElement.lang) {
return document.documentElement.lang;
}
return navigator.language || "en";
}
function getCountryDisplayName(country) {
const code = `${country || ""}`.trim().toUpperCase();
if (!/^[A-Z]{2}$/.test(code)) {
return "";
}
const language = getLuciLanguage();
const cacheKey = `${language}:${code}`;
if (regionDisplayNamesCache[cacheKey]) {
return regionDisplayNamesCache[cacheKey];
}
try {
const displayNamesConstructor = Intl.DisplayNames;
if (displayNamesConstructor) {
const displayNames = new displayNamesConstructor([language, "en"], {
type: "region"
});
const displayName = displayNames.of(code);
if (displayName && displayName !== code) {
regionDisplayNamesCache[cacheKey] = displayName;
return displayName;
}
}
} catch (_error) {
}
const fallback = REGION_NAME_FALLBACKS[code] || code;
regionDisplayNamesCache[cacheKey] = fallback;
return fallback;
}
function getCountryFlagEmoji(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 renderCountryFlag(country) {
const countryFlag = getCountryFlagEmoji(country);
if (!countryFlag) {
return void 0;
}
const countryName = getCountryDisplayName(country);
return E(
"span",
{
class: "pdk_dashboard-page__outbound-grid__item__country",
title: countryName || void 0,
"aria-label": countryName || void 0
},
countryFlag
);
}
function renderFailedState() {
return E(
"div",
{
class: "pdk_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: "pdk_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 pdk_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: "pdk_dashboard-page__subscription-meta" }, [
E("div", { class: "pdk_dashboard-page__subscription-meta__main" }, [
E(
"div",
{ class: "pdk_dashboard-page__subscription-meta__heading" },
_("Subscription info:")
),
title ? E(
"div",
{ class: "pdk_dashboard-page__subscription-meta__title" },
title
) : "",
rows.length ? E(
"div",
{ class: "pdk_dashboard-page__subscription-meta__facts" },
rows.map(
(row) => E(
"div",
{ class: "pdk_dashboard-page__subscription-meta__fact" },
[
E(
"span",
{
class: "pdk_dashboard-page__subscription-meta__fact-key"
},
row.label
),
E(
"span",
{
class: "pdk_dashboard-page__subscription-meta__fact-value"
},
row.value
)
]
)
)
) : "",
actions.length ? E(
"div",
{ class: "pdk_dashboard-page__subscription-meta__actions" },
actions
) : ""
]),
metadata.announce ? E(
"blockquote",
{ class: "pdk_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 pdk_dashboard-page__outbound-section__subscription-update",
title: _("Update subscriptions"),
"aria-label": _("Update subscriptions"),
disabled: subscriptionUpdating ? true : void 0,
click: (event) => {
event.stopPropagation();
if (subscriptionUpdating) {
return;
}
onUpdateSubscription(section);
}
},
subscriptionUpdating ? renderLoaderCircleIcon24() : renderRotateCcwIcon24()
);
}
function renderDefaultState({
section,
onChooseOutbound,
onCopyOutbound,
onTestLatency,
onUpdateSubscription,
latencyFetching,
subscriptionUpdating,
selectorSwitchingTag
}) {
function testLatency() {
if (section.withTagSelect) {
return onTestLatency(section.latencyTestCode || section.code);
}
if (section.outbounds.length) {
return onTestLatency(section.outbounds[0].code);
}
}
function renderOutbound(outbound) {
function getLatencyClass() {
if (!outbound.latency) {
return "pdk_dashboard-page__outbound-grid__item__latency--empty";
}
if (outbound.latency < 800) {
return "pdk_dashboard-page__outbound-grid__item__latency--green";
}
if (outbound.latency < 1500) {
return "pdk_dashboard-page__outbound-grid__item__latency--yellow";
}
return "pdk_dashboard-page__outbound-grid__item__latency--red";
}
const canCopyLink = Boolean(outbound.canCopyLink) || isCopyableProxyLink(outbound.link);
const countryFlag = renderCountryFlag(outbound.country);
const selectorSwitching = Boolean(selectorSwitchingTag);
const outboundSwitching = selectorSwitchingTag === outbound.code;
const canChooseOutbound = section.withTagSelect && !selectorSwitching && !outbound.selected;
const className = [
"pdk_dashboard-page__outbound-grid__item",
outbound.selected ? "pdk_dashboard-page__outbound-grid__item--active" : "",
canChooseOutbound ? "pdk_dashboard-page__outbound-grid__item--selectable" : "",
section.withTagSelect && !canChooseOutbound ? "pdk_dashboard-page__outbound-grid__item--disabled" : "",
outboundSwitching ? "pdk_dashboard-page__outbound-grid__item--switching" : ""
].filter(Boolean).join(" ");
const typeChildren = countryFlag ? [countryFlag, outbound.type ? ` ${outbound.type}` : ""] : [outbound.type].filter(Boolean);
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: "pdk_dashboard-page__outbound-grid__item__snake" },
[
svgEl("rect", {
width: "100%",
height: "100%",
fill: "none",
rx: 4,
ry: 4,
pathLength: 100
})
]
)
] : [],
E("div", { class: "pdk_dashboard-page__outbound-grid__item__header" }, [
E("b", {}, outbound.displayName),
...canCopyLink ? [
E(
"button",
{
type: "button",
class: "btn pdk_dashboard-page__outbound-grid__item__copy-button",
title: _("Copy proxy link"),
"aria-label": _("Copy proxy link"),
click: (event) => {
event.stopPropagation();
onCopyOutbound(section, outbound);
}
},
renderCopyIcon24()
)
] : []
]),
E("div", { class: "pdk_dashboard-page__outbound-grid__item__footer" }, [
E(
"div",
{ class: "pdk_dashboard-page__outbound-grid__item__type" },
typeChildren
),
E(
"div",
{ class: getLatencyClass() },
outbound.latency ? `${outbound.latency}ms` : "N/A"
)
])
]
);
}
const metadataNodes = (section.subscriptionMetadata || []).map((metadata) => renderSubscriptionMetadata(metadata)).filter(Boolean);
const subscriptionUpdateAction = renderSubscriptionUpdateAction(
section,
subscriptionUpdating,
onUpdateSubscription
);
return E("div", { class: "pdk_dashboard-page__outbound-section" }, [
// Title with test latency
E("div", { class: "pdk_dashboard-page__outbound-section__title-section" }, [
E(
"div",
{
class: "pdk_dashboard-page__outbound-section__title-section__title"
},
section.displayName
),
E(
"div",
{
class: "pdk_dashboard-page__outbound-section__title-section__actions"
},
[
...subscriptionUpdateAction ? [subscriptionUpdateAction] : [],
E(
"button",
{
class: "btn dashboard-sections-grid-item-test-latency",
disabled: latencyFetching ? true : void 0,
click: () => {
if (latencyFetching) {
return;
}
testLatency();
}
},
latencyFetching ? [renderLoaderCircleIcon24(), _("Test latency")] : _("Test latency")
)
]
)
]),
E("div", { class: "pdk_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/podkop/tabs/dashboard/partials/renderWidget.ts
function renderFailedState2() {
return E(
"div",
{
id: "",
style: "height: 78px",
class: "pdk_dashboard-page__widgets-section__item centered"
},
_("Currently unavailable")
);
}
function renderLoadingState2() {
return E(
"div",
{
id: "",
style: "height: 78px",
class: "pdk_dashboard-page__widgets-section__item skeleton"
},
""
);
}
function renderDefaultState2({ title, items }) {
return E("div", { class: "pdk_dashboard-page__widgets-section__item" }, [
E(
"b",
{ class: "pdk_dashboard-page__widgets-section__item__title" },
title
),
...items.map(
(item) => E(
"div",
{
class: `pdk_dashboard-page__widgets-section__item__row ${item?.attributes?.class || ""}`
},
[
E(
"span",
{ class: "pdk_dashboard-page__widgets-section__item__row__key" },
`${item.key}: `
),
E(
"span",
{ class: "pdk_dashboard-page__widgets-section__item__row__value" },
item.value
)
]
)
)
]);
}
function renderWidget(props) {
if (props.loading) {
return renderLoadingState2();
}
if (props.failed) {
return renderFailedState2();
}
return renderDefaultState2(props);
}
// src/podkop/tabs/dashboard/render.ts
function render() {
return E(
"div",
{
id: "dashboard-status",
class: "pdk_dashboard-page"
},
[
// Widgets section
E("div", { class: "pdk_dashboard-page__widgets-section" }, [
E(
"div",
{ id: "dashboard-widget-traffic" },
renderWidget({ loading: true, failed: false, title: "", items: [] })
),
E(
"div",
{ id: "dashboard-widget-traffic-total" },
renderWidget({ loading: true, failed: false, title: "", items: [] })
),
E(
"div",
{ id: "dashboard-widget-system-info" },
renderWidget({ loading: true, failed: false, title: "", items: [] })
),
E(
"div",
{ id: "dashboard-widget-service-info" },
renderWidget({ loading: true, failed: false, title: "", items: [] })
)
]),
// All outbounds
E(
"div",
{ id: "dashboard-sections-grid" },
renderSections({
loading: true,
failed: false,
section: {
code: "",
sectionName: "",
displayName: "",
outbounds: [],
withTagSelect: false
},
onTestLatency: () => {
},
onChooseOutbound: () => {
},
onCopyOutbound: () => {
},
onUpdateSubscription: () => {
},
latencyFetching: false,
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(_("Successfully copied!"), "success");
} catch (_err) {
showToast(_("Failed to copy!"), "error");
console.error("copyToClipboard - e", _err);
}
document.body.removeChild(textarea);
}
// src/podkop/methods/custom/getConfigSections.ts
async function getConfigSections() {
return uci.load(PODKOP_UCI_PACKAGE).then(() => uci.sections(PODKOP_UCI_PACKAGE));
}
// src/podkop/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",
"dns-in",
"service-mixed-in",
"direct-out"
]);
function hasReservedNumberedParent(base, postfix) {
const match = base.match(/^(.*)-\d+$/);
return Boolean(match && RESERVED_RUNTIME_TAGS.has(`${match[1]}-${postfix}`));
}
function allocateRuntimeTag(base, postfix) {
let suffix = hasReservedNumberedParent(base, postfix) ? 1 : 0;
let candidate = suffix > 0 ? `${base}-${suffix}-${postfix}` : `${base}-${postfix}`;
while (RESERVED_RUNTIME_TAGS.has(candidate)) {
suffix += 1;
candidate = `${base}-${suffix}-${postfix}`;
}
return candidate;
}
function getOutboundTagBySection(sectionName) {
return allocateRuntimeTag(sectionName, "out");
}
// src/podkop/methods/shell/callBaseMethod.ts
async function callBaseMethod(method, args = [], command = "/usr/bin/podkop-plus", 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/podkop/types.ts
var Podkop;
((Podkop2) => {
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_BYEDPI_RUNTIME"] = "check_byedpi_runtime";
AvailableMethods2["GET_STATUS"] = "get_status";
AvailableMethods2["GET_OUTBOUND_LINK"] = "get_outbound_link";
AvailableMethods2["GET_OUTBOUND_LINK_STATES"] = "get_outbound_link_states";
AvailableMethods2["GET_OUTBOUND_METADATA"] = "get_outbound_metadata";
AvailableMethods2["GET_SUBSCRIPTION_METADATA"] = "get_subscription_metadata";
AvailableMethods2["CHECK_SING_BOX"] = "check_sing_box";
AvailableMethods2["GET_SING_BOX_STATUS"] = "get_sing_box_status";
AvailableMethods2["GET_ZAPRET_STATUS"] = "get_zapret_status";
AvailableMethods2["GET_BYEDPI_STATUS"] = "get_byedpi_status";
AvailableMethods2["CLASH_API"] = "clash_api";
AvailableMethods2["RESTART"] = "restart";
AvailableMethods2["START"] = "start";
AvailableMethods2["STOP"] = "stop";
AvailableMethods2["ENABLE"] = "enable";
AvailableMethods2["DISABLE"] = "disable";
AvailableMethods2["GLOBAL_CHECK"] = "global_check";
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["COMPONENT_ACTION"] = "component_action";
AvailableMethods2["COMPONENT_ACTION_ASYNC"] = "component_action_async";
AvailableMethods2["COMPONENT_ACTION_STATUS"] = "component_action_status";
AvailableMethods2["SUBSCRIPTION_UPDATE"] = "subscription_update";
})(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {}));
let AvailableClashAPIMethods;
((AvailableClashAPIMethods2) => {
AvailableClashAPIMethods2["GET_PROXIES"] = "get_proxies";
AvailableClashAPIMethods2["GET_CONNECTIONS"] = "get_connections";
AvailableClashAPIMethods2["GET_PROXY_LATENCY"] = "get_proxy_latency";
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 = Podkop2.AvailableClashAPIMethods || (Podkop2.AvailableClashAPIMethods = {}));
})(Podkop || (Podkop = {}));
// src/podkop/methods/shell/index.ts
var SUBSCRIPTION_UPDATE_TIMEOUT_MS = 10 * 60 * 1e3;
var COMPONENT_ACTION_TIMEOUT_MS = 10 * 60 * 1e3;
var COMPONENT_ACTION_RPC_TIMEOUT_MS = 15e3;
var COMPONENT_ACTION_POLL_INTERVAL_MS = 1500;
var COMPONENT_ACTION_SELF_UPDATE_SETTLE_MS = 3e4;
var COMPONENT_ACTION_STATE_DIR = "/var/run/podkop-plus/component-actions";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function parseComponentActionOutput(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 parseComponentActionResult(response) {
return parseComponentActionOutput(response.stdout);
}
function parseComponentActionStartResult(response) {
const parsedResponse = parseComponentActionResult(response);
if (!parsedResponse) {
return null;
}
return parsedResponse;
}
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 readPodkopVersion() {
const response = await executeShellCommand({
command: "/usr/bin/podkop-plus",
args: ["show_version"],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS
});
if ((response.code ?? 0) !== 0 || !response.stdout) {
return "";
}
return response.stdout.trim();
}
function componentActionFailure(response, parsedResponse) {
return {
success: false,
error: parsedResponse?.message || response.stderr || _("Failed to execute")
};
}
var PodkopShellMethods = {
checkDNSAvailable: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_DNS_AVAILABLE
),
checkFakeIP: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_FAKEIP
),
checkNftRules: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_NFT_RULES
),
checkZapretRuntime: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_ZAPRET_RUNTIME
),
checkByedpiRuntime: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_BYEDPI_RUNTIME
),
getStatus: async () => callBaseMethod(Podkop.AvailableMethods.GET_STATUS),
getOutboundLink: async (section, tag) => callBaseMethod(
Podkop.AvailableMethods.GET_OUTBOUND_LINK,
[section, tag]
),
getOutboundLinkStates: async (section) => callBaseMethod(
Podkop.AvailableMethods.GET_OUTBOUND_LINK_STATES,
[section]
),
getOutboundMetadata: async (section) => callBaseMethod(
Podkop.AvailableMethods.GET_OUTBOUND_METADATA,
[section]
),
getSubscriptionMetadata: async (section) => callBaseMethod(
Podkop.AvailableMethods.GET_SUBSCRIPTION_METADATA,
[section]
),
checkSingBox: async () => callBaseMethod(
Podkop.AvailableMethods.CHECK_SING_BOX
),
getSingBoxStatus: async () => callBaseMethod(
Podkop.AvailableMethods.GET_SING_BOX_STATUS
),
getZapretStatus: async () => callBaseMethod(
Podkop.AvailableMethods.GET_ZAPRET_STATUS
),
getByedpiStatus: async () => callBaseMethod(
Podkop.AvailableMethods.GET_BYEDPI_STATUS
),
getClashApiProxies: async () => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
Podkop.AvailableClashAPIMethods.GET_PROXIES
]),
getClashApiConnections: async () => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
Podkop.AvailableClashAPIMethods.GET_CONNECTIONS
]),
getClashApiProxyLatency: async (tag) => callBaseMethod(
Podkop.AvailableMethods.CLASH_API,
[Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, tag, "5000"]
),
getClashApiGroupLatency: async (tag) => callBaseMethod(
Podkop.AvailableMethods.CLASH_API,
[Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, tag, "10000"]
),
setClashApiGroupProxy: async (group, proxy) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY,
group,
proxy
]),
closeClashApiConnection: async (connectionId) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
Podkop.AvailableClashAPIMethods.CLOSE_CONNECTION,
connectionId
]),
closeAllClashApiConnections: async () => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
Podkop.AvailableClashAPIMethods.CLOSE_ALL_CONNECTIONS
]),
restart: async () => callBaseMethod(
Podkop.AvailableMethods.RESTART,
[],
"/etc/init.d/podkop-plus"
),
start: async () => callBaseMethod(
Podkop.AvailableMethods.START,
[],
"/etc/init.d/podkop-plus"
),
stop: async () => callBaseMethod(
Podkop.AvailableMethods.STOP,
[],
"/etc/init.d/podkop-plus"
),
enable: async () => callBaseMethod(
Podkop.AvailableMethods.ENABLE,
[],
"/etc/init.d/podkop-plus"
),
disable: async () => callBaseMethod(
Podkop.AvailableMethods.DISABLE,
[],
"/etc/init.d/podkop-plus"
),
globalCheck: async () => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK),
showSingBoxConfig: async () => callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG),
checkLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_LOGS),
checkSingBoxLogs: async () => callBaseMethod(Podkop.AvailableMethods.CHECK_SING_BOX_LOGS),
getSystemInfo: async () => callBaseMethod(
Podkop.AvailableMethods.GET_SYSTEM_INFO
),
componentAction: async (component, action, expectedLatestVersion) => {
const startedAt = Date.now();
let selfUpdateVersionMatchedAt = 0;
const startResponse = await executeShellCommand({
command: "/usr/bin/podkop-plus",
args: [
Podkop.AvailableMethods.COMPONENT_ACTION_ASYNC,
component,
action
],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS
});
const parsedStartResponse = parseComponentActionStartResult(startResponse);
if ((startResponse.code ?? 0) !== 0 || !parsedStartResponse?.success || !parsedStartResponse.job_id) {
return componentActionFailure(startResponse, parsedStartResponse);
}
const jobId = parsedStartResponse.job_id;
while (Date.now() - startedAt < COMPONENT_ACTION_TIMEOUT_MS) {
await sleep(COMPONENT_ACTION_POLL_INTERVAL_MS);
const stateResponse = await readComponentActionState(jobId);
if (stateResponse) {
if (stateResponse.success === false) {
return {
success: false,
error: stateResponse.message || _("Failed to execute")
};
}
if (stateResponse.running) {
continue;
}
return {
success: true,
data: stateResponse
};
}
const statusResponse = await executeShellCommand({
command: "/usr/bin/podkop-plus",
args: [
Podkop.AvailableMethods.COMPONENT_ACTION_STATUS,
jobId
],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS
});
const parsedResponse = parseComponentActionResult(statusResponse);
if (parsedResponse?.success === false) {
return componentActionFailure(statusResponse, parsedResponse);
}
if ((statusResponse.code ?? 0) !== 0 || !parsedResponse) {
if (component === "podkop" && action === "install") {
const installedVersion = expectedLatestVersion ? await readPodkopVersion() : "";
if (expectedLatestVersion && installedVersion === expectedLatestVersion) {
if (!selfUpdateVersionMatchedAt) {
selfUpdateVersionMatchedAt = Date.now();
}
if (Date.now() - selfUpdateVersionMatchedAt >= COMPONENT_ACTION_SELF_UPDATE_SETTLE_MS) {
return {
success: true,
data: {
success: true,
component,
action,
message: _("Podkop Plus has been installed"),
current_version: installedVersion,
latest_version: expectedLatestVersion,
changed: true,
status: "latest"
}
};
}
}
continue;
}
return componentActionFailure(statusResponse);
}
if (parsedResponse.running) {
continue;
}
return {
success: true,
data: parsedResponse
};
}
return {
success: false,
error: _("Operation timed out")
};
},
subscriptionUpdate: async (section, sourceIndex) => {
const args = [
Podkop.AvailableMethods.SUBSCRIPTION_UPDATE,
...section ? [section] : [],
...section && sourceIndex !== void 0 ? [String(sourceIndex)] : []
];
const response = await executeShellCommand({
command: "/usr/bin/podkop-plus",
args,
timeout: SUBSCRIPTION_UPDATE_TIMEOUT_MS
});
if ((response.code ?? 0) !== 0) {
return {
success: false,
error: response.stderr || _("Subscription update failed")
};
}
return {
success: true,
data: response.stdout
};
}
};
// src/podkop/methods/custom/getDashboardSections.ts
var DASHBOARD_SECTION_CACHE_DIR = "/var/run/podkop-plus/section-cache";
function getDisplayName(section) {
return section.label || section[".name"];
}
function getSectionAction(section) {
return section.action || "";
}
function getSettingsSection(configSections) {
return configSections.find((section) => section[".type"] === "settings");
}
function getClashApiSecret(configSections) {
return getSettingsSection(configSections)?.yacd_secret_key || "";
}
function canFetchClashApiDirectly() {
return typeof window !== "undefined" && typeof window.location?.hostname === "string" && typeof fetch === "function";
}
async function getClashApiProxies(configSections) {
if (canFetchClashApiDirectly()) {
const secret = getClashApiSecret(configSections);
try {
const response = await fetch(`${getClashHttpUrl()}/proxies`, {
headers: secret ? { Authorization: `Bearer ${secret}` } : void 0
});
if (response.ok) {
return {
success: true,
data: await response.json()
};
}
} catch (_error) {
}
}
return PodkopShellMethods.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 getManualProxyLinks(section) {
return getListValues(section.selector_proxy_links);
}
function hasSubscriptionSources(section) {
return getSubscriptionSourceCount(section) > 0;
}
function getSubscriptionSourceCount(section) {
return getListValues(section.subscription_urls).length;
}
function isUrlTestEnabled(section) {
return section.urltest_enabled === "1";
}
function isUrlTestFilteringEnabled(section) {
return ["exclude", "include", "mixed"].includes(
section.urltest_filter_mode || "disabled"
);
}
function shouldHideFilteredUrlTestOutbounds(section) {
return isUrlTestEnabled(section) && isUrlTestFilteringEnabled(section) && section.urltest_hide_filtered_outbounds === "1";
}
function shouldUseProxyGroup(section) {
return getManualProxyLinks(section).length > 0 || hasSubscriptionSources(section);
}
function getSectionProxyConfigType(section) {
if (hasSubscriptionSources(section)) {
return "subscription";
}
if (isUrlTestEnabled(section) && shouldUseProxyGroup(section)) {
return "urltest";
}
if (getManualProxyLinks(section).length > 0) {
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 isUrlTestOutbound(outbound) {
return outbound.type?.toLowerCase() === "urltest";
}
function getLatencySortValue(outbound) {
const latency = Number(outbound.latency);
return Number.isFinite(latency) && latency > 0 ? latency : Number.POSITIVE_INFINITY;
}
function getOutboundSortBucket(outbound) {
if (isUrlTestOutbound(outbound)) {
return 0;
}
return getLatencySortValue(outbound) === Number.POSITIVE_INFINITY ? 2 : 1;
}
function sortOutboundsForDashboard(outbounds) {
return outbounds.map((outbound, index) => ({ outbound, index })).sort((left, right) => {
const leftBucket = getOutboundSortBucket(left.outbound);
const rightBucket = getOutboundSortBucket(right.outbound);
if (leftBucket !== rightBucket) {
return leftBucket - rightBucket;
}
if (leftBucket === 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])
);
}
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 buildProxyGroupOutbounds(section, proxies, outboundMetadata, subscriptionCopyableCodes = /* @__PURE__ */ new Set()) {
const sectionName = section[".name"];
const proxyByCode = getProxyEntryByCode(proxies);
const selectorTag = getOutboundTagBySection(sectionName);
const urltestTag = getOutboundTagBySection(`${sectionName}-urltest`);
const selector = proxyByCode.get(selectorTag);
const fallbackUrltest = proxyByCode.get(urltestTag);
const manualLinkByCode = buildManualLinkByCode(section);
const selectorCodes = selector?.value?.all ?? [];
const urltestCodes = fallbackUrltest?.value?.all ?? [];
const hideFilteredUrlTestOutbounds = shouldHideFilteredUrlTestOutbounds(section) && Boolean(fallbackUrltest?.code) && urltestCodes.length > 0;
const groupCodes = hideFilteredUrlTestOutbounds ? [fallbackUrltest?.code || "", ...urltestCodes] : selectorCodes.length ? selectorCodes : [fallbackUrltest?.code || "", ...urltestCodes];
const outbounds = uniqueCodes(groupCodes).flatMap((code) => {
const item = proxyByCode.get(code);
if (!item) {
return [];
}
const isFastest = item.code === urltestTag;
const link = manualLinkByCode.get(item.code) || "";
const canCopyLink = isCopyableProxyLink(link) || subscriptionCopyableCodes.has(item.code);
return [
{
code: item.code,
displayName: isFastest ? _("Fastest") : getProxyUrlName(link) || outboundMetadata?.names?.[item.code] || item.value.name || item.code,
latency: item.value.history?.[0]?.delay || 0,
type: item.value.type || "",
selected: selector?.value?.now === item.code,
link,
canCopyLink,
country: outboundMetadata?.countries?.[item.code]
}
];
});
return {
selector,
latencyTestCode: hideFilteredUrlTestOutbounds ? fallbackUrltest?.code : selector?.code,
outbounds: sortOutboundsForDashboard(outbounds)
};
}
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 getSubscriptionMetadata(sectionName, 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(sectionName, 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 getSubscriptionCopyableCodes(dashboardCache) {
const legacyLinks = objectMap(dashboardCache?.links);
const linkRefs = dashboardCache?.linkRefs;
const codes = new Set(
Object.entries(legacyLinks).filter(([, link]) => isCopyableProxyLink(link)).map(([code]) => code)
);
if (linkRefs && typeof linkRefs === "object" && !Array.isArray(linkRefs)) {
Object.keys(linkRefs).forEach((code) => codes.add(code));
}
return codes;
}
async function getDashboardSections(options = {}) {
const includeSubscriptionCopyState = options.includeSubscriptionCopyState ?? true;
const configSections = 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" && ["proxy", "outbound", "vpn", "byedpi"].includes(
getSectionAction(section)
)
).map(async (section) => {
const displayName = getDisplayName(section);
const sectionName = section[".name"];
const sectionAction = getSectionAction(section);
const proxyConfigType = getSectionProxyConfigType(section);
if (sectionAction === "vpn") {
const outboundTag = getOutboundTagBySection(sectionName);
const outbound = proxies.find(
(proxy) => proxy.code === outboundTag
);
return {
withTagSelect: false,
code: outbound?.code || sectionName,
sectionName,
displayName,
outbounds: [
{
code: outbound?.code || sectionName,
displayName: section.interface || outbound?.value?.name || "",
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true,
canCopyLink: false
}
]
};
}
if (sectionAction === "byedpi") {
const outboundTag = getOutboundTagBySection(sectionName);
const outbound = proxies.find(
(proxy) => proxy.code === outboundTag
);
return {
withTagSelect: false,
code: outbound?.code || sectionName,
sectionName,
displayName,
outbounds: [
{
code: outbound?.code || sectionName,
displayName: "ByeDPI",
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true,
canCopyLink: false
}
]
};
}
if (sectionAction === "outbound") {
const outboundTag = getOutboundTagBySection(sectionName);
const outbound = proxies.find(
(proxy) => proxy.code === outboundTag
);
return {
withTagSelect: false,
code: outbound?.code || sectionName,
sectionName,
displayName,
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 (sectionAction === "proxy" && shouldUseProxyGroup(section)) {
const subscriptionSourceCount = getSubscriptionSourceCount(section);
const subscriptionEnabled = subscriptionSourceCount > 0;
const dashboardCache = await readDashboardSectionCache(sectionName);
const outboundMetadata = getOutboundMetadata(dashboardCache);
const subscriptionMetadata = subscriptionEnabled ? getSubscriptionMetadata(
sectionName,
subscriptionSourceCount,
dashboardCache
) : void 0;
const subscriptionCopyableCodes = includeSubscriptionCopyState ? getSubscriptionCopyableCodes(dashboardCache) : /* @__PURE__ */ new Set();
const { selector, latencyTestCode, outbounds } = buildProxyGroupOutbounds(
section,
proxies,
outboundMetadata,
subscriptionCopyableCodes
);
return {
withTagSelect: true,
code: selector?.code || sectionName,
sectionName,
displayName,
latencyTestCode,
proxyConfigType,
subscriptionSourceCount,
subscriptionMetadata,
outbounds
};
}
return {
withTagSelect: false,
code: sectionName,
sectionName,
displayName,
outbounds: []
};
})
);
return {
success: true,
data
};
}
// src/podkop/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/podkop/methods/custom/index.ts
var CustomPodkopMethods = {
getConfigSections,
getDashboardSections,
getClashApiSecret: getClashApiSecret2
};
// src/podkop/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/podkop/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/podkop/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/podkop/methods/fakeip/index.ts
var RemoteFakeIPMethods = {
getFakeIpCheck,
getIpCheck
};
// src/podkop/services/tab.service.ts
var TabService = class _TabService {
constructor() {
this.observer = null;
this.lastActiveId = null;
this.init();
}
static getInstance() {
if (!_TabService.instance) {
_TabService.instance = new _TabService();
}
return _TabService.instance;
}
init() {
this.observer = new MutationObserver(() => this.handleMutations());
this.observer.observe(document.body, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ["class"]
});
this.notify();
}
handleMutations() {
this.notify();
}
getTabsInfo() {
const tabs = Array.from(
document.querySelectorAll(".cbi-tab, .cbi-tab-disabled")
);
return tabs.map((el) => ({
el,
id: el.dataset.tab || "",
active: el.classList.contains("cbi-tab") && !el.classList.contains("cbi-tab-disabled")
}));
}
getActiveTabId() {
const active = document.querySelector(
".cbi-tab:not(.cbi-tab-disabled)"
);
return active?.dataset.tab || null;
}
notify() {
const tabs = this.getTabsInfo();
const activeId = this.getActiveTabId();
if (activeId !== this.lastActiveId) {
this.lastActiveId = activeId;
this.callback?.(activeId, tabs);
}
}
onChange(callback) {
this.callback = callback;
this.notify();
}
};
var TabServiceInstance = TabService.getInstance();
// src/podkop/tabs/diagnostic/helpers/getCheckTitle.ts
function getCheckTitle(name) {
return `${name} ${_("checks")}`;
}
// src/podkop/tabs/diagnostic/checks/contstants.ts
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: 3,
title: getCheckTitle("Nftables"),
code: "NFT" /* NFT */
},
["ZAPRET" /* ZAPRET */]: {
order: 4,
title: getCheckTitle("Zapret"),
code: "ZAPRET" /* ZAPRET */
},
["BYEDPI" /* BYEDPI */]: {
order: 5,
title: getCheckTitle("ByeDPI"),
code: "BYEDPI" /* BYEDPI */
},
["OUTBOUNDS" /* OUTBOUNDS */]: {
order: 6,
title: getCheckTitle("Outbounds"),
code: "OUTBOUNDS" /* OUTBOUNDS */
},
["FAKEIP" /* FAKEIP */]: {
order: 7,
title: getCheckTitle("FakeIP"),
code: "FAKEIP" /* FAKEIP */
}
};
// src/podkop/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 */,
"NFT" /* NFT */
];
if (options.includeZapret) {
checks.push("ZAPRET" /* ZAPRET */);
}
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,
podkop_version: "loading",
podkop_latest_version: "loading",
luci_app_version: "loading",
sing_box_version: "loading",
sing_box_extended: 0,
zapret_version: "loading",
zapret_installed: 0,
byedpi_version: "loading",
byedpi_installed: 0,
openwrt_version: "loading",
device_model: "loading"
},
diagnosticsActions: {
restart: {
loading: false
},
start: {
loading: false
},
stop: {
loading: false
},
enable: {
loading: false
},
disable: {
loading: false
},
globalCheck: {
loading: false
},
viewLogs: {
loading: false
},
showSingBoxConfig: {
loading: false
}
},
diagnosticsRunAction: { loading: false },
diagnosticsChecks: getDiagnosticsChecks(_("Not running")),
updatesActions: {
podkopCheck: { loading: false },
podkopInstall: { loading: false },
singBoxCheck: { loading: false },
singBoxInstall: { loading: false },
singBoxInstallExtended: { loading: false },
singBoxInstallStable: { loading: false },
zapretCheck: { loading: false },
zapretInstall: { loading: false },
zapretRemove: { loading: false },
byedpiCheck: { loading: false },
byedpiInstall: { loading: false },
byedpiRemove: { loading: false }
},
updatesChecks: {
podkop: { status: null, latest_version: "", release_url: "" },
sing_box: { status: null, latest_version: "", release_url: "" },
zapret: { status: null, latest_version: "", release_url: "" },
byedpi: { status: null, latest_version: "", release_url: "" }
}
};
// src/podkop/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 (!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,
podkopRunning: 0,
podkopEnabled: 0,
podkopStatus: ""
}
},
sectionsWidget: {
loading: true,
failed: false,
latencyFetchingSections: {},
selectorSwitchingSections: {},
subscriptionUpdatingSections: {},
data: []
},
...initialDiagnosticStore
};
var store = new StoreService(initialStore);
// src/podkop/services/podkopLogWatcher.service.ts
var PodkopLogWatcher = class _PodkopLogWatcher {
constructor() {
this.intervalMs = 5e3;
this.lastLines = [];
this.suppressInitialLogs = false;
this.initialized = false;
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 (!_PodkopLogWatcher.instance) {
_PodkopLogWatcher.instance = new _PodkopLogWatcher();
}
return _PodkopLogWatcher.instance;
}
init(fetcher, options) {
this.fetcher = fetcher;
this.onNewLog = options?.onNewLog;
this.intervalMs = options?.intervalMs ?? 5e3;
this.suppressInitialLogs = options?.suppressInitialLogs ?? false;
this.maxTrackedLines = options?.maxTrackedLines ?? 500;
this.lastLines = [];
this.initialized = false;
logger.info(
"[PodkopLogWatcher]",
`initialized (interval: ${this.intervalMs}ms)`
);
}
normalizeLines(raw) {
return raw.split("\n").filter(Boolean).slice(-this.maxTrackedLines);
}
findOverlapLength(lines) {
const maxOverlap = Math.min(this.lastLines.length, lines.length);
for (let length = maxOverlap; length > 0; length--) {
let matches = true;
const previousStart = this.lastLines.length - length;
for (let index = 0; index < length; index++) {
if (this.lastLines[previousStart + index] !== lines[index]) {
matches = false;
break;
}
}
if (matches) {
return length;
}
}
return 0;
}
async checkOnce() {
if (!this.fetcher) {
logger.warn("[PodkopLogWatcher]", "fetcher not found");
return;
}
if (this.paused) {
logger.debug("[PodkopLogWatcher]", "skipped check \u2014 tab not visible");
return;
}
if (this.checking) {
logger.debug(
"[PodkopLogWatcher]",
"skipped check \u2014 previous check is running"
);
return;
}
this.checking = true;
try {
const raw = await this.fetcher();
const lines = this.normalizeLines(raw);
if (!this.initialized) {
this.initialized = true;
this.lastLines = lines;
if (this.suppressInitialLogs) {
return;
}
for (const line of lines) {
this.onNewLog?.(line);
}
return;
}
const overlapLength = this.findOverlapLength(lines);
const newLines = this.lastLines.length ? lines.slice(overlapLength) : lines;
for (const line of newLines) {
this.onNewLog?.(line);
}
this.lastLines = lines;
} catch (err) {
logger.error("[PodkopLogWatcher]", "failed to read logs:", err);
} finally {
this.checking = false;
}
}
start() {
if (this.running) return;
if (!this.fetcher) {
logger.warn("[PodkopLogWatcher]", "attempted to start without fetcher");
return;
}
this.running = true;
void this.checkOnce();
this.timer = setInterval(() => this.checkOnce(), this.intervalMs);
logger.info(
"[PodkopLogWatcher]",
`started (interval: ${this.intervalMs}ms)`
);
}
stop() {
if (!this.running) return;
this.running = false;
if (this.timer) clearInterval(this.timer);
logger.info("[PodkopLogWatcher]", "stopped");
}
pause() {
if (!this.running || this.paused) return;
this.paused = true;
logger.info("[PodkopLogWatcher]", "paused (tab not visible)");
}
resume() {
if (!this.running || !this.paused) return;
this.paused = false;
logger.info("[PodkopLogWatcher]", "resumed (tab active)");
this.checkOnce();
}
reset() {
this.lastLines = [];
this.initialized = false;
this.checking = false;
logger.info("[PodkopLogWatcher]", "log history reset");
}
};
// src/podkop/services/core.service.ts
var LOG_NOTIFICATION_DEDUPE_WINDOW_MS = 15e3;
var recentErrorNotifications = /* @__PURE__ */ new Map();
var activeErrorNotifications = /* @__PURE__ */ new Map();
function isErrorLogLine(line) {
const lower = line.toLowerCase();
return lower.includes("[error]") || lower.includes("[fatal]");
}
function isLogLifecycleBoundary(line) {
const lower = line.toLowerCase();
return lower.includes("[info] starting podkop plus") || lower.includes("[info] stopping podkop plus") || lower.includes("[info] podkop plus reload") || lower.includes("[info] podkop plus restart");
}
function getNotificationKey(line) {
const lower = line.toLowerCase();
const errorIndex = lower.indexOf("[error]");
const fatalIndex = lower.indexOf("[fatal]");
const markerIndex = errorIndex >= 0 && fatalIndex >= 0 ? Math.min(errorIndex, fatalIndex) : Math.max(errorIndex, fatalIndex);
return (markerIndex >= 0 ? line.slice(markerIndex) : line).trim();
}
function shouldNotifyAboutLogLine(line) {
const key = getNotificationKey(line);
const now = Date.now();
const lastShownAt = recentErrorNotifications.get(key) ?? 0;
recentErrorNotifications.forEach((shownAt, storedKey) => {
if (now - shownAt > LOG_NOTIFICATION_DEDUPE_WINDOW_MS) {
recentErrorNotifications.delete(storedKey);
}
});
if (now - lastShownAt < LOG_NOTIFICATION_DEDUPE_WINDOW_MS) {
return false;
}
recentErrorNotifications.set(key, now);
return true;
}
function removeNotification(notification) {
if (!notification.parentNode) {
return;
}
notification.classList.add("fade-out");
notification.classList.remove("fade-in");
setTimeout(() => notification.remove(), 500);
}
function clearLogErrorNotifications() {
activeErrorNotifications.forEach(removeNotification);
activeErrorNotifications.clear();
recentErrorNotifications.clear();
}
function showLogErrorNotification(line) {
const key = getNotificationKey(line);
const existingNotification = activeErrorNotifications.get(key);
if (existingNotification) {
removeNotification(existingNotification);
}
const notification = ui.addNotification(
_("Podkop Plus Error"),
E("div", {}, line),
"error",
"pdk-log-error-notification"
);
activeErrorNotifications.set(key, notification);
}
function coreService() {
TabServiceInstance.onChange((activeId, tabs) => {
logger.info("[TAB]", activeId);
store.set({
tabService: {
current: activeId || "",
all: tabs.map((tab) => tab.id)
}
});
});
const watcher = PodkopLogWatcher.getInstance();
watcher.init(
async () => {
const logs = await PodkopShellMethods.checkLogs();
if (logs.success) {
return logs.data;
}
return "";
},
{
intervalMs: 3e3,
suppressInitialLogs: true,
onNewLog: (line) => {
if (isLogLifecycleBoundary(line)) {
clearLogErrorNotifications();
}
if (isErrorLogLine(line) && shouldNotifyAboutLogLine(line)) {
showLogErrorNotification(line);
}
}
}
);
watcher.start();
}
// src/podkop/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();
}
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
);
}
}
this.sockets.clear();
this.listeners.clear();
this.errorListeners.clear();
this.connected.clear();
logger.info("[SOCKET]", "All connections and state have been reset.");
}
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));
return;
}
this.sockets.set(url, ws);
this.connected.set(url, false);
this.listeners.set(url, /* @__PURE__ */ new Set());
this.errorListeners.set(url, /* @__PURE__ */ new Set());
ws.addEventListener("open", () => {
this.connected.set(url, true);
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");
});
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.sockets.has(url)) {
this.connect(url);
}
if (!this.listeners.has(url)) {
this.listeners.set(url, /* @__PURE__ */ new Set());
}
this.listeners.get(url)?.add(listener);
}
unsubscribe(url, listener, onError) {
this.listeners.get(url)?.delete(listener);
if (onError) {
this.errorListeners.get(url)?.delete(onError);
}
}
// eslint-disable-next-line
send(url, data) {
const ws = this.sockets.get(url);
if (ws && this.connected.get(url)) {
ws.send(typeof data === "string" ? data : JSON.stringify(data));
} else {
logger.warn("[SOCKET]", `Cannot send: not connected to ${url}`);
this.triggerError(url, "Not connected");
}
}
disconnect(url) {
const ws = this.sockets.get(url);
if (ws) {
ws.close();
this.sockets.delete(url);
this.listeners.delete(url);
this.errorListeners.delete(url);
this.connected.delete(url);
}
}
disconnectAll() {
for (const url of this.sockets.keys()) {
this.disconnect(url);
}
}
triggerError(url, err) {
const handlers = this.errorListeners.get(url);
if (handlers) {
for (const cb of handlers) {
try {
cb(err);
} catch (e) {
logger.error("[SOCKET]", `Error handler threw for ${url}:`, e);
}
}
}
}
};
var socket = SocketManager.getInstance();
// src/podkop/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 [podkopResult, singboxResult] = await Promise.allSettled([
PodkopShellMethods.getStatus(),
PodkopShellMethods.getSingBoxStatus()
]);
if (requestId !== latestServicesInfoRequestId) {
return;
}
const podkop = getSettledMethodResponse("getStatus", podkopResult);
const singbox = getSettledMethodResponse("getSingBoxStatus", singboxResult);
store.set({
servicesInfoWidget: {
loading: false,
failed: !podkop.success || !singbox.success,
data: {
singbox: singbox.success ? singbox.data.running : 0,
podkopRunning: podkop.success ? podkop.data.running : 0,
podkopEnabled: podkop.success ? podkop.data.enabled : 0,
podkopStatus: podkop.success ? podkop.data.status : ""
}
}
});
}
// src/podkop/tabs/dashboard/initController.ts
var SECTIONS_REFRESH_INTERVAL_MS = 1e4;
var sectionsRefreshTimer = null;
var sectionsRefreshPromise = null;
var sectionsRefreshQueued = false;
var dashboardMounted = false;
var dashboardMountId = 0;
async function fetchDashboardSectionsOnce(mountId) {
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 CustomPodkopMethods.getDashboardSections();
if (!dashboardMounted || mountId !== dashboardMountId) {
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) {
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) {
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) {
const sectionsWidget = store.get().sectionsWidget;
const latencyFetchingSections = {
...sectionsWidget.latencyFetchingSections
};
if (fetching) {
latencyFetchingSections[sectionName] = true;
} else {
delete latencyFetchingSections[sectionName];
}
store.set({
sectionsWidget: {
...sectionsWidget,
latencyFetchingSections
}
});
}
async function connectToClashSockets() {
const clashApiSecret = await getClashApiSecret2();
socket.subscribe(
`${getClashWsUrl()}/traffic?token=${clashApiSecret}`,
(msg) => {
const parsedMsg = JSON.parse(msg);
store.set({
bandwidthWidget: {
loading: false,
failed: false,
data: { up: parsedMsg.up, down: parsedMsg.down }
}
});
},
(_err) => {
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) => {
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) => {
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
}
}
});
}
);
}
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] || section.outbounds.some(
(outbound) => outbound.code === tag && outbound.selected
)) {
return;
}
setSelectorSwitching(sectionName, tag);
try {
await PodkopShellMethods.setClashApiGroupProxy(selector, tag);
await fetchDashboardSections({ force: true });
} finally {
setSelectorSwitching(sectionName);
}
}
async function handleTestGroupLatency(sectionName, tag) {
if (store.get().sectionsWidget.latencyFetchingSections[sectionName]) {
return;
}
setLatencyFetching(sectionName, true);
try {
await PodkopShellMethods.getClashApiGroupLatency(tag);
await fetchDashboardSections({ force: true });
} finally {
setLatencyFetching(sectionName, false);
}
}
async function handleTestProxyLatency(sectionName, tag) {
if (store.get().sectionsWidget.latencyFetchingSections[sectionName]) {
return;
}
setLatencyFetching(sectionName, true);
try {
await PodkopShellMethods.getClashApiProxyLatency(tag);
await fetchDashboardSections({ force: true });
} finally {
setLatencyFetching(sectionName, false);
}
}
async function handleCopyOutbound(section, outbound) {
const link = outbound.link;
if (link && isCopyableProxyLink(link)) {
copyToClipboard(link);
return;
}
const response = await PodkopShellMethods.getOutboundLink(
section.sectionName,
outbound.code
);
if (response.success && isCopyableProxyLink(response.data.link)) {
copyToClipboard(response.data.link);
return;
}
showToast(_("Proxy link is unavailable"), "error");
}
async function handleUpdateSubscription(section) {
if (store.get().sectionsWidget.subscriptionUpdatingSections[section.sectionName]) {
return;
}
setSubscriptionUpdating(section.sectionName, true);
try {
const response = await PodkopShellMethods.subscriptionUpdate(
section.sectionName
);
if (!response.success) {
setSubscriptionUpdating(section.sectionName, false);
showToast(_("Failed to update subscriptions"), "error");
return;
}
setSubscriptionUpdating(section.sectionName, false);
showToast(_("Subscription update completed"), "success");
void fetchDashboardSections({ force: true });
void fetchServicesInfo();
} catch (error) {
logger.error("[DASHBOARD]", "handleUpdateSubscription: failed", error);
setSubscriptionUpdating(section.sectionName, false);
showToast(_("Failed to update subscriptions"), "error");
}
}
async function renderSectionsWidget() {
logger.debug("[DASHBOARD]", "renderSectionsWidget");
const sectionsWidget = store.get().sectionsWidget;
const container = document.getElementById("dashboard-sections-grid");
if (!container) {
return;
}
if (sectionsWidget.loading || sectionsWidget.failed) {
const renderedWidget = renderSections({
loading: sectionsWidget.loading,
failed: sectionsWidget.failed,
section: {
code: "",
sectionName: "",
displayName: "",
outbounds: [],
withTagSelect: false
},
onTestLatency: () => {
},
onChooseOutbound: () => {
},
onCopyOutbound: () => {
},
onUpdateSubscription: () => {
},
latencyFetching: false,
subscriptionUpdating: false,
selectorSwitchingTag: void 0
});
return preserveScrollForPage(() => {
container.replaceChildren(renderedWidget);
});
}
const renderedWidgets = sectionsWidget.data.map(
(section) => renderSections({
loading: sectionsWidget.loading,
failed: sectionsWidget.failed,
section,
latencyFetching: Boolean(
sectionsWidget.latencyFetchingSections[section.sectionName]
),
subscriptionUpdating: Boolean(
sectionsWidget.subscriptionUpdatingSections[section.sectionName]
),
selectorSwitchingTag: sectionsWidget.selectorSwitchingSections[section.sectionName],
onTestLatency: (tag) => {
if (section.withTagSelect) {
return handleTestGroupLatency(section.sectionName, tag);
}
return handleTestProxyLatency(section.sectionName, tag);
},
onChooseOutbound: (sectionName, selector, tag) => {
void handleChooseOutbound(sectionName, selector, tag);
},
onCopyOutbound: (section2, outbound) => {
void handleCopyOutbound(section2, outbound);
},
onUpdateSubscription: (section2) => {
void handleUpdateSubscription(section2);
}
})
);
return preserveScrollForPage(() => {
container.replaceChildren(...renderedWidgets);
});
}
async function renderBandwidthWidget() {
logger.debug("[DASHBOARD]", "renderBandwidthWidget");
const traffic = store.get().bandwidthWidget;
const container = document.getElementById("dashboard-widget-traffic");
if (!container) {
return;
}
if (traffic.loading || traffic.failed) {
const renderedWidget2 = renderWidget({
loading: traffic.loading,
failed: traffic.failed,
title: "",
items: []
});
return container.replaceChildren(renderedWidget2);
}
const renderedWidget = renderWidget({
loading: traffic.loading,
failed: traffic.failed,
title: _("Traffic"),
items: [
{ key: _("Uplink"), value: `${prettyBytes(traffic.data.up)}/s` },
{ key: _("Downlink"), value: `${prettyBytes(traffic.data.down)}/s` }
]
});
container.replaceChildren(renderedWidget);
}
async function renderTrafficTotalWidget() {
logger.debug("[DASHBOARD]", "renderTrafficTotalWidget");
const trafficTotalWidget = store.get().trafficTotalWidget;
const container = document.getElementById("dashboard-widget-traffic-total");
if (!container) {
return;
}
if (trafficTotalWidget.loading || trafficTotalWidget.failed) {
const renderedWidget2 = renderWidget({
loading: trafficTotalWidget.loading,
failed: trafficTotalWidget.failed,
title: "",
items: []
});
return container.replaceChildren(renderedWidget2);
}
const renderedWidget = renderWidget({
loading: trafficTotalWidget.loading,
failed: trafficTotalWidget.failed,
title: _("Traffic Total"),
items: [
{
key: _("Uplink"),
value: String(prettyBytes(trafficTotalWidget.data.uploadTotal))
},
{
key: _("Downlink"),
value: String(prettyBytes(trafficTotalWidget.data.downloadTotal))
}
]
});
container.replaceChildren(renderedWidget);
}
async function renderSystemInfoWidget() {
logger.debug("[DASHBOARD]", "renderSystemInfoWidget");
const systemInfoWidget = store.get().systemInfoWidget;
const container = document.getElementById("dashboard-widget-system-info");
if (!container) {
return;
}
if (systemInfoWidget.loading || systemInfoWidget.failed) {
const renderedWidget2 = renderWidget({
loading: systemInfoWidget.loading,
failed: systemInfoWidget.failed,
title: "",
items: []
});
return container.replaceChildren(renderedWidget2);
}
const renderedWidget = renderWidget({
loading: systemInfoWidget.loading,
failed: systemInfoWidget.failed,
title: _("System info"),
items: [
{
key: _("Active Connections"),
value: String(systemInfoWidget.data.connections)
},
{
key: _("Memory Usage"),
value: String(prettyBytes(systemInfoWidget.data.memory))
}
]
});
container.replaceChildren(renderedWidget);
}
async function renderServicesInfoWidget() {
logger.debug("[DASHBOARD]", "renderServicesInfoWidget");
const servicesInfoWidget = store.get().servicesInfoWidget;
const container = document.getElementById("dashboard-widget-service-info");
if (!container) {
return;
}
if (servicesInfoWidget.loading || servicesInfoWidget.failed) {
const renderedWidget2 = renderWidget({
loading: servicesInfoWidget.loading,
failed: servicesInfoWidget.failed,
title: "",
items: []
});
return container.replaceChildren(renderedWidget2);
}
const renderedWidget = renderWidget({
loading: servicesInfoWidget.loading,
failed: servicesInfoWidget.failed,
title: _("Services info"),
items: [
{
key: "Podkop Plus",
value: servicesInfoWidget.data.podkopRunning ? _("\u2714 Running") : _("\u2718 Stopped"),
attributes: {
class: servicesInfoWidget.data.podkopRunning ? "pdk_dashboard-page__widgets-section__item__row--success" : "pdk_dashboard-page__widgets-section__item__row--error"
}
},
{
key: "Sing-box",
value: servicesInfoWidget.data.singbox ? _("\u2714 Running") : _("\u2718 Stopped"),
attributes: {
class: servicesInfoWidget.data.singbox ? "pdk_dashboard-page__widgets-section__item__row--success" : "pdk_dashboard-page__widgets-section__item__row--error"
}
}
]
});
container.replaceChildren(renderedWidget);
}
async function onStoreUpdate(_next, _prev, diff) {
if (diff.sectionsWidget) {
renderSectionsWidget();
}
if (diff.bandwidthWidget) {
renderBandwidthWidget();
}
if (diff.trafficTotalWidget) {
renderTrafficTotalWidget();
}
if (diff.systemInfoWidget) {
renderSystemInfoWidget();
}
if (diff.servicesInfoWidget) {
renderServicesInfoWidget();
}
}
async function onPageMount() {
onPageUnmount();
dashboardMounted = true;
dashboardMountId += 1;
store.subscribe(onStoreUpdate);
void fetchDashboardSections({ force: true });
void fetchServicesInfo();
void connectToClashSockets();
sectionsRefreshTimer = setInterval(() => {
void fetchDashboardSections();
}, SECTIONS_REFRESH_INTERVAL_MS);
}
function onPageUnmount() {
dashboardMounted = false;
dashboardMountId += 1;
if (sectionsRefreshTimer) {
clearInterval(sectionsRefreshTimer);
sectionsRefreshTimer = null;
}
sectionsRefreshQueued = false;
sectionsRefreshPromise = null;
store.unsubscribe(onStoreUpdate);
store.reset([
"bandwidthWidget",
"trafficTotalWidget",
"systemInfoWidget",
"servicesInfoWidget",
"sectionsWidget"
]);
socket.resetAll();
}
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");
onPageMount();
registerLifecycleListeners();
});
}
// src/podkop/tabs/dashboard/styles.ts
var styles = `
#cbi-${PODKOP_UCI_PACKAGE}-dashboard-_mount_node > div {
width: 100%;
}
#cbi-${PODKOP_UCI_PACKAGE}-dashboard > h3 {
display: none;
}
.pdk_dashboard-page {
width: 100%;
--dashboard-grid-columns: 4;
--dashboard-grid-min-width: 180px;
}
@media (max-width: 900px) {
.pdk_dashboard-page {
--dashboard-grid-columns: 2;
}
}
@media (max-width: 560px) {
.pdk_dashboard-page {
--dashboard-grid-columns: 1;
--dashboard-grid-min-width: 0;
}
}
.pdk_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;
}
.pdk_dashboard-page__widgets-section__item {
border: 2px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 10px;
min-width: 0;
}
.pdk_dashboard-page__widgets-section__item__title {}
.pdk_dashboard-page__widgets-section__item__row {}
.pdk_dashboard-page__widgets-section__item__row--success .pdk_dashboard-page__widgets-section__item__row__value {
color: var(--success-color-medium, green);
}
.pdk_dashboard-page__widgets-section__item__row--error .pdk_dashboard-page__widgets-section__item__row__value {
color: var(--error-color-medium, red);
}
.pdk_dashboard-page__widgets-section__item__row__key {}
.pdk_dashboard-page__widgets-section__item__row__value {}
.pdk_dashboard-page__outbound-section {
margin-top: 10px;
border: 2px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 10px;
}
.pdk_dashboard-page__outbound-section__title-section {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px 10px;
min-width: 0;
}
.pdk_dashboard-page__outbound-section__title-section__title {
color: var(--text-color-high);
font-weight: 700;
min-width: 0;
overflow-wrap: anywhere;
}
.pdk_dashboard-page__outbound-section__title-section__actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
flex: 0 0 auto;
}
.pdk_dashboard-page .btn.pdk_dashboard-page__outbound-section__subscription-update {
width: 28px;
height: 28px;
min-width: 28px;
min-height: 28px;
padding: 2px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
margin: 0;
}
.pdk_dashboard-page__outbound-section__subscription-update svg {
width: 15px;
height: 15px;
display: block;
flex: 0 0 auto;
}
.pdk_dashboard-page__outbound-section__subscription-update[disabled] {
cursor: not-allowed;
opacity: 0.65;
}
.pdk_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;
}
.pdk_dashboard-page .btn.dashboard-sections-grid-item-test-latency svg {
width: 15px;
height: 15px;
display: block;
flex: 0 0 auto;
}
.pdk_dashboard-page .btn.dashboard-sections-grid-item-test-latency[disabled] {
cursor: not-allowed;
opacity: 0.65;
}
.pdk_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;
}
.pdk_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);
}
.pdk_dashboard-page__subscription-meta__main {
display: flex;
align-items: center;
gap: 6px 10px;
min-width: 0;
}
.pdk_dashboard-page__subscription-meta__heading {
flex: 0 0 auto;
color: var(--text-color-high);
font-weight: 700;
line-height: 1.25;
white-space: nowrap;
}
.pdk_dashboard-page__subscription-meta__title {
flex: 0 1 auto;
width: max-content;
max-width: min(28ch, 30%);
min-width: min-content;
color: var(--text-color-high);
font-weight: 700;
line-height: 1.25;
overflow-wrap: anywhere;
}
.pdk_dashboard-page__subscription-meta__facts {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 5px 12px;
}
.pdk_dashboard-page__subscription-meta__fact {
display: flex;
align-items: baseline;
gap: 4px;
min-width: 0;
line-height: 1.25;
}
.pdk_dashboard-page__subscription-meta__fact-key {
color: var(--text-color-medium);
font-size: 12px;
white-space: nowrap;
}
.pdk_dashboard-page__subscription-meta__fact-value {
color: var(--text-color-high);
font-weight: 600;
overflow-wrap: anywhere;
}
.pdk_dashboard-page__subscription-meta__actions {
flex: 0 0 auto;
margin-left: auto;
display: flex;
justify-content: flex-end;
gap: var(--subscription-meta-action-gap);
}
.pdk_dashboard-page .btn.pdk_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;
}
.pdk_dashboard-page__subscription-meta__action svg {
width: 15px;
height: 15px;
display: block;
flex: 0 0 auto;
}
.pdk_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) {
.pdk_dashboard-page__subscription-meta__main {
align-items: flex-start;
flex-wrap: wrap;
}
.pdk_dashboard-page__subscription-meta__heading,
.pdk_dashboard-page__subscription-meta__title {
order: 1;
}
.pdk_dashboard-page__subscription-meta__actions {
order: 2;
}
.pdk_dashboard-page__subscription-meta__facts {
order: 3;
flex-basis: 100%;
}
.pdk_dashboard-page__subscription-meta__title {
max-width: calc(100% - 42px);
}
}
.pdk_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;
}
.pdk_dashboard-page__outbound-grid__item--selectable {
cursor: pointer;
}
.pdk_dashboard-page__outbound-grid__item--selectable:hover {
border-color: var(--primary-color-high, dodgerblue);
}
.pdk_dashboard-page__outbound-grid__item--active {
border-color: var(--success-color-medium, green);
}
.pdk_dashboard-page__outbound-grid__item--disabled {
cursor: default;
}
.pdk_dashboard-page__outbound-grid__item--switching {
border-color: transparent !important;
overflow: hidden;
cursor: wait;
}
.pdk_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;
}
.pdk_dashboard-page__outbound-grid__item__snake rect {
stroke: var(--primary-color-high, dodgerblue);
stroke-width: 4;
animation: pdk-dashboard-selector-snake-svg 1.2s linear infinite;
}
@keyframes pdk-dashboard-selector-snake-svg {
0% {
stroke-dasharray: 30 70;
stroke-dashoffset: 100;
}
100% {
stroke-dasharray: 30 70;
stroke-dashoffset: 0;
}
}
.pdk_dashboard-page__outbound-grid__item__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
}
.pdk_dashboard-page__outbound-grid__item__header b {
min-width: 0;
line-height: 1.25;
overflow-wrap: anywhere;
}
.pdk_dashboard-page .btn.pdk_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;
}
.pdk_dashboard-page__outbound-grid__item__copy-button svg {
width: 13px;
height: 13px;
display: block;
flex: 0 0 auto;
}
.pdk_dashboard-page__outbound-grid__item__footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 10px;
}
.pdk_dashboard-page__outbound-grid__item__type {
min-width: 0;
overflow-wrap: anywhere;
}
.pdk_dashboard-page__outbound-grid__item__latency--empty {
color: var(--primary-color-low, lightgray);
}
.pdk_dashboard-page__outbound-grid__item__latency--green {
color: var(--success-color-medium, green);
}
.pdk_dashboard-page__outbound-grid__item__latency--yellow {
color: var(--warn-color-medium, orange);
}
.pdk_dashboard-page__outbound-grid__item__latency--red {
color: var(--error-color-medium, red);
}
`;
// src/podkop/tabs/dashboard/index.ts
var DashboardTab = {
render,
initController,
styles
};
// src/podkop/tabs/diagnostic/renderDiagnostic.ts
function render2() {
return E("div", { id: "diagnostic-status", class: "pdk_diagnostic-page" }, [
E("div", { class: "pdk_diagnostic-page__left-bar" }, [
E("div", { id: "pdk_diagnostic-page-run-check" }),
E("div", {
class: "pdk_diagnostic-page__checks",
id: "pdk_diagnostic-page-checks"
})
]),
E("div", { class: "pdk_diagnostic-page__right-bar" }, [
E("div", { id: "pdk_diagnostic-page-wiki" }),
E("div", { id: "pdk_diagnostic-page-actions" }),
E("div", { id: "pdk_diagnostic-page-system-info" })
])
]);
}
// src/podkop/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/podkop/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/podkop/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 PodkopShellMethods.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 allGood = Boolean(data.dns_on_router) && Boolean(data.dhcp_config_status) && Boolean(data.bootstrap_dns_status) && Boolean(data.dns_status);
const atLeastOneGood = Boolean(data.dns_on_router) || Boolean(data.dhcp_config_status) || Boolean(data.bootstrap_dns_status) || Boolean(data.dns_status);
const { state, description } = getMeta({ atLeastOneGood, allGood });
updateCheckStore({
order,
code,
title,
description,
state,
items: [
...insertIf(
data.dns_type === "doh" || data.dns_type === "dot" || !data.bootstrap_dns_status,
[
{
state: data.bootstrap_dns_status ? "success" : "error",
key: _("Bootsrap DNS"),
value: data.bootstrap_dns_server
}
]
),
{
state: data.dns_status ? "success" : "error",
key: _("Main DNS"),
value: `${data.dns_server} [${data.dns_type}]`
},
{
state: data.dns_on_router ? "success" : "error",
key: _("DNS on router"),
value: ""
},
{
state: data.dhcp_config_status ? "success" : "error",
key: _("DHCP has DNS server"),
value: ""
}
]
});
if (!atLeastOneGood) {
throw new Error("DNS checks failed");
}
}
// src/podkop/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 PodkopShellMethods.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/podkop/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 PodkopShellMethods.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" : "error",
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/podkop/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 PodkopShellMethods.checkFakeIP();
const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck();
const checkIPResponse = await RemoteFakeIPMethods.getIpCheck();
const checks = {
singBoxFakeIP: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip,
browserFakeIP: checkFakeIPResponse.success && checkFakeIPResponse.data.fakeip,
differentIP: checkFakeIPResponse.success && checkIPResponse.success && checkFakeIPResponse.data.IP !== checkIPResponse.data.IP
};
const allGood = checks.singBoxFakeIP && checks.browserFakeIP && checks.differentIP;
const atLeastOneGood = checks.singBoxFakeIP || checks.browserFakeIP || checks.differentIP;
const { state, description } = getMeta({ atLeastOneGood, allGood });
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: checks.browserFakeIP ? "success" : "error",
key: checks.browserFakeIP ? _("Browser is using FakeIP correctly") : _("Browser is not using FakeIP"),
value: ""
},
...insertIf(checks.browserFakeIP, [
{
state: checks.differentIP ? "success" : "error",
key: checks.differentIP ? _("Proxy traffic is routed via FakeIP") : _("Proxy traffic is not routed via FakeIP"),
value: ""
}
])
]
});
}
// src/podkop/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 PodkopShellMethods.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 ready = Boolean(data.ready);
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 podkopRuntimeReady = !hasZapretRules || runningProcesses === expectedProcesses && supervisorProcesses === expectedProcesses;
const unexpectedRuntime = !hasZapretRules && (runningProcesses > 0 || supervisorProcesses > 0);
let state = "success";
let description = _("Checks passed");
if (hasZapretRules && !providerAvailable) {
state = "error";
description = _("Checks failed");
} else if (hasZapretRules && !ready) {
state = "error";
description = _("Checks failed");
} else if (queueOverlap) {
state = "error";
description = _("Checks failed");
} else if (!hasZapretRules && !providerAvailable) {
state = "warning";
description = _("Issues detected");
} else if (standaloneConflict) {
state = "warning";
description = _("Issues detected");
}
const items = [
{
state: providerAvailable ? "success" : hasZapretRules ? "error" : "warning",
key: providerAvailable ? _("Zapret provider binary is available") : _("Zapret provider binary is not available"),
value: ""
},
{
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 || !podkopRuntimeReady ? "error" : "success",
key: hasZapretRules ? podkopRuntimeReady ? _("Podkop Plus-managed nfqws runtime is ready") : _("Podkop Plus-managed nfqws runtime is not ready") : unexpectedRuntime ? _("Unexpected Podkop Plus-managed nfqws runtime is running") : _("Podkop Plus-managed nfqws runtime is not running"),
value: ""
},
{
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: standaloneConflict ? "warning" : "success",
key: standaloneServiceRunning ? hasZapretRules ? _(
"Standalone Zapret is active together with Podkop Plus Zapret rules"
) : _("Standalone Zapret service is active") : _("Standalone Zapret service is inactive"),
value: ""
}
];
updateCheckStore({
order,
code,
title,
description,
state,
items
});
}
// src/podkop/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 PodkopShellMethods.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 ready = Boolean(data.ready);
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 podkopRuntimeReady = !hasByedpiRules || runningProcesses === expectedProcesses && supervisorProcesses === expectedProcesses;
const unexpectedRuntime = !hasByedpiRules && (runningProcesses > 0 || supervisorProcesses > 0);
const outboundsConfigured = Boolean(data.outbounds_configured);
const routesConfigured = Boolean(data.routes_configured);
const standaloneServiceEnabled = Boolean(data.standalone_service_enabled);
const standaloneServiceRunning = Boolean(data.standalone_service_running);
const standaloneConflict = hasByedpiRules && standaloneServiceRunning;
const standaloneAutostartRisk = hasByedpiRules && standaloneServiceEnabled && !standaloneServiceRunning;
let state = "success";
let description = _("Checks passed");
if (hasByedpiRules && !providerAvailable) {
state = "error";
description = _("Checks failed");
} else if (hasByedpiRules && !ready) {
state = "error";
description = _("Checks failed");
} else if (standaloneConflict) {
state = "error";
description = _("Checks failed");
} else if (runtimeUnstable) {
state = "warning";
description = _("Issues detected");
} else if (standaloneAutostartRisk) {
state = "warning";
description = _("Issues detected");
} else if (!packageInstalled) {
state = "warning";
description = _("Issues detected");
}
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 || !podkopRuntimeReady ? "error" : runtimeUnstable ? "warning" : "success",
key: hasByedpiRules ? runtimeUnstable ? _("Podkop Plus-managed ciadpi runtime has restarted") : podkopRuntimeReady ? _("Podkop Plus-managed ciadpi runtime is ready") : _("Podkop Plus-managed ciadpi runtime is not ready") : unexpectedRuntime ? _("Unexpected Podkop Plus-managed ciadpi runtime is running") : _("Podkop Plus-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: !hasByedpiRules || routesConfigured ? "success" : "error",
key: routesConfigured ? _("ByeDPI route rules are configured") : _("ByeDPI route rules are not configured"),
value: ""
},
{
state: standaloneConflict ? "error" : standaloneAutostartRisk ? "warning" : "success",
key: standaloneServiceRunning ? hasByedpiRules ? _(
"Standalone ByeDPI is active together with Podkop Plus ByeDPI rules"
) : _("Standalone ByeDPI service is active") : standaloneAutostartRisk ? _("Standalone ByeDPI autostart is enabled") : _("Standalone ByeDPI service is inactive"),
value: ""
}
];
updateCheckStore({
order,
code,
title,
description,
state,
items
});
}
// src/podkop/services/systemInfo.service.ts
var UNKNOWN_SYSTEM_INFO = {
loading: false,
loaded: false,
providerInfoLoaded: false,
podkop_version: _("unknown"),
podkop_latest_version: _("unknown"),
luci_app_version: _("unknown"),
sing_box_version: _("unknown"),
sing_box_extended: 0,
zapret_version: _("unknown"),
zapret_installed: 0,
byedpi_version: _("unknown"),
byedpi_installed: 0,
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 PodkopShellMethods.getSystemInfo();
if (requestId !== latestSystemInfoRequestId) {
return store.get().diagnosticsSystemInfo;
}
if (systemInfo.success) {
const nextSystemInfo = {
...UNKNOWN_SYSTEM_INFO,
loading: false,
loaded: true,
providerInfoLoaded: true,
...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,
byedpi_installed: latestSystemInfo.byedpi_installed
};
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 = `
.pdk-partial-button {
text-align: center;
}
.pdk-partial-button--with-icon {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
}
.pdk-partial-button--loading {
}
.pdk-partial-button--disabled {
}
.pdk-partial-button__icon {
flex: 0 0 auto;
}
.pdk-partial-button__icon {
display: inline-flex;
align-items: center;
justify-content: center;
}
.pdk-partial-button__icon svg {
width: 16px;
height: 16px;
display: block;
flex: 0 0 auto;
}
`;
// src/partials/modal/styles.ts
var styles3 = `
.pdk-partial-modal__body {}
.pdk-partial-modal__content {
max-height: 70vh;
overflow: scroll;
border-radius: 4px;
}
.pdk-partial-modal__footer {
display: flex;
justify-content: flex-end;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.pdk-partial-modal__footer button {
margin-left: 0;
}
.pdk-partial-modal__checkbox {
display: inline-flex;
align-items: center;
gap: 8px;
margin-right: auto;
cursor: pointer;
user-select: none;
}
.pdk-partial-modal__checkbox-text {
line-height: 1.2;
}
`;
// src/partials/button/renderButton.ts
function renderButton({
classNames = [],
disabled,
loading: loading2,
onClick,
text,
icon
}) {
const hasIcon = !!loading2 || !!icon;
function getWrappedIcon() {
const iconWrap = E("span", {
class: "pdk-partial-button__icon"
});
if (loading2) {
iconWrap.appendChild(renderLoaderCircleIcon24());
return iconWrap;
}
if (icon) {
iconWrap.appendChild(icon());
return iconWrap;
}
return iconWrap;
}
function getClass() {
return [
"btn",
"pdk-partial-button",
...insertIf(Boolean(disabled), ["pdk-partial-button--disabled"]),
...insertIf(Boolean(loading2), ["pdk-partial-button--loading"]),
...insertIf(Boolean(hasIcon), ["pdk-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 currentText = text ?? "";
let refreshInFlight = false;
let pendingRefresh = false;
let refreshSessionId = 0;
let timer;
let observer;
let autoRefreshEnabled = options?.initialAutoRefresh ?? Boolean(options?.getText);
let shouldScrollToBottomOnMount = Boolean(options?.startAtEnd);
let autoRefreshInput;
const codeEl = E("code", {}, currentText);
const contentEl = E(
"pre",
{ class: "pdk-partial-modal__content" },
codeEl
);
const stopRefreshTimer = () => {
if (timer) {
clearInterval(timer);
timer = void 0;
}
};
const destroyLiveRefresh = () => {
refreshSessionId += 1;
pendingRefresh = 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 refreshText = async () => {
if (!options?.getText || !autoRefreshEnabled || refreshInFlight) {
return;
}
if (!body.isConnected) {
return;
}
refreshInFlight = true;
const sessionId = refreshSessionId;
try {
const nextText = await options.getText();
if (!body.isConnected || !autoRefreshEnabled) {
return;
}
if (sessionId !== refreshSessionId) {
return;
}
updateText(nextText ?? "");
} catch (error) {
console.warn("[renderModal] failed to refresh modal content", error);
} finally {
refreshInFlight = false;
if (pendingRefresh) {
pendingRefresh = false;
if (autoRefreshEnabled && body.isConnected) {
void refreshText();
}
}
}
};
const requestRefresh = () => {
if (!options?.getText || !autoRefreshEnabled) {
return;
}
if (refreshInFlight) {
pendingRefresh = true;
return;
}
void refreshText();
};
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;
if (autoRefreshInput) {
autoRefreshInput.checked = nextValue;
}
if (nextValue) {
startRefreshTimer();
requestRefresh();
return;
}
stopRefreshTimer();
};
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: "pdk-partial-modal__checkbox" }, [
autoRefreshInput,
E(
"span",
{ class: "pdk-partial-modal__checkbox-text" },
options.autoRefreshLabel ?? _("Auto refresh")
)
])
);
}
const body = E("div", { class: "pdk-partial-modal__body" }, [
E("div", {}, [
contentEl,
E("div", { class: "pdk-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();
}
return body;
}
// src/partials/index.ts
var PartialStyles = `
${styles2}
${styles3}
`;
// src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts
function renderAvailableActions({
restart,
start,
stop,
enable,
disable,
globalCheck,
viewLogs,
showSingBoxConfig
}) {
return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [
E("b", {}, _("Available actions")),
...insertIf(restart.visible, [
renderButton({
classNames: ["cbi-button-apply"],
onClick: restart.onClick,
icon: renderRotateCcwIcon24,
text: _("Restart Podkop Plus"),
loading: restart.loading,
disabled: restart.disabled
})
]),
...insertIf(stop.visible, [
renderButton({
classNames: ["cbi-button-remove"],
onClick: stop.onClick,
icon: renderCircleStopIcon24,
text: _("Stop Podkop Plus"),
loading: stop.loading,
disabled: stop.disabled
})
]),
...insertIf(start.visible, [
renderButton({
classNames: ["cbi-button-save"],
onClick: start.onClick,
icon: renderCirclePlayIcon24,
text: _("Start Podkop Plus"),
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(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
})
])
]);
}
// src/podkop/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: "pdk_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: `pdk_diagnostic_alert__summary__item pdk_diagnostic_alert__summary__item--${item.state}`
},
[getIcon(), E("b", {}, item.key), E("div", {}, item.value)]
);
});
return E("div", { class: "pdk_diagnostic_alert__summary" }, renderedItems);
}
function renderLoadingState3(props) {
const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" });
iconWrap.appendChild(renderLoaderCircleIcon24());
return E(
"div",
{ class: "pdk_diagnostic_alert pdk_diagnostic_alert--loading" },
[
iconWrap,
E("div", { class: "pdk_diagnostic_alert__content" }, [
E("b", { class: "pdk_diagnostic_alert__title" }, props.title),
E(
"div",
{ class: "pdk_diagnostic_alert__description" },
props.description
)
]),
E("div", {}, ""),
renderCheckSummary(props.items)
]
);
}
function renderWarningState(props) {
const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" });
iconWrap.appendChild(renderCircleAlertIcon24());
return E(
"div",
{ class: "pdk_diagnostic_alert pdk_diagnostic_alert--warning" },
[
iconWrap,
E("div", { class: "pdk_diagnostic_alert__content" }, [
E("b", { class: "pdk_diagnostic_alert__title" }, props.title),
E(
"div",
{ class: "pdk_diagnostic_alert__description" },
props.description
)
]),
E("div", {}, ""),
renderCheckSummary(props.items)
]
);
}
function renderErrorState(props) {
const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" });
iconWrap.appendChild(renderCircleXIcon24());
return E(
"div",
{ class: "pdk_diagnostic_alert pdk_diagnostic_alert--error" },
[
iconWrap,
E("div", { class: "pdk_diagnostic_alert__content" }, [
E("b", { class: "pdk_diagnostic_alert__title" }, props.title),
E(
"div",
{ class: "pdk_diagnostic_alert__description" },
props.description
)
]),
E("div", {}, ""),
renderCheckSummary(props.items)
]
);
}
function renderSuccessState(props) {
const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" });
iconWrap.appendChild(renderCircleCheckIcon24());
return E(
"div",
{ class: "pdk_diagnostic_alert pdk_diagnostic_alert--success" },
[
iconWrap,
E("div", { class: "pdk_diagnostic_alert__content" }, [
E("b", { class: "pdk_diagnostic_alert__title" }, props.title),
E(
"div",
{ class: "pdk_diagnostic_alert__description" },
props.description
)
]),
E("div", {}, ""),
renderCheckSummary(props.items)
]
);
}
function renderSkippedState(props) {
const iconWrap = E("span", { class: "pdk_diagnostic_alert__icon" });
iconWrap.appendChild(renderCircleSlashIcon24());
return E(
"div",
{ class: "pdk_diagnostic_alert pdk_diagnostic_alert--skipped" },
[
iconWrap,
E("div", { class: "pdk_diagnostic_alert__content" }, [
E("b", { class: "pdk_diagnostic_alert__title" }, props.title),
E(
"div",
{ class: "pdk_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/podkop/tabs/diagnostic/partials/renderRunAction.ts
function renderRunAction({
loading: loading2,
disabled,
click
}) {
return E("div", { class: "pdk_diagnostic-page__run_check_wrapper" }, [
renderButton({
text: _("Run Diagnostic"),
onClick: click,
icon: renderSearchIcon24,
loading: loading2,
disabled,
classNames: ["cbi-button-apply"]
})
]);
}
// src/podkop/tabs/diagnostic/partials/renderSystemInfo.ts
function renderSystemInfo({ items }) {
return E("div", { class: "pdk_diagnostic-page__right-bar__system-info" }, [
E(
"b",
{ class: "pdk_diagnostic-page__right-bar__system-info__title" },
_("System information")
),
...items.map((item) => {
const tagClass = [
"pdk_diagnostic-page__right-bar__system-info__row__tag",
...insertIf(item.tag?.kind === "neutral", [
"pdk_diagnostic-page__right-bar__system-info__row__tag--neutral"
]),
...insertIf(item.tag?.kind === "warning", [
"pdk_diagnostic-page__right-bar__system-info__row__tag--warning"
]),
...insertIf(item.tag?.kind === "success", [
"pdk_diagnostic-page__right-bar__system-info__row__tag--success"
])
].filter(Boolean).join(" ");
return E(
"div",
{ class: "pdk_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) {
if (version.includes("COMPILED")) {
return "dev";
}
return version.replace(/^(\d+(?:\.\d+)*)-r(\d+)$/i, "$1-$2");
}
// src/podkop/tabs/diagnostic/partials/renderWikiDisclaimer.ts
function renderWikiDisclaimer(kind) {
const iconWrap = E("span", {
class: "pdk_diagnostic-page__right-bar__wiki__icon"
});
iconWrap.appendChild(renderBookOpenTextIcon24());
const className = [
"pdk_diagnostic-page__right-bar__wiki",
...insertIf(kind === "error", [
"pdk_diagnostic-page__right-bar__wiki--error"
]),
...insertIf(kind === "warning", [
"pdk_diagnostic-page__right-bar__wiki--warning"
])
].join(" ");
return E("div", { class: className }, [
E("div", { class: "pdk_diagnostic-page__right-bar__wiki__content" }, [
iconWrap,
E("div", { class: "pdk_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/ushan0v/podkop-plus#readme",
"_blank",
"noopener,noreferrer"
)
})
]);
}
// src/podkop/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 latencyGroup = await PodkopShellMethods.getClashApiGroupLatency(
section.code
);
const selectedOutbound = section.outbounds.find((item) => item.selected) ?? section.outbounds.find(
(item) => item.type?.toLowerCase() === "urltest"
) ?? section.outbounds[0];
const isUrlTest = selectedOutbound?.type?.toLowerCase() === "urltest";
const isSubscription = section.proxyConfigType === "subscription";
const success2 = latencyGroup.success && !latencyGroup.data.message;
if (success2) {
const latencyValues = Object.values(latencyGroup.data);
const sectionState = isSubscription ? getSubscriptionLatencyState(latencyValues) : "success";
if (isUrlTest) {
const latency2 = latencyValues.map((item) => item ? `${item}ms` : "n/a").join(" / ");
return {
state: sectionState,
latency: `[${_("Fastest")}] ${latency2}`
};
}
const selectedProxyDelay = latencyGroup.data?.[selectedOutbound?.code ?? ""];
if (selectedProxyDelay) {
return {
state: sectionState,
latency: `[${selectedOutbound?.displayName ?? ""}] ${selectedProxyDelay}ms`
};
}
return {
state: "error",
latency: `[${selectedOutbound?.displayName ?? ""}] ${_("Not responding")}`
};
}
return {
state: "error",
latency: _("Not responding")
};
}
const latencyProxy = await PodkopShellMethods.getClashApiProxyLatency(
section.code
);
const success = latencyProxy.success && !latencyProxy.data.message;
if (success) {
return {
state: "success",
latency: `${latencyProxy.data.delay} ms`
};
}
return {
state: "error",
latency: _("Not responding")
};
}
const { latency, state: state2 } = await getLatency();
items.push({
state: state2,
key: section.displayName,
value: latency
});
}
const allGood = items.every((item) => item.state === "success");
const atLeastOneGood = items.some((item) => item.state !== "error");
const { state, description } = getMeta({ atLeastOneGood, allGood });
updateCheckStore({
order,
code,
title,
description,
state,
items
});
if (!atLeastOneGood) {
throw new Error("Rule outbounds checks failed");
}
}
// src/podkop/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 servicesInfoRefreshTimer = null;
var servicesInfoRefreshPromise = null;
function sleep2(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function getDiagnosticsProviderOptions(systemInfo = store.get().diagnosticsSystemInfo) {
return {
includeZapret: Boolean(systemInfo.zapret_installed),
includeByedpi: Boolean(systemInfo.byedpi_installed)
};
}
function getNotRunningDiagnosticsChecks() {
return getDiagnosticsChecks(
_("Not running"),
getDiagnosticsProviderOptions()
);
}
function resetDiagnosticsChecks() {
store.set({
diagnosticsChecks: getNotRunningDiagnosticsChecks()
});
}
function setDiagnosticActionLoading(action, loading2) {
const diagnosticsActions = store.get().diagnosticsActions;
store.set({
diagnosticsActions: {
...diagnosticsActions,
[action]: { loading: loading2 }
}
});
}
function isDiagnosticMountActive(mountId = diagnosticMountId) {
return diagnosticMounted && diagnosticMountId === mountId;
}
function isMutatingServiceActionLoading() {
const actions = store.get().diagnosticsActions;
return actions.restart.loading || actions.start.loading || actions.stop.loading || actions.enable.loading || actions.disable.loading;
}
function getPodkopStatusText(running, enabled) {
if (running) {
return enabled ? "running & enabled" : "running but disabled";
}
return enabled ? "stopped but enabled" : "stopped & disabled";
}
function setDisplayedPodkopRunning(running) {
const servicesInfoWidget = store.get().servicesInfoWidget;
const enabled = Boolean(servicesInfoWidget.data.podkopEnabled);
store.set({
servicesInfoWidget: {
...servicesInfoWidget,
loading: false,
data: {
...servicesInfoWidget.data,
podkopRunning: running ? 1 : 0,
podkopStatus: getPodkopStatusText(running, enabled)
}
}
});
}
async function refreshDiagnosticServicesInfo({
force = false,
mountId = diagnosticMountId,
allowInactive = false
} = {}) {
if (!allowInactive && !isDiagnosticMountActive(mountId)) {
return;
}
if (!force && isMutatingServiceActionLoading()) {
return;
}
if (servicesInfoRefreshPromise) {
return servicesInfoRefreshPromise;
}
const promise = fetchServicesInfo().catch((error) => {
logger.error(
"[DIAGNOSTIC]",
"refreshDiagnosticServicesInfo failed",
error
);
}).finally(() => {
if (servicesInfoRefreshPromise === promise) {
servicesInfoRefreshPromise = null;
}
});
servicesInfoRefreshPromise = promise;
return promise;
}
async function waitForPodkopRunningState(expectedRunning) {
const startedAt = Date.now();
while (Date.now() - startedAt < SERVICE_ACTION_STATUS_TIMEOUT_MS) {
await refreshDiagnosticServicesInfo({ force: true, allowInactive: true });
const podkopRunning = Boolean(
store.get().servicesInfoWidget.data.podkopRunning
);
if (podkopRunning === expectedRunning) {
return true;
}
await sleep2(SERVICE_STATUS_REFRESH_INTERVAL_MS);
}
return false;
}
function startServicesInfoRefreshTimer() {
if (servicesInfoRefreshTimer) {
clearInterval(servicesInfoRefreshTimer);
}
servicesInfoRefreshTimer = setInterval(() => {
void refreshDiagnosticServicesInfo();
}, SERVICE_STATUS_REFRESH_INTERVAL_MS);
}
function stopServicesInfoRefreshTimer() {
if (!servicesInfoRefreshTimer) {
return;
}
clearInterval(servicesInfoRefreshTimer);
servicesInfoRefreshTimer = null;
}
async function fetchSystemInfo() {
const systemInfo = await ensureSystemInfo();
store.set({
diagnosticsChecks: getDiagnosticsChecks(
_("Not running"),
getDiagnosticsProviderOptions(systemInfo)
)
});
}
async function fetchDiagnosticsProviderInfo() {
const requestId = ++latestProviderInfoRequestId;
try {
const [zapretRuntime, byedpiRuntime] = await Promise.all([
PodkopShellMethods.checkZapretRuntime(),
PodkopShellMethods.checkByedpiRuntime()
]);
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,
byedpi_installed: byedpiRuntime.success ? byedpiRuntime.data.byedpi_installed : currentSystemInfo.byedpi_installed
};
if (!zapretRuntime.success) {
logger.error("[DIAGNOSTIC]", "fetchZapretRuntime failed", zapretRuntime);
}
if (!byedpiRuntime.success) {
logger.error("[DIAGNOSTIC]", "fetchByedpiRuntime failed", byedpiRuntime);
}
if (!nextSystemInfo.zapret_installed) {
nextSystemInfo.zapret_version = "not installed";
}
if (!nextSystemInfo.byedpi_installed) {
nextSystemInfo.byedpi_version = "not installed";
}
store.set({
diagnosticsSystemInfo: nextSystemInfo,
diagnosticsChecks: getDiagnosticsChecks(
_("Not running"),
getDiagnosticsProviderOptions(nextSystemInfo)
)
});
} catch (error) {
logger.error("[DIAGNOSTIC]", "fetchDiagnosticsProviderInfo failed", error);
if (requestId === latestProviderInfoRequestId) {
const currentSystemInfo = store.get().diagnosticsSystemInfo;
store.set({
diagnosticsSystemInfo: {
...currentSystemInfo,
providerInfoLoaded: true
}
});
}
}
}
async function ensureDiagnosticsProviderInfo() {
if (store.get().diagnosticsSystemInfo.providerInfoLoaded) {
return;
}
await fetchDiagnosticsProviderInfo();
}
function renderDiagnosticsChecks() {
logger.debug("[DIAGNOSTIC]", "renderDiagnosticsChecks");
const diagnosticsChecks = [...store.get().diagnosticsChecks].sort(
(a, b) => a.order - b.order
);
const container = document.getElementById("pdk_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 container = document.getElementById("pdk_diagnostic-page-run-check");
const renderedAction = renderRunAction({
loading: loading2,
disabled: !providerInfoLoaded,
click: () => runChecks()
});
return preserveScrollForPage(() => {
container.replaceChildren(renderedAction);
});
}
async function handleServiceRuntimeAction({
action,
command,
expectedRunning,
optimisticRunning
}) {
setDiagnosticActionLoading(action, true);
if (optimisticRunning !== void 0) {
setDisplayedPodkopRunning(optimisticRunning);
}
try {
await command();
await waitForPodkopRunningState(expectedRunning);
} catch (e) {
logger.error("[DIAGNOSTIC]", `handleServiceRuntimeAction(${action})`, e);
} finally {
setDiagnosticActionLoading(action, false);
resetDiagnosticsChecks();
}
}
async function handleRestart() {
await handleServiceRuntimeAction({
action: "restart",
command: PodkopShellMethods.restart,
expectedRunning: true,
optimisticRunning: false
});
}
async function handleStart() {
await handleServiceRuntimeAction({
action: "start",
command: PodkopShellMethods.start,
expectedRunning: true
});
}
async function handleStop() {
await handleServiceRuntimeAction({
action: "stop",
command: PodkopShellMethods.stop,
expectedRunning: false
});
}
async function handleEnable() {
setDiagnosticActionLoading("enable", true);
try {
await PodkopShellMethods.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 PodkopShellMethods.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 PodkopShellMethods.globalCheck();
if (globalCheck.success) {
ui.showModal(
_("Global check"),
renderModal(globalCheck.data, "global_check")
);
} else {
logger.error("[DIAGNOSTIC]", "handleShowGlobalCheck - e", globalCheck);
}
} catch (e) {
logger.error("[DIAGNOSTIC]", "handleShowGlobalCheck - e", e);
} finally {
setDiagnosticActionLoading("globalCheck", false);
}
}
async function handleViewLogs() {
setDiagnosticActionLoading("viewLogs", true);
try {
const viewLogs = await PodkopShellMethods.checkLogs();
if (viewLogs.success) {
const getLatestLogs = async () => {
const latestLogs = await PodkopShellMethods.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 {
logger.error("[DIAGNOSTIC]", "handleViewLogs - e", viewLogs);
}
} catch (e) {
logger.error("[DIAGNOSTIC]", "handleViewLogs - e", e);
} finally {
setDiagnosticActionLoading("viewLogs", false);
}
}
async function handleShowSingBoxConfig() {
setDiagnosticActionLoading("showSingBoxConfig", true);
try {
const showSingBoxConfig = await PodkopShellMethods.showSingBoxConfig();
if (showSingBoxConfig.success) {
ui.showModal(
_("Show sing-box config"),
renderModal(
JSON.stringify(showSingBoxConfig.data, null, 2),
"show_sing_box_config"
)
);
} else {
logger.error(
"[DIAGNOSTIC]",
"handleShowSingBoxConfig - e",
showSingBoxConfig
);
}
} catch (e) {
logger.error("[DIAGNOSTIC]", "handleShowSingBoxConfig - e", e);
} finally {
setDiagnosticActionLoading("showSingBoxConfig", 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("pdk_diagnostic-page-wiki");
return preserveScrollForPage(() => {
container.replaceChildren(renderWikiDisclaimer(getWikiKind()));
});
}
function renderDiagnosticAvailableActionsWidget() {
const diagnosticsActions = store.get().diagnosticsActions;
const servicesInfoWidget = store.get().servicesInfoWidget;
logger.debug("[DIAGNOSTIC]", "renderDiagnosticAvailableActionsWidget");
const podkopEnabled = Boolean(servicesInfoWidget.data.podkopEnabled);
const podkopRunning = Boolean(servicesInfoWidget.data.podkopRunning);
const restartLoading = diagnosticsActions.restart.loading;
const atLeastOneMutatingActionLoading = restartLoading || diagnosticsActions.start.loading || diagnosticsActions.stop.loading || diagnosticsActions.enable.loading || diagnosticsActions.disable.loading;
const serviceControlsDisabled = servicesInfoWidget.loading || atLeastOneMutatingActionLoading;
const utilityActionsDisabled = atLeastOneMutatingActionLoading;
const container = document.getElementById("pdk_diagnostic-page-actions");
const renderedActions = renderAvailableActions({
restart: {
loading: restartLoading,
visible: true,
onClick: handleRestart,
disabled: serviceControlsDisabled
},
start: {
loading: diagnosticsActions.start.loading,
visible: !podkopRunning,
onClick: handleStart,
disabled: serviceControlsDisabled
},
stop: {
loading: diagnosticsActions.stop.loading,
visible: podkopRunning,
onClick: handleStop,
disabled: serviceControlsDisabled
},
enable: {
loading: diagnosticsActions.enable.loading,
visible: !podkopEnabled,
onClick: handleEnable,
disabled: serviceControlsDisabled
},
disable: {
loading: diagnosticsActions.disable.loading,
visible: podkopEnabled,
onClick: handleDisable,
disabled: serviceControlsDisabled
},
globalCheck: {
loading: diagnosticsActions.globalCheck.loading,
visible: true,
onClick: handleShowGlobalCheck,
disabled: utilityActionsDisabled
},
viewLogs: {
loading: diagnosticsActions.viewLogs.loading,
visible: true,
onClick: handleViewLogs,
disabled: false
},
showSingBoxConfig: {
loading: diagnosticsActions.showSingBoxConfig.loading,
visible: true,
onClick: handleShowSingBoxConfig,
disabled: utilityActionsDisabled
}
});
return preserveScrollForPage(() => {
container.replaceChildren(renderedActions);
});
}
function renderDiagnosticSystemInfoWidget() {
logger.debug("[DIAGNOSTIC]", "renderDiagnosticSystemInfoWidget");
const diagnosticsSystemInfo = store.get().diagnosticsSystemInfo;
const container = document.getElementById("pdk_diagnostic-page-system-info");
const items = [
{
key: "Podkop Plus",
value: normalizeCompiledVersion(diagnosticsSystemInfo.podkop_version)
},
{
key: "Luci App",
value: normalizeCompiledVersion(PODKOP_LUCI_APP_VERSION)
},
{
key: "Sing-box",
value: diagnosticsSystemInfo.sing_box_version
}
];
if (diagnosticsSystemInfo.zapret_installed) {
items.push({
key: "Zapret",
value: diagnosticsSystemInfo.zapret_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) {
renderDiagnosticAvailableActionsWidget();
}
if (diff.diagnosticsSystemInfo) {
renderDiagnosticSystemInfoWidget();
renderDiagnosticRunActionWidget();
}
}
async function runChecks() {
try {
await ensureDiagnosticsProviderInfo();
const providerOptions = getDiagnosticsProviderOptions();
const runners = [
runDnsCheck,
runSingBoxCheck,
runNftCheck,
...providerOptions.includeZapret ? [runZapretCheck] : [],
...providerOptions.includeByedpi ? [runByedpiCheck] : [],
runSectionsCheck,
runFakeIPCheck
];
store.set({
diagnosticsRunAction: { loading: true },
diagnosticsChecks: getLoadingDiagnosticsChecks(providerOptions).diagnosticsChecks
});
for (const runner of runners) {
try {
await runner();
} catch (e) {
logger.error("[DIAGNOSTIC]", `runChecks - ${runner.name} failed`, e);
}
}
} catch (e) {
logger.error("[DIAGNOSTIC]", "runChecks - e", e);
} finally {
store.set({ diagnosticsRunAction: { loading: false } });
}
}
async function loadInitialDiagnosticData() {
const diagnosticStatus = document.getElementById("diagnostic-status");
if (diagnosticStatus?.isConnected && diagnosticStatus.offsetParent !== null) {
await fetchSystemInfo();
await ensureDiagnosticsProviderInfo();
}
}
function onPageMount2() {
onPageUnmount2();
diagnosticMounted = true;
diagnosticMountId += 1;
store.subscribe(onStoreUpdate2);
renderDiagnosticsChecks();
renderDiagnosticRunActionWidget();
renderDiagnosticAvailableActionsWidget();
renderDiagnosticSystemInfoWidget();
renderWikiDisclaimerWidget();
void refreshDiagnosticServicesInfo({ force: true });
startServicesInfoRefreshTimer();
void loadInitialDiagnosticData();
}
function onPageUnmount2() {
diagnosticMounted = false;
diagnosticMountId += 1;
stopServicesInfoRefreshTimer();
servicesInfoRefreshPromise = null;
store.unsubscribe(onStoreUpdate2);
store.reset(["diagnosticsRunAction"]);
resetDiagnosticsChecks();
}
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");
onPageMount2();
registerLifecycleListeners2();
});
}
// src/podkop/tabs/diagnostic/styles.ts
var styles4 = `
#cbi-${PODKOP_UCI_PACKAGE}-diagnostic-_mount_node > div {
width: 100%;
}
#cbi-${PODKOP_UCI_PACKAGE}-diagnostic > h3 {
display: none;
}
.pdk_diagnostic-page {
display: grid;
grid-template-columns: 2fr 1fr;
grid-column-gap: 10px;
align-items: start;
}
@media (max-width: 800px) {
.pdk_diagnostic-page {
grid-template-columns: 1fr;
}
}
.pdk_diagnostic-page__right-bar {
display: grid;
grid-template-columns: 1fr;
grid-row-gap: 10px;
}
.pdk_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;
}
.pdk_diagnostic-page__right-bar__wiki--warning {
border: 2px var(--warn-color-medium, orange) solid;
}
.pdk_diagnostic-page__right-bar__wiki--error {
border: 2px var(--error-color-medium, red) solid;
}
.pdk_diagnostic-page__right-bar__wiki__content {
display: grid;
grid-template-columns: 1fr 5fr;
grid-column-gap: 10px;
}
.pdk_diagnostic-page__right-bar__wiki__texts {}
.pdk_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;
}
.pdk_diagnostic-page__right-bar__actions > .pdk-partial-button {
width: 100%;
min-width: 0;
margin-left: 0;
}
.pdk_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;
}
.pdk_diagnostic-page__right-bar__system-info__title {
}
.pdk_diagnostic-page__right-bar__system-info__row {
display: grid;
grid-template-columns: auto 1fr;
grid-column-gap: 5px;
}
.pdk_diagnostic-page__right-bar__system-info__row__tag {
padding: 2px 4px;
border: 1px transparent solid;
border-radius: 4px;
margin-left: 5px;
}
.pdk_diagnostic-page__right-bar__system-info__row__tag--neutral {
border: 1px var(--background-color-high, gray) solid;
color: var(--text-color-medium, gray);
}
.pdk_diagnostic-page__right-bar__system-info__row__tag--warning {
border: 1px var(--warn-color-medium, orange) solid;
color: var(--warn-color-medium, orange);
}
.pdk_diagnostic-page__right-bar__system-info__row__tag--success {
border: 1px var(--success-color-medium, green) solid;
color: var(--success-color-medium, green);
}
.pdk_diagnostic-page__left-bar {
display: grid;
grid-template-columns: 1fr;
grid-row-gap: 10px;
}
.pdk_diagnostic-page__run_check_wrapper {}
.pdk_diagnostic-page__run_check_wrapper button {
width: 100%;
}
.pdk_diagnostic-page__checks {
display: grid;
grid-template-columns: 1fr;
grid-row-gap: 10px;
}
.pdk_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;
}
.pdk_diagnostic_alert--loading {
border: 2px var(--primary-color-high, dodgerblue) solid;
}
.pdk_diagnostic_alert--warning {
border: 2px var(--warn-color-medium, orange) solid;
color: var(--warn-color-medium, orange);
}
.pdk_diagnostic_alert--error {
border: 2px var(--error-color-medium, red) solid;
color: var(--error-color-medium, red);
}
.pdk_diagnostic_alert--success {
border: 2px var(--success-color-medium, green) solid;
color: var(--success-color-medium, green);
}
.pdk_diagnostic_alert--skipped {}
.pdk_diagnostic_alert__icon {}
.pdk_diagnostic_alert__content {}
.pdk_diagnostic_alert__title {
display: block;
}
.pdk_diagnostic_alert__description {}
.pdk_diagnostic_alert__summary {
margin-top: 10px;
}
.pdk_diagnostic_alert__summary__item {
display: grid;
grid-template-columns: 16px auto 1fr;
grid-column-gap: 10px;
}
.pdk_diagnostic_alert__summary__item--error {
color: var(--error-color-medium, red);
}
.pdk_diagnostic_alert__summary__item--warning {
color: var(--warn-color-medium, orange);
}
.pdk_diagnostic_alert__summary__item--success {
color: var(--success-color-medium, green);
}
.pdk_diagnostic_alert__summary__item__icon {
width: 16px;
height: 16px;
}
`;
// src/podkop/tabs/diagnostic/index.ts
var DiagnosticTab = {
render: render2,
initController: initController2,
styles: styles4
};
// src/podkop/tabs/monitoring/render.ts
function render3() {
return E(
"div",
{
id: "monitoring-status",
class: "pdk_monitoring-page"
},
[
E("div", { class: "pdk_monitoring-page__panel" }, [
E("div", { class: "pdk_monitoring-page__controls" }, [
E("div", { class: "pdk_monitoring-page__tabs" }, [
E(
"button",
{
id: "monitoring-tab-active",
class: "btn cbi-button pdk_monitoring-page__tab pdk_monitoring-page__tab--active",
type: "button"
},
`${_("Active")} 0`
),
E(
"button",
{
id: "monitoring-tab-closed",
class: "btn cbi-button pdk_monitoring-page__tab",
type: "button"
},
`${_("Closed")} 0`
)
]),
E("div", { class: "pdk_monitoring-page__filters" }, [
E(
"select",
{
id: "monitoring-device-filter",
class: "cbi-input-select pdk_monitoring-page__device-filter"
},
[E("option", { value: "all" }, _("All"))]
),
E("label", { class: "pdk_monitoring-page__search" }, [
E("span", { class: "pdk_monitoring-page__search-icon" }, []),
E("input", {
id: "monitoring-search",
class: "cbi-input-text pdk_monitoring-page__search-input",
type: "search",
placeholder: _("Search"),
autocomplete: "off"
})
])
]),
E("div", { class: "pdk_monitoring-page__actions" }, [
E(
"button",
{
id: "monitoring-close-all",
class: "btn cbi-button pdk_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 pdk_monitoring-page__icon-button",
title: _("Pause updates"),
"aria-label": _("Pause updates"),
type: "button"
},
[]
)
])
]),
E(
"div",
{ id: "monitoring-connections", class: "pdk_monitoring-page__body" },
[
E(
"div",
{
class: "pdk_monitoring-page__state pdk_monitoring-page__state--loading"
},
_("Loading connections")
)
]
)
])
]
);
}
// src/podkop/tabs/monitoring/initController.ts
var RENDER_INTERVAL_MS = 500;
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 renderTimer = null;
var connectionsSocketUrl = "";
var renderSkippedForSelection = false;
var pendingConnectionsPayload = null;
var activeTab = "active";
var selectedDeviceFilter = ALL_FILTER_VALUE;
var searchQuery = "";
var localDeviceChoices = {};
var routeDisplayNames = {};
var routeSections = [];
var serverDisplayNames = {};
var lastDeviceFilterSignature = "";
var loading = true;
var failed = false;
var closingAll = false;
var monitoringPaused = false;
var monitoringPausedAt = null;
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 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 = {
"direct-out": "bypass"
};
const serverMap = {};
const routeSectionItems = [];
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;
map[getOutboundTagBySection(`${sectionName}-urltest`)] = 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) {
return normalizeString(localDeviceChoices[ip]);
}
function getDeviceFilterLabel(ip) {
const deviceName = getDeviceName(ip);
return deviceName || 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,
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);
if (selectedDeviceFilter !== ALL_FILTER_VALUE && sourceIp !== selectedDeviceFilter) {
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();
renderConnections();
}
}
function setTab(tab) {
if (activeTab === tab) {
return;
}
activeTab = tab;
renderControls();
renderConnections();
}
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 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("pdk_monitoring-page__tab--active", active);
}
function renderTabButtonContent(label, count) {
return [
E("span", { class: "pdk_monitoring-page__tab-label" }, label),
E("span", { class: "pdk_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)
);
}
if (closedButton) {
closedButton.replaceChildren(
...renderTabButtonContent(_("Closed"), closedConnections.size)
);
}
setButtonActive(activeButton, activeTab === "active");
setButtonActive(closedButton, activeTab === "closed");
if (closeAllButton) {
closeAllButton.replaceChildren(renderXIcon24());
closeAllButton.disabled = 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.classList.toggle(
"pdk_monitoring-page__icon-button--active",
monitoringPaused
);
}
const searchIcon = document.querySelector(
".pdk_monitoring-page__search-icon"
);
if (searchIcon && searchIcon.childNodes.length === 0) {
searchIcon.replaceChildren(renderSearchIcon24());
}
renderDeviceFilterOptions();
}
function renderValue(value, className = "") {
const text = value || "-";
const element = E(
"span",
{
class: ["pdk_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: "pdk_monitoring-page__value pdk_monitoring-page__source-value pdk_monitoring-page__source-value--ip-only",
title: fullText
},
source.primary || "-"
);
element2.setAttribute("data-copy-value", fullText);
return element2;
}
const element = E(
"span",
{
class: "pdk_monitoring-page__value pdk_monitoring-page__source-value",
title: fullText
},
[
E("span", { class: "pdk_monitoring-page__source-name" }, source.primary),
E("span", { class: "pdk_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 pdk_monitoring-page__row-action",
title: _("Close connection"),
"aria-label": _("Close connection"),
type: "button",
value: connection.id,
...isClosing ? { disabled: true } : {}
},
[renderXIcon24()]
) : E("span", {}, "-");
return E(
"tr",
{
class: isClosing ? "pdk_monitoring-page__row--closing" : ""
},
[
renderTableCell(_("Host"), [renderValue(target.primary)]),
renderTableCell(_("Type"), [
renderValue(getNetwork(connection), "pdk_monitoring-page__network")
]),
renderTableCell(_("Route"), [
renderValue(getRoute(connection), "pdk_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])
]
);
}
function renderStateRow(text, className = "") {
return E("tr", { class: "pdk_monitoring-page__state-row" }, [
E(
"td",
{
class: "pdk_monitoring-page__state-cell",
colSpan: 8
},
[
E(
"div",
{
class: ["pdk_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: "pdk_monitoring-page__table-wrap" }, [
E(
"table",
{ class: "table cbi-section-table pdk_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 renderConnections(options = {}) {
const container = document.getElementById("monitoring-connections");
if (!container) {
return;
}
if (!options.force && isTextSelectionInsideMonitoring()) {
renderSkippedForSelection = true;
return;
}
renderSkippedForSelection = false;
const previousScrollLeft = container.scrollLeft;
if (loading) {
container.replaceChildren(
renderConnectionsTable([], {
text: _("Loading connections"),
className: "pdk_monitoring-page__state--loading"
})
);
return;
}
if (failed) {
container.replaceChildren(
renderConnectionsTable([], {
text: _("Connections are unavailable"),
className: "pdk_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;
}
container.replaceChildren(renderConnectionsTable(visibleConnections));
container.scrollLeft = previousScrollLeft;
}
function flushRenderAfterSelection() {
if (!renderSkippedForSelection || isTextSelectionInsideMonitoring()) {
return;
}
renderConnections({ 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;
}
}
renderConnections();
}
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(
".pdk_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);
renderConnections();
try {
const response = await PodkopShellMethods.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);
renderConnections();
}
}
async function closeAllConnections() {
if (activeConnections.size === 0 || closingAll) {
return;
}
closingAll = true;
renderControls();
try {
const response = await PodkopShellMethods.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();
renderConnections();
}
}
function bindControls() {
const activeButton = document.getElementById("monitoring-tab-active");
const closedButton = document.getElementById("monitoring-tab-closed");
const select = document.getElementById(
"monitoring-device-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);
}
if (select) {
select.onchange = () => {
selectedDeviceFilter = select.value || ALL_FILTER_VALUE;
renderConnections();
};
}
if (searchInput) {
searchInput.oninput = () => {
searchQuery = searchInput.value;
renderConnections();
};
}
if (connectionsContainer) {
connectionsContainer.onclick = (event) => {
const target = event.target;
const button = target?.closest(
".pdk_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();
renderConnections();
}
}
async function loadRouteDisplayNames() {
try {
buildRouteDisplayNames(await CustomPodkopMethods.getConfigSections());
} catch (error) {
logger.warn("[MONITORING]", "loadRouteDisplayNames: failed", error);
buildRouteDisplayNames([]);
} finally {
renderConnections();
}
}
async function connectToConnectionsSocket() {
const mountId = monitoringMountId;
const clashApiSecret = await getClashApiSecret2();
if (!monitoringMounted || mountId !== monitoringMountId) {
return;
}
connectionsSocketUrl = `${getClashWsUrl()}/connections?token=${clashApiSecret}`;
socket.subscribe(
connectionsSocketUrl,
(msg) => {
try {
applyConnectionsPayload(JSON.parse(msg));
} catch (error) {
logger.error("[MONITORING]", "connections socket parse failed", error);
}
},
(_err) => {
if (!monitoringMounted || mountId !== monitoringMountId) {
return;
}
failed = true;
loading = false;
renderConnections();
}
);
}
function resetMonitoringState() {
activeTab = "active";
selectedDeviceFilter = ALL_FILTER_VALUE;
searchQuery = "";
lastDeviceFilterSignature = "";
loading = true;
failed = false;
closingAll = false;
monitoringPaused = false;
monitoringPausedAt = null;
pendingConnectionsPayload = null;
activeConnections.clear();
closedConnections.clear();
closingConnectionIds.clear();
const searchInput = document.getElementById(
"monitoring-search"
);
if (searchInput) {
searchInput.value = "";
}
}
function onPageMount3() {
onPageUnmount3();
monitoringMounted = true;
monitoringMountId += 1;
resetMonitoringState();
bindControls();
renderControls();
renderConnections();
void loadLocalDevices();
void loadRouteDisplayNames();
void connectToConnectionsSocket();
document.addEventListener("selectionchange", flushRenderAfterSelection);
document.addEventListener("copy", handleMonitoringValueCopy);
renderTimer = setInterval(() => {
if (monitoringPaused) {
return;
}
renderConnections();
}, RENDER_INTERVAL_MS);
}
function onPageUnmount3() {
monitoringMounted = false;
monitoringMountId += 1;
if (renderTimer) {
clearInterval(renderTimer);
renderTimer = null;
}
if (connectionsSocketUrl) {
socket.disconnect(connectionsSocketUrl);
connectionsSocketUrl = "";
}
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/podkop/tabs/monitoring/styles.ts
var styles5 = `
#cbi-${PODKOP_UCI_PACKAGE}-monitoring-_mount_node {
margin: 16px 0 22px;
padding: 0;
}
#cbi-${PODKOP_UCI_PACKAGE}-monitoring-_mount_node > .cbi-value-title {
display: none;
}
#cbi-${PODKOP_UCI_PACKAGE}-monitoring-_mount_node > .cbi-value-field {
margin-left: 0;
width: 100%;
}
#cbi-${PODKOP_UCI_PACKAGE}-monitoring-_mount_node > div {
width: 100%;
}
#cbi-${PODKOP_UCI_PACKAGE}-monitoring > h3 {
display: none;
}
.pdk_monitoring-page {
--pdk-monitoring-control-height: 34px;
--pdk-monitoring-row-action-size: 24px;
--pdk-monitoring-divider-color: rgba(127, 127, 127, 0.22);
--pdk-monitoring-soft-bg: rgba(127, 127, 127, 0.08);
--pdk-monitoring-soft-bg-hover: rgba(127, 127, 127, 0.14);
--pdk-monitoring-danger-color: var(--error-color-medium, #d32f2f);
--pdk-monitoring-success-color: var(--success-color-medium, #2e7d32);
--pdk-monitoring-paused-color: var(--primary-color-high, #1976d2);
width: 100%;
min-width: 0;
}
.pdk_monitoring-page__panel {
margin-top: 0;
border: 2px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 10px;
box-sizing: border-box;
width: 100%;
min-width: 0;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__icon-button {
width: var(--pdk-monitoring-control-height);
height: var(--pdk-monitoring-control-height);
min-width: var(--pdk-monitoring-control-height);
min-height: var(--pdk-monitoring-control-height);
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(--pdk-monitoring-divider-color) !important;
border-radius: 4px;
background: var(--pdk-monitoring-soft-bg) !important;
color: var(--text-color-medium) !important;
box-shadow: none;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__icon-button:hover:not(:disabled) {
background: var(--pdk-monitoring-soft-bg-hover) !important;
color: var(--text-color-high) !important;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__icon-button--active {
background: rgba(25, 118, 210, 0.16) !important;
color: var(--primary-color-high, #1976d2) !important;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__icon-button:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.pdk_monitoring-page #monitoring-close-all.btn.pdk_monitoring-page__icon-button {
border-color: var(--pdk-monitoring-danger-color) !important;
background: var(--pdk-monitoring-soft-bg) !important;
color: var(--pdk-monitoring-danger-color) !important;
}
.pdk_monitoring-page #monitoring-close-all.btn.pdk_monitoring-page__icon-button:hover:not(:disabled) {
border-color: var(--pdk-monitoring-danger-color) !important;
background: var(--pdk-monitoring-soft-bg-hover) !important;
color: var(--pdk-monitoring-danger-color) !important;
}
.pdk_monitoring-page #monitoring-pause-toggle.btn.pdk_monitoring-page__icon-button {
border-color: var(--pdk-monitoring-success-color) !important;
background: var(--pdk-monitoring-soft-bg) !important;
color: var(--pdk-monitoring-success-color) !important;
}
.pdk_monitoring-page #monitoring-pause-toggle.btn.pdk_monitoring-page__icon-button:hover:not(:disabled) {
border-color: var(--pdk-monitoring-success-color) !important;
background: var(--pdk-monitoring-soft-bg-hover) !important;
color: var(--pdk-monitoring-success-color) !important;
}
.pdk_monitoring-page #monitoring-pause-toggle.btn.pdk_monitoring-page__icon-button--active {
border-color: var(--pdk-monitoring-paused-color) !important;
background: var(--pdk-monitoring-soft-bg) !important;
color: var(--pdk-monitoring-paused-color) !important;
}
.pdk_monitoring-page #monitoring-pause-toggle.btn.pdk_monitoring-page__icon-button--active:hover:not(:disabled) {
border-color: var(--pdk-monitoring-paused-color) !important;
background: var(--pdk-monitoring-soft-bg-hover) !important;
color: var(--pdk-monitoring-paused-color) !important;
}
.pdk_monitoring-page__icon-button svg,
.pdk_monitoring-page__row-action svg {
width: 16px;
height: 16px;
display: block;
flex: 0 0 auto;
}
.pdk_monitoring-page__controls {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
justify-content: stretch;
gap: 10px;
width: 100%;
min-width: 0;
}
.pdk_monitoring-page__actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
min-width: 0;
}
.pdk_monitoring-page__tabs {
display: inline-flex;
align-items: center;
gap: 2px;
width: max-content;
padding: 2px;
border: 1px solid var(--pdk-monitoring-divider-color);
border-radius: 6px;
background: var(--pdk-monitoring-soft-bg);
box-sizing: border-box;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__tab {
height: calc(var(--pdk-monitoring-control-height) - 6px);
min-height: calc(var(--pdk-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;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__tab:hover {
background: var(--pdk-monitoring-soft-bg-hover) !important;
color: var(--text-color-high) !important;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__tab--active {
background: rgba(25, 118, 210, 0.16) !important;
color: var(--primary-color-high, #1976d2) !important;
font-weight: 700;
}
.pdk_monitoring-page__tab-label {
display: inline-block;
}
.pdk_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;
}
.pdk_monitoring-page__tab--active .pdk_monitoring-page__tab-badge {
background: rgba(25, 118, 210, 0.22);
color: var(--primary-color-high, #1976d2);
}
.pdk_monitoring-page__filters {
display: grid;
grid-template-columns: minmax(150px, 220px) minmax(200px, 320px);
align-items: center;
justify-content: flex-end;
gap: 10px;
min-width: 0;
}
.pdk_monitoring-page__device-filter {
width: 100%;
min-width: 0;
height: var(--pdk-monitoring-control-height) !important;
min-height: var(--pdk-monitoring-control-height) !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
margin: 0 !important;
box-sizing: border-box;
line-height: calc(var(--pdk-monitoring-control-height) - 2px) !important;
}
.pdk_monitoring-page__search {
position: relative;
display: flex;
align-items: center;
width: 100%;
min-width: 0;
height: var(--pdk-monitoring-control-height);
margin: 0;
}
.pdk_monitoring-page__search-icon {
position: absolute;
left: 8px;
width: 16px;
height: 16px;
color: var(--text-color-medium);
pointer-events: none;
}
.pdk_monitoring-page__search-icon svg {
width: 16px;
height: 16px;
display: block;
}
.pdk_monitoring-page__search-input {
width: 100%;
height: var(--pdk-monitoring-control-height) !important;
min-height: var(--pdk-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(--pdk-monitoring-control-height) - 2px) !important;
}
.pdk_monitoring-page__body {
margin-top: 10px;
width: 100%;
min-width: 0;
}
.pdk_monitoring-page__table-wrap {
width: 100%;
overflow-x: auto;
margin-bottom: 0;
}
.pdk_monitoring-page__table {
width: 100%;
min-width: 840px;
table-layout: fixed;
border-collapse: collapse;
border-spacing: 0;
margin-bottom: 0;
}
.pdk_monitoring-page__table th,
.pdk_monitoring-page__table td {
padding: 8px 6px;
border-bottom: 1px solid var(--pdk-monitoring-divider-color);
box-sizing: border-box;
text-align: center;
vertical-align: middle;
overflow: hidden;
white-space: nowrap;
}
.pdk_monitoring-page__table th {
color: var(--text-color-medium);
font-weight: 700;
white-space: nowrap;
border-bottom-color: rgba(127, 127, 127, 0.32);
}
.pdk_monitoring-page__table th:nth-child(1) {
width: 28%;
}
.pdk_monitoring-page__table th:nth-child(2) {
width: 6%;
}
.pdk_monitoring-page__table th:nth-child(3) {
width: 16%;
}
.pdk_monitoring-page__table th:nth-child(4) {
width: 8%;
}
.pdk_monitoring-page__table th:nth-child(5) {
width: 9.5%;
}
.pdk_monitoring-page__table th:nth-child(6) {
width: 8.5%;
}
.pdk_monitoring-page__table th:nth-child(7) {
width: 16%;
}
.pdk_monitoring-page__table th:nth-child(8) {
width: 8%;
}
.pdk_monitoring-page__table tbody tr:last-child td {
border-bottom: 0;
}
.pdk_monitoring-page__table td:last-child {
padding-top: 0;
padding-bottom: 0;
}
.pdk_monitoring-page__value {
display: block;
max-width: 100%;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
line-height: 1.3;
color: var(--text-color-high);
user-select: text;
}
.pdk_monitoring-page__source-value {
display: flex;
align-items: baseline;
justify-content: center;
gap: 5px;
}
.pdk_monitoring-page__source-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pdk_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;
}
.pdk_monitoring-page__source-value--ip-only {
color: var(--text-color-high);
}
.pdk_monitoring-page__cell-main {
color: var(--text-color-high);
font-weight: 600;
line-height: 1.25;
}
.pdk_monitoring-page__cell-secondary {
margin-top: 2px;
color: var(--text-color-medium);
font-size: 12px;
line-height: 1.25;
}
.pdk_monitoring-page__route {
font-weight: 600;
}
.pdk_monitoring-page__network {
text-transform: lowercase;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__row-action {
width: var(--pdk-monitoring-row-action-size);
height: var(--pdk-monitoring-row-action-size);
min-width: var(--pdk-monitoring-row-action-size);
min-height: var(--pdk-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(--pdk-monitoring-danger-color) !important;
box-shadow: none;
cursor: pointer;
}
.pdk_monitoring-page__row-action svg {
width: 14px;
height: 14px;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__row-action:hover:not(:disabled) {
background: var(--pdk-monitoring-soft-bg-hover) !important;
color: var(--pdk-monitoring-danger-color) !important;
}
.pdk_monitoring-page .btn.pdk_monitoring-page__row-action:disabled {
opacity: 0.45;
cursor: wait;
}
.pdk_monitoring-page__row--closing {
opacity: 0.55;
}
.pdk_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;
}
.pdk_monitoring-page__state-cell {
padding: 0 !important;
}
.pdk_monitoring-page__state--error {
color: var(--error-color-medium, #d32f2f);
}
@media (max-width: 900px) {
.pdk_monitoring-page__controls {
grid-template-columns: 1fr auto;
}
.pdk_monitoring-page__tabs {
grid-column: 1 / -1;
}
.pdk_monitoring-page__filters {
grid-template-columns: minmax(150px, 220px) minmax(180px, 320px);
justify-content: stretch;
}
.pdk_monitoring-page__device-filter,
.pdk_monitoring-page__search {
max-width: none;
}
.pdk_monitoring-page__table {
min-width: 0;
}
.pdk_monitoring-page__table thead {
display: none;
}
.pdk_monitoring-page__table,
.pdk_monitoring-page__table tbody,
.pdk_monitoring-page__table tr,
.pdk_monitoring-page__table td {
display: block;
width: 100%;
}
.pdk_monitoring-page__table tr {
border: 1px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 8px;
box-sizing: border-box;
margin-bottom: 8px;
}
.pdk_monitoring-page__table td {
display: grid;
grid-template-columns: minmax(92px, 34%) minmax(0, 1fr);
gap: 8px;
border: 0;
border-bottom: 1px solid var(--pdk-monitoring-divider-color);
padding: 4px 0;
box-sizing: border-box;
text-align: left;
}
.pdk_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;
}
.pdk_monitoring-page__table td:last-child {
grid-template-columns: minmax(92px, 34%) minmax(0, 1fr);
align-items: center;
border-bottom: 0;
min-height: var(--pdk-monitoring-row-action-size);
padding: 0;
}
.pdk_monitoring-page__value {
text-align: right;
}
.pdk_monitoring-page__source-value {
justify-content: flex-end;
}
.pdk_monitoring-page__state-row td::before {
display: none;
}
}
@media (max-width: 520px) {
.pdk_monitoring-page__controls,
.pdk_monitoring-page__filters {
align-items: stretch;
}
.pdk_monitoring-page__tabs,
.pdk_monitoring-page__filters,
.pdk_monitoring-page__device-filter,
.pdk_monitoring-page__search {
width: 100%;
}
.pdk_monitoring-page__controls,
.pdk_monitoring-page__filters {
grid-template-columns: 1fr;
}
.pdk_monitoring-page__tabs {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
width: 100%;
}
.pdk_monitoring-page__table td {
grid-template-columns: 1fr;
gap: 2px;
}
.pdk_monitoring-page__value {
text-align: left;
}
.pdk_monitoring-page__source-value {
justify-content: flex-start;
}
}
`;
// src/podkop/tabs/monitoring/index.ts
var MonitoringTab = {
render: render3,
initController: initController3,
styles: styles5
};
// src/podkop/tabs/updates/render.ts
function render4() {
return E("div", { id: "updates-status", class: "pdk_updates-page" }, [
E("div", {
id: "pdk_updates-components",
class: "pdk_updates-page__components"
})
]);
}
// src/podkop/tabs/updates/initController.ts
var updatesLifecycleRegistered = false;
var updatesControllerInitialized = false;
var updatesMounted = false;
function isNotInstalled(version) {
return !version || version === "not installed";
}
function getCheckTag(component) {
const status = store.get().updatesChecks[component].status;
if (!status) {
return void 0;
}
if (status === "latest") {
return { label: _("Latest"), kind: "success" };
}
if (status === "outdated") {
return { label: _("Outdated"), kind: "warning" };
}
return { label: _("Dev"), kind: "neutral" };
}
function shouldShowInstallAfterCheck(component) {
const status = store.get().updatesChecks[component].status;
return status === "outdated" || status === "dev";
}
function getInstallActionText(component) {
const checkResult = store.get().updatesChecks[component];
if (shouldShowInstallAfterCheck(component) && checkResult.latest_version) {
return _("Install %s").replace("%s", checkResult.latest_version);
}
return _("Install");
}
function getGitHubReleaseUrl(component) {
const checkResult = store.get().updatesChecks[component];
if (!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) {
const updatesActions = store.get().updatesActions;
store.set({
updatesActions: {
...updatesActions,
[action]: { loading: loading2 }
}
});
}
function setCheckResult(component, status, latestVersion, releaseUrl = "") {
const updatesChecks = store.get().updatesChecks;
store.set({
updatesChecks: {
...updatesChecks,
[component]: {
status,
latest_version: latestVersion,
release_url: releaseUrl
}
}
});
}
function resetCheckResult(component) {
setCheckResult(component, null, "");
}
function getExpectedLatestVersionForAction(button) {
if (button.component !== "podkop" || button.action !== "install") {
return void 0;
}
return store.get().updatesChecks[button.component].latest_version || void 0;
}
function getCheckToastMessage(status) {
if (status === "outdated") {
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(PODKOP_ACTION_PROVIDERS_AVAILABILITY_EVENT, {
detail: {
zapretInstalled: Boolean(systemInfo.zapret_installed),
byedpiInstalled: Boolean(systemInfo.byedpi_installed)
}
})
);
}
function reloadPageAfterPodkopUpdate() {
window.setTimeout(() => {
window.location.reload();
}, 1200);
}
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 === "podkop" && result.action === "install") {
nextSystemInfo.podkop_version = version;
}
if (result.component === "sing_box") {
nextSystemInfo.sing_box_version = version;
if (result.action === "install_extended") {
nextSystemInfo.sing_box_extended = 1;
}
if (result.action === "install_stable") {
nextSystemInfo.sing_box_extended = 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 === "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;
}
}
store.set({
diagnosticsSystemInfo: nextSystemInfo
});
if (result.component === "zapret" || result.component === "byedpi") {
notifyActionProvidersAvailabilityChanged(nextSystemInfo);
}
}
async function handleComponentAction(button) {
if (isAnyActionLoading()) {
return;
}
setActionLoading(button.key, true);
try {
const response = await PodkopShellMethods.componentAction(
button.component,
button.action,
getExpectedLatestVersionForAction(button)
);
if (!response.success) {
setActionLoading(button.key, false);
showToast(response.error || _("Failed to execute"), "error");
return;
}
const result = response.data;
if (button.action === "check_update") {
const status = result.status || null;
if (status === "latest" || status === "outdated" || status === "dev") {
setCheckResult(
button.component,
status,
result.latest_version || "",
result.release_url || ""
);
}
setActionLoading(button.key, false);
showToast(getCheckToastMessage(status), "success");
return;
}
if (button.action === "install" || button.action.startsWith("install_")) {
setCheckResult(button.component, "latest", result.latest_version || "");
} else {
resetCheckResult(button.component);
}
patchSystemInfoAfterMutation(result);
setActionLoading(button.key, false);
if (result.component === "podkop" && result.action === "install") {
if (result.message) {
showToast(result.message, "success", 1200);
}
reloadPageAfterPodkopUpdate();
return;
}
if (result.message) {
showToast(result.message, "success");
}
void refreshSystemInfoAfterMutation();
} catch (error) {
logger.error("[UPDATES]", "handleComponentAction failed", error);
setActionLoading(button.key, false);
showToast(_("Failed to execute"), "error");
}
}
function getPrimaryUpdateAction(component, checkKey, installKey) {
if (shouldShowInstallAfterCheck(component)) {
return {
key: installKey,
text: getInstallActionText(component),
icon: renderRotateCcwIcon24,
component,
action: "install"
};
}
return {
key: checkKey,
text: _("Check update"),
icon: renderSearchIcon24,
component,
action: "check_update"
};
}
function getComponentCards() {
const systemInfo = store.get().diagnosticsSystemInfo;
const systemInfoLoading = isSystemInfoLoading();
const zapretInstalled = Boolean(systemInfo.zapret_installed);
const byedpiInstalled = Boolean(systemInfo.byedpi_installed);
const singBoxInstallAction = systemInfo.sing_box_extended ? {
key: "singBoxInstallStable",
text: _("Install stable"),
icon: renderRotateCcwIcon24,
component: "sing_box",
action: "install_stable"
} : {
key: "singBoxInstallExtended",
text: _("Install extended"),
icon: renderRotateCcwIcon24,
component: "sing_box",
action: "install_extended"
};
return [
{
title: "Podkop Plus",
version: normalizeCompiledVersion(systemInfo.podkop_version),
releaseUrl: getGitHubReleaseUrl("podkop"),
tag: getCheckTag("podkop"),
actions: [
getPrimaryUpdateAction("podkop", "podkopCheck", "podkopInstall")
]
},
{
title: "Sing-box",
version: isNotInstalled(systemInfo.sing_box_version) ? _("Not installed") : systemInfo.sing_box_version,
releaseUrl: getGitHubReleaseUrl("sing_box"),
tag: getCheckTag("sing_box"),
actions: [
getPrimaryUpdateAction(
"sing_box",
"singBoxCheck",
"singBoxInstall"
),
singBoxInstallAction
]
},
{
title: "Zapret",
version: systemInfoLoading ? "loading" : zapretInstalled ? systemInfo.zapret_version : _("Not installed"),
releaseUrl: getGitHubReleaseUrl("zapret"),
tag: zapretInstalled ? getCheckTag("zapret") : void 0,
actions: zapretInstalled ? [
getPrimaryUpdateAction("zapret", "zapretCheck", "zapretInstall"),
{
key: "zapretRemove",
text: _("Remove"),
icon: renderXIcon24,
component: "zapret",
action: "remove"
}
] : [
{
key: "zapretInstall",
text: _("Install"),
icon: renderRotateCcwIcon24,
component: "zapret",
action: "install"
}
]
},
{
title: "ByeDPI",
version: systemInfoLoading ? "loading" : byedpiInstalled ? systemInfo.byedpi_version : _("Not installed"),
releaseUrl: getGitHubReleaseUrl("byedpi"),
tag: byedpiInstalled ? getCheckTag("byedpi") : void 0,
actions: byedpiInstalled ? [
getPrimaryUpdateAction("byedpi", "byedpiCheck", "byedpiInstall"),
{
key: "byedpiRemove",
text: _("Remove"),
icon: renderXIcon24,
component: "byedpi",
action: "remove"
}
] : [
{
key: "byedpiInstall",
text: _("Install"),
icon: renderRotateCcwIcon24,
component: "byedpi",
action: "install"
}
]
}
];
}
function renderComponentTag(card) {
if (!card.tag) {
return null;
}
return E(
"span",
{
class: [
"pdk_updates-page__component__tag",
card.tag.kind === "success" ? "pdk_updates-page__component__tag--success" : "",
card.tag.kind === "warning" ? "pdk_updates-page__component__tag--warning" : ""
].filter(Boolean).join(" ")
},
card.tag.label
);
}
function renderComponentCard(card) {
const updatesActions = store.get().updatesActions;
const anyActionLoading = isAnyActionLoading();
const systemInfoLoading = isSystemInfoLoading();
const tag = renderComponentTag(card);
const headerChildren = [
E("b", { class: "pdk_updates-page__component__title" }, card.title)
];
const statusChildren = [];
if (card.releaseUrl) {
statusChildren.push(
E(
"a",
{
class: "pdk_updates-page__component__release-link",
href: card.releaseUrl,
target: "_blank",
rel: "noopener noreferrer"
},
_("GitHub release")
)
);
}
if (tag) {
statusChildren.push(tag);
}
if (statusChildren.length > 0) {
headerChildren.push(
E(
"div",
{ class: "pdk_updates-page__component__status" },
statusChildren
)
);
}
return E("div", { class: "pdk_updates-page__component" }, [
E(
"div",
{ class: "pdk_updates-page__component__header" },
headerChildren
),
E("div", { class: "pdk_updates-page__component__version" }, [
E(
"span",
{ class: "pdk_updates-page__component__version__label" },
_("Version")
),
E(
"span",
{ class: "pdk_updates-page__component__version__value" },
card.version
)
]),
E(
"div",
{ class: "pdk_updates-page__component__actions" },
card.actions.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)
});
})
)
]);
}
function renderUpdatesComponents() {
const container = document.getElementById("pdk_updates-components");
if (!container) {
return;
}
const renderedComponents = getComponentCards().map(renderComponentCard);
return preserveScrollForPage(() => {
container.replaceChildren(...renderedComponents);
});
}
function onStoreUpdate3(_next, _prev, diff) {
if (diff.diagnosticsSystemInfo || diff.updatesActions || diff.updatesChecks) {
renderUpdatesComponents();
}
}
function onPageMount4() {
onPageUnmount4();
updatesMounted = true;
store.subscribe(onStoreUpdate3);
renderUpdatesComponents();
void ensureSystemInfo();
}
function onPageUnmount4() {
updatesMounted = false;
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;
onMount("updates-status").then(() => {
logger.debug("[UPDATES]", "initController", "onMount");
onPageMount4();
registerLifecycleListeners4();
});
}
// src/podkop/tabs/updates/styles.ts
var styles6 = `
#cbi-${PODKOP_UCI_PACKAGE}-updates-_mount_node > div {
width: 100%;
}
#cbi-${PODKOP_UCI_PACKAGE}-updates > h3 {
display: none;
}
.pdk_updates-page {
width: 100%;
}
.pdk_updates-page__components {
display: grid;
grid-template-columns: repeat(2, minmax(240px, 1fr));
grid-gap: 10px;
}
@media (max-width: 760px) {
.pdk_updates-page__components {
grid-template-columns: 1fr;
}
}
.pdk_updates-page__component {
border: 2px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 10px;
min-width: 0;
display: grid;
grid-template-columns: 1fr;
grid-row-gap: 10px;
}
.pdk_updates-page__component__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
min-width: 0;
}
.pdk_updates-page__component__title {
color: var(--text-color-high);
line-height: 1.25;
overflow-wrap: anywhere;
}
.pdk_updates-page__component__status {
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
flex: 0 0 auto;
}
.pdk_updates-page__component__version {
display: grid;
grid-template-columns: auto 1fr;
grid-column-gap: 6px;
align-items: baseline;
min-width: 0;
}
.pdk_updates-page__component__version__label {
color: var(--text-color-medium);
}
.pdk_updates-page__component__version__value {
min-width: 0;
overflow-wrap: anywhere;
}
.pdk_updates-page__component__tag {
flex: 0 0 auto;
padding: 2px 5px;
border: 1px var(--background-color-high, gray) solid;
border-radius: 4px;
color: var(--text-color-medium, gray);
line-height: 1.2;
}
.pdk_updates-page__component__tag--success {
border-color: var(--success-color-medium, green);
color: var(--success-color-medium, green);
}
.pdk_updates-page__component__tag--warning {
border-color: var(--warn-color-medium, orange);
color: var(--warn-color-medium, orange);
}
.pdk_updates-page__component__actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.pdk_updates-page__component__actions > .pdk-partial-button {
margin-left: 0;
}
.pdk_updates-page__component__release-link {
font-size: 11px;
line-height: 1.2;
text-align: right;
white-space: nowrap;
}
`;
// src/podkop/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-${PODKOP_UCI_PACKAGE}-settings > h3 {
display: none;
}
/* Hide extra H3 for rules tab */
#cbi-${PODKOP_UCI_PACKAGE}-section > h3:nth-child(1) {
display: none;
}
/* Vertical align for remove rule action button */
#cbi-${PODKOP_UCI_PACKAGE}-section > .cbi-section-remove {
margin-bottom: -32px;
}
#cbi-${PODKOP_UCI_PACKAGE}-section .cbi-section-actions > div {
display: inline-flex;
align-items: center;
gap: 4px;
}
#cbi-${PODKOP_UCI_PACKAGE}-section .cbi-section-actions {
text-align: right;
}
/* Rule reorder visuals */
#cbi-${PODKOP_UCI_PACKAGE}-section {
position: relative;
}
#cbi-${PODKOP_UCI_PACKAGE}-section .cbi-section-table-row {
position: relative;
}
#cbi-${PODKOP_UCI_PACKAGE}-section .cbi-section-table-row.placeholder {
opacity: 1;
}
#cbi-${PODKOP_UCI_PACKAGE}-section .cbi-section-table-row.placeholder em {
font-style: italic;
}
#cbi-${PODKOP_UCI_PACKAGE}-section .cbi-section-table-row.drag-over-above::after,
#cbi-${PODKOP_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-${PODKOP_UCI_PACKAGE}-section .cbi-section-table-row.drag-over-above::after {
top: -1px;
}
#cbi-${PODKOP_UCI_PACKAGE}-section .cbi-section-table-row.drag-over-below::after {
bottom: -1px;
}
/* Centered class helper */
.centered {
display: flex;
align-items: center;
justify-content: center;
}
/* Rotate class helper */
.rotate {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Skeleton styles*/
.skeleton {
background-color: var(--background-color-low, #e0e0e0);
border-radius: 4px;
position: relative;
overflow: hidden;
}
.skeleton::after {
content: '';
position: absolute;
top: 0;
left: -150%;
width: 150%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.4),
transparent
);
animation: skeleton-shimmer 1.6s infinite;
}
@keyframes skeleton-shimmer {
100% {
left: 150%;
}
}
/* Toast */
.toast-container {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
z-index: 9999;
font-family: system-ui, sans-serif;
}
.toast {
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
padding: 10px 16px;
border-radius: 6px;
color: #fff;
font-size: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
min-width: 220px;
max-width: 340px;
text-align: center;
}
.toast-success {
background-color: #28a745;
}
.toast-error {
background-color: #dc3545;
}
.toast.visible {
opacity: 1;
transform: translateY(0);
}
`;
// src/helpers/injectGlobalStyles.ts
var PODKOP_GLOBAL_STYLES_ID = "podkop-global-styles";
function injectGlobalStyles() {
if (document.getElementById(PODKOP_GLOBAL_STYLES_ID)) {
return;
}
document.head.insertAdjacentHTML(
"beforeend",
`
<style id="${PODKOP_GLOBAL_STYLES_ID}">
${GlobalStyles}
</style>
`
);
}
// src/main.ts
if (typeof structuredClone !== "function")
globalThis.structuredClone = (obj) => JSON.parse(JSON.stringify(obj));
return baseclass.extend({
ALLOWED_WITH_RUSSIA_INSIDE,
BOOTSTRAP_DNS_SERVER_OPTIONS,
DNS_SERVER_OPTIONS,
DOMAIN_LIST_OPTIONS,
DashboardTab,
DiagnosticTab,
MonitoringTab,
PODKOP_ACTION_PROVIDERS_AVAILABILITY_EVENT,
PODKOP_UCI_PACKAGE,
PodkopShellMethods,
REGIONAL_OPTIONS,
UpdatesTab,
bulkValidate,
coreService,
getClashUIUrl,
injectGlobalStyles,
parseValueList,
showToast,
store,
validateDNS,
validateDomain,
validateIPV4,
validateOutboundJson,
validatePath,
validateProxyUrl,
validateSubnet,
validateUrl
});