Finalize assistant and dashboard health updates

This commit is contained in:
2026-03-15 13:19:46 +03:00
parent a8563a8369
commit 6b66f2fb49
11 changed files with 7662 additions and 5275 deletions

View File

@@ -3,16 +3,19 @@
@TIER: STANDARD
@PURPOSE: Main page for the Dashboard Health Center.
@LAYER: UI/Page
@RELATION: DEPENDS_ON -> frontend.src.lib.components.health.HealthMatrix
@RELATION: CALLS -> frontend.src.lib.api.api_module
@UX_STATE: Idle -> Displays the Health Center with HealthMatrix.
@UX_STATE: Loading -> Displays a skeleton or spinner.
@UX_STATE: Error -> Displays an error message.
@UX_REATIVITY: State: $state, Derived: $derived.
@UX_REACTIVITY: State: $state, Derived: $derived.
-->
<script>
import { onMount } from 'svelte';
import HealthMatrix from '$lib/components/health/HealthMatrix.svelte';
import { getHealthSummary, getEnvironments } from '$lib/api.js';
import { getHealthSummary, getEnvironments, requestApi } from '$lib/api.js';
import { t } from '$lib/i18n';
import { addToast } from '$lib/toasts.js';
import { formatDistanceToNow } from 'date-fns';
let healthData = $state({
@@ -26,7 +29,13 @@
let selectedEnvId = $state('');
let loading = $state(true);
let error = $state(null);
let deletingReportIds = $state(new Set());
// [DEF:loadData:Function]
// @PURPOSE: Load health summary rows and environment options for the current filter.
// @PRE: Page is mounted or environment selection changed.
// @POST: `healthData` and `environments` reflect latest backend response.
// @SIDE_EFFECT: Calls backend API endpoints for health summary and environments.
async function loadData() {
loading = true;
error = null;
@@ -46,15 +55,49 @@
loading = false;
}
}
// [/DEF:loadData:Function]
onMount(() => {
loadData();
});
// [DEF:handleEnvChange:Function]
// @PURPOSE: Apply environment filter and trigger health summary reload.
// @PRE: DOM change event carries target value.
// @POST: selectedEnvId is updated and new data load starts.
function handleEnvChange(e) {
selectedEnvId = e.target.value;
loadData();
}
// [/DEF:handleEnvChange:Function]
// [DEF:handleDeleteReport:Function]
// @PURPOSE: Delete one health report row with confirmation and optimistic button lock.
// @PRE: item contains `record_id` from health summary payload.
// @POST: Row is removed from backend and page data is reloaded on success.
// @SIDE_EFFECT: Calls DELETE health API and emits toast notifications.
async function handleDeleteReport(item) {
if (!item?.record_id || deletingReportIds.has(item.record_id)) return;
if (!confirm($t.health?.delete_confirm.replace('{slug}', item.dashboard_slug || item.dashboard_id))) return;
deletingReportIds.add(item.record_id);
deletingReportIds = new Set(deletingReportIds);
try {
await requestApi(`/health/summary/${item.record_id}`, 'DELETE');
addToast($t.health?.delete_success.replace('{slug}', item.dashboard_slug || item.dashboard_id), 'success');
await loadData();
} catch (e) {
addToast(
$t.health?.delete_failed.replace('{error}', e?.message || $t.common?.error),
'error',
);
} finally {
deletingReportIds.delete(item.record_id);
deletingReportIds = new Set(deletingReportIds);
}
}
// [/DEF:handleDeleteReport:Function]
</script>
<div class="container mx-auto p-6">
@@ -136,8 +179,13 @@
{:else}
{#each healthData.items as item}
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{item.dashboard_title || item.dashboard_id}
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a
href={`/dashboards/${encodeURIComponent(String(item.dashboard_slug || item.dashboard_id))}?env_id=${encodeURIComponent(item.environment_id)}`}
class="text-indigo-600 hover:text-indigo-900 hover:underline"
>
{item.dashboard_slug || item.dashboard_id}
</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span class="px-2 py-1 bg-gray-100 rounded text-xs font-mono">{item.environment_id}</span>
@@ -161,6 +209,14 @@
{#if item.task_id}
<a href="/reports/llm/{item.task_id}" class="text-indigo-600 hover:text-indigo-900">{$t.health?.view_report}</a>
{/if}
<button
type="button"
class="ml-3 text-red-600 hover:text-red-900"
onclick={() => handleDeleteReport(item)}
disabled={deletingReportIds.has(item.record_id)}
>
{deletingReportIds.has(item.record_id) ? '...' : $t.common?.delete}
</button>
</td>
</tr>
{/each}

View File

@@ -0,0 +1,35 @@
// [DEF:frontend.src.routes.dashboards.health.__tests__.health_page_integration:Module]
// @TIER: STANDARD
// @SEMANTICS: health-page, integration-test, slug-link, delete-flow
// @PURPOSE: Lock dashboard health page contract for slug navigation and report deletion.
// @LAYER: UI Tests
// @RELATION: VERIFIES -> frontend/src/routes/dashboards/health/+page.svelte
import { describe, it, expect } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
const PAGE_PATH = path.resolve(
process.cwd(),
'src/routes/dashboards/health/+page.svelte',
);
describe('Dashboard health page contract', () => {
it('renders slug-first dashboard link bound to environment route context', () => {
const source = fs.readFileSync(PAGE_PATH, 'utf-8');
expect(source).toContain("{$t.health?.table_dashboard}");
expect(source).toContain("item.dashboard_slug || item.dashboard_id");
expect(source).toContain("href={`/dashboards/${encodeURIComponent(String(item.dashboard_slug || item.dashboard_id))}?env_id=${encodeURIComponent(item.environment_id)}`}");
});
it('keeps explicit delete report flow with confirm and DELETE request', () => {
const source = fs.readFileSync(PAGE_PATH, 'utf-8');
expect(source).toContain('async function handleDeleteReport(item)');
expect(source).toContain("requestApi(`/health/summary/${item.record_id}`, 'DELETE')");
expect(source).toContain("$t.health?.delete_confirm.replace('{slug}', item.dashboard_slug || item.dashboard_id)");
expect(source).toContain("$t.common?.delete");
});
});
// [/DEF:frontend.src.routes.dashboards.health.__tests__.health_page_integration:Module]