Split Logs menu into subcategories

This commit is contained in:
2026-02-08 02:48:25 +03:00
parent 0f7f53cb27
commit fc061ece30
4 changed files with 98 additions and 33 deletions

View File

@@ -11,6 +11,9 @@ from keyboards import (
system_info_kb, system_info_kb,
system_ops_kb, system_ops_kb,
system_logs_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): async def sys_logs(msg: Message):
if is_admin_msg(msg): if is_admin_msg(msg):
await msg.answer("📄 System logs", reply_markup=system_logs_kb) 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)

View File

@@ -3,7 +3,7 @@ from aiogram import F
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from app import dp, ADMIN_ID from app import dp, ADMIN_ID
from auth import is_admin_msg 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 services.processes import get_top_processes, search_processes, terminate_process
from state import PROC_SEARCH_PENDING, PROC_KILL_PENDING from state import PROC_SEARCH_PENDING, PROC_KILL_PENDING
@@ -61,11 +61,11 @@ async def proc_actions(cb: CallbackQuery):
return return
if action == "search": if action == "search":
PROC_SEARCH_PENDING[cb.from_user.id] = {} 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 return
if action == "kill": if action == "kill":
PROC_KILL_PENDING[cb.from_user.id] = {} 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 return
@@ -76,12 +76,12 @@ async def proc_search(msg: Message):
PROC_SEARCH_PENDING.pop(msg.from_user.id, None) PROC_SEARCH_PENDING.pop(msg.from_user.id, None)
query = (msg.text or "").strip() query = (msg.text or "").strip()
if not query: 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 return
rows = await asyncio.to_thread(search_processes, query) rows = await asyncio.to_thread(search_processes, query)
if not rows: 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 return
lines = ["PID NAME CMD"] lines = ["PID NAME CMD"]
@@ -92,7 +92,7 @@ async def proc_search(msg: Message):
lines.append(f"{row['pid']:<5} {row['name']:<6} {cmd}") lines.append(f"{row['pid']:<5} {row['name']:<6} {cmd}")
text = "🔍 **Search results**\n```\n" + "\n".join(lines) + "\n```" 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)) @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: try:
pid = int(raw) pid = int(raw)
except ValueError: except ValueError:
await msg.answer("⚠️ Invalid PID", reply_markup=system_logs_kb) await msg.answer("⚠️ Invalid PID", reply_markup=system_logs_tools_kb)
return return
kb = InlineKeyboardMarkup( kb = InlineKeyboardMarkup(
@@ -138,4 +138,4 @@ async def proc_kill_confirm(cb: CallbackQuery):
return return
await cb.answer() await cb.answer()
result = await asyncio.to_thread(terminate_process, pid) 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)

View File

@@ -4,7 +4,13 @@ from aiogram import F
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from app import dp, cfg from app import dp, cfg
from auth import is_admin_msg 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 system_checks import security, disks, hardware, list_disks, smart_last_test
from services.http_checks import get_url_checks, check_url from services.http_checks import get_url_checks, check_url
from services.queue import enqueue from services.queue import enqueue
@@ -39,10 +45,10 @@ async def urls(msg: Message):
checks = list(get_url_checks(cfg)) checks = list(get_url_checks(cfg))
if not checks: if not checks:
await msg.answer("⚠️ Нет URL для проверки", reply_markup=system_logs_kb) await msg.answer("⚠️ Нет URL для проверки", reply_markup=system_logs_security_kb)
return return
await msg.answer("⏳ Проверяю URL…", reply_markup=system_logs_kb) await msg.answer("⏳ Проверяю URL…", reply_markup=system_logs_security_kb)
async def worker(): async def worker():
tasks = [asyncio.to_thread(check_url, url) for _, url in checks] tasks = [asyncio.to_thread(check_url, url) for _, url in checks]
@@ -58,7 +64,7 @@ async def urls(msg: Message):
reason = err or "error" reason = err or "error"
lines.append(f"🔴 {alias}: {reason} ({ms}ms)") 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()) asyncio.create_task(worker())
@@ -68,7 +74,7 @@ async def ssh_log(msg: Message):
if not is_admin_msg(msg): if not is_admin_msg(msg):
return 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(): async def worker():
raw_lines: list[str] = [] raw_lines: list[str] = []
@@ -98,13 +104,13 @@ async def ssh_log(msg: Message):
raw_lines.append(line) raw_lines.append(line)
if not raw_lines: 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 return
recent = raw_lines[-30:] recent = raw_lines[-30:]
pretty = [_format_ssh_line(line) for line in recent] pretty = [_format_ssh_line(line) for line in recent]
text = "🔑 SSH logins (today)\n```\n" + "\n".join(pretty) + "\n```" 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()) asyncio.create_task(worker())
@@ -194,16 +200,16 @@ async def audit_log(msg: Message):
return return
text = read_audit_tail(cfg, limit=200) text = read_audit_tail(cfg, limit=200)
if text.startswith("⚠️") or text.startswith(""): 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: 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") @dp.message(F.text == "🌍 External")
async def external_checks(msg: Message): async def external_checks(msg: Message):
if not is_admin_msg(msg): if not is_admin_msg(msg):
return 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") @dp.message(F.text == "📣 Incidents")
@@ -212,7 +218,7 @@ async def incidents(msg: Message):
return return
path = incidents_path(cfg) path = incidents_path(cfg)
if not os.path.exists(path): 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 return
last_24h = read_recent(cfg, hours=24, limit=500) last_24h = read_recent(cfg, hours=24, limit=500)
last_7d = read_recent(cfg, hours=24 * 7, limit=1000) last_7d = read_recent(cfg, hours=24 * 7, limit=1000)
@@ -226,7 +232,7 @@ async def incidents(msg: Message):
"Recent (24h):\n" "Recent (24h):\n"
f"```\n{body}\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") @dp.message(F.text == "🔒 SSL")
@@ -234,7 +240,7 @@ async def ssl_certs(msg: Message):
if not is_admin_msg(msg): if not is_admin_msg(msg):
return 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(): async def worker():
try: try:
@@ -242,7 +248,7 @@ async def ssl_certs(msg: Message):
text = format_certificates(certs) text = format_certificates(certs)
except Exception as e: except Exception as e:
text = f"⚠️ NPMplus error: {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()) asyncio.create_task(worker())
@@ -252,14 +258,14 @@ async def gitea_health(msg: Message):
if not is_admin_msg(msg): if not is_admin_msg(msg):
return 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(): async def worker():
try: try:
text = await asyncio.to_thread(get_gitea_health, cfg) text = await asyncio.to_thread(get_gitea_health, cfg)
except Exception as e: except Exception as e:
text = f"⚠️ Gitea error: {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()) asyncio.create_task(worker())
@@ -269,17 +275,17 @@ async def npmplus_hosts(msg: Message):
if not is_admin_msg(msg): if not is_admin_msg(msg):
return 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(): async def worker():
try: try:
hosts = await asyncio.to_thread(list_proxy_hosts, cfg) hosts = await asyncio.to_thread(list_proxy_hosts, cfg)
except Exception as e: 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 return
if not hosts: 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 return
lines = ["🧩 NPMplus proxy hosts\n"] lines = ["🧩 NPMplus proxy hosts\n"]
@@ -461,9 +467,9 @@ async def npmplus_toggle(cb: CallbackQuery):
await cb.answer("Working…") await cb.answer("Working…")
ok, info = await asyncio.to_thread(set_proxy_host, cfg, host_id, enable) ok, info = await asyncio.to_thread(set_proxy_host, cfg, host_id, enable)
if ok: if ok:
await cb.message.answer("✅ Updated", reply_markup=system_logs_kb) await cb.message.answer("✅ Updated", reply_markup=system_logs_integrations_kb)
else: 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)) @dp.message(F.text, F.func(lambda msg: msg.from_user and msg.from_user.id in REBOOT_PENDING))

View File

@@ -81,15 +81,46 @@ system_ops_kb = ReplyKeyboardMarkup(
system_logs_kb = ReplyKeyboardMarkup( system_logs_kb = ReplyKeyboardMarkup(
keyboard=[ keyboard=[
[KeyboardButton(text="🧾 Audit"), KeyboardButton(text="📣 Incidents")], [KeyboardButton(text="🧾 Audit/Incidents"), KeyboardButton(text="🔒 Security")],
[KeyboardButton(text="🧰 Processes"), KeyboardButton(text="🔒 SSL")], [KeyboardButton(text="🧩 Integrations"), KeyboardButton(text="🧰 Processes")],
[KeyboardButton(text="🔑 SSH log"), KeyboardButton(text="🧩 NPMplus"), KeyboardButton(text="🍵 Gitea")],
[KeyboardButton(text="🌍 External"), KeyboardButton(text="🌐 URLs")],
[KeyboardButton(text="⬅️ System")], [KeyboardButton(text="⬅️ System")],
], ],
resize_keyboard=True, 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: def docker_inline_kb(action: str) -> InlineKeyboardMarkup:
rows = [] rows = []