feat(observability): phases 4-7 - complete frontend UI (big bang)
Add all frontend pages for observability & proxy management: - Proxy Viewer: /proxies with grouped view, filtering, health indicators - Proxy Creation: form with live validation, diagnostic hints, edit/delete - Stale Containers: /containers/stale with dashboard widget, cleanup actions - Event Log: /events with filters, pagination, real-time SSE streaming - Navigation: proxies and events links in sidebar - i18n: full EN/RU translations for all new features - Settings: stale threshold configuration
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { Project, Instance } from '$lib/types';
|
||||
import type { Project, Instance, StaleContainer } from '$lib/types';
|
||||
import * as api from '$lib/api';
|
||||
import ProjectCard from '$lib/components/ProjectCard.svelte';
|
||||
import SkeletonCard from '$lib/components/SkeletonCard.svelte';
|
||||
import EmptyState from '$lib/components/EmptyState.svelte';
|
||||
import { IconDeploy, IconBox, IconServer, IconAlert } from '$lib/components/icons';
|
||||
import { IconDeploy, IconBox, IconServer, IconAlert, IconClock } from '$lib/components/icons';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
let projects = $state<Project[]>([]);
|
||||
let instancesByProject = $state<Record<string, Instance[]>>({});
|
||||
let staleContainers = $state<StaleContainer[]>([]);
|
||||
let loading = $state(true);
|
||||
let error = $state('');
|
||||
|
||||
@@ -31,12 +32,16 @@
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(detailPromises);
|
||||
const [results, staleResult] = await Promise.all([
|
||||
Promise.all(detailPromises),
|
||||
api.fetchStaleContainers().catch(() => [] as StaleContainer[])
|
||||
]);
|
||||
const mapped: Record<string, Instance[]> = {};
|
||||
for (const r of results) {
|
||||
mapped[r.projectId] = r.instances;
|
||||
}
|
||||
instancesByProject = mapped;
|
||||
staleContainers = staleResult;
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : $t('dashboard.loadFailed');
|
||||
} finally {
|
||||
@@ -59,6 +64,7 @@
|
||||
.flat()
|
||||
.filter((i) => i.status === 'failed').length
|
||||
);
|
||||
const totalStale = $derived(staleContainers.length);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -79,7 +85,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Stats cards -->
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div class="flex items-center gap-4 rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)]">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-[var(--color-brand-50)] text-[var(--color-brand-600)]">
|
||||
<IconBox size={24} />
|
||||
@@ -107,6 +113,15 @@
|
||||
<p class="mt-0.5 text-2xl font-bold {totalFailed > 0 ? 'text-red-600' : 'text-[var(--text-primary)]'}">{totalFailed}</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/containers/stale" class="flex items-center gap-4 rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)] transition-colors hover:bg-[var(--surface-card-hover)]">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl {totalStale > 0 ? 'bg-amber-50 text-amber-600' : 'bg-gray-50 text-gray-400'}">
|
||||
<IconClock size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-[var(--text-secondary)]">{$t('dashboard.staleContainers')}</p>
|
||||
<p class="mt-0.5 text-2xl font-bold {totalStale > 0 ? 'text-amber-600' : 'text-[var(--text-primary)]'}">{totalStale}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Project cards -->
|
||||
|
||||
Reference in New Issue
Block a user