mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 21:26:04 +00:00
misc: added UI for load backup
This commit is contained in:
+194
-25
@@ -1,20 +1,32 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, FormControl, Select, SelectItem } from "@app/components/v2";
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useOrganization,
|
useOrganization,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useGetActiveProjectKms, useGetExternalKmsList, useUpdateProjectKms } from "@app/hooks/api";
|
import { useGetActiveProjectKms, useGetExternalKmsList, useUpdateProjectKms } from "@app/hooks/api";
|
||||||
import { fetchProjectKmsBackup } from "@app/hooks/api/kms/queries";
|
import { fetchProjectKmsBackup } from "@app/hooks/api/kms/queries";
|
||||||
|
import { Organization, Workspace } from "@app/hooks/api/types";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
kmsKeyId: z.string()
|
kmsKeyId: z.string()
|
||||||
@@ -24,6 +36,155 @@ type TForm = z.infer<typeof formSchema>;
|
|||||||
|
|
||||||
const INTERNAL_KMS_KEY_ID = "internal";
|
const INTERNAL_KMS_KEY_ID = "internal";
|
||||||
|
|
||||||
|
const BackupConfirmationModal = ({
|
||||||
|
isOpen,
|
||||||
|
onOpenChange,
|
||||||
|
org,
|
||||||
|
workspace
|
||||||
|
}: {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (state: boolean) => void;
|
||||||
|
org?: Organization;
|
||||||
|
workspace?: Workspace;
|
||||||
|
}) => {
|
||||||
|
const downloadKmsBackup = async () => {
|
||||||
|
if (!workspace || !org) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { secretManager } = await fetchProjectKmsBackup(workspace.id);
|
||||||
|
|
||||||
|
const [, , kmsFunction] = secretManager.split(".");
|
||||||
|
const file = secretManager;
|
||||||
|
|
||||||
|
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
|
||||||
|
FileSaver.saveAs(blob, `kms-backup-${org.slug}-${workspace.slug}-${kmsFunction}.infisical.txt`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<ModalContent title="Create KMS backup">
|
||||||
|
<p className="mb-8 text-bunker-300">
|
||||||
|
In case of interruptions with your configured external KMS, use this generated backup to
|
||||||
|
set the project's KMS back to the default Infisical KMS.
|
||||||
|
</p>
|
||||||
|
<p className="mb-8 text-bunker-300">
|
||||||
|
Note: The project data key will be encrypted the organization's default Infisical
|
||||||
|
KMS.
|
||||||
|
</p>
|
||||||
|
<Button onClick={downloadKmsBackup}>Continue</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => onOpenChange(false)}
|
||||||
|
colorSchema="secondary"
|
||||||
|
variant="plain"
|
||||||
|
className="ml-4"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LoadBackupModal = ({
|
||||||
|
isOpen,
|
||||||
|
onOpenChange,
|
||||||
|
org,
|
||||||
|
workspace
|
||||||
|
}: {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (state: boolean) => void;
|
||||||
|
org?: Organization;
|
||||||
|
workspace?: Workspace;
|
||||||
|
}) => {
|
||||||
|
const fileUploadRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [backupContent, setBackupContent] = useState("");
|
||||||
|
const [backupFileName, setBackupFileName] = useState("");
|
||||||
|
|
||||||
|
const uploadKmsBackup = async () => {
|
||||||
|
if (!workspace || !org) {
|
||||||
|
// eslint-disable-next-line no-useless-return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseFile = (file?: File) => {
|
||||||
|
if (!file) {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to parse uploaded file.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
if (!event?.target?.result) return;
|
||||||
|
const data = event.target.result.toString();
|
||||||
|
setBackupContent(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.readAsText(file);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
parseFile(e.target?.files?.[0]);
|
||||||
|
setBackupFileName(e.target?.files?.[0]?.name || "");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onOpenChange={(state: boolean) => {
|
||||||
|
setBackupContent("");
|
||||||
|
setBackupFileName("");
|
||||||
|
onOpenChange(state);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent title="Load KMS backup">
|
||||||
|
<p className="mb-8 text-bunker-300">
|
||||||
|
By loading a backup, the project's KMS will be switched to the default Infisical KMS.
|
||||||
|
</p>
|
||||||
|
<p className="mb-8 text-bunker-300">
|
||||||
|
If left unmodified, the filename of the backup should look like the following:
|
||||||
|
<p className="mt-2 font-bold">{`kms-backup-${org?.slug}-${workspace?.slug}-secretManager.infisical.txt`}</p>
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<input
|
||||||
|
id="fileSelect"
|
||||||
|
className="hidden"
|
||||||
|
type="file"
|
||||||
|
accept=".txt"
|
||||||
|
onChange={handleFileUpload}
|
||||||
|
ref={fileUploadRef}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
className="p-10"
|
||||||
|
ariaLabel="upload-backup"
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
fileUploadRef?.current?.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faUpload} size="3x" />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
{backupFileName && <div className="mt-2 flex justify-center">{backupFileName}</div>}
|
||||||
|
{backupContent && (
|
||||||
|
<Button onClick={uploadKmsBackup} className="mt-10 w-fit">
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const EncryptionTab = () => {
|
export const EncryptionTab = () => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
@@ -32,6 +193,10 @@ export const EncryptionTab = () => {
|
|||||||
const { data: activeKms } = useGetActiveProjectKms(currentWorkspace?.id!);
|
const { data: activeKms } = useGetActiveProjectKms(currentWorkspace?.id!);
|
||||||
|
|
||||||
const { mutateAsync: updateProjectKms } = useUpdateProjectKms(currentWorkspace?.id!);
|
const { mutateAsync: updateProjectKms } = useUpdateProjectKms(currentWorkspace?.id!);
|
||||||
|
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([
|
||||||
|
"createBackupConfirmation",
|
||||||
|
"loadBackup"
|
||||||
|
] as const);
|
||||||
const [kmsKeyId, setKmsKeyId] = useState("");
|
const [kmsKeyId, setKmsKeyId] = useState("");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -72,23 +237,6 @@ export const EncryptionTab = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadKmsBackup = async () => {
|
|
||||||
if (!currentWorkspace || !currentOrg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { secretManager } = await fetchProjectKmsBackup(currentWorkspace.id);
|
|
||||||
|
|
||||||
const [, , kmsFunction] = secretManager.split(".");
|
|
||||||
const file = secretManager;
|
|
||||||
|
|
||||||
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
|
|
||||||
FileSaver.saveAs(
|
|
||||||
blob,
|
|
||||||
`kms-backup-${currentOrg.slug}-${currentWorkspace.slug}-${kmsFunction}.infisical.txt`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onUpdateProjectKms)}
|
onSubmit={handleSubmit(onUpdateProjectKms)}
|
||||||
@@ -96,12 +244,21 @@ export const EncryptionTab = () => {
|
|||||||
>
|
>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">Key Management</h2>
|
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">Key Management</h2>
|
||||||
<div className="space-x-2">
|
{kmsKeyId !== INTERNAL_KMS_KEY_ID && (
|
||||||
<Button colorSchema="secondary">Load Backup</Button>
|
<div className="space-x-2">
|
||||||
<Button colorSchema="secondary" onClick={downloadKmsBackup}>
|
<Button colorSchema="secondary" onClick={() => handlePopUpOpen("loadBackup")}>
|
||||||
Create Backup
|
Load Backup
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("createBackupConfirmation");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create Backup
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mb-4 text-gray-400">
|
<p className="mb-4 text-gray-400">
|
||||||
@@ -150,6 +307,18 @@ export const EncryptionTab = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
<BackupConfirmationModal
|
||||||
|
isOpen={popUp.createBackupConfirmation.isOpen}
|
||||||
|
onOpenChange={(state: boolean) => handlePopUpToggle("createBackupConfirmation", state)}
|
||||||
|
org={currentOrg}
|
||||||
|
workspace={currentWorkspace}
|
||||||
|
/>
|
||||||
|
<LoadBackupModal
|
||||||
|
isOpen={popUp.loadBackup.isOpen}
|
||||||
|
onOpenChange={(state: boolean) => handlePopUpToggle("loadBackup", state)}
|
||||||
|
org={currentOrg}
|
||||||
|
workspace={currentWorkspace}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user