142 lines
4.7 KiB
Python
142 lines
4.7 KiB
Python
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)
|