Unify retained operation history in Activity
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
export const activityTypes = {
|
||||
create: 'Create server', start: 'Start server', restart: 'Restart server', rebuild: 'Rebuild server',
|
||||
stop: 'Stop server', remove: 'Remove server', update: 'Game update', files: 'File changes',
|
||||
health: 'Health finding', drift: 'File drift', host: 'Host operation', beta: 'Beta test'
|
||||
} as const;
|
||||
export type ActivityType = keyof typeof activityTypes;
|
||||
export const activitySources = {
|
||||
provisioning: 'Server operations', maintenance: 'Maintenance & recovery', recovery: 'Recovery',
|
||||
campaign: 'Campaigns', beta: 'Betas', host: 'Hosts', autoscaling: 'Autoscaling'
|
||||
} as const;
|
||||
export type ActivitySource = keyof typeof activitySources;
|
||||
export const activityStates = {
|
||||
queued: 'Queued', running: 'In progress', succeeded: 'Completed', warning: 'Completed with warnings',
|
||||
failed: 'Failed', cancelled: 'Cancelled', attention: 'Needs attention', unknown: 'Unknown'
|
||||
} as const;
|
||||
export type ActivityState = keyof typeof activityStates;
|
||||
|
||||
export interface ActivityRow {
|
||||
id: string;
|
||||
at: string;
|
||||
type: ActivityType;
|
||||
source: ActivitySource;
|
||||
/** A shared operation can have more than one caller; filters match any membership. */
|
||||
sources: ActivitySource[];
|
||||
state: ActivityState;
|
||||
rawState: string;
|
||||
title: string;
|
||||
detail: string | null;
|
||||
server: string | null;
|
||||
serverDeletedAt: string | null;
|
||||
hostId: string | null;
|
||||
hostName: string | null;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface ActivityQuery {
|
||||
server?: string;
|
||||
host?: string;
|
||||
type?: ActivityType;
|
||||
source?: ActivitySource;
|
||||
state?: ActivityState;
|
||||
days?: number;
|
||||
q?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
export interface ActivityPage {
|
||||
rows: ActivityRow[];
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
query: ActivityQuery;
|
||||
}
|
||||
|
||||
function selection<T extends string>(value: string | null, labels: Record<T, string>): T | undefined {
|
||||
return value !== null && Object.hasOwn(labels, value) ? value as T : undefined;
|
||||
}
|
||||
function integer(value: string | null, fallback: number, min: number, max: number): number {
|
||||
if (value === null || !/^\d+$/.test(value)) return fallback;
|
||||
const parsed = Number(value);
|
||||
return Number.isSafeInteger(parsed) ? Math.min(max, Math.max(min, parsed)) : fallback;
|
||||
}
|
||||
|
||||
/** Server and host are exact identities; q searches friendly names and safe summaries. */
|
||||
export function parseActivityQuery(params: URLSearchParams): ActivityQuery {
|
||||
const query: ActivityQuery = {
|
||||
limit: integer(params.get('limit'), 25, 1, 100),
|
||||
offset: integer(params.get('offset'), 0, 0, 1_000_000_000)
|
||||
};
|
||||
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;
|
||||
}
|
||||
query.type = selection(params.get('type'), activityTypes);
|
||||
query.source = selection(params.get('source'), activitySources);
|
||||
query.state = selection(params.get('state'), activityStates);
|
||||
// Missing or zero days means all retained history, including removed servers.
|
||||
const days = integer(params.get('days'), 0, 0, 36_500);
|
||||
if (days) query.days = days;
|
||||
return query;
|
||||
}
|
||||
|
||||
export function normalizeActivityQuery(query: ActivityQuery): ActivityQuery {
|
||||
const params = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(query)) if (value !== undefined) params.set(key, String(value));
|
||||
return parseActivityQuery(params);
|
||||
}
|
||||
Reference in New Issue
Block a user