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,94 @@
|
||||
<!--
|
||||
Phase 6: Edit Proxy page — loads a standalone proxy and wraps ProxyForm in edit mode.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import type { StandaloneProxy } from '$lib/types';
|
||||
import { getProxy } from '$lib/api';
|
||||
import { t } from '$lib/i18n';
|
||||
import ProxyForm from '$lib/components/ProxyForm.svelte';
|
||||
import { IconGlobe, IconLoader } from '$lib/components/icons';
|
||||
|
||||
let proxy: StandaloneProxy | null = $state(null);
|
||||
let loading = $state(true);
|
||||
let error = $state('');
|
||||
|
||||
const proxyId = $derived($page.params.id);
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
proxy = await getProxy(proxyId);
|
||||
} catch (err: unknown) {
|
||||
error = err instanceof Error ? err.message : 'Failed to load proxy';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
function handleSave(_proxy: StandaloneProxy): void {
|
||||
goto('/proxies');
|
||||
}
|
||||
|
||||
function handleDelete(_id: string): void {
|
||||
goto('/proxies');
|
||||
}
|
||||
|
||||
function handleCancel(): void {
|
||||
goto('/proxies');
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t('proxies.form.editTitle')} - {$t('app.name')}</title>
|
||||
</svelte:head>
|
||||
|
||||
<!-- Back link -->
|
||||
<div class="mb-6">
|
||||
<a
|
||||
href="/proxies"
|
||||
class="inline-flex items-center gap-1 text-sm text-[var(--text-secondary)] hover:text-[var(--color-brand-600)] transition-colors"
|
||||
>
|
||||
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19 12H5" /><path d="m12 19-7-7 7-7" />
|
||||
</svg>
|
||||
{$t('common.back')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="mb-6 flex items-center gap-3">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-[var(--color-brand-50)] text-[var(--color-brand-600)]">
|
||||
<IconGlobe size={22} />
|
||||
</div>
|
||||
<h1 class="text-xl font-bold text-[var(--text-primary)]">{$t('proxies.form.editTitle')}</h1>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center py-20">
|
||||
<IconLoader size={24} class="text-[var(--color-brand-500)]" />
|
||||
<span class="ml-2 text-sm text-[var(--text-secondary)]">{$t('common.loading')}</span>
|
||||
</div>
|
||||
{:else if error}
|
||||
<div class="rounded-xl border border-red-200 bg-red-50 p-6 text-center dark:border-red-900 dark:bg-red-950">
|
||||
<p class="text-sm text-red-700 dark:text-red-300">{error}</p>
|
||||
<a
|
||||
href="/proxies"
|
||||
class="mt-3 inline-block rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 transition-colors"
|
||||
>
|
||||
{$t('common.back')}
|
||||
</a>
|
||||
</div>
|
||||
{:else if proxy}
|
||||
<!-- Form card -->
|
||||
<div class="mx-auto max-w-2xl rounded-2xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-6 shadow-[var(--shadow-sm)]">
|
||||
<ProxyForm
|
||||
mode="edit"
|
||||
{proxy}
|
||||
onsave={handleSave}
|
||||
ondelete={handleDelete}
|
||||
oncancel={handleCancel}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user