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, )