Files
security-alert-center/frontend/src/views/ReportsView.vue
T
PapaTramp befaf86bf4 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>
2026-06-11 10:25:53 +10:00

148 lines
4.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<h1>Отчёты агентов</h1>
<p class="report-intro">
Ежедневные сводки с агентов. Нажмите на строку, чтобы открыть полный отчёт с форматированием.
</p>
<div class="filters">
<input v-model="hostname" placeholder="hostname / имя сервера" @keyup.enter="load(1)" />
<select v-model="reportType" @change="load(1)">
<option value="report.daily.ssh">Отчёты ssh клиентов</option>
<option value="report.daily.rdp">Отчёты RDP клиентов</option>
</select>
<button type="button" @click="load(1)">Применить</button>
</div>
<p v-if="error" class="error">{{ error }}</p>
<p v-else-if="loading">Загрузка</p>
<template v-else>
<p>Всего: {{ data?.total ?? 0 }}</p>
<table class="reports-table">
<thead>
<tr>
<th>Время</th>
<th>Хост</th>
<th>Имя сервера</th>
<th>Тип</th>
<th>Сводка</th>
</tr>
</thead>
<tbody>
<tr
v-for="e in data?.items ?? []"
:key="e.id"
class="report-row"
tabindex="0"
@click="openReport(e.id)"
@keyup.enter="openReport(e.id)"
>
<td>{{ formatDt(e.occurred_at) }}</td>
<td>
<strong>{{ e.hostname }}</strong>
</td>
<td>{{ formatServerName(e.display_name) }}</td>
<td>{{ reportTypeLabel(e.type) }}</td>
<td class="report-summary-cell">{{ e.summary }}</td>
</tr>
</tbody>
</table>
<div class="filters" style="margin-top: 1rem">
<button type="button" class="secondary" :disabled="page <= 1" @click="load(page - 1)">Назад</button>
<span>Стр. {{ page }}</span>
<button
type="button"
class="secondary"
:disabled="!data || page * pageSize >= data.total"
@click="load(page + 1)"
>
Вперёд
</button>
</div>
</template>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { apiFetch, type EventListResponse } from "../api";
import { formatServerName } from "../utils/hostDisplay";
import { dailyReportTypeLabel } from "../utils/reportDisplay";
function reportTypeLabel(type: string) {
return dailyReportTypeLabel(type);
}
const route = useRoute();
const router = useRouter();
const data = ref<EventListResponse | null>(null);
const loading = ref(false);
const error = ref("");
const page = ref(1);
const pageSize = 30;
const hostname = ref("");
const reportType = ref("report.daily.ssh");
const REPORT_TYPE_KEY = "sac_reports_type";
function formatDt(iso: string) {
return new Date(iso).toLocaleString("ru-RU");
}
function openReport(id: number) {
const query: Record<string, string> = { from: "reports", type: reportType.value };
if (hostname.value.trim()) query.hostname = hostname.value.trim();
router.push({ path: `/events/${id}`, query });
}
async function load(p: number) {
page.value = p;
loading.value = true;
error.value = "";
try {
const params = new URLSearchParams({
page: String(page.value),
page_size: String(pageSize),
severity: "info",
});
params.set("type", reportType.value);
if (hostname.value.trim()) {
params.set("hostname", hostname.value.trim());
}
data.value = await apiFetch<EventListResponse>(`/api/v1/events?${params}`);
} catch (e) {
error.value = e instanceof Error ? e.message : "Ошибка загрузки";
} finally {
loading.value = false;
}
}
onMounted(() => {
const h = route.query.hostname;
if (typeof h === "string" && h) hostname.value = h;
const t = route.query.type;
if (typeof t === "string" && t) {
reportType.value = t;
} else {
const savedType = localStorage.getItem(REPORT_TYPE_KEY);
if (savedType === "report.daily.ssh" || savedType === "report.daily.rdp") {
reportType.value = savedType;
}
}
load(1);
});
watch(
() => route.query.hostname,
(h) => {
if (typeof h === "string") {
hostname.value = h;
load(1);
}
}
);
watch(reportType, (value) => {
localStorage.setItem(REPORT_TYPE_KEY, value);
});
</script>