diff --git a/backend/src/db/migrations/20250507185010_pit-projects-commits-initialization.ts b/backend/src/db/migrations/20250507185010_pit-projects-commits-initialization.ts index f3e5c9e7e..459727035 100644 --- a/backend/src/db/migrations/20250507185010_pit-projects-commits-initialization.ts +++ b/backend/src/db/migrations/20250507185010_pit-projects-commits-initialization.ts @@ -3,13 +3,15 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; import { ProjectType, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationPITServices } from "./utils/services"; export async function up(knex: Knex): Promise { const hasFolderCommitTable = await knex.schema.hasTable(TableName.FolderCommit); if (hasFolderCommitTable) { const keyStore = inMemoryKeyStore(); - const { folderCommitService } = await getMigrationPITServices({ db: knex, keyStore }); + const envConfig = getMigrationEnvConfig(); + const { folderCommitService } = await getMigrationPITServices({ db: knex, keyStore, envConfig }); const projects = await knex(TableName.Project).where({ version: 3, type: ProjectType.SecretManager }).select("id"); for (const project of projects) { // eslint-disable-next-line no-await-in-loop diff --git a/backend/src/db/migrations/utils/services.ts b/backend/src/db/migrations/utils/services.ts index 4e0188c98..73c630adb 100644 --- a/backend/src/db/migrations/utils/services.ts +++ b/backend/src/db/migrations/utils/services.ts @@ -64,7 +64,15 @@ export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore } return { kmsService }; }; -export const getMigrationPITServices = async ({ db, keyStore }: { db: Knex; keyStore: TKeyStoreFactory }) => { +export const getMigrationPITServices = async ({ + db, + keyStore, + envConfig +}: { + db: Knex; + keyStore: TKeyStoreFactory; + envConfig: TMigrationEnvConfig; +}) => { const projectDAL = projectDALFactory(db); const folderCommitDAL = folderCommitDALFactory(db); const folderCommitChangesDAL = folderCommitChangesDALFactory(db); @@ -79,6 +87,33 @@ export const getMigrationPITServices = async ({ db, keyStore }: { db: Knex; keyS const secretV2BridgeDAL = secretV2BridgeDALFactory({ db, keyStore }); const folderTreeCheckpointResourcesDAL = folderTreeCheckpointResourcesDALFactory(db); + const orgDAL = orgDALFactory(db); + const kmsRootConfigDAL = kmsRootConfigDALFactory(db); + const kmsDAL = kmskeyDALFactory(db); + const internalKmsDAL = internalKmsDALFactory(db); + + const hsmModule = initializeHsmModule(envConfig); + hsmModule.initialize(); + + const hsmService = hsmServiceFactory({ + hsmModule: hsmModule.getModule(), + envConfig + }); + + const kmsService = kmsServiceFactory({ + kmsRootConfigDAL, + keyStore, + kmsDAL, + internalKmsDAL, + orgDAL, + projectDAL, + hsmService, + envConfig + }); + + await hsmService.startService(); + await kmsService.startService(); + const folderCommitService = folderCommitServiceFactory({ folderCommitDAL, folderCommitChangesDAL, @@ -92,7 +127,8 @@ export const getMigrationPITServices = async ({ db, keyStore }: { db: Knex; keyS projectDAL, folderCheckpointResourcesDAL, secretV2BridgeDAL, - folderTreeCheckpointResourcesDAL + folderTreeCheckpointResourcesDAL, + kmsService }); return { folderCommitService }; diff --git a/backend/src/ee/routes/v1/pit-router.ts b/backend/src/ee/routes/v1/pit-router.ts index e35091569..21ffa5d92 100644 --- a/backend/src/ee/routes/v1/pit-router.ts +++ b/backend/src/ee/routes/v1/pit-router.ts @@ -267,7 +267,7 @@ export const registerPITRouter = async (server: FastifyZodProvider) => { body: z.object({ folderId: z.string().trim(), deepRollback: z.boolean().default(false), - message: z.string().trim().optional(), + message: z.string().max(256).trim().optional(), envId: z.string().trim(), projectId: z.string().trim() }), diff --git a/backend/src/ee/services/pit/pit-service.ts b/backend/src/ee/services/pit/pit-service.ts index 15a460e73..cf9a2a084 100644 --- a/backend/src/ee/services/pit/pit-service.ts +++ b/backend/src/ee/services/pit/pit-service.ts @@ -12,6 +12,7 @@ import { isSecretCommitChange } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; import { TSecretServiceFactory } from "@app/services/secret/secret-service"; +import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; import { TPermissionServiceFactory } from "../permission/permission-service"; @@ -21,6 +22,7 @@ type TPitServiceFactoryDep = { secretService: Pick; folderService: Pick; permissionService: Pick; + folderDAL: Pick; }; export type TPitServiceFactory = ReturnType; @@ -29,7 +31,8 @@ export const pitServiceFactory = ({ folderCommitService, secretService, folderService, - permissionService + permissionService, + folderDAL }: TPitServiceFactoryDep) => { const getCommitsCount = async ({ actor, @@ -134,6 +137,8 @@ export const pitServiceFactory = ({ commitId }); + const [folderWithPath] = await folderDAL.findSecretPathByFolderIds(projectId, [changes.folderId]); + for (const change of changes.changes) { if (isSecretCommitChange(change)) { change.versions = await secretService.getChangeVersions( @@ -149,7 +154,9 @@ export const pitServiceFactory = ({ actor, actorOrgId, actorAuthMethod, - change.folderId + changes.envId, + projectId, + folderWithPath?.path || "" ); } else if (isFolderCommitChange(change)) { change.versions = await folderService.getFolderVersions( @@ -261,7 +268,9 @@ export const pitServiceFactory = ({ actor, actorOrgId, actorAuthMethod, - diff.folderId + envId, + projectId, + diff.folderPath || "" ); } } else if (change.type === ResourceType.FOLDER) { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 19038d8cb..3fb339a3d 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -598,7 +598,8 @@ export const registerRoutes = async ( secretV2BridgeDAL, folderTreeCheckpointResourcesDAL, folderCommitQueueService, - permissionService + permissionService, + kmsService }); const scimService = scimServiceFactory({ licenseService, @@ -1525,7 +1526,8 @@ export const registerRoutes = async ( folderCommitService, secretService, folderService, - permissionService + permissionService, + folderDAL }); const identityOidcAuthService = identityOidcAuthServiceFactory({ diff --git a/backend/src/services/folder-commit-changes/folder-commit-changes-dal.ts b/backend/src/services/folder-commit-changes/folder-commit-changes-dal.ts index 4ce9d9199..b2d74c5af 100644 --- a/backend/src/services/folder-commit-changes/folder-commit-changes-dal.ts +++ b/backend/src/services/folder-commit-changes/folder-commit-changes-dal.ts @@ -52,6 +52,7 @@ export type FolderCommitChange = BaseCommitChangeInfo & { folderVersion: string; folderChangeId: string; versions?: { + version: string; name?: string; }[]; }; diff --git a/backend/src/services/folder-commit/folder-commit-schemas.ts b/backend/src/services/folder-commit/folder-commit-schemas.ts index 2a61c0886..d676b8249 100644 --- a/backend/src/services/folder-commit/folder-commit-schemas.ts +++ b/backend/src/services/folder-commit/folder-commit-schemas.ts @@ -36,6 +36,7 @@ const secretVersionSchema = z.object({ // Folder-specific versions schema const folderVersionSchema = z.object({ + version: z.string().optional(), name: z.string().optional() }); diff --git a/backend/src/services/folder-commit/folder-commit-service.test.ts b/backend/src/services/folder-commit/folder-commit-service.test.ts index 30da5a09a..6c0b59e8e 100644 --- a/backend/src/services/folder-commit/folder-commit-service.test.ts +++ b/backend/src/services/folder-commit/folder-commit-service.test.ts @@ -4,7 +4,7 @@ import { Knex } from "knex"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { TSecretFolderVersions, TSecretVersionsV2 } from "@app/db/schemas"; +import { ProjectType, TSecretFolderVersions, TSecretVersionsV2 } from "@app/db/schemas"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { ActorType } from "../auth/auth-type"; @@ -53,6 +53,10 @@ describe("folderCommitServiceFactory", () => { findLatestCommitByFolderIds: vi.fn().mockResolvedValue({}) }; + const mockKmsService = { + createCipherPairWithDataKey: vi.fn().mockResolvedValue({}) + }; + const mockFolderCommitChangesDAL = { create: vi.fn().mockResolvedValue({}), findByCommitId: vi.fn().mockResolvedValue([]), @@ -136,7 +140,8 @@ describe("folderCommitServiceFactory", () => { }; const mockProjectDAL = { - findById: vi.fn().mockResolvedValue({}) + findById: vi.fn().mockResolvedValue({}), + findProjectByEnvId: vi.fn().mockResolvedValue({}) }; const mockFolderCommitQueueService = { @@ -181,7 +186,8 @@ describe("folderCommitServiceFactory", () => { secretV2BridgeDAL: mockSecretV2BridgeDAL, folderCommitQueueService: mockFolderCommitQueueService, // @ts-expect-error - Mock implementation doesn't need all interface methods for testing - permissionService: mockPermissionService + permissionService: mockPermissionService, + kmsService: mockKmsService }); }); @@ -408,6 +414,11 @@ describe("folderCommitServiceFactory", () => { { folderVersionId: "folder-version-1", referencedFolderId: "folder-1" } ]); mockFolderCommitDAL.findCommitsToRecreate.mockResolvedValue([]); + mockProjectDAL.findProjectByEnvId.mockResolvedValue({ + id: "project-id", + name: "test-project", + type: ProjectType.SecretManager + }); // Act const result = await folderCommitService.compareFolderStates({ diff --git a/backend/src/services/folder-commit/folder-commit-service.ts b/backend/src/services/folder-commit/folder-commit-service.ts index 2c3a11411..0adf3dcb1 100644 --- a/backend/src/services/folder-commit/folder-commit-service.ts +++ b/backend/src/services/folder-commit/folder-commit-service.ts @@ -17,6 +17,8 @@ import { TFolderCommitChangesDALFactory } from "../folder-commit-changes/folder- import { TFolderTreeCheckpointDALFactory } from "../folder-tree-checkpoint/folder-tree-checkpoint-dal"; import { TFolderTreeCheckpointResourcesDALFactory } from "../folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal"; import { TIdentityDALFactory } from "../identity/identity-dal"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TProjectDALFactory } from "../project/project-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal"; @@ -80,7 +82,7 @@ type BaseChange = { fromVersion?: string; }; -type SecretChange = { +type SecretChange = BaseChange & { type: ResourceType.SECRET; secretKey?: string; secretVersion?: string; @@ -98,7 +100,7 @@ type SecretChange = { }[]; }; -type FolderChange = { +type FolderChange = BaseChange & { type: ResourceType.FOLDER; folderName?: string; folderVersion?: string; @@ -107,7 +109,7 @@ type FolderChange = { }[]; }; -export type ResourceChange = BaseChange & (SecretChange | FolderChange); +export type ResourceChange = SecretChange | FolderChange; type ActorInfo = { actorType: string; @@ -134,12 +136,13 @@ type TFolderCommitServiceFactoryDep = { folderVersionDAL: TSecretFolderVersionDALFactory; secretVersionV2BridgeDAL: TSecretVersionV2DALFactory; secretV2BridgeDAL: secretV2BridgeDal.TSecretV2BridgeDALFactory; - projectDAL: Pick; + projectDAL: Pick; folderCommitQueueService?: Pick< TFolderCommitQueueServiceFactory, "scheduleTreeCheckpoint" | "createFolderTreeCheckpoint" >; permissionService?: TPermissionServiceFactory; + kmsService: Pick; }; export const folderCommitServiceFactory = ({ @@ -157,7 +160,8 @@ export const folderCommitServiceFactory = ({ secretV2BridgeDAL, folderTreeCheckpointResourcesDAL, folderCommitQueueService, - permissionService + permissionService, + kmsService }: TFolderCommitServiceFactoryDep) => { const appCfg = getConfig(); @@ -394,6 +398,12 @@ export const folderCommitServiceFactory = ({ throw new NotFoundError({ message: `Commit with ID ${targetCommitId} not found` }); } + const project = await projectDAL.findProjectByEnvId(targetCommit.envId, tx); + + if (!project) { + throw new NotFoundError({ message: `No project found for envId ${targetCommit.envId}` }); + } + // If currentCommitId is not provided, mark all resources in target as creates if (!currentCommitId) { const targetState = await reconstructFolderState(targetCommitId, tx); @@ -565,7 +575,63 @@ export const folderCommitServiceFactory = ({ } }); - return differences; + const removeNoChangeUpdate: string[] = []; + + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: project.id + }); + + await Promise.all( + differences.map(async (change) => { + if (change.changeType === ChangeType.UPDATE) { + if (change.type === ResourceType.FOLDER && change.folderVersion && change.fromVersion) { + const versions = await folderVersionDAL.find({ + folderId: change.id, + $in: { + version: [Number(change.folderVersion), Number(change.fromVersion)] + } + }); + const versionsShaped = [...new Set(versions.map((version) => version.name))]; + if (versionsShaped.length === 1) { + removeNoChangeUpdate.push(change.id); + } + } else if (change.type === ResourceType.SECRET && change.secretVersion && change.fromVersion) { + const versions = await secretVersionV2BridgeDAL.findVersionsBySecretIdWithActors({ + secretId: change.id, + projectId: project.id, + secretVersions: [change.secretVersion, change.fromVersion] + }); + const versionsShaped = versions.map((el) => ({ + secretKey: el.key, + secretComment: el.encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() + : "", + skipMultilineEncoding: el.skipMultilineEncoding, + secretReminderRepeatDays: el.reminderRepeatDays, + tags: el.tags, + metadata: el.metadata, + secretReminderNote: el.reminderNote, + secretValue: el.encryptedValue + ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() + : "" + })); + const uniqueVersions = versionsShaped.filter( + (item, index, arr) => + arr.findIndex((other) => + Object.entries(item).every( + ([key, value]) => JSON.stringify(value) === JSON.stringify(other[key as keyof typeof other]) + ) + ) === index + ); + if (uniqueVersions.length === 1) { + removeNoChangeUpdate.push(change.id); + } + } + } + }) + ); + return differences.filter((change) => !removeNoChangeUpdate.includes(change.id)); }; /** @@ -1375,6 +1441,63 @@ export const folderCommitServiceFactory = ({ ); }; + const addNestedFolderChanges = async ({ + changes, + beforeCommit, + folderId, + folderName, + folderPath, + step = 1, + tx + }: { + changes: { + folderId: string; + folderName: string; + changes: ResourceChange[]; + folderPath?: string; + }[]; + beforeCommit: bigint; + folderId: string; + folderName?: string; + folderPath?: string; + step?: number; + tx?: Knex; + }) => { + if (step > 20) { + return; + } + const latestFolderCommit = await folderCommitDAL.findCommitBefore(folderId, beforeCommit, tx); + if (!latestFolderCommit) { + return; + } + const diff = await compareFolderStates({ + targetCommitId: latestFolderCommit.id, + tx + }); + changes.push({ + folderId, + folderName: folderName || "", + changes: diff, + folderPath: folderPath || "" + }); + + await Promise.all( + diff.map(async (change) => { + if (change.type === ResourceType.FOLDER && change.changeType === ChangeType.CREATE) { + await addNestedFolderChanges({ + changes, + beforeCommit, + folderId: change.id, + folderName: change.folderName, + folderPath: `${folderPath}/${change.folderName}`, + step: step + 1, + tx + }); + } + }) + ); + }; + const deepCompareFolder = async ({ targetCommitId, envId, @@ -1463,7 +1586,12 @@ export const folderCommitServiceFactory = ({ const folders = await folderDAL.findFoldersByRootAndIds({ rootId: targetCommit.folderId, folderIds }, tx); const sortedFolders = sortFoldersByHierarchy(folders); - const response = []; + const response: { + folderId: string; + folderName: string; + changes: ResourceChange[]; + folderPath?: string; + }[] = []; for (const folder of sortedFolders) { const diff = folderDiffs.get(folder.id); if (diff) { @@ -1474,6 +1602,29 @@ export const folderCommitServiceFactory = ({ changes: diff, folderPath: folderPath?.[0]?.path }); + const recreatedFolders = diff + .filter( + (change): change is FolderChange => + change.type === ResourceType.FOLDER && change.changeType === ChangeType.CREATE + ) + .map((change) => ({ + id: change.id, + folderName: change.folderName, + folderPath: folderPath?.[0]?.path + })); + await Promise.all( + recreatedFolders.map(async (change) => { + const nestedFolderPath = folderPath?.[0]?.path; + await addNestedFolderChanges({ + changes: response, + beforeCommit: targetCommit.commitId, + folderId: change.id, + folderName: change.folderName, + folderPath: `${nestedFolderPath !== "/" ? nestedFolderPath : ""}/${change.folderName}`, + tx + }); + }) + ); } } return response; diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 43f1d57e4..4796bcdca 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -12,7 +12,7 @@ import { TProjectsUpdate } from "@app/db/schemas"; import { BadRequestError, DatabaseError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; -import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; +import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; import { ActorType } from "../auth/auth-type"; import { Filter, ProjectFilterType, SearchProjectSortBy } from "./project-types"; @@ -425,6 +425,16 @@ export const projectDALFactory = (db: TDbClient) => { return { docs, totalCount: Number(docs?.[0]?.count ?? 0) }; }; + const findProjectByEnvId = async (envId: string, tx?: Knex) => { + const project = await (tx || db.replicaNode())(TableName.Project) + .leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`) + // eslint-disable-next-line @typescript-eslint/no-misused-promises + .where(buildFindFilter({ id: envId }, TableName.Environment)) + .select(selectAllTableCols(TableName.Project)) + .first(); + return project; + }; + return { ...projectOrm, findUserProjects, @@ -437,6 +447,7 @@ export const projectDALFactory = (db: TDbClient) => { findProjectWithOrg, checkProjectUpgradeStatus, getProjectFromSplitId, - searchProjects + searchProjects, + findProjectByEnvId }; }; diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 0012aa3e1..354f7e7da 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -800,6 +800,7 @@ export const secretFolderServiceFactory = ({ change.isUpdate || change.changeType === ChangeType.UPDATE ? [currentVersion, fromVersion] : [currentVersion] }); return versions.map((v) => ({ + version: v.version?.toString() || "1", name: v.name })); }; diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index f83a3e148..6525a5909 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -2875,24 +2875,21 @@ export const secretV2BridgeServiceFactory = ({ actorAuthMethod, secretId, secretVersionNumbers, - folderId + secretPath, + envId, + projectId }: TGetSecretVersionsDTO & { secretVersionNumbers: string[]; - folderId: string; + secretPath: string; + envId: string; + projectId: string; }) => { - const folder = await folderDAL.findById(folderId); - if (!folder) throw new NotFoundError({ message: `Folder with ID '${folderId}' not found` }); - - const [folderWithPath] = await folderDAL.findSecretPathByFolderIds(folder.projectId, [folder.id]); - - if (!folderWithPath) { - throw new NotFoundError({ message: `Folder with ID '${folder.id}' not found` }); - } + const environment = await projectEnvDAL.findOne({ id: envId }); const { permission } = await permissionService.getProjectPermission({ actor, actorId, - projectId: folder.projectId, + projectId, actorAuthMethod, actorOrgId, actionProjectType: ActionProjectType.SecretManager @@ -2906,11 +2903,11 @@ export const secretV2BridgeServiceFactory = ({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, - projectId: folder.projectId + projectId }); const secretVersions = await secretVersionDAL.findVersionsBySecretIdWithActors({ secretId, - projectId: folder.projectId, + projectId, secretVersions: secretVersionNumbers }); return secretVersions.map((el) => { @@ -2918,8 +2915,8 @@ export const secretV2BridgeServiceFactory = ({ permission, ProjectPermissionSecretActions.ReadValue, { - environment: folder.environment.envSlug, - secretPath: folderWithPath.path, + environment: environment.slug, + secretPath, secretName: el.key, ...(el.tags?.length && { secretTags: el.tags.map((tag) => tag.slug) @@ -2928,9 +2925,9 @@ export const secretV2BridgeServiceFactory = ({ ); return reshapeBridgeSecret( - folder.projectId, - folder.environment.envSlug, - folderWithPath.path, + projectId, + environment.slug, + secretPath, { ...el, value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "", diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index c28e8da1c..28b91492c 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -2529,10 +2529,14 @@ export const secretServiceFactory = ({ actorAuthMethod, secretId, secretVersions, - folderId + secretPath, + envId, + projectId }: TGetSecretVersionsDTO & { secretVersions: string[]; - folderId: string; + secretPath: string; + envId: string; + projectId: string; }) => { const secretVersionV2 = await secretV2BridgeService.getSecretVersionsByIds({ actorId, @@ -2540,8 +2544,10 @@ export const secretServiceFactory = ({ actorOrgId, actorAuthMethod, secretId, - folderId, - secretVersionNumbers: secretVersions + secretVersionNumbers: secretVersions, + secretPath, + envId, + projectId }); return secretVersionV2; }; @@ -3317,7 +3323,9 @@ export const secretServiceFactory = ({ actor: ActorType, actorOrgId: string, actorAuthMethod: ActorAuthMethod, - folderId: string + envId: string, + projectId: string, + secretPath: string ) => { const currentVersion = change.secretVersion; const secretId = change.secretId ? change.secretId : change.id; @@ -3335,7 +3343,9 @@ export const secretServiceFactory = ({ change.isUpdate || change.changeType === ChangeType.UPDATE ? [currentVersion, previousVersion] : [currentVersion], - folderId + secretPath, + envId, + projectId }); return versions?.map((v) => ({ secretKey: v.secretKey, diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/CommitDetailsPage.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/CommitDetailsPage.tsx index 9a5a4f313..28955901b 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/CommitDetailsPage.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/CommitDetailsPage.tsx @@ -1,15 +1,17 @@ -import { Helmet } from "react-helmet"; -import { useTranslation } from "react-i18next"; import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { ROUTE_PATHS } from "@app/const/routes"; import { useWorkspace } from "@app/context"; +import { + ProjectPermissionCommitsActions, + ProjectPermissionSub +} from "@app/context/ProjectPermissionContext/types"; import { ProjectType } from "@app/hooks/api/workspace/types"; import { CommitDetailsTab } from "./components/CommitDetailsTab"; export const CommitDetailsPage = () => { - const { t } = useTranslation(); const envSlug = useParams({ from: ROUTE_PATHS.SecretManager.CommitDetailsPage.id, select: (el) => el.environment @@ -64,17 +66,20 @@ export const CommitDetailsPage = () => { return (
- - {t("common.head-title", { title: "Commit History" })} -
- + + +
); diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/components/CommitDetailsTab/CommitDetailsTab.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/components/CommitDetailsTab/CommitDetailsTab.tsx index e563bf6f4..1477faa25 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/components/CommitDetailsTab/CommitDetailsTab.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/components/CommitDetailsTab/CommitDetailsTab.tsx @@ -243,10 +243,10 @@ export const CommitDetailsTab = ({
-
+
-

+

{parsedCommitDetails.changes?.message || "No message"}

@@ -266,7 +266,7 @@ export const CommitDetailsTab = ({
{(isAllowed) => ( diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx index 0e32fd54d..237803b6a 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx @@ -6,6 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal, @@ -17,6 +18,10 @@ import { } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; import { useWorkspace } from "@app/context"; +import { + ProjectPermissionCommitsActions, + ProjectPermissionSub +} from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; import { useCommitRollback, useGetRollbackPreview } from "@app/hooks/api/folderCommits/queries"; import { ProjectType } from "@app/hooks/api/workspace/types"; @@ -294,71 +299,77 @@ export const RollbackPreviewTab = (): JSX.Element => { return (
-
-
-
- + +
+
+
+ -
- {renderSidebar()} - {renderMainContent()} -
+
+ {renderSidebar()} + {renderMainContent()} +
-
-
-
- - - - +
+
+ + + + + Recursively Restore Child Folders + +
+
+
+ +
+
+ setMessage(e.target.value)} + className="w-full border-mineshaft-500 bg-mineshaft-700 py-2 text-sm" + maxLength={256} + /> +
-
-
- setMessage(e.target.value)} - className="w-full border-mineshaft-500 bg-mineshaft-700 py-2 text-sm" - /> - -
-
-
- - { } Any changes made after this commit will be permanently removed. `} - onChange={(isOpen) => handlePopUpToggle("rollbackConfirm", isOpen)} - onDeleteApproved={handleRollback} - buttonText="Restore" - /> + onChange={(isOpen) => handlePopUpToggle("rollbackConfirm", isOpen)} + onDeleteApproved={handleRollback} + buttonText="Restore" + /> +
{" "}
{" "} -
{" "} +
); }; diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView/SecretVersionDiffView.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView/SecretVersionDiffView.tsx index 222f7d99c..7744d3626 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView/SecretVersionDiffView.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView/SecretVersionDiffView.tsx @@ -505,7 +505,6 @@ export const SecretVersionDiffView = ({ } const sortedVersions = [...item.versions].sort((a, b) => b.version - a.version); - let oldVersion = null; let newVersion = null; let oldVersionContent = null; @@ -562,13 +561,11 @@ export const SecretVersionDiffView = ({ } else if (item.isAdded) { [newVersion] = sortedVersions; const cleanNewVersion = cleanVersionForComparison(newVersion); - oldVersionContent =
No previous version
; newVersionContent = formatAddedJson(cleanNewVersion); } else if (item.isDeleted) { [oldVersion] = sortedVersions; const cleanOldVersion = cleanVersionForComparison(oldVersion); oldVersionContent = formatDeletedJson(cleanOldVersion); - newVersionContent =
Version deleted
; } else { return null; } diff --git a/frontend/src/pages/secret-manager/CommitsPage/CommitsPage.tsx b/frontend/src/pages/secret-manager/CommitsPage/CommitsPage.tsx index a7f5b6dd3..5479f4e36 100644 --- a/frontend/src/pages/secret-manager/CommitsPage/CommitsPage.tsx +++ b/frontend/src/pages/secret-manager/CommitsPage/CommitsPage.tsx @@ -1,16 +1,18 @@ -import { Helmet } from "react-helmet"; -import { useTranslation } from "react-i18next"; import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { PageHeader } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; import { useWorkspace } from "@app/context"; +import { + ProjectPermissionCommitsActions, + ProjectPermissionSub +} from "@app/context/ProjectPermissionContext/types"; import { ProjectType } from "@app/hooks/api/workspace/types"; import { CommitHistoryTab } from "./components/CommitHistoryTab"; export const CommitsPage = () => { - const { t } = useTranslation(); const envSlug = useParams({ from: ROUTE_PATHS.SecretManager.CommitsPage.id, select: (el) => el.environment @@ -45,20 +47,23 @@ export const CommitsPage = () => { return (
- - {t("common.head-title", { title: "Commit History" })} -
- + + +
);