feat: improved migration wizard to info user prerequisite check list

This commit is contained in:
=
2024-07-30 23:19:32 +05:30
parent e2caa98c74
commit 549d388f59
9 changed files with 254 additions and 93 deletions
@@ -489,11 +489,26 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
} }
}; };
const deleteByProjectId = async (projectId: string, tx?: Knex) => {
try {
const query = await (tx || db.replicaNode())(TableName.SecretApprovalRequest)
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.where({ projectId })
.delete();
return query;
} catch (error) {
throw new DatabaseError({ error, name: "DeleteByProjectId" });
}
};
return { return {
...secretApprovalRequestOrm, ...secretApprovalRequestOrm,
findById, findById,
findProjectRequestCount, findProjectRequestCount,
findByProjectId, findByProjectId,
findByProjectIdBridgeSecretV2 findByProjectIdBridgeSecretV2,
deleteByProjectId
}; };
}; };
@@ -1274,7 +1274,7 @@ export const secretApprovalRequestServiceFactory = ({
const commitsGroupByKey = groupBy(approvalCommits, (i) => i.key); const commitsGroupByKey = groupBy(approvalCommits, (i) => i.key);
if (tagIds.length) { if (tagIds.length) {
await secretApprovalRequestSecretDAL.insertApprovalSecretTags( await secretApprovalRequestSecretDAL.insertApprovalSecretV2Tags(
Object.keys(commitTagIds).flatMap((blindIndex) => Object.keys(commitTagIds).flatMap((blindIndex) =>
commitTagIds[blindIndex] commitTagIds[blindIndex]
? commitTagIds[blindIndex].map((tagId) => ({ ? commitTagIds[blindIndex].map((tagId) => ({
@@ -743,7 +743,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
db.ref("envId").withSchema(TableName.SnapshotSecret).as("snapshotEnvId"), db.ref("envId").withSchema(TableName.SnapshotSecret).as("snapshotEnvId"),
db.ref("id").withSchema(TableName.SecretVersionTag).as("secretVersionTagId"), db.ref("id").withSchema(TableName.SecretVersionTag).as("secretVersionTagId"),
db.ref("secret_versionsId").withSchema(TableName.SecretVersionTag).as("secretVersionTagSecretId"), db.ref("secret_versionsId").withSchema(TableName.SecretVersionTag).as("secretVersionTagSecretId"),
db.ref("secret_versionsId").withSchema(TableName.SecretVersionTag).as("secretVersionTagSecretTagId"), db.ref("secret_tagsId").withSchema(TableName.SecretVersionTag).as("secretVersionTagSecretTagId"),
db.raw( db.raw(
`DENSE_RANK() OVER (partition by ${TableName.Snapshot}."id" ORDER BY ${TableName.SecretVersion}."createdAt") as rank` `DENSE_RANK() OVER (partition by ${TableName.Snapshot}."id" ORDER BY ${TableName.SecretVersion}."createdAt") as rank`
) )
@@ -789,6 +789,19 @@ export const snapshotDALFactory = (db: TDbClient) => {
} }
}; };
const deleteSnapshotsAboveLimit = async (folderId: string, n = 15, tx?: Knex) => {
try {
const query = await (tx || db.replicaNode())(TableName.Snapshot)
.orderBy(`${TableName.Snapshot}.createdAt`, "desc")
.where(`${TableName.Snapshot}.folderId`, folderId)
.offset(n)
.delete();
return query;
} catch (error) {
throw new DatabaseError({ error, name: "DeleteSnapshotsAboveLimit" });
}
};
return { return {
...secretSnapshotOrm, ...secretSnapshotOrm,
findById, findById,
@@ -799,6 +812,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
findSecretSnapshotDataById, findSecretSnapshotDataById,
findSecretSnapshotV2DataById, findSecretSnapshotV2DataById,
pruneExcessSnapshots, pruneExcessSnapshots,
findNSecretV1SnapshotByFolderId findNSecretV1SnapshotByFolderId,
deleteSnapshotsAboveLimit
}; };
}; };
+2 -2
View File
@@ -723,8 +723,8 @@ export const registerRoutes = async (
secretRotationDAL, secretRotationDAL,
integrationAuthDAL, integrationAuthDAL,
snapshotDAL, snapshotDAL,
secretApprovalRequestSecretDAL, snapshotSecretV2BridgeDAL,
snapshotSecretV2BridgeDAL secretApprovalRequestDAL
}); });
const secretImportService = secretImportServiceFactory({ const secretImportService = secretImportServiceFactory({
licenseService, licenseService,
+78 -76
View File
@@ -2,7 +2,7 @@
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { ProjectUpgradeStatus, ProjectVersion, TSecretSnapshotSecretsV2, TSecretVersionsV2 } from "@app/db/schemas"; import { ProjectUpgradeStatus, ProjectVersion, TSecretSnapshotSecretsV2, TSecretVersionsV2 } from "@app/db/schemas";
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal"; import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal"; import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal";
@@ -76,11 +76,8 @@ type TSecretQueueFactoryDep = {
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">; secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">; secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
secretRotationDAL: Pick<TSecretRotationDALFactory, "secretOutputV2InsertMany" | "find">; secretRotationDAL: Pick<TSecretRotationDALFactory, "secretOutputV2InsertMany" | "find">;
secretApprovalRequestSecretDAL: Pick< secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "deleteByProjectId">;
TSecretApprovalRequestSecretDALFactory, snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId" | "deleteSnapshotsAboveLimit">;
"findByProjectId" | "insertV2Bridge" | "insertApprovalSecretV2Tags"
>;
snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId">;
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany">; snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany">;
}; };
@@ -123,9 +120,9 @@ export const secretQueueFactory = ({
kmsService, kmsService,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
secretRotationDAL, secretRotationDAL,
secretApprovalRequestSecretDAL,
snapshotDAL, snapshotDAL,
snapshotSecretV2BridgeDAL snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL
}: TSecretQueueFactoryDep) => { }: TSecretQueueFactoryDep) => {
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => { const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -792,11 +789,6 @@ export const secretQueueFactory = ({
QueueJobs.ProjectV3Migration, QueueJobs.ProjectV3Migration,
{ projectId }, { projectId },
{ {
attempts: 2,
backoff: {
type: "exponential",
delay: 3000
},
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true removeOnFail: true
} }
@@ -805,12 +797,17 @@ export const secretQueueFactory = ({
queueService.start(QueueName.ProjectV3Migration, async (job) => { queueService.start(QueueName.ProjectV3Migration, async (job) => {
const { projectId } = job.data; const { projectId } = job.data;
const { botKey, shouldUseSecretV2Bridge: isProjectUpgradedToV3 } = await projectBotService.getBotKey(projectId); const {
if (isProjectUpgradedToV3) { botKey,
shouldUseSecretV2Bridge: isProjectUpgradedToV3,
project
} = await projectBotService.getBotKey(projectId);
if (isProjectUpgradedToV3 || project.upgradeStatus === ProjectUpgradeStatus.InProgress) {
return; return;
} }
if (!botKey) throw new BadRequestError({ message: "Bot not found" }); if (!botKey) throw new BadRequestError({ message: "Bot not found" });
await projectDAL.updateById(projectId, { upgradeStatus: ProjectUpgradeStatus.InProgress }); await projectDAL.updateById(projectId, { upgradeStatus: ProjectUpgradeStatus.InProgress });
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
projectId, projectId,
type: KmsDataKey.SecretManager type: KmsDataKey.SecretManager
@@ -884,7 +881,8 @@ export const secretQueueFactory = ({
await secretV2BridgeDAL.upsertSecretReferences(secretReferences, tx); await secretV2BridgeDAL.upsertSecretReferences(secretReferences, tx);
} }
const snapshots = await snapshotDAL.findNSecretV1SnapshotByFolderId(folderId, 10, tx); const SNAPSHOT_BATCH_SIZE = 15;
const snapshots = await snapshotDAL.findNSecretV1SnapshotByFolderId(folderId, SNAPSHOT_BATCH_SIZE, tx);
const projectV3SecretVersionsGroupById: Record<string, TSecretVersionsV2> = {}; const projectV3SecretVersionsGroupById: Record<string, TSecretVersionsV2> = {};
const projectV3SecretVersionTags: { secret_versions_v2Id: string; secret_tagsId: string }[] = []; const projectV3SecretVersionTags: { secret_versions_v2Id: string; secret_tagsId: string }[] = [];
const projectV3SnapshotSecrets: Omit<TSecretSnapshotSecretsV2, "id">[] = []; const projectV3SnapshotSecrets: Omit<TSecretSnapshotSecretsV2, "id">[] = [];
@@ -959,6 +957,7 @@ export const secretQueueFactory = ({
if (projectV3SnapshotSecrets.length) { if (projectV3SnapshotSecrets.length) {
await snapshotSecretV2BridgeDAL.insertMany(projectV3SnapshotSecrets, tx); await snapshotSecretV2BridgeDAL.insertMany(projectV3SnapshotSecrets, tx);
} }
await snapshotDAL.deleteSnapshotsAboveLimit(folderId, SNAPSHOT_BATCH_SIZE, tx);
} }
/* /*
* Secret Tag Migration * Secret Tag Migration
@@ -1056,67 +1055,70 @@ export const secretQueueFactory = ({
); );
/* /*
* approvals * approvals: we will delete all approvals this is because some secret versions may not be added yet
* Thus doesn't make sense for rest to be there
* */ * */
const projectV1ApprovalSecrets = await secretApprovalRequestSecretDAL.findByProjectId(projectId); await secretApprovalRequestDAL.deleteByProjectId(projectId, tx);
if (projectV1ApprovalSecrets.length) { // const projectV1ApprovalSecrets = await secretApprovalRequestSecretDAL.findByProjectId(projectId);
await secretApprovalRequestSecretDAL.insertV2Bridge( // if (projectV1ApprovalSecrets.length) {
projectV1ApprovalSecrets.map((el) => { // await secretApprovalRequestSecretDAL.insertV2Bridge(
const key = decryptSymmetric128BitHexKeyUTF8({ // projectV1ApprovalSecrets.map((el) => {
ciphertext: el.secretKeyCiphertext, // const key = decryptSymmetric128BitHexKeyUTF8({
iv: el.secretKeyIV, // ciphertext: el.secretKeyCiphertext,
tag: el.secretKeyTag, // iv: el.secretKeyIV,
key: botKey // tag: el.secretKeyTag,
}); // key: botKey
const value = decryptSymmetric128BitHexKeyUTF8({ // });
ciphertext: el.secretValueCiphertext, // const value = decryptSymmetric128BitHexKeyUTF8({
iv: el.secretValueIV, // ciphertext: el.secretValueCiphertext,
tag: el.secretValueTag, // iv: el.secretValueIV,
key: botKey // tag: el.secretValueTag,
}); // key: botKey
const comment = // });
el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV // const comment =
? decryptSymmetric128BitHexKeyUTF8({ // el.secretCommentCiphertext && el.secretCommentTag && el.secretCommentIV
ciphertext: el.secretCommentCiphertext, // ? decryptSymmetric128BitHexKeyUTF8({
iv: el.secretCommentIV, // ciphertext: el.secretCommentCiphertext,
tag: el.secretCommentTag, // iv: el.secretCommentIV,
key: botKey // tag: el.secretCommentTag,
}) // key: botKey
: ""; // })
const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob; // : "";
const encryptedComment = comment // const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob;
? secretManagerEncryptor({ plainText: Buffer.from(comment) }).cipherTextBlob // const encryptedComment = comment
: null; // ? secretManagerEncryptor({ plainText: Buffer.from(comment) }).cipherTextBlob
return { // : null;
id: el.id, // return {
createdAt: el.createdAt, // id: el.id,
updatedAt: el.updatedAt, // createdAt: el.createdAt,
skipMultilineEncoding: el.skipMultilineEncoding, // updatedAt: el.updatedAt,
encryptedComment, // skipMultilineEncoding: el.skipMultilineEncoding,
encryptedValue, // encryptedComment,
key, // encryptedValue,
version: el.version, // key,
metadata: el.metadata, // version: el.version,
reminderNote: el.secretReminderNote, // metadata: el.metadata,
reminderRepeatDays: el.secretReminderRepeatDays, // reminderNote: el.secretReminderNote,
requestId: el.requestId, // reminderRepeatDays: el.secretReminderRepeatDays,
op: el.op, // requestId: el.requestId,
secretId: el.secretId, // op: el.op,
secretVersion: el.secretVersion // secretId: el.secretId,
}; // secretVersion: el.secretVersion
}), // };
tx // }),
); // tx
} // );
const projectV1SecretApprovalSecretTags = projectV1ApprovalSecrets.flatMap((el) => // }
el.tags.map((tag) => ({ // const projectV1SecretApprovalSecretTags = projectV1ApprovalSecrets.flatMap((el) =>
secretId: tag.secretApprovalTagSecretId, // el.tags.map((tag) => ({
tagId: tag.secretApprovalTagId // secretId: tag.secretApprovalTagSecretId,
})) // tagId: tag.secretApprovalTagId
); // }))
if (projectV1SecretApprovalSecretTags.length) { // );
await secretApprovalRequestSecretDAL.insertApprovalSecretV2Tags(projectV1SecretApprovalSecretTags, tx); // if (projectV1SecretApprovalSecretTags.length) {
} // await secretApprovalRequestSecretDAL.insertApprovalSecretV2Tags(projectV1SecretApprovalSecretTags, tx);
// }
await projectDAL.updateById(projectId, { upgradeStatus: null, version: ProjectVersion.V3 }, tx); await projectDAL.updateById(projectId, { upgradeStatus: null, version: ProjectVersion.V3 }, tx);
}); });
}); });
@@ -4,6 +4,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import { import {
ProjectMembershipRole, ProjectMembershipRole,
ProjectUpgradeStatus,
SecretEncryptionAlgo, SecretEncryptionAlgo,
SecretKeyEncoding, SecretKeyEncoding,
SecretsSchema, SecretsSchema,
@@ -2666,8 +2667,11 @@ export const secretServiceFactory = ({
if (!hasRole(ProjectMembershipRole.Admin)) if (!hasRole(ProjectMembershipRole.Admin))
throw new BadRequestError({ message: "Only admins are allowed to take this action" }); throw new BadRequestError({ message: "Only admins are allowed to take this action" });
const { shouldUseSecretV2Bridge: isProjectV3 } = await projectBotService.getBotKey(projectId); const { shouldUseSecretV2Bridge: isProjectV3, project } = await projectBotService.getBotKey(projectId);
if (isProjectV3) throw new BadRequestError({ message: "project is already in v3" }); if (isProjectV3) throw new BadRequestError({ message: "project is already in v3" });
if (project.upgradeStatus === ProjectUpgradeStatus.InProgress)
throw new BadRequestError({ message: "project is upgrading" });
await secretQueueService.startSecretV2Migration(projectId); await secretQueueService.startSecretV2Migration(projectId);
return { message: "Migrating project to new KMS architecture" }; return { message: "Migrating project to new KMS architecture" };
}; };
@@ -14,6 +14,7 @@ export type CheckboxProps = Omit<
isChecked?: boolean; isChecked?: boolean;
isRequired?: boolean; isRequired?: boolean;
checkIndicatorBg?: string | undefined; checkIndicatorBg?: string | undefined;
isError?: boolean;
}; };
export const Checkbox = ({ export const Checkbox = ({
@@ -24,6 +25,7 @@ export const Checkbox = ({
isDisabled, isDisabled,
isRequired, isRequired,
checkIndicatorBg, checkIndicatorBg,
isError,
...props ...props
}: CheckboxProps): JSX.Element => { }: CheckboxProps): JSX.Element => {
return ( return (
@@ -46,7 +48,10 @@ export const Checkbox = ({
<FontAwesomeIcon icon={faCheck} size="sm" /> <FontAwesomeIcon icon={faCheck} size="sm" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
<label className="truncate whitespace-nowrap text-sm" htmlFor={id}> <label
className={twMerge("truncate whitespace-nowrap text-sm", isError && "text-red-400")}
htmlFor={id}
>
{children} {children}
{isRequired && <span className="pl-1 text-red">*</span>} {isRequired && <span className="pl-1 text-red">*</span>}
</label> </label>
@@ -28,6 +28,7 @@ import {
useGetWsTags useGetWsTags
} from "@app/hooks/api"; } from "@app/hooks/api";
import { SecretV2MigrationSection } from "../SecretOverviewPage/components/SecretV2MigrationSection";
import { ActionBar } from "./components/ActionBar"; import { ActionBar } from "./components/ActionBar";
import { CreateSecretForm } from "./components/CreateSecretForm"; import { CreateSecretForm } from "./components/CreateSecretForm";
import { DynamicSecretListView } from "./components/DynamicSecretListView"; import { DynamicSecretListView } from "./components/DynamicSecretListView";
@@ -231,6 +232,7 @@ export const SecretMainPage = () => {
return ( return (
<StoreProvider> <StoreProvider>
<div className="container mx-auto flex h-full flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]"> <div className="container mx-auto flex h-full flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]">
<SecretV2MigrationSection />
<div className="relative right-6 -top-2 mb-2 ml-6"> <div className="relative right-6 -top-2 mb-2 ml-6">
<NavHeader <NavHeader
pageName={t("dashboard.title")} pageName={t("dashboard.title")}
@@ -1,6 +1,14 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, Spinner } from "@app/components/v2"; import { Button, Checkbox, Modal, ModalContent, Spinner } from "@app/components/v2";
import { useProjectPermission, useWorkspace } from "@app/context"; import { useProjectPermission, useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useGetWorkspaceById, useMigrateProjectToV3 } from "@app/hooks/api"; import { useGetWorkspaceById, useMigrateProjectToV3 } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { ProjectVersion } from "@app/hooks/api/workspace/types";
@@ -11,9 +19,17 @@ enum ProjectUpgradeStatus {
Failed = "FAILED" Failed = "FAILED"
} }
const formSchema = z.object({
isCLIChecked: z.literal(true),
isOperatorChecked: z.literal(true),
doesKnowSnapshotLimit: z.literal(true),
shouldCloseOpenApprovals: z.literal(true)
});
export const SecretV2MigrationSection = () => { export const SecretV2MigrationSection = () => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["migrationInfo"] as const);
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data: workspaceDetails } = useGetWorkspaceById( const { data: workspaceDetails, refetch } = useGetWorkspaceById(
// if v3 no need to fetch // if v3 no need to fetch
currentWorkspace?.version === ProjectVersion.V3 ? "" : currentWorkspace?.id || "", currentWorkspace?.version === ProjectVersion.V3 ? "" : currentWorkspace?.id || "",
{ {
@@ -23,17 +39,26 @@ export const SecretV2MigrationSection = () => {
); );
const { membership } = useProjectPermission(); const { membership } = useProjectPermission();
const migrateProjectToV3 = useMigrateProjectToV3(); const migrateProjectToV3 = useMigrateProjectToV3();
const { handleSubmit, control, reset } = useForm({ resolver: zodResolver(formSchema) });
useEffect(() => {
if (!popUp.migrationInfo.isOpen) {
reset();
}
}, [popUp.migrationInfo.isOpen]);
const isProjectUpgraded = workspaceDetails?.version === ProjectVersion.V3; const isProjectUpgraded = workspaceDetails?.version === ProjectVersion.V3;
if (isProjectUpgraded || currentWorkspace?.version === ProjectVersion.V3) return null; if (isProjectUpgraded || currentWorkspace?.version === ProjectVersion.V3) return null;
const isUpgrading = workspaceDetails?.upgradeStatus === ProjectUpgradeStatus.InProgress; const isUpgrading = workspaceDetails?.upgradeStatus === ProjectUpgradeStatus.InProgress;
const didProjectUpgradeFailed = workspaceDetails?.upgradeStatus === ProjectUpgradeStatus.Failed;
const handleMigrationSecretV2 = async () => { const handleMigrationSecretV2 = async () => {
try { try {
handlePopUpToggle("migrationInfo");
await migrateProjectToV3.mutateAsync({ workspaceId: currentWorkspace?.id || "" }); await migrateProjectToV3.mutateAsync({ workspaceId: currentWorkspace?.id || "" });
refetch();
createNotification({ createNotification({
text: "Migrated project to new KMS", text: "Project upgrade started",
type: "success" type: "success"
}); });
} catch { } catch {
@@ -46,7 +71,7 @@ export const SecretV2MigrationSection = () => {
const isAdmin = membership?.roles.includes(ProjectMembershipRole.Admin); const isAdmin = membership?.roles.includes(ProjectMembershipRole.Admin);
return ( return (
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mt-4 rounded-lg border border-primary-600 bg-mineshaft-900 p-4">
{isUpgrading && ( {isUpgrading && (
<div className="absolute top-0 left-0 z-50 flex h-screen w-screen items-center justify-center bg-bunker-500 bg-opacity-80"> <div className="absolute top-0 left-0 z-50 flex h-screen w-screen items-center justify-center bg-bunker-500 bg-opacity-80">
<Spinner size="lg" className="text-primary" /> <Spinner size="lg" className="text-primary" />
@@ -58,19 +83,113 @@ export const SecretV2MigrationSection = () => {
)} )}
<p className="mb-2 text-lg font-semibold">Action Required</p> <p className="mb-2 text-lg font-semibold">Action Required</p>
<p className="mb-4 leading-7 text-gray-400"> <p className="mb-4 leading-7 text-gray-400">
There is a new update for your project. Introducing Infisical KMS. Your project can now leverage Infisical KMS! Ready to enhance your security?
<b>{!isAdmin && "This is an admin only operation."}</b> <b>{!isAdmin && "This is an admin only operation."}</b>
</p> </p>
<Button <Button
onClick={handleMigrationSecretV2} onClick={() => handlePopUpOpen("migrationInfo")}
isDisabled={!isAdmin || isUpgrading} isDisabled={!isAdmin || isUpgrading}
color="mineshaft" color="mineshaft"
type="submit"
isLoading={migrateProjectToV3.isLoading} isLoading={migrateProjectToV3.isLoading}
> >
Start Migration Update Now
</Button> </Button>
{didProjectUpgradeFailed && (
<p className="mt-2 text-sm leading-7 text-red-400">
<FontAwesomeIcon icon={faTriangleExclamation} className="mr-2" />
Project upgrade unsuccessful. For assistance, please contact the Infisical support team.
</p>
)}
<Modal
isOpen={popUp.migrationInfo.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("migrationInfo", isOpen)}
>
<ModalContent
title="Upgrade Checklist"
subTitle="Before proceeding with the upgrade, please ensure the following requirements are met:"
>
<div>
<form onSubmit={handleSubmit(handleMigrationSecretV2)}>
<div className="flex flex-col space-y-4">
<Controller
control={control}
name="isCLIChecked"
defaultValue={false}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
id="is-cli-checked"
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
CLI version: v0.25.0 or above
</Checkbox>
)}
/>
<Controller
control={control}
name="isOperatorChecked"
defaultValue={false}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
id="is-operator-checked"
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
Operator version: v0.25.0 or above
</Checkbox>
)}
/>
<Controller
control={control}
name="doesKnowSnapshotLimit"
defaultValue={false}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
id="is-snapshot-checked"
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
Folders keep 10 latest snapshots due to migration time limit.
</Checkbox>
)}
/>
<Controller
control={control}
name="shouldCloseOpenApprovals"
defaultValue={false}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
id="is-approvals-checked"
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
Close/Merge all open approval requests as it will be reset.
</Checkbox>
)}
/>
</div>
<div className="mt-4 text-sm">
Meeting these prerequisites will ensure system compatibility and a smooth upgrade
process.
</div>
<div className="mt-8 flex space-x-4">
<Button type="submit">Update</Button>
<Button variant="outline_bg" onClick={() => handlePopUpToggle("migrationInfo")}>
Cancel
</Button>
</div>
</form>
</div>
</ModalContent>
</Modal>
</div> </div>
); );
}; };