Resolve PR issues

This commit is contained in:
Tuan Dang
2024-06-12 13:11:21 -07:00
parent 868d0345d6
commit f5322abe85
26 changed files with 244 additions and 243 deletions
@@ -1,9 +1,9 @@
import { useEffect } from "react";
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import FileSaver from "file-saver";
import { IconButton } from "@app/components/v2";
import { useToggle } from "@app/hooks";
import { IconButton, Tooltip } from "@app/components/v2";
import { useTimedReset } from "@app/hooks";
type Props = {
serialNumber: string;
@@ -18,42 +18,28 @@ export const CertificateContent = ({
certificateChain,
privateKey
}: Props) => {
const [isSerialNumberCopied, setIsSerialNumberCopied] = useToggle(false);
const [isCertificateCopied, setIsCertificateCopied] = useToggle(false);
const [isCertificateChainCopied, setIsCertificateChainCopied] = useToggle(false);
const [isCertificateSkCopied, setIsCertificateSkCopied] = useToggle(false);
useEffect(() => {
let timer: NodeJS.Timeout;
if (isSerialNumberCopied) {
timer = setTimeout(() => setIsSerialNumberCopied.off(), 2000);
const [copyTextSerialNumber, isCopyingSerialNumber, setCopyTextSerialNumber] =
useTimedReset<string>({
initialState: "Copy to clipboard"
});
const [copyTextCertificate, isCopyingCertificate, setCopyTextCertificate] = useTimedReset<string>(
{
initialState: "Copy to clipboard"
}
);
const [copyTextCertificateChain, isCopyingCertificateChain, setCopyTextCertificateChain] =
useTimedReset<string>({
initialState: "Copy to clipboard"
});
if (isCertificateCopied) {
timer = setTimeout(() => setIsCertificateCopied.off(), 2000);
}
if (isCertificateChainCopied) {
timer = setTimeout(() => setIsCertificateChainCopied.off(), 2000);
}
if (isCertificateSkCopied) {
timer = setTimeout(() => setIsCertificateSkCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isSerialNumberCopied, isCertificateCopied, isCertificateChainCopied, isCertificateSkCopied]);
const [copyTextCertificateSk, isCopyingCertificateSk, setCopyTextCertificateSk] =
useTimedReset<string>({
initialState: "Copy to clipboard"
});
const downloadTxtFile = (filename: string, content: string) => {
const blob = new Blob([content], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(blob, filename);
};
return (
@@ -61,51 +47,48 @@ export const CertificateContent = ({
<h2 className="mb-4">Serial Number</h2>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{serialNumber}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(serialNumber);
setIsSerialNumberCopied.on();
}}
>
<FontAwesomeIcon icon={isSerialNumberCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Click to copy
</span>
</IconButton>
</div>
<div className="mb-4 flex items-center justify-between">
<h2>Certificate Body</h2>
<div className="flex">
<Tooltip content={copyTextSerialNumber}>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(certificate);
setIsCertificateCopied.on();
navigator.clipboard.writeText(serialNumber);
setCopyTextSerialNumber("Copied");
}}
>
<FontAwesomeIcon icon={isCertificateCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("cert.pem", certificate);
}}
>
<FontAwesomeIcon icon={faDownload} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Download
</span>
<FontAwesomeIcon icon={isCopyingSerialNumber ? faCheck : faCopy} />
</IconButton>
</Tooltip>
</div>
<div className="mb-4 flex items-center justify-between">
<h2>Certificate Body</h2>
<div className="flex">
<Tooltip content={copyTextCertificate}>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(certificate);
setCopyTextCertificate("Copied");
}}
>
<FontAwesomeIcon icon={isCopyingCertificate ? faCheck : faCopy} />
</IconButton>
</Tooltip>
<Tooltip content="Download">
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("cert.pem", certificate);
}}
>
<FontAwesomeIcon icon={faDownload} />
</IconButton>
</Tooltip>
</div>
</div>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
@@ -116,33 +99,31 @@ export const CertificateContent = ({
<div className="mb-4 flex items-center justify-between">
<h2>Certificate Chain</h2>
<div className="flex">
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(certificateChain);
setIsCertificateChainCopied.on();
}}
>
<FontAwesomeIcon icon={isCertificateChainCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("chain.pem", certificateChain);
}}
>
<FontAwesomeIcon icon={faDownload} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Download
</span>
</IconButton>
<Tooltip content={copyTextCertificateChain}>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(certificateChain);
setCopyTextCertificateChain("Copied");
}}
>
<FontAwesomeIcon icon={isCopyingCertificateChain ? faCheck : faCopy} />
</IconButton>
</Tooltip>
<Tooltip content="Download">
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("chain.pem", certificateChain);
}}
>
<FontAwesomeIcon icon={faDownload} />
</IconButton>
</Tooltip>
</div>
</div>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
@@ -155,33 +136,31 @@ export const CertificateContent = ({
<div className="mb-4 flex items-center justify-between">
<h2>Certificate Private Key</h2>
<div className="flex">
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(privateKey);
setIsCertificateSkCopied.on();
}}
>
<FontAwesomeIcon icon={isCertificateSkCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("private_key.txt", privateKey);
}}
>
<FontAwesomeIcon icon={faDownload} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Download
</span>
</IconButton>
<Tooltip content={copyTextCertificateSk}>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(privateKey);
setCopyTextCertificateSk("Copied");
}}
>
<FontAwesomeIcon icon={isCopyingCertificateSk ? faCheck : faCopy} />
</IconButton>
</Tooltip>
<Tooltip content={copyTextCertificateSk}>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("private_key.txt", privateKey);
}}
>
<FontAwesomeIcon icon={faDownload} />
</IconButton>
</Tooltip>
</div>
</div>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
@@ -1,7 +1,6 @@
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
@@ -21,21 +20,11 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "./CertificateContent";
const isValidDate = (dateString: string) => {
if (dateString === "") return true;
const date = new Date(dateString);
return !Number.isNaN(date.getTime());
};
const schema = z.object({
caId: z.string(),
friendlyName: z.string(),
commonName: z.string().trim().min(1),
ttl: z.string().trim().optional(),
notAfter: z
.string()
.trim()
.refine(isValidDate, { message: "Invalid date format" })
.transform((val) => (val === "" ? undefined : val))
ttl: z.string().trim()
});
export type FormData = z.infer<typeof schema>;
@@ -80,31 +69,30 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
if (cert) {
reset({
caId: cert.caId,
friendlyName: cert.friendlyName,
commonName: cert.commonName,
ttl: "",
notAfter: format(new Date(cert.notAfter), "yyyy-MM-dd")
ttl: ""
});
} else {
reset({
caId: "",
friendlyName: "",
commonName: "",
ttl: "",
notAfter: ""
ttl: ""
});
}
}, [cert]);
const onFormSubmit = async ({ caId, commonName, ttl, notAfter }: FormData) => {
const onFormSubmit = async ({ caId, friendlyName, commonName, ttl }: FormData) => {
try {
if (!currentWorkspace?.slug) return;
const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({
projectSlug: currentWorkspace.slug,
caId,
friendlyName,
commonName,
ttl: ttl ? Number(ttl) : undefined,
notBefore: new Date().toISOString(),
notAfter
ttl
});
reset();
@@ -175,6 +163,20 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="friendlyName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Friendly Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="My Certificate" isDisabled={Boolean(cert)} />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
@@ -195,25 +197,16 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
name="ttl"
render={({ field, fieldState: { error } }) => (
<FormControl
label="TTL (seconds)"
label="TTL"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="86400" isDisabled={Boolean(cert)} />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="notAfter"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Valid Until"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="YYYY-MM-DD" isDisabled={Boolean(cert)} />
<Input
{...field}
placeholder="2 days, 1d, 2h, 1y, ..."
isDisabled={Boolean(cert)}
/>
</FormControl>
)}
/>
@@ -53,7 +53,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
<Table>
<THead>
<Tr>
<Th>Common Name</Th>
<Th>Friendly Name</Th>
<Th>Status</Th>
<Th>Valid Until</Th>
<Th />
@@ -67,7 +67,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
data.map((certificate) => {
return (
<Tr className="h-10" key={`certificate-${certificate.id}`}>
<Td>{certificate.commonName}</Td>
<Td>{certificate.friendlyName}</Td>
<Td>{certStatusToNameMap[certificate.status]}</Td>
<Td>
{certificate.notAfter