Finalize assistant and dashboard health updates
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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]
|
||||
Reference in New Issue
Block a user