diff --git a/frontend/src/api/backend/uploadCertificate.ts b/frontend/src/api/backend/uploadCertificate.ts index 94549a79..b28c4e09 100644 --- a/frontend/src/api/backend/uploadCertificate.ts +++ b/frontend/src/api/backend/uploadCertificate.ts @@ -1,14 +1,9 @@ import * as api from "./base"; import type { Certificate } from "./models"; -export async function uploadCertificate( - id: number, - certificate: string, - certificateKey: string, - intermediateCertificate?: string, -): Promise { +export async function uploadCertificate(id: number, data: FormData): Promise { return await api.post({ url: `/nginx/certificates/${id}/upload`, - data: { certificate, certificateKey, intermediateCertificate }, + data, }); } diff --git a/frontend/src/api/backend/validateCertificate.ts b/frontend/src/api/backend/validateCertificate.ts index a1901e03..d404e514 100644 --- a/frontend/src/api/backend/validateCertificate.ts +++ b/frontend/src/api/backend/validateCertificate.ts @@ -1,13 +1,9 @@ import * as api from "./base"; import type { ValidatedCertificateResponse } from "./responseTypes"; -export async function validateCertificate( - certificate: string, - certificateKey: string, - intermediateCertificate?: string, -): Promise { +export async function validateCertificate(data: FormData): Promise { return await api.post({ url: "/nginx/certificates/validate", - data: { certificate, certificateKey, intermediateCertificate }, + data, }); } diff --git a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx index 4cb58bc6..d83adbd8 100644 --- a/frontend/src/components/Table/Formatter/DomainsFormatter.tsx +++ b/frontend/src/components/Table/Formatter/DomainsFormatter.tsx @@ -1,8 +1,10 @@ +import type { ReactNode } from "react"; import { DateTimeFormat, T } from "src/locale"; interface Props { domains: string[]; createdOn?: string; + niceName?: string; } const DomainLink = ({ domain }: { domain: string }) => { @@ -24,14 +26,28 @@ const DomainLink = ({ domain }: { domain: string }) => { ); }; -export function DomainsFormatter({ domains, createdOn }: Props) { +export function DomainsFormatter({ domains, createdOn, niceName }: Props) { + const elms: ReactNode[] = []; + if (domains.length === 0 && !niceName) { + elms.push( + + Unknown + , + ); + } + if (niceName) { + elms.push( + + {niceName} + , + ); + } + + domains.map((domain: string) => elms.push()); + return (
-
- {domains.map((domain: string) => ( - - ))} -
+
{...elms}
{createdOn ? (
diff --git a/frontend/src/locale/lang/en.json b/frontend/src/locale/lang/en.json index cf4f8bfe..511059c5 100644 --- a/frontend/src/locale/lang/en.json +++ b/frontend/src/locale/lang/en.json @@ -24,6 +24,9 @@ "auditlogs": "Audit Logs", "cancel": "Cancel", "certificate": "Certificate", + "certificate.custom-certificate": "Certificate", + "certificate.custom-certificate-key": "Certificate Key", + "certificate.custom-intermediate": "Intermediate Certificate", "certificate.in-use": "In Use", "certificate.none.subtitle": "No certificate assigned", "certificate.none.subtitle.for-http": "This host will not use HTTPS", @@ -31,6 +34,7 @@ "certificate.not-in-use": "Not Used", "certificates": "Certificates", "certificates.custom": "Custom Certificate", + "certificates.custom.warning": "Key files protected with a passphrase are not supported.", "certificates.dns.credentials": "Credentials File Content", "certificates.dns.credentials-note": "This plugin requires a configuration file containing an API token or other credentials for your provider", "certificates.dns.credentials-warning": "This data will be stored as plaintext in the database and in a file!", diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index 31684362..c81de9f0 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -74,6 +74,15 @@ "certificate": { "defaultMessage": "Certificate" }, + "certificate.custom-certificate": { + "defaultMessage": "Certificate" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "Certificate Key" + }, + "certificate.custom-intermediate": { + "defaultMessage": "Intermediate Certificate" + }, "certificate.in-use": { "defaultMessage": "In Use" }, @@ -95,6 +104,9 @@ "certificates.custom": { "defaultMessage": "Custom Certificate" }, + "certificates.custom.warning": { + "defaultMessage": "Key files protected with a passphrase are not supported." + }, "certificates.dns.credentials": { "defaultMessage": "Credentials File Content" }, diff --git a/frontend/src/modals/CustomCertificateModal.tsx b/frontend/src/modals/CustomCertificateModal.tsx index 1a67a2fc..7fe2f4bd 100644 --- a/frontend/src/modals/CustomCertificateModal.tsx +++ b/frontend/src/modals/CustomCertificateModal.tsx @@ -1,11 +1,14 @@ +import { IconAlertTriangle } from "@tabler/icons-react"; +import { useQueryClient } from "@tanstack/react-query"; import EasyModal, { type InnerModalProps } from "ez-modal-react"; -import { Form, Formik } from "formik"; +import { Field, Form, Formik } from "formik"; import { type ReactNode, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; -import { Button, DomainNamesField } from "src/components"; -import { useSetProxyHost } from "src/hooks"; +import { type Certificate, createCertificate, uploadCertificate, validateCertificate } from "src/api/backend"; +import { Button } from "src/components"; import { T } from "src/locale"; +import { validateString } from "src/modules/Validations"; import { showObjectSuccess } from "src/notifications"; const showCustomCertificateModal = () => { @@ -13,7 +16,7 @@ const showCustomCertificateModal = () => { }; const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModalProps) => { - const { mutate: setProxyHost } = useSetProxyHost(); + const queryClient = useQueryClient(); const [errorMsg, setErrorMsg] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); @@ -22,17 +25,35 @@ const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModal setIsSubmitting(true); setErrorMsg(null); - setProxyHost(values, { - onError: (err: any) => setErrorMsg(), - onSuccess: () => { - showObjectSuccess("certificate", "saved"); - remove(); - }, - onSettled: () => { - setIsSubmitting(false); - setSubmitting(false); - }, - }); + try { + const { niceName, provider, certificate, certificateKey, intermediateCertificate } = values; + const formData = new FormData(); + + formData.append("certificate", certificate); + formData.append("certificate_key", certificateKey); + if (intermediateCertificate !== null) { + formData.append("intermediate_certificate", intermediateCertificate); + } + + // Validate + await validateCertificate(formData); + + // Create certificate, as other without anything else + const cert = await createCertificate({ niceName, provider } as Certificate); + + // Upload the certificates to the created certificate + await uploadCertificate(cert.id, formData); + + // Success + showObjectSuccess("certificate", "saved"); + remove(); + } catch (err: any) { + setErrorMsg(); + } + + queryClient.invalidateQueries({ queryKey: ["certificates"] }); + setIsSubmitting(false); + setSubmitting(false); }; return ( @@ -40,7 +61,11 @@ const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModal - + @@ -57,9 +82,128 @@ const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModal {errorMsg}
-
asd
- +

+ + +

+ + {({ field, form }: any) => ( +
+ + + {form.errors.niceName ? ( +
+ {form.errors.niceName && form.touched.niceName + ? form.errors.niceName + : null} +
+ ) : null} +
+ )} +
+ + {({ field, form }: any) => ( +
+ + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.certificateKey ? ( +
+ {form.errors.certificateKey && form.touched.certificateKey + ? form.errors.certificateKey + : null} +
+ ) : null} +
+ )} +
+ + {({ field, form }: any) => ( +
+ + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.certificate ? ( +
+ {form.errors.certificate && form.touched.certificate + ? form.errors.certificate + : null} +
+ ) : null} +
+ )} +
+ + {({ field, form }: any) => ( +
+ + { + form.setFieldValue( + field.name, + event.currentTarget.files?.length + ? event.currentTarget.files[0] + : null, + ); + }} + /> + {form.errors.intermediateCertificate ? ( +
+ {form.errors.intermediateCertificate && + form.touched.intermediateCertificate + ? form.errors.intermediateCertificate + : null} +
+ ) : null} +
+ )} +
@@ -70,7 +214,7 @@ const CustomCertificateModal = EasyModal.create(({ visible, remove }: InnerModal