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:
@@ -0,0 +1,85 @@
|
||||
<!--
|
||||
Card displaying a single stale container with cleanup action.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { StaleContainer } from '$lib/types';
|
||||
import { IconClock, IconTag, IconTrash } from '$lib/components/icons';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
interface Props {
|
||||
container: StaleContainer;
|
||||
cleaning?: boolean;
|
||||
oncleanup: (id: string) => void;
|
||||
}
|
||||
|
||||
const { container, cleaning = false, oncleanup }: Props = $props();
|
||||
|
||||
const badgeClass = $derived(
|
||||
container.days_stale >= 14
|
||||
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
|
||||
: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'
|
||||
);
|
||||
|
||||
const displayName = $derived(
|
||||
`${container.project_name}-${container.stage_name}-${container.image_tag}`
|
||||
);
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
if (!iso) return '-';
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="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)]">
|
||||
<!-- Header row -->
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="truncate text-sm font-semibold text-[var(--text-primary)]" title={displayName}>
|
||||
{displayName}
|
||||
</h3>
|
||||
<div class="mt-1.5 flex flex-wrap items-center gap-2">
|
||||
<span class="inline-flex items-center gap-1 rounded-md bg-[var(--color-brand-50)] px-2 py-0.5 text-xs font-medium text-[var(--color-brand-600)]">
|
||||
{container.project_name}
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 rounded-md bg-[var(--surface-card-hover)] px-2 py-0.5 text-xs font-medium text-[var(--text-secondary)]">
|
||||
{container.stage_name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Days stale badge -->
|
||||
<span class="inline-flex flex-shrink-0 items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold {badgeClass}">
|
||||
<IconClock size={12} />
|
||||
{container.days_stale} {$t('stale.daysStale')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Details -->
|
||||
<div class="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-[var(--text-secondary)]">
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<IconTag size={12} />
|
||||
{container.image_tag}
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<IconClock size={12} />
|
||||
{$t('stale.lastAlive')}: {formatDate(container.last_alive_at)}
|
||||
</span>
|
||||
<span class="rounded bg-[var(--surface-card-hover)] px-1.5 py-0.5 font-mono text-[10px]">
|
||||
{container.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Cleanup button -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
disabled={cleaning}
|
||||
onclick={() => oncleanup(container.id)}
|
||||
class="inline-flex items-center gap-1.5 rounded-lg border border-[var(--color-danger)] px-3 py-1.5 text-xs font-medium text-[var(--color-danger)] transition-colors hover:bg-[var(--color-danger-light)] disabled:opacity-50 active:animate-press"
|
||||
>
|
||||
<IconTrash size={14} />
|
||||
{$t('stale.cleanup')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user