Add selftest scheduler, queue history, and OpenWrt signal stats
This commit is contained in:
@@ -56,6 +56,9 @@ HELP_PAGES = [
|
||||
"🛠 **Admin & Deploy**\n\n"
|
||||
"Config: `/config_check`, файл `config.yaml` (см. config.example.yaml).\n"
|
||||
"Deploy: `deploy.sh` (ssh 10.10.10.10:1090 → git pull → systemctl restart tg-bot).\n"
|
||||
"Incidents summary: `/incidents_summary`.\n"
|
||||
"Disk snapshot: `/disk_snapshot`.\n"
|
||||
"BotFather list: `/botfather_list`.\n"
|
||||
"Безопасность: `safety.dry_run: true` блокирует опасные действия.\n"
|
||||
"OpenWrt: кнопка в System → Info.",
|
||||
),
|
||||
@@ -110,3 +113,33 @@ async def help_cb(cb: CallbackQuery):
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
await cb.answer()
|
||||
|
||||
|
||||
BOTFATHER_LIST = """\
|
||||
help - Show help pages
|
||||
status_short - Compact host status
|
||||
health_short - Compact health report
|
||||
selftest - Health + restic snapshot probe
|
||||
alerts - Manage alerts
|
||||
alerts_list - List active mutes
|
||||
alerts_recent - Show recent incidents (24h)
|
||||
alerts_mute_load - Mute load alerts for 60m
|
||||
backup_run - Run backup (queued)
|
||||
backup_history - Show backup log tail
|
||||
docker_status - Docker summary
|
||||
docker_health - Docker inspect/health by alias
|
||||
openwrt - Full OpenWrt status
|
||||
openwrt_wan - OpenWrt WAN only
|
||||
openwrt_clients - OpenWrt wifi clients
|
||||
openwrt_leases - OpenWrt DHCP leases
|
||||
incidents_summary - Incidents counters (24h/7d)
|
||||
disk_snapshot - Disk usage snapshot
|
||||
config_check - Validate config
|
||||
"""
|
||||
|
||||
|
||||
@dp.message(F.text == "/botfather_list")
|
||||
async def botfather_list(msg: Message):
|
||||
if not is_admin_msg(msg):
|
||||
return
|
||||
await msg.answer(f"Commands for BotFather:\n```\n{BOTFATHER_LIST}\n```", parse_mode="Markdown")
|
||||
|
||||
@@ -12,6 +12,7 @@ from services.system import format_disks
|
||||
from services.health import health
|
||||
from state import DOCKER_MAP
|
||||
from services.runner import run_cmd_full
|
||||
from services.selftest import run_selftest
|
||||
|
||||
|
||||
async def cmd_status(msg: Message):
|
||||
@@ -125,34 +126,8 @@ async def selftest(msg: Message):
|
||||
await msg.answer("⏳ Self-test…", reply_markup=menu_kb)
|
||||
|
||||
async def worker():
|
||||
lines = ["🧪 Self-test"]
|
||||
# health
|
||||
try:
|
||||
htext = await asyncio.to_thread(health, cfg, DOCKER_MAP)
|
||||
h_lines = [ln for ln in htext.splitlines() if ln.strip()]
|
||||
brief = " | ".join(h_lines[1:5]) if len(h_lines) > 1 else h_lines[0] if h_lines else "n/a"
|
||||
lines.append(f"🟢 Health: {brief}")
|
||||
except Exception as e:
|
||||
lines.append(f"🔴 Health failed: {e}")
|
||||
|
||||
# restic snapshots check
|
||||
rc, out = await run_cmd_full(["restic", "snapshots", "--json"], use_restic_env=True, timeout=40)
|
||||
if rc == 0:
|
||||
try:
|
||||
snaps = json.loads(out)
|
||||
if isinstance(snaps, list) and snaps:
|
||||
snaps.sort(key=lambda s: s.get("time", ""), reverse=True)
|
||||
last = snaps[0]
|
||||
t = last.get("time", "?").replace("Z", "").replace("T", " ")[:16]
|
||||
lines.append(f"🟢 Restic snapshots: {len(snaps)}, last {t}")
|
||||
else:
|
||||
lines.append("🟡 Restic snapshots: empty")
|
||||
except Exception:
|
||||
lines.append("🟡 Restic snapshots: invalid JSON")
|
||||
else:
|
||||
lines.append(f"🔴 Restic snapshots error: {out.strip() or rc}")
|
||||
|
||||
await msg.answer("\n".join(lines), reply_markup=menu_kb)
|
||||
text = await run_selftest(cfg, DOCKER_MAP)
|
||||
await msg.answer(text, reply_markup=menu_kb)
|
||||
|
||||
asyncio.create_task(worker())
|
||||
|
||||
|
||||
@@ -19,12 +19,14 @@ from services.runner import run_cmd
|
||||
from services.npmplus import fetch_certificates, format_certificates, list_proxy_hosts, set_proxy_host
|
||||
from services.gitea import get_gitea_health
|
||||
from services.openwrt import get_openwrt_status
|
||||
from services.system import worst_disk_usage
|
||||
import state
|
||||
from state import UPDATES_CACHE, REBOOT_PENDING
|
||||
from services.metrics import summarize
|
||||
from services.audit import read_audit_tail
|
||||
from services.incidents import read_recent, incidents_path
|
||||
from services.external_checks import format_report
|
||||
from services.disk_report import build_disk_report
|
||||
|
||||
|
||||
@dp.message(F.text == "💽 Disks")
|
||||
@@ -308,6 +310,48 @@ async def incidents(msg: Message):
|
||||
await msg.answer(text, reply_markup=system_logs_audit_kb, parse_mode="Markdown")
|
||||
|
||||
|
||||
@dp.message(F.text == "/incidents_summary")
|
||||
async def incidents_summary(msg: Message):
|
||||
if not is_admin_msg(msg):
|
||||
return
|
||||
last_24h = read_recent(cfg, hours=24, limit=2000)
|
||||
last_7d = read_recent(cfg, hours=24 * 7, limit=4000)
|
||||
|
||||
def count(lines):
|
||||
import re
|
||||
total = len(lines)
|
||||
cats = {}
|
||||
for line in lines:
|
||||
m = re.search(r"category=([A-Za-z0-9_-]+)", line)
|
||||
if m:
|
||||
cats[m.group(1)] = cats.get(m.group(1), 0) + 1
|
||||
top = ", ".join(f"{k}:{v}" for k, v in sorted(cats.items(), key=lambda x: x[1], reverse=True)[:5]) or "n/a"
|
||||
return total, top
|
||||
|
||||
t24, top24 = count(last_24h)
|
||||
t7, top7 = count(last_7d)
|
||||
text = (
|
||||
"📣 Incidents summary\n\n"
|
||||
f"24h: {t24} (top: {top24})\n"
|
||||
f"7d: {t7} (top: {top7})"
|
||||
)
|
||||
await msg.answer(text, reply_markup=system_logs_audit_kb)
|
||||
|
||||
|
||||
@dp.message(F.text == "/disk_snapshot")
|
||||
async def disk_snapshot(msg: Message):
|
||||
if not is_admin_msg(msg):
|
||||
return
|
||||
usage, mount = worst_disk_usage()
|
||||
mount = mount or "/"
|
||||
try:
|
||||
report = await build_disk_report(cfg, mount, usage or 0)
|
||||
except Exception as e:
|
||||
await msg.answer(f"⚠️ Disk snapshot error: {e}")
|
||||
return
|
||||
await msg.answer(f"💽 Disk snapshot ({mount})\n\n{report}", reply_markup=system_info_kb)
|
||||
|
||||
|
||||
@dp.message(F.text == "🔒 SSL")
|
||||
async def ssl_certs(msg: Message):
|
||||
if not is_admin_msg(msg):
|
||||
|
||||
Reference in New Issue
Block a user