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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user