diff --git a/handlers/menu.py b/handlers/menu.py index 2fd4f16..c4dbe76 100644 --- a/handlers/menu.py +++ b/handlers/menu.py @@ -11,6 +11,9 @@ from keyboards import ( system_info_kb, system_ops_kb, system_logs_kb, + system_logs_audit_kb, + system_logs_security_kb, + system_logs_integrations_kb, ) @@ -72,3 +75,28 @@ async def sys_ops(msg: Message): async def sys_logs(msg: Message): if is_admin_msg(msg): await msg.answer("📄 System logs", reply_markup=system_logs_kb) + + +@dp.message(F.text == "⬅️ Logs") +async def back_logs(msg: Message): + if is_admin_msg(msg): + await msg.answer("📄 System logs", reply_markup=system_logs_kb) + + +@dp.message(F.text == "🧾 Audit/Incidents") +async def logs_audit_menu(msg: Message): + if is_admin_msg(msg): + await msg.answer("🧾 Logs: Audit/Incidents", reply_markup=system_logs_audit_kb) + + +@dp.message(F.text == "🔒 Security") +async def logs_security_menu(msg: Message): + if is_admin_msg(msg): + await msg.answer("🔒 Logs: Security", reply_markup=system_logs_security_kb) + + +@dp.message(F.text == "🧩 Integrations") +async def logs_integrations_menu(msg: Message): + if is_admin_msg(msg): + await msg.answer("🧩 Logs: Integrations", reply_markup=system_logs_integrations_kb) + diff --git a/handlers/processes.py b/handlers/processes.py index bd78faa..b740e93 100644 --- a/handlers/processes.py +++ b/handlers/processes.py @@ -3,7 +3,7 @@ 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_kb +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 @@ -61,11 +61,11 @@ async def proc_actions(cb: CallbackQuery): return if action == "search": PROC_SEARCH_PENDING[cb.from_user.id] = {} - await cb.message.answer("🔍 Send search text", reply_markup=system_logs_kb) + 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_kb) + await cb.message.answer("🛑 Send PID to terminate", reply_markup=system_logs_tools_kb) return @@ -76,12 +76,12 @@ async def proc_search(msg: Message): 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_kb) + 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_kb) + await msg.answer("🔍 No matches", reply_markup=system_logs_tools_kb) return lines = ["PID NAME CMD"] @@ -92,7 +92,7 @@ async def proc_search(msg: Message): 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_kb, parse_mode="Markdown") + 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)) @@ -104,7 +104,7 @@ async def proc_kill_pid(msg: Message): try: pid = int(raw) except ValueError: - await msg.answer("⚠️ Invalid PID", reply_markup=system_logs_kb) + await msg.answer("⚠️ Invalid PID", reply_markup=system_logs_tools_kb) return kb = InlineKeyboardMarkup( @@ -138,4 +138,4 @@ async def proc_kill_confirm(cb: CallbackQuery): return await cb.answer() result = await asyncio.to_thread(terminate_process, pid) - await cb.message.answer(result, reply_markup=system_logs_kb) + await cb.message.answer(result, reply_markup=system_logs_tools_kb) diff --git a/handlers/system.py b/handlers/system.py index 5536259..a929a6c 100644 --- a/handlers/system.py +++ b/handlers/system.py @@ -4,7 +4,13 @@ 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_info_kb, system_ops_kb, system_logs_kb +from keyboards import ( + system_info_kb, + system_ops_kb, + system_logs_audit_kb, + system_logs_security_kb, + system_logs_integrations_kb, +) from system_checks import security, disks, hardware, list_disks, smart_last_test from services.http_checks import get_url_checks, check_url from services.queue import enqueue @@ -39,10 +45,10 @@ async def urls(msg: Message): checks = list(get_url_checks(cfg)) if not checks: - await msg.answer("⚠️ Нет URL для проверки", reply_markup=system_logs_kb) + await msg.answer("⚠️ Нет URL для проверки", reply_markup=system_logs_security_kb) return - await msg.answer("⏳ Проверяю URL…", reply_markup=system_logs_kb) + await msg.answer("⏳ Проверяю URL…", reply_markup=system_logs_security_kb) async def worker(): tasks = [asyncio.to_thread(check_url, url) for _, url in checks] @@ -58,7 +64,7 @@ async def urls(msg: Message): reason = err or "error" lines.append(f"🔴 {alias}: {reason} ({ms}ms)") - await msg.answer("\n".join(lines), reply_markup=system_logs_kb) + await msg.answer("\n".join(lines), reply_markup=system_logs_security_kb) asyncio.create_task(worker()) @@ -68,7 +74,7 @@ async def ssh_log(msg: Message): if not is_admin_msg(msg): return - await msg.answer("⏳ Loading SSH logins…", reply_markup=system_logs_kb) + await msg.answer("⏳ Loading SSH logins…", reply_markup=system_logs_security_kb) async def worker(): raw_lines: list[str] = [] @@ -98,13 +104,13 @@ async def ssh_log(msg: Message): raw_lines.append(line) if not raw_lines: - await msg.answer("🔑 SSH log\n\n(no logins today)", reply_markup=system_logs_kb) + await msg.answer("🔑 SSH log\n\n(no logins today)", reply_markup=system_logs_security_kb) return recent = raw_lines[-30:] pretty = [_format_ssh_line(line) for line in recent] text = "🔑 SSH logins (today)\n```\n" + "\n".join(pretty) + "\n```" - await msg.answer(text, reply_markup=system_logs_kb, parse_mode="Markdown") + await msg.answer(text, reply_markup=system_logs_security_kb, parse_mode="Markdown") asyncio.create_task(worker()) @@ -194,16 +200,16 @@ async def audit_log(msg: Message): return text = read_audit_tail(cfg, limit=200) if text.startswith("⚠️") or text.startswith("ℹ️"): - await msg.answer(text, reply_markup=system_logs_kb) + await msg.answer(text, reply_markup=system_logs_audit_kb) else: - await msg.answer(text, reply_markup=system_logs_kb, parse_mode="Markdown") + await msg.answer(text, reply_markup=system_logs_audit_kb, parse_mode="Markdown") @dp.message(F.text == "🌍 External") async def external_checks(msg: Message): if not is_admin_msg(msg): return - await msg.answer(format_report(cfg), reply_markup=system_logs_kb) + await msg.answer(format_report(cfg), reply_markup=system_logs_security_kb) @dp.message(F.text == "📣 Incidents") @@ -212,7 +218,7 @@ async def incidents(msg: Message): return path = incidents_path(cfg) if not os.path.exists(path): - await msg.answer("⚠️ Incidents log not found", reply_markup=system_logs_kb) + await msg.answer("⚠️ Incidents log not found", reply_markup=system_logs_audit_kb) return last_24h = read_recent(cfg, hours=24, limit=500) last_7d = read_recent(cfg, hours=24 * 7, limit=1000) @@ -226,7 +232,7 @@ async def incidents(msg: Message): "Recent (24h):\n" f"```\n{body}\n```" ) - await msg.answer(text, reply_markup=system_logs_kb, parse_mode="Markdown") + await msg.answer(text, reply_markup=system_logs_audit_kb, parse_mode="Markdown") @dp.message(F.text == "🔒 SSL") @@ -234,7 +240,7 @@ async def ssl_certs(msg: Message): if not is_admin_msg(msg): return - await msg.answer("⏳ Checking SSL certificates…", reply_markup=system_logs_kb) + await msg.answer("⏳ Checking SSL certificates…", reply_markup=system_logs_security_kb) async def worker(): try: @@ -242,7 +248,7 @@ async def ssl_certs(msg: Message): text = format_certificates(certs) except Exception as e: text = f"⚠️ NPMplus error: {e}" - await msg.answer(text, reply_markup=system_logs_kb) + await msg.answer(text, reply_markup=system_logs_security_kb) asyncio.create_task(worker()) @@ -252,14 +258,14 @@ async def gitea_health(msg: Message): if not is_admin_msg(msg): return - await msg.answer("⏳ Checking Gitea health…", reply_markup=system_logs_kb) + await msg.answer("⏳ Checking Gitea health…", reply_markup=system_logs_integrations_kb) async def worker(): try: text = await asyncio.to_thread(get_gitea_health, cfg) except Exception as e: text = f"⚠️ Gitea error: {e}" - await msg.answer(text, reply_markup=system_logs_kb) + await msg.answer(text, reply_markup=system_logs_integrations_kb) asyncio.create_task(worker()) @@ -269,17 +275,17 @@ async def npmplus_hosts(msg: Message): if not is_admin_msg(msg): return - await msg.answer("⏳ Loading NPMplus hosts…", reply_markup=system_logs_kb) + await msg.answer("⏳ Loading NPMplus hosts…", reply_markup=system_logs_integrations_kb) async def worker(): try: hosts = await asyncio.to_thread(list_proxy_hosts, cfg) except Exception as e: - await msg.answer(f"⚠️ NPMplus error: {e}", reply_markup=system_logs_kb) + await msg.answer(f"⚠️ NPMplus error: {e}", reply_markup=system_logs_integrations_kb) return if not hosts: - await msg.answer("🧩 NPMplus\n\n(no proxy hosts)", reply_markup=system_logs_kb) + await msg.answer("🧩 NPMplus\n\n(no proxy hosts)", reply_markup=system_logs_integrations_kb) return lines = ["🧩 NPMplus proxy hosts\n"] @@ -461,9 +467,9 @@ async def npmplus_toggle(cb: CallbackQuery): await cb.answer("Working…") ok, info = await asyncio.to_thread(set_proxy_host, cfg, host_id, enable) if ok: - await cb.message.answer("✅ Updated", reply_markup=system_logs_kb) + await cb.message.answer("✅ Updated", reply_markup=system_logs_integrations_kb) else: - await cb.message.answer(f"❌ NPMplus error: {info}", reply_markup=system_logs_kb) + await cb.message.answer(f"❌ NPMplus error: {info}", reply_markup=system_logs_integrations_kb) @dp.message(F.text, F.func(lambda msg: msg.from_user and msg.from_user.id in REBOOT_PENDING)) diff --git a/keyboards.py b/keyboards.py index 5fdda20..2ae13ec 100644 --- a/keyboards.py +++ b/keyboards.py @@ -81,15 +81,46 @@ system_ops_kb = ReplyKeyboardMarkup( system_logs_kb = ReplyKeyboardMarkup( keyboard=[ - [KeyboardButton(text="🧾 Audit"), KeyboardButton(text="📣 Incidents")], - [KeyboardButton(text="🧰 Processes"), KeyboardButton(text="🔒 SSL")], - [KeyboardButton(text="🔑 SSH log"), KeyboardButton(text="🧩 NPMplus"), KeyboardButton(text="🍵 Gitea")], - [KeyboardButton(text="🌍 External"), KeyboardButton(text="🌐 URLs")], + [KeyboardButton(text="🧾 Audit/Incidents"), KeyboardButton(text="🔒 Security")], + [KeyboardButton(text="🧩 Integrations"), KeyboardButton(text="🧰 Processes")], [KeyboardButton(text="⬅️ System")], ], resize_keyboard=True, ) +system_logs_audit_kb = ReplyKeyboardMarkup( + keyboard=[ + [KeyboardButton(text="🧾 Audit"), KeyboardButton(text="📣 Incidents")], + [KeyboardButton(text="⬅️ Logs")], + ], + resize_keyboard=True, +) + +system_logs_security_kb = ReplyKeyboardMarkup( + keyboard=[ + [KeyboardButton(text="🔑 SSH log"), KeyboardButton(text="🔒 SSL")], + [KeyboardButton(text="🌍 External"), KeyboardButton(text="🌐 URLs")], + [KeyboardButton(text="⬅️ Logs")], + ], + resize_keyboard=True, +) + +system_logs_integrations_kb = ReplyKeyboardMarkup( + keyboard=[ + [KeyboardButton(text="🧩 NPMplus"), KeyboardButton(text="🍵 Gitea")], + [KeyboardButton(text="⬅️ Logs")], + ], + resize_keyboard=True, +) + +system_logs_tools_kb = ReplyKeyboardMarkup( + keyboard=[ + [KeyboardButton(text="🧰 Processes")], + [KeyboardButton(text="⬅️ Logs")], + ], + resize_keyboard=True, +) + def docker_inline_kb(action: str) -> InlineKeyboardMarkup: rows = []