feat: reports page with formatted daily report cards

This commit is contained in:
2026-05-27 14:15:15 +10:00
parent 04301ab359
commit 61c9f9b673
10 changed files with 453 additions and 49 deletions
+49
View File
@@ -0,0 +1,49 @@
/** Безопасный вывод HTML из агента (только базовая разметка). */
export function sanitizeAgentHtml(html: string): string {
const allowed = /^(b|strong|i|em|u|code|pre|br|p|div|span|ul|ol|li|hr)$/i;
return html.replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, (tag, name: string) => {
if (allowed.test(name)) {
if (tag.toLowerCase().startsWith("</")) {
return `</${name.toLowerCase()}>`;
}
if (name.toLowerCase() === "br") {
return "<br>";
}
return `<${name.toLowerCase()}>`;
}
return "";
});
}
export interface DailyReportStats {
successful_ssh?: number;
failed_ssh?: number;
sudo_commands?: number;
active_bans?: number;
active_sessions?: number;
active_sessions_rdp?: number;
unique_users?: string[];
}
export function isDailyReportType(type: string): boolean {
return type === "report.daily.ssh" || type === "report.daily.rdp";
}
export function reportStatsFromDetails(details: Record<string, unknown> | null): DailyReportStats | null {
if (!details || typeof details.stats !== "object" || details.stats === null) {
return null;
}
return details.stats as DailyReportStats;
}
export function reportHtmlFromDetails(details: Record<string, unknown> | null): string | null {
if (!details) return null;
const html = details.report_html;
return typeof html === "string" && html.trim() ? html : null;
}
export function reportBodyFromDetails(details: Record<string, unknown> | null): string | null {
if (!details) return null;
const body = details.report_body;
return typeof body === "string" && body.trim() ? body : null;
}