chore(home): mirror from kalinamall (9883e6a) with papatramp URLs
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
/** Безопасный вывод HTML из агента (DOMPurify, белый список тегов). */
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
const ALLOWED_REPORT_TAGS = [
|
||||
"b",
|
||||
"strong",
|
||||
"i",
|
||||
"em",
|
||||
"u",
|
||||
"code",
|
||||
"pre",
|
||||
"br",
|
||||
"p",
|
||||
"div",
|
||||
"span",
|
||||
"ul",
|
||||
"ol",
|
||||
"li",
|
||||
"hr",
|
||||
];
|
||||
|
||||
export function sanitizeAgentHtml(html: string): string {
|
||||
return DOMPurify.sanitize(html, {
|
||||
ALLOWED_TAGS: ALLOWED_REPORT_TAGS,
|
||||
ALLOWED_ATTR: [],
|
||||
});
|
||||
}
|
||||
export interface DailyReportStats {
|
||||
platform?: "ssh" | "windows";
|
||||
successful_logins?: number;
|
||||
failed_logins?: number;
|
||||
sudo_commands?: number;
|
||||
active_bans?: number;
|
||||
top_failed_ips?: string[];
|
||||
active_users?: string[];
|
||||
/** legacy */
|
||||
successful_ssh?: number;
|
||||
failed_ssh?: number;
|
||||
rdp_success?: number;
|
||||
rdp_failed?: number;
|
||||
unique_users?: string[];
|
||||
active_sessions?: number;
|
||||
active_sessions_rdp?: number;
|
||||
}
|
||||
|
||||
export interface NormalizedReportStat {
|
||||
key: string;
|
||||
label: string;
|
||||
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";
|
||||
}
|
||||
|
||||
/** Приводит stats агента/SAC к единым полям для карточки. */
|
||||
export function normalizeReportStats(
|
||||
stats: DailyReportStats | null,
|
||||
type: string,
|
||||
): DailyReportStats | null {
|
||||
if (!stats) return null;
|
||||
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 = 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 = 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 = asInt(stats.active_bans);
|
||||
return out;
|
||||
}
|
||||
|
||||
export function reportStatsFromDetails(
|
||||
details: Record<string, unknown> | null,
|
||||
type = "report.daily.ssh",
|
||||
): DailyReportStats | null {
|
||||
if (!details || typeof details.stats !== "object" || details.stats === null) {
|
||||
return null;
|
||||
}
|
||||
return normalizeReportStats(details.stats as DailyReportStats, type);
|
||||
}
|
||||
|
||||
export function reportStatEntries(
|
||||
stats: DailyReportStats | null,
|
||||
type: string,
|
||||
): NormalizedReportStat[] {
|
||||
const s = normalizeReportStats(stats, type);
|
||||
if (!s) return [];
|
||||
const platform = s.platform ?? reportPlatform(type);
|
||||
const entries: NormalizedReportStat[] = [
|
||||
{
|
||||
key: "successful",
|
||||
label: platform === "ssh" ? "Успешных SSH" : "Успешных RDP",
|
||||
value: s.successful_logins ?? 0,
|
||||
},
|
||||
{
|
||||
key: "failed",
|
||||
label: platform === "ssh" ? "Неудачных SSH" : "Неудачных RDP",
|
||||
value: s.failed_logins ?? 0,
|
||||
},
|
||||
{ key: "bans", label: "Активных банов", value: s.active_bans ?? 0 },
|
||||
];
|
||||
if (platform === "ssh") {
|
||||
entries.splice(2, 0, {
|
||||
key: "sudo",
|
||||
label: "Команд sudo",
|
||||
value: s.sudo_commands ?? 0,
|
||||
});
|
||||
}
|
||||
const users = s.active_users ?? [];
|
||||
if (users.length) {
|
||||
entries.push({
|
||||
key: "active_users",
|
||||
label: "Активные пользователи",
|
||||
value: users.length,
|
||||
});
|
||||
} else if (typeof s.active_sessions === "number" && s.active_sessions > 0) {
|
||||
entries.push({
|
||||
key: "active_sessions",
|
||||
label: "Сессий (SSH)",
|
||||
value: s.active_sessions,
|
||||
});
|
||||
} else if (typeof s.active_sessions_rdp === "number" && s.active_sessions_rdp > 0) {
|
||||
entries.push({
|
||||
key: "active_sessions_rdp",
|
||||
label: "Сессий (RDP)",
|
||||
value: s.active_sessions_rdp,
|
||||
});
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function reportActiveUserLines(
|
||||
stats: DailyReportStats | null,
|
||||
type: string,
|
||||
): string[] {
|
||||
const s = normalizeReportStats(stats, type);
|
||||
if (!s?.active_users?.length) return [];
|
||||
return s.active_users;
|
||||
}
|
||||
Reference in New Issue
Block a user