217 lines
6.1 KiB
Python
217 lines
6.1 KiB
Python
import json
|
|
import time
|
|
from aiogram import F
|
|
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
|
from app import dp, ADMIN_ID
|
|
from services.docker import docker_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"
|
|
)
|
|
|
|
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",
|
|
)
|