feat(managed-secrets): add centrally managed secrets with GITOPS_ prefix pushed to multiple targets

Introduce managed secrets owned by GitOps that can be defined once and
pushed to any combination of projects/groups across connections. Values
are stored in OS keychain, secrets appear on targets as GITOPS_{key}.
This commit is contained in:
2026-02-28 23:43:32 +00:00
parent 78247c1d41
commit 75d35405dc
17 changed files with 1302 additions and 4 deletions

View File

@@ -704,6 +704,142 @@ export const setRefreshIntervalAction = uiStatePart.createAction<{ interval: num
},
);
// ============================================================================
// Managed Secrets State
// ============================================================================
export interface IManagedSecretsState {
managedSecrets: interfaces.data.IManagedSecret[];
}
export const managedSecretsStatePart = await appState.getStatePart<IManagedSecretsState>(
'managedSecrets',
{ managedSecrets: [] },
'soft',
);
export const fetchManagedSecretsAction = managedSecretsStatePart.createAction(
async (statePartArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetManagedSecrets
>('/typedrequest', 'getManagedSecrets');
const response = await typedRequest.fire({ identity: context.identity! });
return { managedSecrets: response.managedSecrets };
} catch (err) {
console.error('Failed to fetch managed secrets:', err);
return statePartArg.getState();
}
},
);
export const createManagedSecretAction = managedSecretsStatePart.createAction<{
key: string;
value: string;
description?: string;
targets: interfaces.data.IManagedSecretTarget[];
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_CreateManagedSecret
>('/typedrequest', 'createManagedSecret');
await typedRequest.fire({ identity: context.identity!, ...dataArg });
// Re-fetch
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetManagedSecrets
>('/typedrequest', 'getManagedSecrets');
const listResp = await listReq.fire({ identity: context.identity! });
return { managedSecrets: listResp.managedSecrets };
} catch (err) {
console.error('Failed to create managed secret:', err);
return statePartArg.getState();
}
});
export const updateManagedSecretAction = managedSecretsStatePart.createAction<{
managedSecretId: string;
value?: string;
description?: string;
targets?: interfaces.data.IManagedSecretTarget[];
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_UpdateManagedSecret
>('/typedrequest', 'updateManagedSecret');
await typedRequest.fire({ identity: context.identity!, ...dataArg });
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetManagedSecrets
>('/typedrequest', 'getManagedSecrets');
const listResp = await listReq.fire({ identity: context.identity! });
return { managedSecrets: listResp.managedSecrets };
} catch (err) {
console.error('Failed to update managed secret:', err);
return statePartArg.getState();
}
});
export const deleteManagedSecretAction = managedSecretsStatePart.createAction<{
managedSecretId: string;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_DeleteManagedSecret
>('/typedrequest', 'deleteManagedSecret');
await typedRequest.fire({ identity: context.identity!, ...dataArg });
const state = statePartArg.getState();
return {
managedSecrets: state.managedSecrets.filter((s) => s.id !== dataArg.managedSecretId),
};
} catch (err) {
console.error('Failed to delete managed secret:', err);
return statePartArg.getState();
}
});
export const pushManagedSecretAction = managedSecretsStatePart.createAction<{
managedSecretId: string;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_PushManagedSecret
>('/typedrequest', 'pushManagedSecret');
await typedRequest.fire({ identity: context.identity!, ...dataArg });
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetManagedSecrets
>('/typedrequest', 'getManagedSecrets');
const listResp = await listReq.fire({ identity: context.identity! });
return { managedSecrets: listResp.managedSecrets };
} catch (err) {
console.error('Failed to push managed secret:', err);
return statePartArg.getState();
}
});
export const pushAllManagedSecretsAction = managedSecretsStatePart.createAction(
async (statePartArg) => {
const context = getActionContext();
try {
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_PushAllManagedSecrets
>('/typedrequest', 'pushAllManagedSecrets');
await typedRequest.fire({ identity: context.identity! });
const listReq = new plugins.domtools.plugins.typedrequest.TypedRequest<
interfaces.requests.IReq_GetManagedSecrets
>('/typedrequest', 'getManagedSecrets');
const listResp = await listReq.fire({ identity: context.identity! });
return { managedSecrets: listResp.managedSecrets };
} catch (err) {
console.error('Failed to push all managed secrets:', err);
return statePartArg.getState();
}
},
);
// ============================================================================
// Sync State
// ============================================================================