import { useEffect, useMemo, useRef, useState } from 'react'; import { ATTEMPT_STATUSES, JOB_ITEM_TERMINAL_STATES, JOB_STATUSES, MAX_PAGE_SIZE, type Attempt, type Job, type JobItem, type JobPage, type JobPageQuery, type JobStatus, type SortDirection, } from '@multi-simadmin/contracts'; const PAGE_SIZE = 25; const MAX_STRING = 256; const TERMINAL_ITEMS = new Set(JOB_ITEM_TERMINAL_STATES); const JOB_STATUS_SET = new Set(JOB_STATUSES); const ATTEMPT_STATUS_SET = new Set(ATTEMPT_STATUSES); type SafeProblem = Pick, 'code' | 'title' | 'status'>; type SafeItem = Omit & { readonly error?: SafeProblem }; type SafeJob = Omit & { readonly items: readonly SafeItem[] }; export interface SafeJobPage { readonly items: readonly SafeJob[]; readonly page: JobPage['page']; } export interface JobsDataSource { load(query: JobPageQuery, signal: AbortSignal): Promise; } export interface JobsPageProps { readonly dataSource?: JobsDataSource; readonly refreshSignal?: number; } type SortField = NonNullable; function record(value: unknown): Record | null { return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record) : null; } function boundedString(value: unknown): string | null { return typeof value === 'string' && value.length > 0 && value.length <= MAX_STRING ? value : null; } function timestamp(value: unknown): string | null { if (typeof value !== 'string' || value.length > MAX_STRING) return null; const time = Date.parse(value); if (!Number.isFinite(time)) return null; const canonical = new Date(time).toISOString(); return canonical === value ? value : null; } function parseProblem(value: unknown): SafeProblem | undefined { const source = record(value); if (!source) return undefined; const code = boundedString(source.code); const title = boundedString(source.title); if ( !code || !title || typeof source.status !== 'number' || !Number.isInteger(source.status) || source.status < 100 || source.status > 599 ) return undefined; return { code, title, status: source.status }; } function hasContractProblem(value: unknown): boolean { const source = record(value); return Boolean( source && boundedString(source.type) && boundedString(source.detail) && boundedString(source.requestId) && parseProblem(source), ); } function parseItem(value: unknown): SafeItem | null { const source = record(value); if (!source) return null; const id = boundedString(source.id); const targetId = boundedString(source.targetId); const state = typeof source.state === 'string' && TERMINAL_ITEMS.has(source.state) ? (source.state as SafeItem['state']) : null; if (!id || !targetId || !state) return null; const sourceJobItemId = boundedString(source.sourceJobItemId); const error = parseProblem(source.error); if (source.sourceJobItemId !== undefined && !sourceJobItemId) return null; if (source.error !== undefined && (!error || !hasContractProblem(source.error))) return null; return { id, targetId, state, ...(sourceJobItemId ? { sourceJobItemId } : {}), ...(error ? { error } : {}), }; } function parseAttempt(value: unknown): Attempt | null { const source = record(value); if (!source) return null; const id = boundedString(source.id); const startedAt = timestamp(source.startedAt); const state = typeof source.state === 'string' && ATTEMPT_STATUS_SET.has(source.state) ? (source.state as Attempt['state']) : null; if (!id || !startedAt || !state) return null; const finishedAt = timestamp(source.finishedAt); if (source.finishedAt !== undefined && !finishedAt) return null; return { id, state, startedAt, ...(finishedAt ? { finishedAt } : {}) }; } function parseJob(value: unknown): SafeJob | null { const source = record(value); if (!source || !Array.isArray(source.items) || !Array.isArray(source.attempts)) return null; const id = boundedString(source.id); const operationId = boundedString(source.operationId); const rootJobId = boundedString(source.rootJobId); const createdAt = timestamp(source.createdAt); const status = typeof source.status === 'string' && JOB_STATUS_SET.has(source.status) ? (source.status as JobStatus) : null; if (!id || !operationId || !rootJobId || !createdAt || !status) return null; if (source.items.length > MAX_PAGE_SIZE || source.attempts.length > MAX_PAGE_SIZE) return null; const items = source.items.map(parseItem); const attempts = source.attempts.map(parseAttempt); if (items.some((item) => item === null) || attempts.some((attempt) => attempt === null)) return null; const retryOfJobId = boundedString(source.retryOfJobId); if (source.retryOfJobId !== undefined && !retryOfJobId) return null; return { id, operationId, rootJobId, createdAt, status, items: items as SafeItem[], attempts: attempts as Attempt[], ...(retryOfJobId ? { retryOfJobId } : {}), }; } /** Converts an untrusted transport response into the only shape retained by this UI. */ export function sanitizeJobPage(value: unknown): SafeJobPage | null { const source = record(value); if (!source || !Array.isArray(source.items)) return null; const page = record(source.page); if (!page) return null; if ( source.items.length > MAX_PAGE_SIZE || !Number.isSafeInteger(page.page) || (page.page as number) < 1 || !Number.isSafeInteger(page.pageSize) || (page.pageSize as number) < 1 || (page.pageSize as number) > MAX_PAGE_SIZE || !Number.isSafeInteger(page.total) || (page.total as number) < 0 || source.items.length > (page.pageSize as number) || source.items.length > (page.total as number) ) return null; const items: SafeJob[] = []; for (const candidate of source.items) { const parsed = parseJob(candidate); if (!parsed) return null; items.push(parsed); } return { items, page: { page: page.page as number, pageSize: page.pageSize as number, total: page.total as number, }, }; } function statusLabel(value: string): string { return value .split('-') .map((part) => part[0]?.toUpperCase() + part.slice(1)) .join(' '); } function safeLoadError(value: unknown): SafeProblem | null { return parseProblem(value) ?? null; } export function JobsPage({ dataSource, refreshSignal = 0 }: JobsPageProps) { const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); const [failed, setFailed] = useState(null); const [manualRefresh, setManualRefresh] = useState(0); const [status, setStatus] = useState(''); const [operation, setOperation] = useState(''); const [rootJob, setRootJob] = useState(''); const [instance, setInstance] = useState(''); const [sort, setSort] = useState('createdAt'); const [direction, setDirection] = useState('desc'); const [page, setPage] = useState(1); const request = useRef(0); const query = useMemo( () => ({ ...(status ? { status } : {}), ...(operation ? { operationId: operation } : {}), ...(rootJob ? { rootJobId: rootJob } : {}), ...(instance ? { instanceId: instance } : {}), sort, direction, page, pageSize: PAGE_SIZE, }), [direction, instance, operation, page, rootJob, sort, status], ); useEffect(() => { if (!dataSource) return; const controller = new AbortController(); const requestId = ++request.current; setLoading(true); setFailed(null); void dataSource.load(query, controller.signal).then( (raw) => { if (controller.signal.aborted || request.current !== requestId) return; const clean = sanitizeJobPage(raw); if (!clean) { setResult(null); setFailed(true); } else { setResult(clean); } setLoading(false); }, (reason: unknown) => { if (controller.signal.aborted || request.current !== requestId) return; setResult(null); setFailed(safeLoadError(reason) ?? true); setLoading(false); }, ); return () => controller.abort(); }, [dataSource, manualRefresh, query, refreshSignal]); if (!dataSource) { return (

Jobs

Jobs are runtime-unavailable because no jobs data source was provided.

); } const total = result?.page.total ?? 0; const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE)); const changeFilter = (change: () => void) => { change(); setPage(1); }; const changeSort = (field: SortField) => { if (sort === field) setDirection((current) => (current === 'asc' ? 'desc' : 'asc')); else { setSort(field); setDirection('asc'); } setPage(1); }; return (

Jobs

Read-only operation history.

{loading ? (

Loading jobs…

) : null} {failed ? (

Jobs could not be loaded. {failed !== true ? ` ${failed.code}: ${failed.title} (${failed.status})` : ''}

) : null} {!loading && !failed && result?.items.length === 0 ? (

No jobs match the current query.

) : null} {!loading && !failed && result ? ( <>
{result.items.map((item) => ( ))}
Job Root job Items Attempts
{item.createdAt} {item.id} {item.operationId} {statusLabel(item.status)} {item.rootJobId} {item.items.length === 0 ? ( {item.status === 'queued' || item.status === 'running' || item.status === 'cancelling' ? 'No terminal item results while this job is active.' : 'No terminal item results.'} ) : null} {item.items.map((entry) => (
{entry.targetId}{statusLabel(entry.state)} {entry.error ? ( <> {' '} — {entry.error.code}{entry.error.title} —{' '} {entry.error.status} ) : null}
))}
{item.attempts.map((entry) => (
{entry.id} — {statusLabel(entry.state)}
))}
) : null}
); }