feat: delete host from UI with events and problems cleanup

DELETE /api/v1/hosts/{id} (JWT); host reappears on next agent ingest

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 16:52:05 +10:00
parent 2670c8cb46
commit 11195ae64f
5 changed files with 206 additions and 2 deletions
+15
View File
@@ -131,6 +131,21 @@ button.secondary {
border-color: #3d4f63;
}
button.danger {
background: transparent;
border-color: #b91c1c;
color: #f87171;
}
button.danger:hover:not(:disabled) {
background: #3f1515;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.filters {
display: flex;
flex-wrap: wrap;
+43
View File
@@ -25,6 +25,7 @@
<th><button type="button" class="th-sort" @click="setSort('last_daily_report_at')">Отчёт {{ sortMark('last_daily_report_at') }}</button></th>
<th><button type="button" class="th-sort" @click="setSort('last_seen_at')">Last seen {{ sortMark('last_seen_at') }}</button></th>
<th><button type="button" class="th-sort" @click="setSort('event_count')">Events {{ sortMark('event_count') }}</button></th>
<th>Действия</th>
</tr>
</thead>
<tbody>
@@ -48,6 +49,17 @@
</td>
<td>{{ formatDt(h.last_seen_at) }}</td>
<td>{{ h.event_count ?? 0 }}</td>
<td class="actions">
<button
type="button"
class="danger"
:disabled="deletingId === h.id"
title="Удалить хост и все его события"
@click="confirmDelete(h)"
>
Удалить
</button>
</td>
</tr>
</tbody>
</table>
@@ -74,6 +86,7 @@ type SortKey =
| "last_daily_report_at";
const sortBy = ref<SortKey>("last_seen_at");
const sortDir = ref<"asc" | "desc">("desc");
const deletingId = ref<number | null>(null);
function formatDt(iso: string) {
return new Date(iso).toLocaleString("ru-RU");
@@ -169,6 +182,36 @@ async function loadHosts() {
}
}
interface HostDeleteResponse {
status: string;
host_id: number;
hostname: string;
deleted_events: number;
deleted_problems: number;
}
async function confirmDelete(h: HostSummary) {
const label = h.display_name || h.hostname;
const n = h.event_count ?? 0;
const msg =
`Удалить хост «${label}» (${h.hostname})?\n\n` +
`Будут удалены все события (${n}) и проблемы этого хоста. ` +
`При новом ingest агент снова появится в списке.`;
if (!window.confirm(msg)) {
return;
}
deletingId.value = h.id;
error.value = "";
try {
await apiFetch<HostDeleteResponse>(`/api/v1/hosts/${h.id}`, { method: "DELETE" });
await loadHosts();
} catch (e) {
error.value = e instanceof Error ? e.message : "Не удалось удалить хост";
} finally {
deletingId.value = null;
}
}
onMounted(() => {
loadHosts();
});