From 6cde9531cb6c944e8b258e897d6c9a18a7ad05ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= <78488229+viktor138irk@users.noreply.github.com> Date: Thu, 7 May 2026 03:50:49 +0900 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BF=D0=B0=D1=82=D1=87=20=D1=86=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B6=D1=83=D1=80=D0=BD=D0=B0=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/patch_logs_center.py | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 scripts/patch_logs_center.py diff --git a/scripts/patch_logs_center.py b/scripts/patch_logs_center.py new file mode 100644 index 0000000..65dfca5 --- /dev/null +++ b/scripts/patch_logs_center.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys + +app_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('/opt/virtuality/web/app.py') +if not app_path.exists(): + raise SystemExit(f'app.py not found: {app_path}') + +text = app_path.read_text() +changed = [] + +helpers = r''' + +LOG_SOURCES = { + "web": {"title": "Virtuality Web", "kind": "journal", "unit": "virtuality-web.service"}, + "update": {"title": "Update Center", "kind": "file", "path": "/var/log/virtuality/update.log"}, + "install": {"title": "Последняя установка", "kind": "glob", "pattern": "/var/log/virtuality/install_web_panel_*.log"}, + "operations": {"title": "Операции", "kind": "operations"}, + "auto-update": {"title": "Auto Update", "kind": "journal", "unit": "virtuality-auto-update.service"}, + "libvirtd": {"title": "libvirtd", "kind": "journal", "unit": "libvirtd.service"}, + "virtlogd": {"title": "virtlogd", "kind": "journal", "unit": "virtlogd.service"}, + "telegram": {"title": "Telegram notifier", "kind": "file", "path": "/var/log/virtuality/telegram_version_bot.log"}, +} + + +def read_log_source(source: str, lines: int = 220) -> dict[str, Any]: + key = source if source in LOG_SOURCES else "web" + cfg = LOG_SOURCES[key] + lines = max(20, min(int(lines or 220), 2000)) + content = "" + path = "" + cmd = "" + if cfg["kind"] == "journal": + unit = cfg["unit"] + cmd = f"journalctl -u {unit} -n {lines} --no-pager" + result = run_cmd(["journalctl", "-u", unit, "-n", str(lines), "--no-pager"], timeout=15) + content = result["stdout"] or result["stderr"] or "Лог пуст или journalctl недоступен" + elif cfg["kind"] == "file": + path = cfg["path"] + cmd = f"tail -n {lines} {path}" + content = tail_text(Path(path), max_lines=lines) or "Файл лога пока пуст или не найден" + elif cfg["kind"] == "glob": + pattern = cfg["pattern"] + files = sorted(Path('/').glob(pattern.lstrip('/')), key=lambda item: item.stat().st_mtime if item.exists() else 0, reverse=True) + if files: + path = str(files[0]) + cmd = f"tail -n {lines} {path}" + content = tail_text(files[0], max_lines=lines) or "Файл лога пуст" + else: + cmd = f"ls {pattern}" + content = "Логи установки ещё не найдены" + elif cfg["kind"] == "operations": + cmd = f"tail -n {lines} /var/log/virtuality/operations/*.log" + parts = [] + ensure_operations_dir() + for item in sorted(OPERATIONS_DIR.glob('*.log'), key=lambda p: p.stat().st_mtime, reverse=True)[:10]: + parts.append(f"===== {item.name} =====\n" + tail_text(item, max_lines=max(20, lines // 5))) + content = "\n\n".join(parts) or "Журналы операций пока пусты" + return {"key": key, "title": cfg["title"], "content": content, "path": path, "cmd": cmd, "lines": lines} +''' + +if 'LOG_SOURCES = {' not in text: + marker = '\n\ndef parse_virsh_list() -> list[dict[str, str]]:' + if marker not in text: + raise SystemExit('parse_virsh_list marker not found') + text = text.replace(marker, helpers + marker, 1) + changed.append('log center helpers added') +else: + changed.append('log center helpers already present') + +route = r''' + +@app.get("/logs", response_class=HTMLResponse) +def logs_page(request: Request, source: str = "web", lines: int = 220): + auth_redirect = require_auth(request) + if auth_redirect: + return auth_redirect + selected = read_log_source(source, lines) + return templates.TemplateResponse("logs.html", { + "request": request, + "app_name": APP_NAME, + "user": AUTH_USER, + "sources": LOG_SOURCES, + "selected": selected, + }) + + +@app.get("/api/logs") +def api_logs(request: Request, source: str = "web", lines: int = 220): + if not get_current_user(request): + return JSONResponse({"ok": False, "error": "Unauthorized"}, status_code=401) + return {"ok": True, "log": read_log_source(source, lines)} +''' + +if '@app.get("/logs"' not in text: + marker = '\n\n@app.get("/vm/create", response_class=HTMLResponse)' + if marker not in text: + marker = '\n\n@app.get("/api/operations")' + if marker not in text: + raise SystemExit('route insert marker not found') + text = text.replace(marker, route + marker, 1) + changed.append('logs routes added') +else: + changed.append('logs routes already present') + +app_path.write_text(text) +print('logs center patch applied:') +for item in changed: + print(f'- {item}')