feat: certificate management with stage/activate/restore lifecycle
Provider-based architecture (Docker now, K8s later): - CertificateManager interface + DockerCertificateManager (file-based) - Atomic swap via .wip files for safe cert replacement - Stage -> Activate -> Archive lifecycle with one-deep rollback - Bootstrap supports user-supplied certs via CERT_FILE/KEY_FILE/CA_FILE - CA bundle aggregates platform + tenant CAs, distributed to containers - Vendor UI: Certificates page with upload, activate, restore, discard - Stale tenant tracking (ca_applied_at) with restart banner - Conditional TLS skip removal when CA bundle exists Includes design spec, migration V012, service + controller tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
68
ui/src/api/certificate-hooks.ts
Normal file
68
ui/src/api/certificate-hooks.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { api } from './client';
|
||||
|
||||
export interface CertificateResponse {
|
||||
id: string;
|
||||
status: string;
|
||||
subject: string;
|
||||
issuer: string;
|
||||
notBefore: string;
|
||||
notAfter: string;
|
||||
fingerprint: string;
|
||||
hasCa: boolean;
|
||||
selfSigned: boolean;
|
||||
activatedAt: string | null;
|
||||
archivedAt: string | null;
|
||||
}
|
||||
|
||||
export interface CertificateOverview {
|
||||
active: CertificateResponse | null;
|
||||
staged: CertificateResponse | null;
|
||||
archived: CertificateResponse | null;
|
||||
staleTenantCount: number;
|
||||
}
|
||||
|
||||
export interface StageResponse {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
certificate: CertificateResponse | null;
|
||||
}
|
||||
|
||||
export function useVendorCertificates() {
|
||||
return useQuery<CertificateOverview>({
|
||||
queryKey: ['vendor', 'certificates'],
|
||||
queryFn: () => api.get('/vendor/certificates'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useStageCertificate() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<StageResponse, Error, FormData>({
|
||||
mutationFn: (formData) => api.post('/vendor/certificates/stage', formData),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'certificates'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useActivateCertificate() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<void, Error, void>({
|
||||
mutationFn: () => api.post('/vendor/certificates/activate'),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'certificates'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useRestoreCertificate() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<void, Error, void>({
|
||||
mutationFn: () => api.post('/vendor/certificates/restore'),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'certificates'] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDiscardStaged() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<void, Error, void>({
|
||||
mutationFn: () => api.delete('/vendor/certificates/staged'),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'certificates'] }),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user