import json import time from aiogram import F from aiogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton from app import dp, ADMIN_ID, cfg from services.docker import docker_cmd from services.incidents import log_incident from services.runner import run_cmd from state import DOCKER_MAP, LOG_FILTER_PENDING from handlers.backup import cmd_backup_status @dp.callback_query(F.data.startswith("docker:")) async def docker_callback(cb: CallbackQuery): if cb.from_user.id != ADMIN_ID: return _, action, alias = cb.data.split(":", 2) real = DOCKER_MAP[alias] if action == "restart": await cb.answer("Restarting…") rc, out = await docker_cmd(["restart", real]) await cb.message.answer( f"🔄 **{alias} restarted**\n```{out}```", parse_mode="Markdown" ) try: log_incident(cfg, f"docker_restart {alias}", category="docker") except Exception: pass elif action == "logs": await cb.answer() opts = InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton(text="Tail 80", callback_data=f"logsopt:tail:{alias}"), InlineKeyboardButton(text="10m", callback_data=f"logsopt:since:{alias}:600"), InlineKeyboardButton(text="1h", callback_data=f"logsopt:since:{alias}:3600"), ], [ InlineKeyboardButton(text="Filter", callback_data=f"logsopt:filter:{alias}") ], ] ) await cb.message.answer( f"📜 **Logs options: {alias}**", reply_markup=opts, parse_mode="Markdown" ) @dp.callback_query(F.data.startswith("snap:")) async def snapshot_details(cb: CallbackQuery): if cb.from_user.id != ADMIN_ID: return snap_id = cb.data.split(":", 1)[1] await cb.answer("Loading snapshot…") # получаем статистику snapshot rc, raw = await run_cmd( ["restic", "stats", snap_id, "--json"], use_restic_env=True, timeout=20 ) if rc != 0: await cb.message.answer(raw) return stats = json.loads(raw) msg = ( f"🧉 **Snapshot {snap_id}**\n\n" f"📁 Files: {stats.get('total_file_count', 'n/a')}\n" f"💽 Size: {stats.get('total_size', 0) / (1024**3):.2f} GiB\n\n" "🧯 Restore:\n" f"`restic restore {snap_id} --target /restore`\n" ) back_kb = InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( text="⬅️ Back to snapshots", callback_data="snapback" ) ] ] ) await cb.message.answer(msg, reply_markup=back_kb, parse_mode="Markdown") @dp.callback_query(F.data == "snapback") async def snapshot_back(cb: CallbackQuery): await cb.answer() # просто вызываем статус снова fake_msg = cb.message await cmd_backup_status(fake_msg) @dp.callback_query(F.data.startswith("logsopt:")) async def logs_options(cb: CallbackQuery): if cb.from_user.id != ADMIN_ID: return parts = cb.data.split(":") if len(parts) < 3: await cb.answer("Bad request") return action = parts[1] alias = parts[2] real = DOCKER_MAP.get(alias) if not real: await cb.answer("Container not found") return if action == "tail": await cb.answer("Loading logs…") rc, out = await docker_cmd(["logs", "--tail", "80", real]) if rc != 0: await cb.message.answer(out) return if not out.strip(): out = "(no logs)" await cb.message.answer( f"📜 **Logs: {alias}**\n```\n{out}\n```", parse_mode="Markdown" ) return if action == "since" and len(parts) == 4: try: seconds = int(parts[3]) except ValueError: await cb.answer("Bad request") return since_ts = str(int(time.time() - seconds)) await cb.answer("Loading logs…") rc, out = await docker_cmd(["logs", "--since", since_ts, "--tail", "200", real]) if rc != 0: await cb.message.answer(out) return if not out.strip(): out = "(no logs for period)" await cb.message.answer( f"📜 **Logs: {alias}**\n```\n{out}\n```", parse_mode="Markdown" ) return if action == "filter": LOG_FILTER_PENDING[cb.from_user.id] = { "alias": alias, "since_sec": 1800, } await cb.message.answer( f"🔎 Send filter text for `{alias}` (logs last 30m).", parse_mode="Markdown", ) return await cb.answer("Bad request") @dp.callback_query(F.data.startswith("wdrestart:")) async def watchdog_restart_request(cb: CallbackQuery): if cb.from_user.id != ADMIN_ID: return _, alias = cb.data.split(":", 1) if alias not in DOCKER_MAP: await cb.answer("Container not found") return kb = InlineKeyboardMarkup( inline_keyboard=[[ InlineKeyboardButton( text="✅ Confirm restart", callback_data=f"wdconfirm:{alias}" ), InlineKeyboardButton( text="✖ Cancel", callback_data="wdcancel" ), ]] ) await cb.message.answer( f"⚠️ Confirm restart `{alias}`?", reply_markup=kb, parse_mode="Markdown", ) await cb.answer() @dp.callback_query(F.data == "wdcancel") async def watchdog_restart_cancel(cb: CallbackQuery): await cb.answer("Cancelled") @dp.callback_query(F.data.startswith("wdconfirm:")) async def watchdog_restart_confirm(cb: CallbackQuery): if cb.from_user.id != ADMIN_ID: return _, alias = cb.data.split(":", 1) real = DOCKER_MAP.get(alias) if not real: await cb.answer("Container not found") return await cb.answer("Restarting…") rc, out = await docker_cmd(["restart", real]) await cb.message.answer( f"🔄 **{alias} restarted**\n```{out}```", parse_mode="Markdown", )