feat: Dashboard MVP with summary API and UI
Add GET /api/v1/dashboards/summary (24h events, open problems, hosts, severity breakdown, recent events) and Vue /dashboard page.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<h1>Dashboard</h1>
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
<p v-else-if="loading">Загрузка…</p>
|
||||
<template v-else-if="data">
|
||||
<div class="dashboard-cards">
|
||||
<div class="dash-card">
|
||||
<div class="dash-value">{{ data.events_last_24h }}</div>
|
||||
<div class="dash-label">Событий за 24 ч</div>
|
||||
<RouterLink to="/events">Все события →</RouterLink>
|
||||
</div>
|
||||
<div class="dash-card">
|
||||
<div class="dash-value">{{ data.problems_open }}</div>
|
||||
<div class="dash-label">Открытых проблем</div>
|
||||
<RouterLink to="/problems">Проблемы →</RouterLink>
|
||||
</div>
|
||||
<div class="dash-card">
|
||||
<div class="dash-value">{{ data.hosts_total }}</div>
|
||||
<div class="dash-label">Хостов</div>
|
||||
<RouterLink to="/hosts">Хосты →</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Severity за 24 ч</h2>
|
||||
<ul class="severity-list">
|
||||
<li v-for="(count, sev) in data.severity_24h" :key="sev">
|
||||
<span :class="'sev-' + sev">{{ sev }}</span>: {{ count }}
|
||||
</li>
|
||||
<li v-if="!Object.keys(data.severity_24h).length">Нет событий</li>
|
||||
</ul>
|
||||
|
||||
<h2>Последние события</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Время</th>
|
||||
<th>Хост</th>
|
||||
<th>Severity</th>
|
||||
<th>Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="e in data.recent_events" :key="e.id">
|
||||
<td>
|
||||
<RouterLink :to="`/events/${e.id}`">{{ e.id }}</RouterLink>
|
||||
</td>
|
||||
<td>{{ formatDt(e.occurred_at) }}</td>
|
||||
<td>{{ e.hostname }}</td>
|
||||
<td :class="'sev-' + e.severity">{{ e.severity }}</td>
|
||||
<td>{{ e.title }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="filters" style="margin-top: 1rem">
|
||||
<button type="button" class="secondary" @click="load">Обновить</button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { apiFetch, type DashboardSummary } from "../api";
|
||||
|
||||
const data = ref<DashboardSummary | null>(null);
|
||||
const loading = ref(false);
|
||||
const error = ref("");
|
||||
|
||||
function formatDt(iso: string) {
|
||||
return new Date(iso).toLocaleString("ru-RU");
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
data.value = await apiFetch<DashboardSummary>("/api/v1/dashboards/summary");
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : String(e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => load());
|
||||
</script>
|
||||
Reference in New Issue
Block a user