diff --git a/backend/app/services/daily_report_format.py b/backend/app/services/daily_report_format.py index b409802..37b3928 100644 --- a/backend/app/services/daily_report_format.py +++ b/backend/app/services/daily_report_format.py @@ -16,6 +16,8 @@ SSH_BAN_TYPES = frozenset({"ssh.ip.banned"}) _SERVER_LINE_RE = re.compile(r"(?m)^🖥️\s*Сервер\s*:") _ACTIVE_USERS_HEADER_RE = re.compile(r"^👥\s*АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ") +_AGENT_VERSION_LINE_RE = re.compile(r"(?m)^Agent version\s+", re.IGNORECASE) +_DAILY_REPORT_TITLE_MARKER = "ЕЖЕДНЕВНЫЙ ОТЧЕТ" _SECTION_HEADER_RE = re.compile(r"^[📈🧾👥🖥️🕐]") @@ -76,6 +78,39 @@ def normalize_active_users_list(users: list[Any] | None) -> list[str]: return out +def format_agent_version_line(version: str) -> str: + v = (version or "").strip() + if not v: + return "" + return f"Agent version {v}" + + +def ensure_agent_version_line(body: str, version: str | None) -> str: + line = format_agent_version_line(version or "") + if not line: + return body + if _AGENT_VERSION_LINE_RE.search(body): + return body + lines = body.replace("\r\n", "\n").split("\n") + for i, ln in enumerate(lines): + if _DAILY_REPORT_TITLE_MARKER in ln: + lines.insert(i + 1, line) + return "\n".join(lines) + return body + + +def _fix_active_users_header_count(header_line: str, user_count: int) -> str: + stripped = header_line.rstrip() + if _ACTIVE_USERS_COUNT_RE.match(stripped): + return re.sub( + r"(\👥\s*АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ\s*\()[^)]+(\))", + rf"\g<1>{user_count}\2", + stripped, + count=1, + ) + return stripped + + def ensure_server_line(body: str, host: Host | None) -> str: server = host_server_line(host) if not server or server == "unknown": @@ -85,7 +120,10 @@ def ensure_server_line(body: str, host: Host | None) -> str: lines = body.replace("\r\n", "\n").split("\n") for i, line in enumerate(lines): if "ЕЖЕДНЕВНЫЙ ОТЧЕТ" in line: - lines.insert(i + 1, f"🖥️ Сервер: {server}") + insert_at = i + 1 + if insert_at < len(lines) and _AGENT_VERSION_LINE_RE.match(lines[insert_at].strip()): + insert_at += 1 + lines.insert(insert_at, f"🖥️ Сервер: {server}") return "\n".join(lines) return f"🖥️ Сервер: {server}\n{body}" @@ -116,6 +154,7 @@ def normalize_active_users_in_body(body: str) -> str: user_lines.extend(split_active_user_tokens(cur)) i += 1 if user_lines: + out[-1] = _fix_active_users_header_count(out[-1], len(user_lines)) out.extend(user_lines) continue out.append(line) @@ -126,6 +165,8 @@ def normalize_active_users_in_body(body: str) -> str: def normalize_report_body(body: str, host: Host | None, platform: Platform) -> str: """Приводит текст отчёта (агент/SAC) к единому компактному виду.""" text = collapse_blank_lines(body.replace("\r\n", "\n")) + agent_version = host.product_version if host else None + text = ensure_agent_version_line(text, agent_version) text = ensure_server_line(text, host) text = normalize_active_users_in_body(text) return collapse_blank_lines(text) @@ -173,6 +214,11 @@ def build_report_body( time_str = when_local.strftime("%d.%m.%Y %H:%M:%S") top = list(stats.get("top_failed_ips") or [])[:5] active_users = normalize_active_users_list(list(stats.get("active_users") or [])) + agent_version = ( + str(stats.get("agent_version") or stats.get("product_version") or host.product_version or "") + ).strip() + if sac_generated and not agent_version: + agent_version = "sac" if platform == "ssh": title = "📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА" @@ -180,8 +226,10 @@ def build_report_body( fail = int(stats.get("failed_logins", stats.get("failed_ssh", 0))) sudo = int(stats.get("sudo_commands", 0)) bans = int(stats.get("active_bans", 0)) - lines = [ - title, + lines = [title] + if agent_version: + lines.append(format_agent_version_line(agent_version)) + lines.extend([ f"🖥️ Сервер: {server}", f"🕐 Время отчета: {time_str}", "", @@ -190,14 +238,16 @@ def build_report_body( f" ❌ Неудачных попыток SSH: {fail}", f" ⚠️ Команд через sudo: {sudo}", f" 🚫 Активных банов (ipset): {bans}", - ] + ]) else: title = "📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ МОНИТОРИНГА WINDOWS" ok = int(stats.get("successful_logins", stats.get("rdp_success", 0))) fail = int(stats.get("failed_logins", stats.get("rdp_failed", 0))) bans = int(stats.get("active_bans", 0)) - lines = [ - title, + lines = [title] + if agent_version: + lines.append(format_agent_version_line(agent_version)) + lines.extend([ f"🖥️ Сервер: {server}", f"🕐 Время отчета: {time_str}", "", @@ -205,7 +255,7 @@ def build_report_body( f" ✅ Успешных RDP подключений: {ok}", f" ❌ Неудачных попыток RDP: {fail}", f" 🚫 Активных банов: {bans}", - ] + ]) lines.append("") lines.extend(_section_top_ips(top)) diff --git a/backend/tests/test_daily_report_format.py b/backend/tests/test_daily_report_format.py index 9e8ec1a..4822c95 100644 --- a/backend/tests/test_daily_report_format.py +++ b/backend/tests/test_daily_report_format.py @@ -35,6 +35,7 @@ def test_build_report_body_ssh_matches_agent_layout(): ) body = build_report_body("ssh", host, stats, when, sac_generated=True) assert "ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА" in body + assert "Agent version sac" in body assert "🖥️ Сервер: HaProxy Kalina (192.168.160.117)" in body assert " 📈 СТАТИСТИКА" in body assert "Команд через sudo: 5" in body @@ -64,6 +65,7 @@ def test_build_report_body_windows_layout(): ) body = build_report_body("windows", host, stats, when, sac_generated=True) assert "ЕЖЕДНЕВНЫЙ ОТЧЕТ МОНИТОРИНГА WINDOWS" in body + assert "Agent version sac" in body assert "K6A-DC3 (10.0.0.10)" in body assert "Успешных RDP подключений: 1" in body assert " 👥 АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ (2)" in body @@ -80,7 +82,12 @@ def test_normalize_active_users_list_splits_combined_line(): def test_normalize_report_body_adds_server_and_collapses_blanks(): - host = Host(hostname="srv", display_name="Unimus Kalina", ipv4="192.168.160.17") + host = Host( + hostname="srv", + display_name="Unimus Kalina", + ipv4="192.168.160.17", + product_version="1.2.10-SAC", + ) raw = "\n".join( [ "📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ SSH МОНИТОРИНГА", @@ -92,12 +99,14 @@ def test_normalize_report_body_adds_server_and_collapses_blanks(): " 📈 СТАТИСТИКА ЗА ПОСЛЕДНИЕ 24 ЧАСА:", " ✅ Успешных SSH подключений: 0", "", - " 👥 АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ (2):", + " 👥 АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ (1):", " 👤 u1 👤 u2", ] ) body = normalize_report_body(raw, host, "ssh") + assert "Agent version 1.2.10-SAC" in body assert "🖥️ Сервер: Unimus Kalina (192.168.160.17)" in body assert "\n\n\n" not in body user_lines = [ln for ln in body.split("\n") if ln.strip().startswith("👤")] assert len(user_lines) == 2 + assert " 👥 АКТИВНЫЕ ПОЛЬЗОВАТЕЛИ (2)" in body