feat: improve SAC UI layout, reports state, and hosts sorting
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,86 +1,177 @@
|
||||
<template>
|
||||
|
||||
<p>
|
||||
|
||||
<RouterLink v-if="fromReports" to="/reports">← Отчёты</RouterLink>
|
||||
|
||||
<RouterLink v-else to="/events">← События</RouterLink>
|
||||
|
||||
</p>
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
|
||||
<p v-else-if="loading">Загрузка…</p>
|
||||
|
||||
<template v-else-if="event">
|
||||
|
||||
<h1>{{ event.title }}</h1>
|
||||
|
||||
<div class="card report-meta">
|
||||
|
||||
<p>
|
||||
|
||||
<strong>ID:</strong> {{ event.id }} · <strong>event_id:</strong> {{ event.event_id }}
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
<strong>Хост:</strong>
|
||||
|
||||
<RouterLink :to="{ path: '/reports', query: { hostname: event.hostname } }">
|
||||
|
||||
{{ event.hostname }}
|
||||
|
||||
</RouterLink>
|
||||
|
||||
· <strong>Severity:</strong>
|
||||
|
||||
<span :class="'sev-' + event.severity">{{ event.severity }}</span>
|
||||
|
||||
</p>
|
||||
|
||||
<p><strong>Type:</strong> <code>{{ event.type }}</code></p>
|
||||
|
||||
<p><strong>Occurred:</strong> {{ formatDt(event.occurred_at) }}</p>
|
||||
|
||||
<p v-if="!isReport"><strong>Summary:</strong> {{ event.summary }}</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<template v-if="isReport">
|
||||
|
||||
<h2>Отчёт</h2>
|
||||
|
||||
<p class="report-summary-line">{{ event.summary }}</p>
|
||||
|
||||
<ReportBodyCard :type="event.type" :summary="event.summary" :details="event.details" />
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<details v-if="event.details && !isReport" class="raw-details">
|
||||
|
||||
<summary>details (JSON)</summary>
|
||||
|
||||
<pre>{{ JSON.stringify(event.details, null, 2) }}</pre>
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
<details v-if="isReport && event.details" class="raw-details">
|
||||
|
||||
<summary>details (JSON)</summary>
|
||||
|
||||
<pre>{{ JSON.stringify(event.details, null, 2) }}</pre>
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
<details class="raw-details">
|
||||
|
||||
<template>
|
||||
|
||||
<p>
|
||||
|
||||
<RouterLink v-if="fromReports" :to="{ path: '/reports', query: backToReportsQuery }">← Отчёты</RouterLink>
|
||||
|
||||
<RouterLink v-else to="/events">← События</RouterLink>
|
||||
|
||||
</p>
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
|
||||
<p v-else-if="loading">Загрузка…</p>
|
||||
|
||||
<template v-else-if="event">
|
||||
|
||||
<h1>{{ event.title }}</h1>
|
||||
|
||||
<div class="card report-meta">
|
||||
|
||||
<p>
|
||||
|
||||
<strong>ID:</strong> {{ event.id }} · <strong>event_id:</strong> {{ event.event_id }}
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
<strong>Хост:</strong>
|
||||
<RouterLink :to="{ path: '/reports', query: { hostname: event.hostname } }">
|
||||
|
||||
{{ event.hostname }}
|
||||
|
||||
</RouterLink>
|
||||
|
||||
· <strong>Severity:</strong>
|
||||
|
||||
<span :class="'sev-' + event.severity">{{ event.severity }}</span>
|
||||
|
||||
</p>
|
||||
|
||||
<p><strong>Type:</strong> <code>{{ event.type }}</code></p>
|
||||
|
||||
<p><strong>Occurred:</strong> {{ formatDt(event.occurred_at) }}</p>
|
||||
|
||||
<p v-if="!isReport"><strong>Summary:</strong> {{ event.summary }}</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<template v-if="isReport">
|
||||
|
||||
<h2>Отчёт</h2>
|
||||
|
||||
<p class="report-summary-line">{{ event.summary }}</p>
|
||||
|
||||
<ReportBodyCard :type="event.type" :summary="event.summary" :details="event.details" />
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<details v-if="event.details && !isReport" class="raw-details">
|
||||
|
||||
<summary>details (JSON)</summary>
|
||||
|
||||
<pre>{{ JSON.stringify(event.details, null, 2) }}</pre>
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
<details v-if="isReport && event.details" class="raw-details">
|
||||
|
||||
<summary>details (JSON)</summary>
|
||||
|
||||
<pre>{{ JSON.stringify(event.details, null, 2) }}</pre>
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
<details class="raw-details">
|
||||
|
||||
<summary>payload (JSON)</summary>
|
||||
|
||||
<pre>{{ JSON.stringify(event.payload, null, 2) }}</pre>
|
||||
|
||||
</details>
|
||||
|
||||
</template>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import { apiFetch, type EventDetail } from "../api";
|
||||
|
||||
import ReportBodyCard from "../components/ReportBodyCard.vue";
|
||||
|
||||
import { isDailyReportType } from "../utils/reportDisplay";
|
||||
|
||||
|
||||
|
||||
const props = defineProps<{ id: string }>();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
|
||||
|
||||
const event = ref<EventDetail | null>(null);
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const error = ref("");
|
||||
|
||||
|
||||
|
||||
const fromReports = computed(() => route.query.from === "reports");
|
||||
|
||||
const isReport = computed(() => (event.value ? isDailyReportType(event.value.type) : false));
|
||||
|
||||
const backToReportsQuery = computed(() => {
|
||||
const q: Record<string, string> = {};
|
||||
if (typeof route.query.type === "string" && route.query.type) q.type = route.query.type;
|
||||
if (typeof route.query.hostname === "string" && route.query.hostname) q.hostname = route.query.hostname;
|
||||
return q;
|
||||
});
|
||||
|
||||
|
||||
|
||||
function formatDt(iso: string) {
|
||||
|
||||
return new Date(iso).toLocaleString("ru-RU");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function load() {
|
||||
|
||||
loading.value = true;
|
||||
|
||||
error.value = "";
|
||||
|
||||
try {
|
||||
|
||||
event.value = await apiFetch<EventDetail>(`/api/v1/events/${props.id}`);
|
||||
|
||||
} catch (e) {
|
||||
|
||||
error.value = e instanceof Error ? e.message : "Не найдено";
|
||||
|
||||
} finally {
|
||||
|
||||
loading.value = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(load);
|
||||
|
||||
watch(() => props.id, load);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user