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:
@@ -17,12 +17,14 @@ describe('certificates API', () => {
|
||||
});
|
||||
|
||||
const mockCert: Certificate = {
|
||||
id: 1,
|
||||
domain: 'example.com',
|
||||
uuid: 'abc-123',
|
||||
domains: 'example.com',
|
||||
issuer: 'Let\'s Encrypt',
|
||||
expires_at: '2023-01-01',
|
||||
status: 'valid',
|
||||
provider: 'letsencrypt',
|
||||
has_key: true,
|
||||
in_use: false,
|
||||
};
|
||||
|
||||
it('getCertificates calls client.get', async () => {
|
||||
@@ -47,7 +49,7 @@ describe('certificates API', () => {
|
||||
|
||||
it('deleteCertificate calls client.delete', async () => {
|
||||
vi.mocked(client.delete).mockResolvedValue({ data: {} });
|
||||
await deleteCertificate(1);
|
||||
expect(client.delete).toHaveBeenCalledWith('/certificates/1');
|
||||
await deleteCertificate('abc-123');
|
||||
expect(client.delete).toHaveBeenCalledWith('/certificates/abc-123');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,53 +1,123 @@
|
||||
import client from './client'
|
||||
|
||||
/** Represents an SSL/TLS certificate. */
|
||||
export interface Certificate {
|
||||
id?: number
|
||||
uuid: string
|
||||
name?: string
|
||||
domain: string
|
||||
common_name?: string
|
||||
domains: string
|
||||
issuer: string
|
||||
issuer_org?: string
|
||||
fingerprint?: string
|
||||
serial_number?: string
|
||||
key_type?: string
|
||||
expires_at: string
|
||||
not_before?: string
|
||||
status: 'valid' | 'expiring' | 'expired' | 'untrusted'
|
||||
provider: string
|
||||
chain_depth?: number
|
||||
has_key: boolean
|
||||
in_use: boolean
|
||||
/** @deprecated Use uuid instead */
|
||||
id?: number
|
||||
}
|
||||
|
||||
export interface AssignedHost {
|
||||
uuid: string
|
||||
name: string
|
||||
domain_names: string
|
||||
}
|
||||
|
||||
export interface ChainEntry {
|
||||
subject: string
|
||||
issuer: string
|
||||
expires_at: string
|
||||
}
|
||||
|
||||
export interface CertificateDetail extends Certificate {
|
||||
assigned_hosts: AssignedHost[]
|
||||
chain: ChainEntry[]
|
||||
auto_renew: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean
|
||||
common_name: string
|
||||
domains: string[]
|
||||
issuer_org: string
|
||||
expires_at: string
|
||||
key_match: boolean
|
||||
chain_valid: boolean
|
||||
chain_depth: number
|
||||
warnings: string[]
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all SSL certificates.
|
||||
* @returns Promise resolving to array of Certificate objects
|
||||
* @throws {AxiosError} If the request fails
|
||||
*/
|
||||
export async function getCertificates(): Promise<Certificate[]> {
|
||||
const response = await client.get<Certificate[]>('/certificates')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a new SSL certificate with its private key.
|
||||
* @param name - Display name for the certificate
|
||||
* @param certFile - The certificate file (PEM format)
|
||||
* @param keyFile - The private key file (PEM format)
|
||||
* @returns Promise resolving to the created Certificate
|
||||
* @throws {AxiosError} If upload fails or certificate is invalid
|
||||
*/
|
||||
export async function uploadCertificate(name: string, certFile: File, keyFile: File): Promise<Certificate> {
|
||||
export async function getCertificateDetail(uuid: string): Promise<CertificateDetail> {
|
||||
const response = await client.get<CertificateDetail>(`/certificates/${uuid}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
export async function uploadCertificate(
|
||||
name: string,
|
||||
certFile: File,
|
||||
keyFile?: File,
|
||||
chainFile?: File,
|
||||
): Promise<Certificate> {
|
||||
const formData = new FormData()
|
||||
formData.append('name', name)
|
||||
formData.append('certificate_file', certFile)
|
||||
formData.append('key_file', keyFile)
|
||||
if (keyFile) formData.append('key_file', keyFile)
|
||||
if (chainFile) formData.append('chain_file', chainFile)
|
||||
|
||||
const response = await client.post<Certificate>('/certificates', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an SSL certificate.
|
||||
* @param id - The ID of the certificate to delete
|
||||
* @throws {AxiosError} If deletion fails or certificate not found
|
||||
*/
|
||||
export async function deleteCertificate(id: number): Promise<void> {
|
||||
await client.delete(`/certificates/${id}`)
|
||||
export async function updateCertificate(uuid: string, name: string): Promise<Certificate> {
|
||||
const response = await client.put<Certificate>(`/certificates/${uuid}`, { name })
|
||||
return response.data
|
||||
}
|
||||
|
||||
export async function deleteCertificate(uuid: string): Promise<void> {
|
||||
await client.delete(`/certificates/${uuid}`)
|
||||
}
|
||||
|
||||
export async function exportCertificate(
|
||||
uuid: string,
|
||||
format: string,
|
||||
includeKey: boolean,
|
||||
password?: string,
|
||||
pfxPassword?: string,
|
||||
): Promise<Blob> {
|
||||
const response = await client.post(
|
||||
`/certificates/${uuid}/export`,
|
||||
{ format, include_key: includeKey, password, pfx_password: pfxPassword },
|
||||
{ responseType: 'blob' },
|
||||
)
|
||||
return response.data as Blob
|
||||
}
|
||||
|
||||
export async function validateCertificate(
|
||||
certFile: File,
|
||||
keyFile?: File,
|
||||
chainFile?: File,
|
||||
): Promise<ValidationResult> {
|
||||
const formData = new FormData()
|
||||
formData.append('certificate_file', certFile)
|
||||
if (keyFile) formData.append('key_file', keyFile)
|
||||
if (chainFile) formData.append('chain_file', chainFile)
|
||||
|
||||
const response = await client.post<ValidationResult>('/certificates/validate', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user