Refactor VPN proxy components and update related behavior
This commit is contained in:
+35
-21
@@ -1,23 +1,36 @@
|
||||
export type OperationKey = 'connection' | 'serverApply' | 'subscriptionImport'
|
||||
| 'subscriptionRefresh' | 'subscriptionDelete' | 'gatewayAuto' | 'routeRules';
|
||||
export interface OperationState { status: 'running'; startedAt: string }
|
||||
export type OperationKey = 'connection' | 'serverApply' | 'profileAdd' | 'profileRename'
|
||||
| 'profileSelect' | 'profileActivate' | 'profileRefresh' | 'profileDelete'
|
||||
| 'gatewayAuto' | 'routeRules';
|
||||
|
||||
export interface OperationState {
|
||||
status: 'running';
|
||||
startedAt: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
export type OperationRegistrySnapshot = Partial<Record<OperationKey, OperationState>>;
|
||||
|
||||
export const OPERATION_CONFLICTS: Readonly<Record<OperationKey, readonly OperationKey[]>> = Object.freeze({
|
||||
connection: ['serverApply', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto', 'routeRules'],
|
||||
serverApply: ['connection', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto', 'routeRules'],
|
||||
subscriptionImport: ['connection', 'serverApply', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto', 'routeRules'],
|
||||
subscriptionRefresh: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionDelete', 'gatewayAuto', 'routeRules'],
|
||||
subscriptionDelete: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionRefresh', 'gatewayAuto', 'routeRules'],
|
||||
gatewayAuto: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'routeRules'],
|
||||
routeRules: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto'],
|
||||
});
|
||||
const OPERATION_KEYS: readonly OperationKey[] = [
|
||||
'connection',
|
||||
'serverApply',
|
||||
'profileAdd',
|
||||
'profileRename',
|
||||
'profileSelect',
|
||||
'profileActivate',
|
||||
'profileRefresh',
|
||||
'profileDelete',
|
||||
'gatewayAuto',
|
||||
'routeRules',
|
||||
];
|
||||
|
||||
// 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',
|
||||
);
|
||||
return OPERATION_CONFLICTS[key].some((conflict) => operations[conflict]?.status === 'running');
|
||||
}
|
||||
|
||||
export function createOperationRegistry(
|
||||
@@ -25,14 +38,15 @@ export function createOperationRegistry(
|
||||
now = () => new Date().toISOString(),
|
||||
) {
|
||||
let operations: OperationRegistrySnapshot = {};
|
||||
const inFlight = new Map<OperationKey, Promise<unknown>>();
|
||||
const inFlight = new Map<string, Promise<unknown>>();
|
||||
|
||||
function run<T>(key: OperationKey, action: () => T | Promise<T>): Promise<T | false> {
|
||||
const existing = inFlight.get(key);
|
||||
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() } };
|
||||
operations = { ...operations, [key]: { status: 'running', startedAt: now(), target } };
|
||||
onChange(operations);
|
||||
|
||||
const promise: Promise<T> = Promise.resolve()
|
||||
@@ -40,10 +54,10 @@ export function createOperationRegistry(
|
||||
.finally(() => {
|
||||
const { [key]: completed, ...remaining } = operations;
|
||||
operations = remaining;
|
||||
inFlight.delete(key);
|
||||
inFlight.delete(identity);
|
||||
onChange(operations);
|
||||
});
|
||||
inFlight.set(key, promise as Promise<unknown>);
|
||||
inFlight.set(identity, promise as Promise<unknown>);
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user