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, hardware 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 import state from state import UPDATES_CACHE, REBOOT_PENDING 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 state.METRICS_STORE is None: await msg.answer("⚠️ Metrics not initialized", reply_markup=system_kb) return await msg.answer(summarize(state.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) @dp.message(F.text == "🧱 Hardware") async def hw(msg: Message): if is_admin_msg(msg): await msg.answer(hardware(), reply_markup=system_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)