From 464d412de9b65be15418a8f673ef2cc0f7514633 Mon Sep 17 00:00:00 2001 From: berkio_admin_gitea Date: Sun, 19 Jul 2026 10:24:39 +0000 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B0:=20?= =?UTF-8?q?=D0=B6=D1=83=D1=80=D0=BD=D0=B0=D0=BB=20=C2=AB=D0=9F=D0=B0=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=82=D1=83=D0=BD=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F=C2=BB=20+=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=20/api/tunnel-event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Таблица WEB_TUNNEL_EVENTS (ts/bad/bad2/recovered). Health-check на vds178 шлёт событие на КАЖДЫЙ авто-рестарт цепочки -51 (auth ALERT_TOKEN). В админке раздел с датой/временем и признаком, помог ли рестарт (24ч/7дн + сколько не восстановилось). Telegram при этом остаётся тихим (только неустранённые простои). Co-Authored-By: Claude Opus 4.8 --- app/main.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/app/main.py b/app/main.py index add22d1..1750c46 100644 --- a/app/main.py +++ b/app/main.py @@ -351,6 +351,16 @@ def _db_ensure_auth_table(): PRIMARY KEY (day, event) ) """) + # Падения туннеля -51 (health-check на vds178): дата/время + помог ли авто-рестарт. + con.execute(""" + CREATE TABLE IF NOT EXISTS WEB_TUNNEL_EVENTS ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ts REAL, + bad INTEGER, + bad2 INTEGER, + recovered INTEGER + ) + """) # Показы пейвола: по каждому придержанному файлу — кто (account/user) и какой файл. con.execute(""" CREATE TABLE IF NOT EXISTS WEB_PAYWALL_EVENTS ( @@ -3225,6 +3235,12 @@ _ADMIN_HTML = """ +
+

Падения туннеля

+
+
ДатаПлохих пробАвто-рестарт
+
+

По дням

@@ -3434,6 +3450,19 @@ _ADMIN_HTML = """ ''; }).join('') : ''; + // Падения туннеля (журнал health-check vds178) + var tun = (d.tunnel && d.tunnel.recent) || []; + document.getElementById('tunnel-note').textContent = d.tunnel + ? ('· 24ч: ' + d.tunnel.count_24h + ' · 7дн: ' + d.tunnel.count_7d + + (d.tunnel.failed_7d ? ' (не восстановилось: ' + d.tunnel.failed_7d + ')' : '')) + : ''; + document.getElementById('t-tunnel').innerHTML = tun.length ? tun.map(function(e){ + var ok = e.recovered + ? 'восстановился сам' + : 'рестарт не помог'; + return ''; + }).join('') : ''; + // По дням (свежие сверху) var daily = (d.daily||[]).slice().reverse(); document.getElementById('t-daily').innerHTML = daily.length ? daily.map(function(x){ @@ -3693,6 +3722,33 @@ async def api_alert(payload: dict): return {"ok": True} +@app.post("/api/tunnel-event") +async def api_tunnel_event(payload: dict): + """Событие падения туннеля -51 с vds178 (health-check) → журнал для админки. + Пишется на КАЖДЫЙ авто-рестарт (в отличие от Telegram-алерта). Авторизация — ALERT_TOKEN.""" + if not ALERT_TOKEN: + raise HTTPException(status_code=503, detail="alerts not configured") + tok = str((payload or {}).get("token") or "") + if not hmac.compare_digest(tok, ALERT_TOKEN): + raise HTTPException(status_code=401, detail="unauthorized") + + def _int(k): + try: + return int((payload or {}).get(k) or 0) + except (TypeError, ValueError): + return 0 + bad, bad2 = _int("bad"), _int("bad2") + + def _ins(): + with sqlite3.connect(DB_PATH, timeout=10) as con: + con.execute( + "INSERT INTO WEB_TUNNEL_EVENTS (ts, bad, bad2, recovered) VALUES (?,?,?,?)", + (time.time(), bad, bad2, 1 if bad2 < 2 else 0)) + con.commit() + await asyncio.to_thread(_ins) + return {"ok": True} + + def _require_admin(request: Request) -> bool: if not ADMIN_TOKEN: return False @@ -3804,6 +3860,19 @@ def _admin_collect_stats() -> dict: "FROM WEB_PAYWALL_EVENTS ORDER BY ts DESC LIMIT 100")] except Exception: out["paywall_events"] = [] + # Падения туннеля -51 (журнал health-check vds178) + try: + tun = [dict(r) for r in con.execute( + "SELECT ts, bad, bad2, recovered FROM WEB_TUNNEL_EVENTS ORDER BY ts DESC LIMIT 100")] + t24 = con.execute("SELECT COUNT(*) c FROM WEB_TUNNEL_EVENTS WHERE ts >= ?", + (now - 86400,)).fetchone()["c"] + t7 = con.execute("SELECT COUNT(*) c FROM WEB_TUNNEL_EVENTS WHERE ts >= ?", + (now - 7 * 86400,)).fetchone()["c"] + tfail7 = con.execute("SELECT COUNT(*) c FROM WEB_TUNNEL_EVENTS WHERE recovered=0 AND ts >= ?", + (now - 7 * 86400,)).fetchone()["c"] + out["tunnel"] = {"recent": tun, "count_24h": t24, "count_7d": t7, "failed_7d": tfail7} + except Exception: + out["tunnel"] = {"recent": [], "count_24h": 0, "count_7d": 0, "failed_7d": 0} # Документы cols = {r[1] for r in con.execute("PRAGMA table_info(FILES)")} pages_expr = "COALESCE(NULLIF(pages_total,0), num_pages, 0)" if "pages_total" in cols else "COALESCE(num_pages,0)"
ДатаЗаходыУник.ФайловСтраницСр/стрИсходникРезультат
'+esc(e.source||'—')+''+esc(st[1])+'
Пока не было показов пейвола
'+dts(e.ts)+''+(e.bad||0)+'/3'+ok+'
Падений туннеля не зафиксировано