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

230 lines
6.4 KiB
Python

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
try:
_, action, alias = cb.data.split(":", 2)
except ValueError:
await cb.answer("Bad request")
return
real = DOCKER_MAP.get(alias)
if not real:
await cb.answer("Container not found")
return
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",
)