mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 13:26:02 +00:00
v2.5.0: show routed services behind port 443
This commit is contained in:
@@ -376,7 +376,7 @@ def parse_ss_listeners(output: str, proto: str, port: int = 443) -> list[dict[st
|
|||||||
return listeners
|
return listeners
|
||||||
|
|
||||||
|
|
||||||
def port_443_status() -> dict[str, Any]:
|
def collect_port_listeners(port: int) -> tuple[list[dict[str, Any]], list[str]]:
|
||||||
listeners: list[dict[str, Any]] = []
|
listeners: list[dict[str, Any]] = []
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
for proto, args in {
|
for proto, args in {
|
||||||
@@ -385,14 +385,75 @@ def port_443_status() -> dict[str, Any]:
|
|||||||
}.items():
|
}.items():
|
||||||
code, stdout, stderr = run(args, timeout=2)
|
code, stdout, stderr = run(args, timeout=2)
|
||||||
if code == 0:
|
if code == 0:
|
||||||
listeners.extend(parse_ss_listeners(stdout, proto, 443))
|
listeners.extend(parse_ss_listeners(stdout, proto, port))
|
||||||
elif stderr.strip():
|
elif stderr.strip():
|
||||||
errors.append(stderr.strip())
|
errors.append(stderr.strip())
|
||||||
listeners.sort(key=lambda item: (item["proto"], item["address"], item["process"]))
|
listeners.sort(key=lambda item: (item["proto"], item["address"], item["process"]))
|
||||||
|
return listeners, errors
|
||||||
|
|
||||||
|
|
||||||
|
def read_telemt_edge_settings() -> dict[str, Any]:
|
||||||
|
settings: dict[str, Any] = {"tls_domain": "", "mask_port": 0, "dns_overrides": []}
|
||||||
|
if not TELEMT_CONFIG.exists():
|
||||||
|
return settings
|
||||||
|
section = ""
|
||||||
|
for raw in TELEMT_CONFIG.read_text(encoding="utf-8", errors="ignore").splitlines():
|
||||||
|
line = raw.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if line.startswith("[") and line.endswith("]"):
|
||||||
|
section = line.strip("[]")
|
||||||
|
continue
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip().split("#", 1)[0].strip()
|
||||||
|
if section == "censorship" and key == "tls_domain":
|
||||||
|
settings["tls_domain"] = value.strip('"').strip("'")
|
||||||
|
elif section == "censorship" and key == "mask_port":
|
||||||
|
try:
|
||||||
|
settings["mask_port"] = int(value)
|
||||||
|
except ValueError:
|
||||||
|
settings["mask_port"] = 0
|
||||||
|
elif section == "network" and key == "dns_overrides":
|
||||||
|
settings["dns_overrides"] = re.findall(r'"([^"]+)"', value)
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
|
def routed_behind_443() -> list[dict[str, Any]]:
|
||||||
|
config = load_json(GOTELEGRAM_CONFIG, {}) or {}
|
||||||
|
mode = str(config.get("mode") or "")
|
||||||
|
domain = str(config.get("domain") or "")
|
||||||
|
settings = read_telemt_edge_settings()
|
||||||
|
mask_port = int(settings.get("mask_port") or 0)
|
||||||
|
tls_domain = str(settings.get("tls_domain") or domain)
|
||||||
|
routes: list[dict[str, Any]] = []
|
||||||
|
if mode == "pro" and domain and mask_port and mask_port != 443:
|
||||||
|
internal, _ = collect_port_listeners(mask_port)
|
||||||
|
site_listener = next((item for item in internal if item.get("role") == "site"), None)
|
||||||
|
routes.append({
|
||||||
|
"role": "site",
|
||||||
|
"proto": "HTTPS",
|
||||||
|
"public": f"{domain}:443",
|
||||||
|
"target": f"127.0.0.1:{mask_port}",
|
||||||
|
"process": (site_listener or {}).get("process") or "nginx",
|
||||||
|
"pid": (site_listener or {}).get("pid") or "",
|
||||||
|
"status": service_status("nginx"),
|
||||||
|
"via": "telemt dns_overrides",
|
||||||
|
"tls_domain": tls_domain,
|
||||||
|
"details": settings.get("dns_overrides") or [],
|
||||||
|
})
|
||||||
|
return routes
|
||||||
|
|
||||||
|
|
||||||
|
def port_443_status() -> dict[str, Any]:
|
||||||
|
listeners, errors = collect_port_listeners(443)
|
||||||
return {
|
return {
|
||||||
"checked_at": int(time.time()),
|
"checked_at": int(time.time()),
|
||||||
"configured_port": read_telemt_port(),
|
"configured_port": read_telemt_port(),
|
||||||
"listeners": listeners,
|
"listeners": listeners,
|
||||||
|
"routes": routed_behind_443(),
|
||||||
"ok": not errors,
|
"ok": not errors,
|
||||||
"error": "; ".join(errors[:2]),
|
"error": "; ".join(errors[:2]),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,14 +136,19 @@ const i18n = {
|
|||||||
languageSaved: "Language saved",
|
languageSaved: "Language saved",
|
||||||
keyEnabled: "Key enabled",
|
keyEnabled: "Key enabled",
|
||||||
keyDisabled: "Key disabled",
|
keyDisabled: "Key disabled",
|
||||||
visualTitle: "Port 443 listeners",
|
visualTitle: "Port 443 map",
|
||||||
visualText: "Actual TCP/UDP listeners on public port 443: telemt, website, Xray/3x-ui, AmneziaWG or another service.",
|
visualText: "Shows the public 443 listener and services routed behind it, including the website on local nginx.",
|
||||||
port443Checked: "checked",
|
port443Checked: "checked",
|
||||||
port443NoListeners: "No 443 listeners found",
|
port443NoListeners: "No 443 listeners found",
|
||||||
port443Listeners: "listeners",
|
port443Listeners: "listeners",
|
||||||
|
port443Routes: "routed",
|
||||||
port443Error: "Port check failed",
|
port443Error: "Port check failed",
|
||||||
port443Public: "public",
|
port443Public: "public",
|
||||||
port443Configured: "telemt: {port}",
|
port443Configured: "telemt: {port}",
|
||||||
|
port443PublicSection: "Public 443",
|
||||||
|
port443BehindSection: "Behind 443",
|
||||||
|
port443NoRoutes: "No routed services detected",
|
||||||
|
port443Via: "via {value}",
|
||||||
roleMtproxy: "MTProxy",
|
roleMtproxy: "MTProxy",
|
||||||
roleSite: "Website",
|
roleSite: "Website",
|
||||||
roleXray: "Xray / 3x-ui",
|
roleXray: "Xray / 3x-ui",
|
||||||
@@ -318,14 +323,19 @@ const i18n = {
|
|||||||
languageSaved: "Язык сохранён",
|
languageSaved: "Язык сохранён",
|
||||||
keyEnabled: "Ключ включён",
|
keyEnabled: "Ключ включён",
|
||||||
keyDisabled: "Ключ отключён",
|
keyDisabled: "Ключ отключён",
|
||||||
visualTitle: "Кто слушает порт 443",
|
visualTitle: "Карта порта 443",
|
||||||
visualText: "Реальные TCP/UDP-процессы на публичном 443: telemt, сайт, Xray/3x-ui, AmneziaWG или другой сервис.",
|
visualText: "Показывает публичного слушателя 443 и сервисы, которые живут за ним, включая сайт на локальном nginx.",
|
||||||
port443Checked: "проверено",
|
port443Checked: "проверено",
|
||||||
port443NoListeners: "Слушателей 443 не найдено",
|
port443NoListeners: "Слушателей 443 не найдено",
|
||||||
port443Listeners: "слушателей",
|
port443Listeners: "слушателей",
|
||||||
|
port443Routes: "за 443",
|
||||||
port443Error: "Проверка порта не удалась",
|
port443Error: "Проверка порта не удалась",
|
||||||
port443Public: "публичный",
|
port443Public: "публичный",
|
||||||
port443Configured: "telemt: {port}",
|
port443Configured: "telemt: {port}",
|
||||||
|
port443PublicSection: "Публичный 443",
|
||||||
|
port443BehindSection: "За портом 443",
|
||||||
|
port443NoRoutes: "Маршрутизируемых сервисов не найдено",
|
||||||
|
port443Via: "через {value}",
|
||||||
roleMtproxy: "MTProxy",
|
roleMtproxy: "MTProxy",
|
||||||
roleSite: "Сайт",
|
roleSite: "Сайт",
|
||||||
roleXray: "Xray / 3x-ui",
|
roleXray: "Xray / 3x-ui",
|
||||||
@@ -637,6 +647,7 @@ function roleLabel(role) {
|
|||||||
|
|
||||||
function renderPort443(payload = {}) {
|
function renderPort443(payload = {}) {
|
||||||
const listeners = Array.isArray(payload.listeners) ? payload.listeners : [];
|
const listeners = Array.isArray(payload.listeners) ? payload.listeners : [];
|
||||||
|
const routes = Array.isArray(payload.routes) ? payload.routes : [];
|
||||||
const summary = $("#port443Summary");
|
const summary = $("#port443Summary");
|
||||||
const list = $("#port443List");
|
const list = $("#port443List");
|
||||||
const configuredPort = Number(payload.configured_port) || 443;
|
const configuredPort = Number(payload.configured_port) || 443;
|
||||||
@@ -649,14 +660,10 @@ function renderPort443(payload = {}) {
|
|||||||
summary.textContent = t("port443NoListeners");
|
summary.textContent = t("port443NoListeners");
|
||||||
summary.className = "port-status warn";
|
summary.className = "port-status warn";
|
||||||
} else {
|
} else {
|
||||||
summary.textContent = `${listeners.length} ${t("port443Listeners")}`;
|
summary.textContent = `${listeners.length} ${t("port443Listeners")}${routes.length ? ` · ${routes.length} ${t("port443Routes")}` : ""}`;
|
||||||
summary.className = "port-status ok";
|
summary.className = "port-status ok";
|
||||||
}
|
}
|
||||||
if (!listeners.length) {
|
const listenerHtml = listeners.length ? listeners.map((item) => {
|
||||||
list.innerHTML = `<div class="port-empty">${escapeHtml(payload.error || t("port443NoListeners"))}</div>`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
list.innerHTML = listeners.map((item) => {
|
|
||||||
const title = `${item.proto || ""} ${item.address || ""} · ${item.process || "unknown"}${item.pid ? ` · pid ${item.pid}` : ""}`;
|
const title = `${item.proto || ""} ${item.address || ""} · ${item.process || "unknown"}${item.pid ? ` · pid ${item.pid}` : ""}`;
|
||||||
return `<article class="port-listener role-${escapeAttr(item.role || "other")}" title="${escapeAttr(title)}">
|
return `<article class="port-listener role-${escapeAttr(item.role || "other")}" title="${escapeAttr(title)}">
|
||||||
<div>
|
<div>
|
||||||
@@ -665,7 +672,24 @@ function renderPort443(payload = {}) {
|
|||||||
</div>
|
</div>
|
||||||
<small>${escapeHtml(item.proto || "--")} · ${escapeHtml(item.address || "--")}</small>
|
<small>${escapeHtml(item.proto || "--")} · ${escapeHtml(item.address || "--")}</small>
|
||||||
</article>`;
|
</article>`;
|
||||||
}).join("");
|
}).join("") : `<div class="port-empty">${escapeHtml(payload.error || t("port443NoListeners"))}</div>`;
|
||||||
|
const routeHtml = routes.length ? routes.map((item) => {
|
||||||
|
const via = item.via ? t("port443Via").replace("{value}", item.via) : "";
|
||||||
|
const title = `${item.public || ""} → ${item.target || ""} · ${item.process || ""}`;
|
||||||
|
return `<article class="port-listener role-${escapeAttr(item.role || "other")}" title="${escapeAttr(title)}">
|
||||||
|
<div>
|
||||||
|
<strong>${escapeHtml(roleLabel(item.role))}</strong>
|
||||||
|
<span>${escapeHtml(item.process || "unknown")}${item.status ? ` · ${escapeHtml(statusLabel(item.status))}` : ""}</span>
|
||||||
|
</div>
|
||||||
|
<small>${escapeHtml(item.public || "--")} → ${escapeHtml(item.target || "--")}${via ? ` · ${escapeHtml(via)}` : ""}</small>
|
||||||
|
</article>`;
|
||||||
|
}).join("") : `<div class="port-empty">${escapeHtml(t("port443NoRoutes"))}</div>`;
|
||||||
|
list.innerHTML = `
|
||||||
|
<div class="port-section-label">${escapeHtml(t("port443PublicSection"))}</div>
|
||||||
|
${listenerHtml}
|
||||||
|
<div class="port-section-label">${escapeHtml(t("port443BehindSection"))}</div>
|
||||||
|
${routeHtml}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOverview() {
|
function renderOverview() {
|
||||||
|
|||||||
@@ -400,6 +400,14 @@ h2 {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.port-section-label {
|
||||||
|
margin-top: 2px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
.port-listener,
|
.port-listener,
|
||||||
.port-empty {
|
.port-empty {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
Reference in New Issue
Block a user