diff --git a/handlers/system.py b/handlers/system.py index 254e32d..7b2a73f 100644 --- a/handlers/system.py +++ b/handlers/system.py @@ -1,5 +1,5 @@ from aiogram import F -from aiogram.types import Message +from aiogram.types import Message, CallbackQuery from app import dp from auth import is_admin_msg from keyboards import system_kb @@ -9,6 +9,8 @@ from services.http_checks import get_url_checks, check_url import asyncio from services.queue import enqueue from services.updates import list_updates, apply_updates +from state import UPDATES_CACHE +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton @dp.message(F.text == "💽 Disks") @@ -60,8 +62,13 @@ async def updates_list(msg: Message): return async def job(): - text = await list_updates() - await msg.answer(text, reply_markup=system_kb, parse_mode="Markdown") + title, lines = await list_updates() + UPDATES_CACHE[msg.from_user.id] = { + "title": title, + "lines": lines, + "page_size": 20, + } + await send_updates_page(msg, 0) pos = await enqueue("pkg-updates", job) await msg.answer(f"🕓 Updates queued (#{pos})", reply_markup=system_kb) @@ -78,3 +85,48 @@ async def updates_apply(msg: Message): pos = await enqueue("pkg-upgrade", job) await msg.answer(f"🕓 Upgrade queued (#{pos})", 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, page: int): + data = UPDATES_CACHE.get(msg.from_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```{body}```" + 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, page) diff --git a/services/updates.py b/services/updates.py index 4ffd1b1..077d01e 100644 --- a/services/updates.py +++ b/services/updates.py @@ -14,16 +14,16 @@ def detect_pkg_manager() -> str | None: return None -async def list_updates() -> str: +async def list_updates() -> tuple[str, list[str]]: pm = detect_pkg_manager() if not pm: - return "⚠️ No supported package manager found" + return "⚠️ Updates", ["No supported package manager found"] if pm == "apt": await run_cmd(["sudo", "apt", "update"], timeout=300) rc, out = await run_cmd(["apt", "list", "--upgradable"], timeout=120) if rc != 0: - return f"❌ apt list failed\n```{out}```" + return "❌ Updates (apt)", [f"apt list failed: {out}"] lines = [] for line in out.splitlines(): @@ -47,26 +47,32 @@ async def list_updates() -> str: else: lines.append(f"{name}: -> {new_ver}") - body = "\n".join(lines) if lines else "No updates" - return f"📦 Updates (apt)\n```{body}```" + if not lines: + lines = ["No updates"] + return "📦 Updates (apt)", lines if pm == "dnf": rc, out = await run_cmd(["sudo", "dnf", "check-update"], timeout=300) if rc in (0, 100): - return f"📦 Updates (dnf)\n```{out}```" - return f"❌ dnf check-update failed\n```{out}```" + lines = out.splitlines() or ["No updates"] + return "📦 Updates (dnf)", lines + return "❌ Updates (dnf)", [f"dnf check-update failed: {out}"] if pm == "yum": rc, out = await run_cmd(["sudo", "yum", "check-update"], timeout=300) if rc in (0, 100): - return f"📦 Updates (yum)\n```{out}```" - return f"❌ yum check-update failed\n```{out}```" + lines = out.splitlines() or ["No updates"] + return "📦 Updates (yum)", lines + return "❌ Updates (yum)", [f"yum check-update failed: {out}"] if pm == "pacman": rc, out = await run_cmd(["pacman", "-Qu"], timeout=120) - return f"📦 Updates (pacman)\n```{out}```" if rc == 0 else f"❌ pacman -Qu failed\n```{out}```" + if rc == 0: + lines = out.splitlines() or ["No updates"] + return "📦 Updates (pacman)", lines + return "❌ Updates (pacman)", [f"pacman -Qu failed: {out}"] - return "⚠️ Unsupported package manager" + return "⚠️ Updates", ["Unsupported package manager"] async def apply_updates() -> str: diff --git a/state.py b/state.py index 5704467..0568564 100644 --- a/state.py +++ b/state.py @@ -2,3 +2,4 @@ from typing import Dict DOCKER_MAP: Dict[str, str] = {} LOG_FILTER_PENDING: Dict[int, dict] = {} +UPDATES_CACHE: Dict[int, dict] = {}