feat: readable report and host detail layout in web UI (0.9.8)

Field grid for event/host cards, human-readable daily report labels,
and normalized plain-text report bodies.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 10:25:53 +10:00
parent fed21c536b
commit befaf86bf4
8 changed files with 203 additions and 139 deletions
+32 -6
View File
@@ -49,10 +49,27 @@ export interface NormalizedReportStat {
value: string | number;
}
export const DAILY_REPORT_TYPE_LABELS: Record<string, string> = {
"report.daily.ssh": "Отчёты ssh клиентов",
"report.daily.rdp": "Отчёты RDP клиентов",
};
export function dailyReportTypeLabel(type: string): string {
return DAILY_REPORT_TYPE_LABELS[type] ?? type;
}
export function isDailyReportType(type: string): boolean {
return type === "report.daily.ssh" || type === "report.daily.rdp";
}
/** Текст отчёта: literal \\n и <br> из legacy-агентов → нормальные переносы. */
export function normalizeReportPlainText(body: string): string {
let text = body.replace(/\r\n/g, "\n");
text = text.replace(/\\n/g, "\n");
text = text.replace(/<br\s*\/?>/gi, "\n");
return text;
}
export function reportPlatform(type: string): "ssh" | "windows" {
return type === "report.daily.rdp" ? "windows" : "ssh";
}
@@ -66,20 +83,29 @@ export function normalizeReportStats(
const platform = stats.platform ?? reportPlatform(type);
const out: DailyReportStats = { ...stats, platform };
const asInt = (v: unknown, fallback = 0): number => {
if (typeof v === "number" && Number.isFinite(v)) return v;
if (typeof v === "string") {
const n = parseInt(v.replace(/[^\d-]/g, ""), 10);
if (Number.isFinite(n)) return n;
}
return fallback;
};
if (platform === "ssh") {
out.successful_logins = stats.successful_logins ?? stats.successful_ssh ?? 0;
out.failed_logins = stats.failed_logins ?? stats.failed_ssh ?? 0;
out.sudo_commands = stats.sudo_commands ?? 0;
out.successful_logins = asInt(stats.successful_logins, asInt(stats.successful_ssh));
out.failed_logins = asInt(stats.failed_logins, asInt(stats.failed_ssh));
out.sudo_commands = asInt(stats.sudo_commands);
} else {
out.successful_logins = stats.successful_logins ?? stats.rdp_success ?? 0;
out.failed_logins = stats.failed_logins ?? stats.rdp_failed ?? 0;
out.successful_logins = asInt(stats.successful_logins, asInt(stats.rdp_success));
out.failed_logins = asInt(stats.failed_logins, asInt(stats.rdp_failed));
if (!out.active_users?.length && stats.unique_users?.length) {
out.active_users = stats.unique_users.map((u) =>
u.startsWith("👤") ? u : `👤 ${u}`,
);
}
}
out.active_bans = stats.active_bans ?? 0;
out.active_bans = asInt(stats.active_bans);
return out;
}