Files
harbor-net/src/web/state/operations.ts
T
dokril daec12e013
Build and Deploy Gateway / build-and-push (push) Failing after 14s
Build and Deploy Gateway / deploy (push) Has been skipped
Update Harbor client and gateway integration workflows
2026-08-19 18:16:10 +03:00

68 lines
2.3 KiB
TypeScript

export type OperationKey = 'connection' | 'serverApply' | 'profileAdd' | 'profileRename'
| 'profileSelect' | 'profileActivate' | 'profileRefresh' | 'profileDelete'
| 'gatewayAuto' | 'routeRules' | 'diagnosticsSettings' | 'failover';
export interface OperationState {
status: 'running';
startedAt: string;
target: string;
}
export type OperationRegistrySnapshot = Partial<Record<OperationKey, OperationState>>;
const OPERATION_KEYS: readonly OperationKey[] = [
'connection',
'serverApply',
'profileAdd',
'profileRename',
'profileSelect',
'profileActivate',
'profileRefresh',
'profileDelete',
'gatewayAuto',
'routeRules',
'diagnosticsSettings',
'failover',
];
// The backend has one canonical revision and one mutation queue, so the UI mirrors that lock.
export const OPERATION_CONFLICTS = Object.freeze(Object.fromEntries(
OPERATION_KEYS.map((key) => [key, OPERATION_KEYS.filter((candidate) => candidate !== key)]),
) as unknown as Record<OperationKey, readonly OperationKey[]>);
export function operationBlocked(operations: OperationRegistrySnapshot, key: OperationKey) {
if (operations[key]?.status === 'running') return true;
return OPERATION_CONFLICTS[key].some((conflict) => operations[conflict]?.status === 'running');
}
export function createOperationRegistry(
onChange: (operations: OperationRegistrySnapshot) => void = () => {},
now = () => new Date().toISOString(),
) {
let operations: OperationRegistrySnapshot = {};
const inFlight = new Map<string, Promise<unknown>>();
function run<T>(key: OperationKey, action: () => T | Promise<T>, target = ''): Promise<T | false> {
const identity = `${key}:${target}`;
const existing = inFlight.get(identity);
if (existing) return existing as Promise<T>;
if (operationBlocked(operations, key)) return Promise.resolve(false);
operations = { ...operations, [key]: { status: 'running', startedAt: now(), target } };
onChange(operations);
const promise: Promise<T> = Promise.resolve()
.then(action)
.finally(() => {
const { [key]: completed, ...remaining } = operations;
operations = remaining;
inFlight.delete(identity);
onChange(operations);
});
inFlight.set(identity, promise as Promise<unknown>);
return promise;
}
return { run, getSnapshot: () => operations };
}