219 lines
7.1 KiB
Python
219 lines
7.1 KiB
Python
import asyncio
|
|
from aiogram import F
|
|
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
|
from app import dp, cfg
|
|
from auth import is_admin_msg
|
|
from keyboards import system_kb
|
|
from system_checks import security, disks
|
|
from services.http_checks import get_url_checks, check_url
|
|
from services.queue import enqueue
|
|
from services.updates import list_updates, apply_updates
|
|
from services.runner import run_cmd
|
|
from state import UPDATES_CACHE, REBOOT_PENDING, METRICS_STORE
|
|
from services.metrics import summarize
|
|
|
|
|
|
@dp.message(F.text == "💽 Disks")
|
|
async def sd(msg: Message):
|
|
if is_admin_msg(msg):
|
|
await msg.answer(disks(), reply_markup=system_kb)
|
|
|
|
|
|
@dp.message(F.text == "🔐 Security")
|
|
async def sec(msg: Message):
|
|
if is_admin_msg(msg):
|
|
await msg.answer(security(), reply_markup=system_kb)
|
|
|
|
|
|
@dp.message(F.text == "🌐 URLs")
|
|
async def urls(msg: Message):
|
|
if not is_admin_msg(msg):
|
|
return
|
|
|
|
checks = list(get_url_checks(cfg))
|
|
if not checks:
|
|
await msg.answer("⚠️ Нет URL для проверки", reply_markup=system_kb)
|
|
return
|
|
|
|
await msg.answer("⏳ Проверяю URL…", reply_markup=system_kb)
|
|
|
|
async def worker():
|
|
tasks = [asyncio.to_thread(check_url, url) for _, url in checks]
|
|
results = await asyncio.gather(*tasks)
|
|
|
|
lines = ["🌐 URLs\n"]
|
|
for (alias, _url), (ok, status, ms, err) in zip(checks, results):
|
|
if ok:
|
|
lines.append(f"🟢 {alias}: {status} ({ms}ms)")
|
|
elif status is not None:
|
|
lines.append(f"🔴 {alias}: {status} ({ms}ms)")
|
|
else:
|
|
reason = err or "error"
|
|
lines.append(f"🔴 {alias}: {reason} ({ms}ms)")
|
|
|
|
await msg.answer("\n".join(lines), reply_markup=system_kb)
|
|
|
|
asyncio.create_task(worker())
|
|
|
|
|
|
@dp.message(F.text == "📈 Metrics")
|
|
async def metrics(msg: Message):
|
|
if not is_admin_msg(msg):
|
|
return
|
|
if METRICS_STORE is None:
|
|
await msg.answer("⚠️ Metrics not initialized", reply_markup=system_kb)
|
|
return
|
|
await msg.answer(summarize(METRICS_STORE, minutes=15), reply_markup=system_kb)
|
|
|
|
|
|
@dp.message(F.text == "📦 Updates")
|
|
async def updates_list(msg: Message):
|
|
if not is_admin_msg(msg):
|
|
return
|
|
|
|
async def job():
|
|
title, lines = await list_updates()
|
|
UPDATES_CACHE[msg.from_user.id] = {
|
|
"title": title,
|
|
"lines": lines,
|
|
"page_size": 20,
|
|
}
|
|
await send_updates_page(msg, msg.from_user.id, 0, edit=False)
|
|
|
|
pos = await enqueue("pkg-updates", job)
|
|
await msg.answer(f"🕓 Updates queued (#{pos})", reply_markup=system_kb)
|
|
|
|
|
|
@dp.message(F.text == "⬆️ Upgrade")
|
|
async def updates_apply(msg: Message):
|
|
if not is_admin_msg(msg):
|
|
return
|
|
|
|
kb = InlineKeyboardMarkup(
|
|
inline_keyboard=[[
|
|
InlineKeyboardButton(text="✅ Confirm", callback_data="upgrade:confirm"),
|
|
InlineKeyboardButton(text="✖ Cancel", callback_data="upgrade:cancel"),
|
|
]]
|
|
)
|
|
await msg.answer("⚠️ Confirm package upgrade?", reply_markup=kb)
|
|
|
|
|
|
@dp.message(F.text == "🔄 Reboot")
|
|
async def reboot_request(msg: Message):
|
|
if not is_admin_msg(msg):
|
|
return
|
|
|
|
kb = InlineKeyboardMarkup(
|
|
inline_keyboard=[[
|
|
InlineKeyboardButton(text="✅ Confirm", callback_data="reboot:confirm"),
|
|
InlineKeyboardButton(text="✖ Cancel", callback_data="reboot:cancel"),
|
|
]]
|
|
)
|
|
await msg.answer("⚠️ Confirm reboot?", reply_markup=kb)
|
|
|
|
|
|
def _updates_kb(page: int, total_pages: int) -> InlineKeyboardMarkup:
|
|
buttons = []
|
|
if total_pages > 1:
|
|
row = []
|
|
if page > 0:
|
|
row.append(InlineKeyboardButton(text="⬅️ Prev", callback_data=f"updpage:{page-1}"))
|
|
if page < total_pages - 1:
|
|
row.append(InlineKeyboardButton(text="Next ➡️", callback_data=f"updpage:{page+1}"))
|
|
if row:
|
|
buttons.append(row)
|
|
return InlineKeyboardMarkup(inline_keyboard=buttons)
|
|
|
|
|
|
async def send_updates_page(msg: Message, user_id: int, page: int, edit: bool):
|
|
data = UPDATES_CACHE.get(user_id)
|
|
if not data:
|
|
await msg.answer("⚠️ Updates cache empty", reply_markup=system_kb)
|
|
return
|
|
|
|
lines = data["lines"]
|
|
page_size = data["page_size"]
|
|
total_pages = max(1, (len(lines) + page_size - 1) // page_size)
|
|
page = max(0, min(page, total_pages - 1))
|
|
start = page * page_size
|
|
end = start + page_size
|
|
body = "\n".join(lines[start:end])
|
|
|
|
text = f"{data['title']} (page {page+1}/{total_pages})\n```\n{body}\n```"
|
|
if edit:
|
|
await msg.edit_text(text, reply_markup=_updates_kb(page, total_pages), parse_mode="Markdown")
|
|
else:
|
|
await msg.answer(text, reply_markup=_updates_kb(page, total_pages), parse_mode="Markdown")
|
|
|
|
|
|
@dp.callback_query(F.data.startswith("updpage:"))
|
|
async def updates_page(cb: CallbackQuery):
|
|
if cb.from_user.id not in UPDATES_CACHE:
|
|
await cb.answer("No cached updates")
|
|
return
|
|
try:
|
|
page = int(cb.data.split(":", 1)[1])
|
|
except ValueError:
|
|
await cb.answer("Bad page")
|
|
return
|
|
await cb.answer()
|
|
await send_updates_page(cb.message, cb.from_user.id, page, edit=True)
|
|
|
|
|
|
@dp.callback_query(F.data == "upgrade:confirm")
|
|
async def upgrade_confirm(cb: CallbackQuery):
|
|
if cb.from_user.id != cfg["telegram"]["admin_id"]:
|
|
return
|
|
await cb.answer()
|
|
|
|
async def job():
|
|
text = await apply_updates()
|
|
await cb.message.answer(text, reply_markup=system_kb, parse_mode="Markdown")
|
|
|
|
pos = await enqueue("pkg-upgrade", job)
|
|
await cb.message.answer(f"🕓 Upgrade queued (#{pos})", reply_markup=system_kb)
|
|
|
|
|
|
@dp.callback_query(F.data == "upgrade:cancel")
|
|
async def upgrade_cancel(cb: CallbackQuery):
|
|
await cb.answer("Cancelled")
|
|
await cb.message.delete()
|
|
|
|
|
|
@dp.callback_query(F.data == "reboot:confirm")
|
|
async def reboot_confirm(cb: CallbackQuery):
|
|
if cb.from_user.id != cfg["telegram"]["admin_id"]:
|
|
return
|
|
await cb.answer()
|
|
REBOOT_PENDING[cb.from_user.id] = {}
|
|
await cb.message.answer("🔐 Send reboot password", reply_markup=system_kb)
|
|
|
|
|
|
@dp.callback_query(F.data == "reboot:cancel")
|
|
async def reboot_cancel(cb: CallbackQuery):
|
|
await cb.answer("Cancelled")
|
|
await cb.message.delete()
|
|
|
|
|
|
@dp.message(F.text, F.func(lambda msg: msg.from_user and msg.from_user.id in REBOOT_PENDING))
|
|
async def reboot_password(msg: Message):
|
|
if not is_admin_msg(msg):
|
|
return
|
|
REBOOT_PENDING.pop(msg.from_user.id, None)
|
|
|
|
expected = cfg.get("security", {}).get("reboot_password")
|
|
if not expected:
|
|
await msg.answer("⚠️ Reboot password not configured", reply_markup=system_kb)
|
|
return
|
|
|
|
if (msg.text or "").strip() != expected:
|
|
await msg.answer("❌ Wrong password", reply_markup=system_kb)
|
|
return
|
|
|
|
async def job():
|
|
await msg.answer("🔄 Rebooting…", reply_markup=system_kb)
|
|
await run_cmd(["sudo", "reboot"], timeout=10)
|
|
|
|
pos = await enqueue("reboot", job)
|
|
await msg.answer(f"🕓 Reboot queued (#{pos})", reply_markup=system_kb)
|