Files
tg-admin-bot/handlers/docker.py

178 lines
5.4 KiB
Python

from aiogram import F
from aiogram.types import Message
from app import dp
from auth import is_admin_msg
from keyboards import docker_kb, docker_inline_kb
from services.docker import container_uptime, docker_cmd
from state import DOCKER_MAP, LOG_FILTER_PENDING
import time
async def cmd_docker_status(msg: Message):
try:
if not DOCKER_MAP:
await msg.answer(
"⚠️ DOCKER_MAP пуст.\n"
"Контейнеры не обнаружены.",
reply_markup=docker_kb,
)
return
lines = ["🐳 Docker containers\n"]
for alias, real in DOCKER_MAP.items():
rc, raw = await docker_cmd(
["inspect", "-f", "{{.State.Status}}|{{.State.StartedAt}}", real],
timeout=10,
)
if rc != 0:
lines.append(f"🔴 {alias}: inspect error")
continue
raw = raw.strip()
if "|" not in raw:
lines.append(f"🟡 {alias}: invalid inspect output")
continue
status, started = raw.split("|", 1)
up = container_uptime(started)
icon = "🟢" if status == "running" else "🔴"
lines.append(f"{icon} {alias}: {status} ({up})")
await msg.answer("\n".join(lines), reply_markup=docker_kb)
except Exception as e:
# ⬅️ КРИТИЧЕСКИ ВАЖНО
await msg.answer(
"❌ Docker status crashed:\n"
f"```{type(e).__name__}: {e}```",
reply_markup=docker_kb,
parse_mode="Markdown",
)
@dp.message(F.text == "🔄 Restart")
async def dr(msg: Message):
if is_admin_msg(msg):
await msg.answer(
"🔄 Выберите контейнер для рестарта:",
reply_markup=docker_inline_kb("restart")
)
@dp.message(F.text == "📜 Logs")
async def dl(msg: Message):
if is_admin_msg(msg):
await msg.answer(
"📜 Выберите контейнер для логов:",
reply_markup=docker_inline_kb("logs")
)
@dp.message(F.text == "🐳 Status")
async def ds(msg: Message):
if is_admin_msg(msg):
await cmd_docker_status(msg)
@dp.message(F.text == "/docker_status")
async def ds_cmd(msg: Message):
if is_admin_msg(msg):
await cmd_docker_status(msg)
@dp.message(F.text == "📈 Stats")
async def dstats(msg: Message):
if not is_admin_msg(msg):
return
if not DOCKER_MAP:
await msg.answer(
"⚠️ DOCKER_MAP пуст.\n"
"Контейнеры не обнаружены.",
reply_markup=docker_kb,
)
return
names = list(DOCKER_MAP.values())
fmt = "{{.Name}}|{{.CPUPerc}}|{{.MemUsage}}|{{.NetIO}}|{{.BlockIO}}"
rc, out = await docker_cmd(["stats", "--no-stream", "--format", fmt] + names)
if rc != 0:
await msg.answer(out, reply_markup=docker_kb)
return
lines = [line.strip() for line in out.splitlines() if line.strip()]
if not lines:
await msg.answer("📈 Stats\n\n(no data)", reply_markup=docker_kb)
return
alias_by_name = {v: k for k, v in DOCKER_MAP.items()}
rows = []
for line in lines:
parts = line.split("|")
if len(parts) != 5:
continue
name, cpu, mem, net, blk = [p.strip() for p in parts]
display = alias_by_name.get(name, name)
try:
cpu_val = float(cpu.strip("%"))
except ValueError:
cpu_val = 0.0
rows.append((cpu_val, display, cpu, mem, net, blk))
if not rows:
await msg.answer("📈 Stats\n\n(no data)", reply_markup=docker_kb)
return
rows.sort(key=lambda r: r[0], reverse=True)
header = f"{'NAME':<18} {'CPU':>6} {'MEM':>18} {'NET':>16} {'IO':>16}"
formatted = [header]
for _cpu_val, name, cpu, mem, net, blk in rows:
formatted.append(f"{name[:18]:<18} {cpu:>6} {mem:>18} {net:>16} {blk:>16}")
body = "\n".join(formatted)
await msg.answer(
f"📈 **Docker stats**\n```\n{body}\n```",
reply_markup=docker_kb,
parse_mode="Markdown",
)
@dp.message(F.text, F.func(lambda msg: msg.from_user and msg.from_user.id in LOG_FILTER_PENDING))
async def log_filter_input(msg: Message):
if not is_admin_msg(msg):
return
pending = LOG_FILTER_PENDING.pop(msg.from_user.id, None)
if not pending:
return
alias = pending["alias"]
real = DOCKER_MAP.get(alias)
if not real:
await msg.answer("⚠️ Container not found", reply_markup=docker_kb)
return
needle = (msg.text or "").strip()
if not needle:
await msg.answer("⚠️ Empty filter text", reply_markup=docker_kb)
return
since_ts = str(int(time.time() - int(pending.get("since_sec", 1800))))
rc, out = await docker_cmd(["logs", "--since", since_ts, "--tail", "400", real])
if rc != 0:
await msg.answer(out, reply_markup=docker_kb)
return
if not out.strip():
await msg.answer("⚠️ Нет логов за выбранный период", reply_markup=docker_kb)
return
lines = [line for line in out.splitlines() if needle.lower() in line.lower()]
filtered = "\n".join(lines) if lines else "(no matches)"
await msg.answer(
f"📜 **Logs filter: {alias}**\n```{filtered}```",
parse_mode="Markdown",
reply_markup=docker_kb,
)