feat: add certificate export and upload dialogs

- Implemented CertificateExportDialog for exporting certificates in various formats (PEM, PFX, DER) with options to include private keys and set passwords.
- Created CertificateUploadDialog for uploading certificates, including validation and support for multiple file types (certificates, private keys, chain files).
- Updated DeleteCertificateDialog to use 'domains' instead of 'domain' for consistency.
- Refactored BulkDeleteCertificateDialog and DeleteCertificateDialog tests to accommodate changes in certificate structure.
- Added FileDropZone component for improved file upload experience.
- Enhanced translation files with new keys for certificate management features.
- Updated Certificates page to utilize the new CertificateUploadDialog and clean up the upload logic.
- Adjusted Dashboard and ProxyHosts pages to reflect changes in certificate data structure.
This commit is contained in:
GitHub Actions
2026-04-11 23:32:22 +00:00
parent e49ea7061a
commit 30c9d735aa
26 changed files with 1428 additions and 531 deletions
+112 -2
View File
@@ -1,6 +1,16 @@
import { useQuery } from '@tanstack/react-query'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { getCertificates } from '../api/certificates'
import {
getCertificates,
getCertificateDetail,
uploadCertificate,
updateCertificate,
deleteCertificate,
exportCertificate,
validateCertificate,
} from '../api/certificates'
import type { CertificateDetail } from '../api/certificates'
interface UseCertificatesOptions {
refetchInterval?: number | false
@@ -20,3 +30,103 @@ export function useCertificates(options?: UseCertificatesOptions) {
refetch,
}
}
export function useCertificateDetail(uuid: string | null) {
const { data, isLoading, error } = useQuery({
queryKey: ['certificates', uuid],
queryFn: () => getCertificateDetail(uuid!),
enabled: !!uuid,
})
return {
detail: data as CertificateDetail | undefined,
isLoading,
error,
}
}
export function useUploadCertificate() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (params: {
name: string
certFile: File
keyFile?: File
chainFile?: File
}) => uploadCertificate(params.name, params.certFile, params.keyFile, params.chainFile),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['certificates'] })
},
})
}
export function useUpdateCertificate() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (params: { uuid: string; name: string }) =>
updateCertificate(params.uuid, params.name),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['certificates'] })
},
})
}
export function useDeleteCertificate() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (uuid: string) => deleteCertificate(uuid),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['certificates'] })
queryClient.invalidateQueries({ queryKey: ['proxyHosts'] })
},
})
}
export function useExportCertificate() {
return useMutation({
mutationFn: (params: {
uuid: string
format: string
includeKey: boolean
password?: string
pfxPassword?: string
}) =>
exportCertificate(
params.uuid,
params.format,
params.includeKey,
params.password,
params.pfxPassword,
),
})
}
export function useValidateCertificate() {
return useMutation({
mutationFn: (params: {
certFile: File
keyFile?: File
chainFile?: File
}) => validateCertificate(params.certFile, params.keyFile, params.chainFile),
})
}
export function useBulkDeleteCertificates() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (uuids: string[]) => {
const results = await Promise.allSettled(uuids.map(uuid => deleteCertificate(uuid)))
const failed = results.filter(r => r.status === 'rejected').length
const succeeded = results.filter(r => r.status === 'fulfilled').length
return { succeeded, failed }
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['certificates'] })
queryClient.invalidateQueries({ queryKey: ['proxyHosts'] })
},
})
}