Unify retained operation history in Activity

This commit is contained in:
2026-10-07 19:30:38 -04:00
parent c39563daef
commit 05bdeaf624
35 changed files with 1008 additions and 120 deletions
+2 -1
View File
@@ -1,4 +1,4 @@
import { redirect } from '@sveltejs/kit';
import { error, redirect } from '@sveltejs/kit';
import { getHost, listHostOperationHistory } from '$lib/server/hosts/index.js';
import { hostOperationScope, isHostOperationFilter } from '$lib/hosts.js';
import type { PageServerLoad } from './$types';
@@ -8,6 +8,7 @@ export const load: PageServerLoad = ({ url }) => {
const requestedFilter = url.searchParams.get('filter');
const filter = isHostOperationFilter(requestedFilter) ? requestedFilter : 'important';
const scope = hostOperationScope(url.searchParams);
if (url.searchParams.has('operation') && !scope.operation) error(400, 'Invalid host operation.');
const history = listHostOperationHistory(Number(requestedPage ?? 1), filter, scope);
if ((requestedPage !== null && requestedPage !== String(history.page))
|| (requestedFilter !== null && requestedFilter !== filter)) {
+12 -2
View File
@@ -27,6 +27,11 @@
{ id: 'all', label: 'All operations', description: 'Every recorded remote operation.', empty: 'No remote operations yet.' }
];
const selectedFilter = $derived(filters.find(filter => filter.id === history.filter)!);
const allOperationsHref = $derived.by(() => {
const params = new URLSearchParams($page.url.searchParams);
params.delete('operation'); params.delete('page');
return `/hosts/operations${params.size ? `?${params}` : ''}`;
});
function pageHref(pageNumber: number, filter: HostOperationFilter = history.filter) {
const params = new URLSearchParams($page.url.searchParams);
params.set('filter', filter); params.delete('page');
@@ -69,7 +74,8 @@
<SectionHeader title="Host operation history" eyebrow="Operations" subtitle="Remote operations, newest first. Filtered views retain their host and server context.">
{#snippet actions()}<Button disabled={refreshing || changingPage} onclick={() => void refresh()}>{refreshing ? 'Refreshing…' : 'Refresh'}</Button>{/snippet}
</SectionHeader>
{#if $page.url.searchParams.has('returnTo')}<BackLink fallback="/hosts" />{/if}
{#if data.scope.operation || $page.url.searchParams.has('returnTo')}<BackLink fallback={data.scope.operation ? '/activity' : '/hosts'} />{/if}
{#if data.scope.operation}<div class="selected-operation"><div><strong>Selected host operation</strong><code>{data.scope.operation}</code></div><a href={allOperationsHref}>View all operations</a></div>{/if}
{#if data.scope.host || data.scope.server}<div class="scope" role="status"><span>Showing {data.hostName ? `host: ${data.hostName}` : ''}{data.scope.host && data.scope.server ? ' · ' : ''}{data.scope.server ? `server: ${data.scope.server}` : ''}</span><a href={`/hosts/operations?filter=${history.filter}`}>Clear scope</a></div>{/if}
<div class="history-filters">
<nav class="filter-links" aria-label="Operation history filters">
@@ -82,7 +88,7 @@
</div>
{#if refreshError}<p class="ui-error" role="alert">{refreshError}</p>{/if}
<section class="panel" aria-label="Host operation history">
{#if !history.operations.length}<p class="empty" role="status">{selectedFilter.empty}</p>
{#if !history.operations.length}<p class="empty" role="status">{data.scope.operation ? 'This operation is unavailable in the selected history. It may have expired or be excluded by the current filters.' : selectedFilter.empty}</p>
{:else}
<!-- svelte-ignore a11y_no_noninteractive_tabindex (Scrollable table for keyboard users.) -->
<div class="table-wrap" role="region" aria-label="Operations" tabindex="0">
@@ -118,6 +124,10 @@
<style>
.scope { display:flex; flex-wrap:wrap; gap:16px; color:var(--text-secondary); font-size:var(--text-sm); }
.selected-operation { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; padding:16px; border:1px solid var(--border-strong); border-radius:var(--radius-md); background:var(--surface); font-size:var(--text-sm); }
.selected-operation > div { min-width:0; }
.selected-operation code { display:block; margin-top:4px; overflow-wrap:anywhere; color:var(--text-secondary); font-size:var(--text-xs); }
.selected-operation a { color:var(--accent-primary-hover); }
.page { display: grid; gap: 24px; min-width: 0; }
.history-filters { min-width: 0; }
.history-filters p { margin: 10px 0 0; color: var(--text-secondary); font-size: var(--text-sm); }