Track client operations and show inline progress
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 16s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-07-11 20:55:20 +03:00
parent 9da4fef1f0
commit 74660d915f
6 changed files with 299 additions and 66 deletions

View File

@@ -0,0 +1,65 @@
export const OPERATION_CONFLICTS = Object.freeze({
connection: ['serverApply', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto'],
serverApply: ['connection', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto'],
subscriptionImport: ['connection', 'serverApply', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto'],
subscriptionRefresh: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionDelete', 'gatewayAuto'],
subscriptionDelete: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionRefresh', 'gatewayAuto'],
gatewayAuto: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete'],
});
export function operationBlocked(operations, key) {
if (operations[key]?.status === 'running') return true;
return (OPERATION_CONFLICTS[key] || []).some(
(conflict) => operations[conflict]?.status === 'running',
);
}
export function createOperationRegistry(onChange = () => {}, now = () => new Date().toISOString()) {
let operations = {};
const inFlight = new Map();
function run(key, action) {
if (inFlight.has(key)) return inFlight.get(key);
if (operationBlocked(operations, key)) return Promise.resolve(false);
operations = { ...operations, [key]: { status: 'running', startedAt: now() } };
onChange(operations);
const promise = Promise.resolve()
.then(action)
.finally(() => {
const { [key]: completed, ...remaining } = operations;
operations = remaining;
inFlight.delete(key);
onChange(operations);
});
inFlight.set(key, promise);
return promise;
}
return { run, getSnapshot: () => operations };
}
export function createLatestRequest() {
let controller = null;
return {
run(action) {
controller?.abort();
controller = new AbortController();
const current = controller;
return Promise.resolve()
.then(() => action(current.signal))
.then(
(value) => current.signal.aborted ? undefined : value,
(error) => {
if (current.signal.aborted) return undefined;
throw error;
},
);
},
cancel() {
controller?.abort();
},
};
}