feat(acme): add DB-backed ACME configuration management and OpsServer certificate settings UI
This commit is contained in:
@@ -197,6 +197,28 @@ export const certificateStatePart = await appState.getStatePart<ICertificateStat
|
||||
'soft'
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// ACME Config State (DB-backed singleton, managed via Domains > Certificates)
|
||||
// ============================================================================
|
||||
|
||||
export interface IAcmeConfigState {
|
||||
config: interfaces.data.IAcmeConfig | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
export const acmeConfigStatePart = await appState.getStatePart<IAcmeConfigState>(
|
||||
'acmeConfig',
|
||||
{
|
||||
config: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastUpdated: 0,
|
||||
},
|
||||
'soft',
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// Remote Ingress State
|
||||
// ============================================================================
|
||||
@@ -1953,6 +1975,72 @@ export const deleteDnsRecordAction = domainsStatePart.createAction<{ id: string;
|
||||
},
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// ACME Config Actions
|
||||
// ============================================================================
|
||||
|
||||
export const fetchAcmeConfigAction = acmeConfigStatePart.createAction(
|
||||
async (statePartArg): Promise<IAcmeConfigState> => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState()!;
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_GetAcmeConfig
|
||||
>('/typedrequest', 'getAcmeConfig');
|
||||
const response = await request.fire({ identity: context.identity });
|
||||
return {
|
||||
config: response.config,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
return {
|
||||
...currentState,
|
||||
isLoading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch ACME config',
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const updateAcmeConfigAction = acmeConfigStatePart.createAction<{
|
||||
accountEmail?: string;
|
||||
enabled?: boolean;
|
||||
useProduction?: boolean;
|
||||
autoRenew?: boolean;
|
||||
renewThresholdDays?: number;
|
||||
}>(async (statePartArg, dataArg, actionContext): Promise<IAcmeConfigState> => {
|
||||
const context = getActionContext();
|
||||
try {
|
||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_UpdateAcmeConfig
|
||||
>('/typedrequest', 'updateAcmeConfig');
|
||||
const response = await request.fire({
|
||||
identity: context.identity!,
|
||||
accountEmail: dataArg.accountEmail,
|
||||
enabled: dataArg.enabled,
|
||||
useProduction: dataArg.useProduction,
|
||||
autoRenew: dataArg.autoRenew,
|
||||
renewThresholdDays: dataArg.renewThresholdDays,
|
||||
});
|
||||
if (!response.success) {
|
||||
return {
|
||||
...statePartArg.getState()!,
|
||||
error: response.message || 'Failed to update ACME config',
|
||||
};
|
||||
}
|
||||
return await actionContext!.dispatch(fetchAcmeConfigAction, null);
|
||||
} catch (error: unknown) {
|
||||
return {
|
||||
...statePartArg.getState()!,
|
||||
error: error instanceof Error ? error.message : 'Failed to update ACME config',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Route Management Actions
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user