mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
feat: improved migration wizard to info user prerequisite check list
This commit is contained in:
@@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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")}
|
||||||
|
|||||||
+128
-9
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user