Exclude maintenance history from activity log
This commit is contained in:
@@ -35,6 +35,7 @@ export interface ActivityRow {
|
||||
|
||||
export interface ActivityQuery {
|
||||
excludeProvisioning?: boolean;
|
||||
excludeMaintenance?: boolean;
|
||||
server?: string;
|
||||
host?: string;
|
||||
type?: ActivityType;
|
||||
@@ -69,6 +70,7 @@ export function parseActivityQuery(params: URLSearchParams): ActivityQuery {
|
||||
offset: integer(params.get('offset'), 0, 0, 1_000_000_000)
|
||||
};
|
||||
if (params.get('excludeProvisioning') === 'true') query.excludeProvisioning = true;
|
||||
if (params.get('excludeMaintenance') === 'true') query.excludeMaintenance = true;
|
||||
for (const key of ['server', 'host', 'q'] as const) {
|
||||
const value = params.get(key)?.trim().slice(0, key === 'q' ? 200 : 255);
|
||||
if (value) query[key] = value;
|
||||
|
||||
@@ -130,6 +130,8 @@ normalized AS (
|
||||
|
||||
type SqlRow = Omit<ActivityRow, 'source' | 'sources' | 'href'> & { sources: string; route: string; recordId: string; secondaryHostId: string | null };
|
||||
const sourceOrder: ActivitySource[] = ['maintenance', 'recovery', 'campaign', 'beta', 'autoscaling', 'provisioning', 'host'];
|
||||
// Use the same membership rule for Maintenance history and its exclusion from the main log.
|
||||
const maintenanceMembershipSql = "(instr(sources,'|maintenance|')>0 OR instr(sources,'|recovery|')>0)";
|
||||
function present(row: SqlRow): ActivityRow {
|
||||
const sources = sourceOrder.filter(source => row.sources.includes(`|${source}|`));
|
||||
const href = row.route === 'maintenance-job' ? `/maintenance?view=overview&job=${encodeURIComponent(row.recordId)}#job-${row.recordId}`
|
||||
@@ -154,12 +156,13 @@ export function activityHistory(input: ActivityQuery = {}, now = Date.now()): Ac
|
||||
// Keep lifecycle jobs in Provisioning, including jobs triggered by automation.
|
||||
// Apply this before totals and pagination; resource histories can still include them.
|
||||
if (query.excludeProvisioning) conditions.push("route <> 'provisioning' AND type <> 'restart'");
|
||||
if (query.excludeMaintenance) conditions.push(`NOT ${maintenanceMembershipSql}`);
|
||||
const bindings: Record<string, string | number> = {};
|
||||
for (const key of ['server', 'type', 'state'] as const) if (query[key]) { conditions.push(`${key}=@${key}`); bindings[key] = query[key]; }
|
||||
if (query.host) { conditions.push('(hostId=@host OR secondaryHostId=@host)'); bindings.host = query.host; }
|
||||
if (query.source) {
|
||||
// Maintenance is the user-facing umbrella; Recovery remains available as a narrower filter.
|
||||
conditions.push(query.source === 'maintenance' ? "(instr(sources,'|maintenance|')>0 OR instr(sources,'|recovery|')>0)" : 'instr(sources,@source)>0');
|
||||
conditions.push(query.source === 'maintenance' ? maintenanceMembershipSql : 'instr(sources,@source)>0');
|
||||
if (query.source !== 'maintenance') bindings.source = `|${query.source}|`;
|
||||
}
|
||||
if (query.days) { conditions.push('julianday(at)>=julianday(@since)'); bindings.since = new Date(now - query.days * 86_400_000).toISOString(); }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { parseActivityQuery, type ActivityQuery, type ActivitySource } from '../activity.js';
|
||||
import { createTimeFormatter } from '../time-zone.js';
|
||||
|
||||
export interface ActivityScope { server?: string; source?: ActivitySource; excludeProvisioning?: boolean }
|
||||
export interface ActivityScope { server?: string; source?: ActivitySource; excludeProvisioning?: boolean; excludeMaintenance?: boolean }
|
||||
const keys = ['server', 'host', 'type', 'source', 'state', 'days', 'q', 'limit', 'offset'] as const;
|
||||
|
||||
/** Scoped views always retain their server/source, including after reset or a pasted URL. */
|
||||
@@ -10,6 +10,7 @@ export function activityQuery(url: URL, scope: ActivityScope = {}): ActivityQuer
|
||||
if (scope.server !== undefined) params.set('server', scope.server);
|
||||
if (scope.source !== undefined) params.set('source', scope.source);
|
||||
if (scope.excludeProvisioning !== undefined) params.set('excludeProvisioning', String(scope.excludeProvisioning));
|
||||
if (scope.excludeMaintenance !== undefined) params.set('excludeMaintenance', String(scope.excludeMaintenance));
|
||||
return parseActivityQuery(params);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import Freshness from './Freshness.svelte';
|
||||
import HistoricalServerIdentity from './HistoricalServerIdentity.svelte';
|
||||
|
||||
let { server, source, initial, excludeProvisioning = false }: { server?: string; source?: ActivitySource; initial?: ActivityPage; excludeProvisioning?: boolean } = $props();
|
||||
let { server, source, initial, excludeProvisioning = false, excludeMaintenance = false }: { server?: string; source?: ActivitySource; initial?: ActivityPage; excludeProvisioning?: boolean; excludeMaintenance?: boolean } = $props();
|
||||
const id = $props.id();
|
||||
const headingId = `${id}-activity-heading`;
|
||||
let data = $state<ActivityPage | undefined>(untrack(() => initial));
|
||||
@@ -27,14 +27,22 @@
|
||||
let filtersOpen = $state(false);
|
||||
let checkedAt = $state<number | null>(untrack(() => initial ? Date.now() : null));
|
||||
let results = $state<HTMLOListElement>();
|
||||
let search = $state(untrack(() => activityQuery($page.url, { server, source, excludeProvisioning }).q ?? ''));
|
||||
let search = $state(untrack(() => activityQuery($page.url, { server, source, excludeProvisioning, excludeMaintenance }).q ?? ''));
|
||||
let pendingUrl: URL | undefined;
|
||||
let navigationId = 0;
|
||||
const filters = $derived(activityQuery($page.url, { server, source, excludeProvisioning }));
|
||||
const apiUrl = $derived(activityApiHref($page.url, { server, source, excludeProvisioning }));
|
||||
const filters = $derived(activityQuery($page.url, { server, source, excludeProvisioning, excludeMaintenance }));
|
||||
const apiUrl = $derived(activityApiHref($page.url, { server, source, excludeProvisioning, excludeMaintenance }));
|
||||
let loadedUrl = $state(untrack(() => initial ? apiUrl : ''));
|
||||
const typeOptions = $derived(Object.entries(activityTypes).filter(([key]) => !excludeProvisioning || !['create', 'start', 'restart', 'rebuild', 'stop', 'remove'].includes(key)));
|
||||
const sourceOptions = $derived(Object.entries(activitySources).filter(([key]) => !excludeProvisioning || !['provisioning', 'autoscaling'].includes(key)));
|
||||
const typeOptions = $derived(Object.entries(activityTypes).filter(([key]) =>
|
||||
(!excludeProvisioning || !['create', 'start', 'restart', 'rebuild', 'stop', 'remove'].includes(key))
|
||||
&& (!excludeMaintenance || !['update', 'health'].includes(key))));
|
||||
const sourceOptions = $derived(Object.entries(activitySources).filter(([key]) =>
|
||||
(!excludeProvisioning || !['provisioning', 'autoscaling'].includes(key))
|
||||
&& (!excludeMaintenance || !['maintenance', 'recovery'].includes(key))));
|
||||
const historyDescription = $derived(excludeMaintenance
|
||||
? 'File changes and host work appear here.'
|
||||
: excludeProvisioning ? 'File changes, maintenance, and host work appear here.'
|
||||
: 'Server operations, file changes, maintenance, and host work appear here.');
|
||||
const filterCount = $derived([filters.type, filters.state, source === undefined ? filters.source : '', server === undefined ? filters.server : '', filters.host, filters.days, filters.q].filter(Boolean).length);
|
||||
const serverOptions = $derived.by(() => {
|
||||
const names = new Set([...$fleet.servers.map(item => item.name), ...(data?.rows.flatMap(row => row.server ? [row.server] : []) ?? [])]);
|
||||
@@ -125,7 +133,7 @@
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
{:else if !error}<EmptyState title={filterCount ? 'No matching activity' : 'No activity recorded yet'} description={filterCount ? 'Try another type, state, server, host, or date range.' : excludeProvisioning ? 'File changes, maintenance, and host work appear here.' : 'Server operations, file changes, maintenance, and host work appear here.'}>
|
||||
{:else if !error}<EmptyState title={filterCount ? 'No matching activity' : 'No activity recorded yet'} description={filterCount ? 'Try another type, state, server, host, or date range.' : historyDescription}>
|
||||
{#snippet actions()}{#if filterCount}<Button size="sm" onclick={reset}>Clear filters</Button>{/if}{/snippet}
|
||||
</EmptyState>{/if}
|
||||
{#if data && !loading && loadedUrl === apiUrl && (data.total > 0 || data.offset > 0)}
|
||||
|
||||
Reference in New Issue
Block a user