Files
electric-cherry/src/lib/provisioning-list.ts
T
2026-10-12 15:52:34 -04:00

120 lines
3.5 KiB
TypeScript

import { provisioningStates, type ProvisioningAction } from './provisioning.js';
import type { ProvisioningDeployment } from './provisioning-detail.js';
import type { ProvisioningSource } from './provisioning-detail.js';
export const provisioningListStatuses = [
...provisioningStates,
// Retain the combined filter for existing links and API clients.
'pending',
'attention'
] as const;
export const provisioningListSources = [
'manual',
'campaign',
'beta',
'maintenance',
'recovery',
'autoscaling',
'rebalance',
'system'
] as const;
export type ProvisioningListSource = (typeof provisioningListSources)[number];
export const provisioningSourceLabels: Record<ProvisioningListSource, string> = {
manual: 'Manual',
campaign: 'Campaign',
beta: 'Beta',
maintenance: 'Maintenance',
recovery: 'Recovery',
autoscaling: 'Autoscaling',
rebalance: 'Rebalance',
system: 'System'
};
export const provisioningSourceKinds = {
campaign: ['campaign'],
beta: ['beta'],
maintenance: ['maintenance'],
recovery: ['watchdog'],
autoscaling: ['autoscaling'],
rebalance: ['rebalance']
} as const satisfies Record<
Exclude<ProvisioningListSource, 'manual' | 'system'>,
readonly ProvisioningSource['kind'][]
>;
/** Record titles and incident summaries belong in expanded details, not the source column. */
export function provisioningSourceLabel(
row: Pick<ProvisioningListRow, 'sources' | 'triggered_by'>
): string {
const kind = row.sources?.[0]?.kind;
return provisioningSourceLabels[
kind === 'watchdog'
? 'recovery'
: (kind ?? (row.triggered_by === 'ui' ? 'manual' : 'system'))
];
}
export function provisioningMatchesSource(
row: Pick<ProvisioningListRow, 'sources' | 'triggered_by'>,
source: ProvisioningListSource
): boolean {
const sources = row.sources ?? [];
if (source === 'manual' || source === 'system')
return (
!sources.length &&
(source === 'manual' ? row.triggered_by === 'ui' : row.triggered_by !== 'ui')
);
const kinds: readonly ProvisioningSource['kind'][] = provisioningSourceKinds[source];
return sources.some((item) => kinds.includes(item.kind));
}
export type ProvisioningListRow = Pick<
ProvisioningDeployment,
| 'id'
| 'server'
| 'server_deleted_at'
| 'action'
| 'state'
| 'stage'
| 'started_at'
| 'finished_at'
| 'triggered_by'
| 'requested_by'
| 'error'
| 'warnings'
| 'server_exists'
> & {
canCancelRemoval: number;
canCancel?: number;
sources?: ProvisioningSource[];
phase?: string | null;
phaseStartedAt?: string | null;
waitCode?: string | null;
waitMessage?: string | null;
};
export interface ProvisioningListOptions {
view?: 'active' | 'history' | 'all';
action?: ProvisioningAction;
status?: (typeof provisioningListStatuses)[number];
source?: ProvisioningListSource;
server?: string;
limit?: number;
offset?: number;
}
export interface ProvisioningListPage {
deployments: ProvisioningListRow[];
total: number;
needsReview: number;
limit: number;
offset: number;
}
export function provisioningPageSize(value: number): number {
return Number.isFinite(value) ? Math.min(100, Math.max(1, Math.floor(value) || 25)) : 25;
}
export function provisioningPageOffset(value: number): number {
return Number.isFinite(value) ? Math.min(1_000_000, Math.max(0, Math.floor(value))) : 0;
}