import asyncio from aiogram import F from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton from app import dp, ADMIN_ID from auth import is_admin_msg from keyboards import system_logs_tools_kb from services.processes import get_top_processes, search_processes, terminate_process from state import PROC_SEARCH_PENDING, PROC_KILL_PENDING def _proc_kb() -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[[ InlineKeyboardButton(text="🔄 Refresh", callback_data="proc:refresh"), InlineKeyboardButton(text="🔍 Search", callback_data="proc:search"), InlineKeyboardButton(text="🛑 Kill", callback_data="proc:kill"), ]] ) def _format_top(title: str, rows: list[dict]) -> str: if not rows: return f"{title}\n(no data)" lines = ["PID CPU% MEM% NAME"] for row in rows: lines.append( f"{row['pid']:<5} {row['cpu']:<5.1f} {row['mem']:<5.1f} {row['name']}" ) return f"{title}\n" + "\n".join(lines) async def send_processes(msg: Message, edit: bool = False): top_cpu, top_mem = await asyncio.to_thread(get_top_processes) body = ( "🧰 **Processes**\n\n" "```\n" f"{_format_top('Top CPU', top_cpu)}\n\n" f"{_format_top('Top RAM', top_mem)}\n" "```" ) if edit: await msg.edit_text(body, reply_markup=_proc_kb(), parse_mode="Markdown") else: await msg.answer(body, reply_markup=_proc_kb(), parse_mode="Markdown") @dp.message(F.text == "🧰 Processes") async def proc_menu(msg: Message): if is_admin_msg(msg): await send_processes(msg, edit=False) @dp.callback_query(F.data.startswith("proc:")) async def proc_actions(cb: CallbackQuery): if cb.from_user.id != ADMIN_ID: return await cb.answer() action = cb.data.split(":", 1)[1] if action == "refresh": await send_processes(cb.message, edit=True) return if action == "search": PROC_SEARCH_PENDING[cb.from_user.id] = {} await cb.message.answer("🔍 Send search text", reply_markup=system_logs_tools_kb) return if action == "kill": PROC_KILL_PENDING[cb.from_user.id] = {} await cb.message.answer("🛑 Send PID to terminate", reply_markup=system_logs_tools_kb) return @dp.message(F.text, F.func(lambda msg: msg.from_user and msg.from_user.id in PROC_SEARCH_PENDING)) async def proc_search(msg: Message): if not is_admin_msg(msg): return PROC_SEARCH_PENDING.pop(msg.from_user.id, None) query = (msg.text or "").strip() if not query: await msg.answer("⚠️ Empty search", reply_markup=system_logs_tools_kb) return rows = await asyncio.to_thread(search_processes, query) if not rows: await msg.answer("🔍 No matches", reply_markup=system_logs_tools_kb) return lines = ["PID NAME CMD"] for row in rows: cmd = row["cmdline"] or "-" if len(cmd) > 80: cmd = cmd[:80] + "…" lines.append(f"{row['pid']:<5} {row['name']:<6} {cmd}") text = "🔍 **Search results**\n```\n" + "\n".join(lines) + "\n```" await msg.answer(text, reply_markup=system_logs_tools_kb, parse_mode="Markdown") @dp.message(F.text, F.func(lambda msg: msg.from_user and msg.from_user.id in PROC_KILL_PENDING)) async def proc_kill_pid(msg: Message): if not is_admin_msg(msg): return PROC_KILL_PENDING.pop(msg.from_user.id, None) raw = (msg.text or "").strip() try: pid = int(raw) except ValueError: await msg.answer("⚠️ Invalid PID", reply_markup=system_logs_tools_kb) return kb = InlineKeyboardMarkup( inline_keyboard=[[ InlineKeyboardButton(text="✅ Confirm", callback_data=f"prockill:{pid}:confirm"), InlineKeyboardButton(text="✖ Cancel", callback_data="prockill:cancel"), ]] ) await msg.answer(f"⚠️ Terminate PID `{pid}`?", reply_markup=kb, parse_mode="Markdown") @dp.callback_query(F.data.startswith("prockill:")) async def proc_kill_confirm(cb: CallbackQuery): if cb.from_user.id != ADMIN_ID: return parts = cb.data.split(":") if len(parts) < 2: await cb.answer("Bad request") return if parts[1] == "cancel": await cb.answer("Cancelled") await cb.message.delete() return if len(parts) != 3 or parts[2] != "confirm": await cb.answer("Bad request") return try: pid = int(parts[1]) except ValueError: await cb.answer("Bad PID") return await cb.answer() result = await asyncio.to_thread(terminate_process, pid) await cb.message.answer(result, reply_markup=system_logs_tools_kb)