mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 08:28:59 +00:00
feat(secrets-ui): add direct reference warning on secrets updates
This commit is contained in:
@@ -154,7 +154,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
secrets: z
|
||||
.object({
|
||||
secretId: z.string(),
|
||||
referencedSecretKey: z.string()
|
||||
referencedSecretKey: z.string(),
|
||||
referencedSecretId: z.string()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
@@ -650,7 +651,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
secrets: z
|
||||
.object({
|
||||
secretId: z.string(),
|
||||
referencedSecretKey: z.string()
|
||||
referencedSecretKey: z.string(),
|
||||
referencedSecretId: z.string()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
|
||||
@@ -203,7 +203,8 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
db.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||
db.ref("id").withSchema(TableName.SecretFolder).as("folderId"),
|
||||
db.ref("secretKey").withSchema(TableName.SecretReferenceV2).as("referencedSecretKey")
|
||||
db.ref("secretKey").withSchema(TableName.SecretReferenceV2).as("referencedSecretKey"),
|
||||
db.ref("id").withSchema(TableName.SecretV2).as("referencedSecretId")
|
||||
);
|
||||
|
||||
const folderResults = folderImports.map(({ envName, envSlug, folderName, folderId }) => ({
|
||||
@@ -214,13 +215,14 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
}));
|
||||
|
||||
const secretResults = secretReferences.map(
|
||||
({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey }) => ({
|
||||
({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey, referencedSecretId }) => ({
|
||||
envName,
|
||||
envSlug,
|
||||
secretId,
|
||||
folderName,
|
||||
folderId,
|
||||
referencedSecretKey
|
||||
referencedSecretKey,
|
||||
referencedSecretId
|
||||
})
|
||||
);
|
||||
|
||||
@@ -235,6 +237,7 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
secrets: {
|
||||
secretId: string;
|
||||
referencedSecretKey: string;
|
||||
referencedSecretId: string;
|
||||
}[];
|
||||
folderId: string;
|
||||
folderImported: boolean;
|
||||
@@ -264,7 +267,11 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
if ("secretId" in item && item.secretId) {
|
||||
updatedAcc[env].folders[folder].secrets = [
|
||||
...updatedAcc[env].folders[folder].secrets,
|
||||
{ secretId: item.secretId, referencedSecretKey: item.referencedSecretKey }
|
||||
{
|
||||
secretId: item.secretId,
|
||||
referencedSecretKey: item.referencedSecretKey,
|
||||
referencedSecretId: item.referencedSecretId
|
||||
}
|
||||
];
|
||||
} else {
|
||||
updatedAcc[env].folders[folder].folderImported = true;
|
||||
|
||||
@@ -808,7 +808,7 @@ export const secretImportServiceFactory = ({
|
||||
actorOrgId,
|
||||
secrets
|
||||
}: TGetSecretImportsDTO & {
|
||||
secrets: { secretKey: string; secretValue: string }[] | undefined;
|
||||
secrets: { secretKey: string; secretValue: string; id: string }[] | undefined;
|
||||
}) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -877,7 +877,8 @@ export const secretImportServiceFactory = ({
|
||||
)
|
||||
.map((otherSecret) => ({
|
||||
secretId: secret.secretKey,
|
||||
referencedSecretKey: otherSecret.secretKey
|
||||
referencedSecretKey: otherSecret.secretKey,
|
||||
referencedSecretId: otherSecret.id
|
||||
}));
|
||||
}) || [];
|
||||
if (locallyReferenced.length > 0) {
|
||||
|
||||
@@ -56,11 +56,12 @@ export type FolderResult = {
|
||||
export type SecretResult = {
|
||||
secretId: string;
|
||||
referencedSecretKey: string;
|
||||
referencedSecretId: string;
|
||||
} & FolderResult;
|
||||
|
||||
export type FolderInfo = {
|
||||
folderName: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string }[];
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[];
|
||||
folderId: string;
|
||||
folderImported: boolean;
|
||||
envSlug?: string;
|
||||
|
||||
@@ -19,6 +19,7 @@ type Props = {
|
||||
formContent?: ReactNode;
|
||||
children?: ReactNode;
|
||||
deletionMessage?: ReactNode;
|
||||
buttonColorSchema?: "danger" | "primary" | "secondary" | "gray" | null;
|
||||
};
|
||||
|
||||
export const DeleteActionModal = ({
|
||||
@@ -32,6 +33,7 @@ export const DeleteActionModal = ({
|
||||
buttonText = "Delete",
|
||||
formContent,
|
||||
deletionMessage,
|
||||
buttonColorSchema,
|
||||
children
|
||||
}: Props): JSX.Element => {
|
||||
const [inputData, setInputData] = useState("");
|
||||
@@ -67,7 +69,7 @@ export const DeleteActionModal = ({
|
||||
<div className="mx-2 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
colorSchema="danger"
|
||||
colorSchema={buttonColorSchema || "danger"}
|
||||
isDisabled={!(deleteKey === inputData) || isLoading}
|
||||
onClick={onDelete}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -51,7 +51,7 @@ export type ProjectSecretsImportedBy = {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string }[];
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
};
|
||||
|
||||
@@ -74,7 +74,7 @@ import {
|
||||
useUpdateSecretV3
|
||||
} from "@app/hooks/api";
|
||||
import { useGetProjectSecretsOverview } from "@app/hooks/api/dashboard/queries";
|
||||
import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
|
||||
import { DashboardSecretsOrderBy, ProjectSecretsImportedBy } from "@app/hooks/api/dashboard/types";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
|
||||
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
|
||||
@@ -727,6 +727,98 @@ export const OverviewPage = () => {
|
||||
}
|
||||
}, [routerSearch.search]);
|
||||
|
||||
const selectedKeysCount = Object.keys(selectedEntries.secret).length;
|
||||
|
||||
const secretsToDeleteKeys = useMemo(() => {
|
||||
return Object.values(selectedEntries.secret).flatMap((entries) =>
|
||||
Object.values(entries).map((secret) => secret.key)
|
||||
);
|
||||
}, [selectedEntries]);
|
||||
|
||||
const filterAndMergeEnvironments = (
|
||||
envNames: string[],
|
||||
envs: { environment: string; importedBy: ProjectSecretsImportedBy[] }[]
|
||||
): ProjectSecretsImportedBy[] => {
|
||||
const filteredEnvs = envs.filter((env) => envNames.includes(env.environment));
|
||||
|
||||
if (filteredEnvs.length === 0) return [];
|
||||
|
||||
const allImportedBy = filteredEnvs.flatMap((env) => env.importedBy);
|
||||
const groupedBySlug: Record<string, ProjectSecretsImportedBy[]> = {};
|
||||
|
||||
allImportedBy.forEach((item) => {
|
||||
const { slug } = item.environment;
|
||||
if (!groupedBySlug[slug]) groupedBySlug[slug] = [];
|
||||
groupedBySlug[slug].push(item);
|
||||
});
|
||||
|
||||
const mergedImportedBy = Object.values(groupedBySlug).map((group) => {
|
||||
const { environment } = group[0];
|
||||
const allFolders = group.flatMap((item) => item.folders);
|
||||
|
||||
const foldersByName: Record<string, (typeof allFolders)[number][]> = {};
|
||||
allFolders.forEach((folder) => {
|
||||
if (!foldersByName[folder.name]) foldersByName[folder.name] = [];
|
||||
foldersByName[folder.name].push(folder);
|
||||
});
|
||||
|
||||
const mergedFolders = Object.entries(foldersByName).map(([name, foldersData]) => {
|
||||
const isImported = foldersData.some((folder) => folder.isImported);
|
||||
const allSecrets = foldersData.flatMap((folder) => folder.secrets || []);
|
||||
|
||||
const uniqueSecrets: {
|
||||
secretId: string;
|
||||
referencedSecretKey: string;
|
||||
referencedSecretId: string;
|
||||
}[] = [];
|
||||
const secretIds = new Set<string>();
|
||||
|
||||
allSecrets
|
||||
.filter(
|
||||
(secret) =>
|
||||
!secretsToDeleteKeys ||
|
||||
secretsToDeleteKeys.length === 0 ||
|
||||
secretsToDeleteKeys.includes(secret.referencedSecretKey)
|
||||
)
|
||||
.forEach((secret) => {
|
||||
if (!secretIds.has(secret.secretId)) {
|
||||
secretIds.add(secret.secretId);
|
||||
uniqueSecrets.push(secret);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
name,
|
||||
isImported,
|
||||
...(uniqueSecrets.length > 0 ? { secrets: uniqueSecrets } : {})
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
environment,
|
||||
folders: mergedFolders.filter(
|
||||
(folder) => folder.isImported || (folder.secrets && folder.secrets.length > 0)
|
||||
)
|
||||
};
|
||||
});
|
||||
|
||||
return mergedImportedBy;
|
||||
};
|
||||
|
||||
const importedBy = useMemo(() => {
|
||||
if (!importedByEnvs) return [];
|
||||
if (selectedKeysCount === 0) {
|
||||
return filterAndMergeEnvironments(
|
||||
visibleEnvs.map(({ slug }) => slug),
|
||||
importedByEnvs
|
||||
);
|
||||
}
|
||||
return filterAndMergeEnvironments(
|
||||
Object.values(selectedEntries.secret).flatMap((entries) => Object.keys(entries)),
|
||||
importedByEnvs
|
||||
);
|
||||
}, [importedByEnvs, selectedEntries, selectedKeysCount]);
|
||||
|
||||
if (isProjectV3 && visibleEnvs.length > 0 && isOverviewLoading) {
|
||||
return (
|
||||
<div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
@@ -1045,7 +1137,8 @@ export const OverviewPage = () => {
|
||||
secretPath={secretPath}
|
||||
selectedEntries={selectedEntries}
|
||||
resetSelectedEntries={resetSelectedEntries}
|
||||
importedByEnvs={importedByEnvs}
|
||||
importedBy={importedBy}
|
||||
secretsToDeleteKeys={secretsToDeleteKeys}
|
||||
usedBySecretSyncs={usedBySecretSyncs}
|
||||
/>
|
||||
<div className="thin-scrollbar mt-4">
|
||||
@@ -1263,6 +1356,7 @@ export const OverviewPage = () => {
|
||||
secretKey={key}
|
||||
getSecretByKey={getSecretByKey}
|
||||
scrollOffset={debouncedScrollOffset}
|
||||
importedBy={importedBy}
|
||||
/>
|
||||
))}
|
||||
<SecretNoAccessOverviewTableRow
|
||||
|
||||
+59
-2
@@ -29,9 +29,10 @@ import { Blur } from "@app/components/v2/Blur";
|
||||
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { usePopUp, useToggle } from "@app/hooks";
|
||||
import { SecretType } from "@app/hooks/api/types";
|
||||
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
||||
import { CollapsibleSecretImports } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretListView/CollapsibleSecretImports";
|
||||
|
||||
type Props = {
|
||||
defaultValue?: string | null;
|
||||
@@ -54,6 +55,14 @@ type Props = {
|
||||
) => Promise<void>;
|
||||
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
|
||||
isRotatedSecret?: boolean;
|
||||
importedBy?: {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export const SecretEditRow = ({
|
||||
@@ -70,8 +79,13 @@ export const SecretEditRow = ({
|
||||
secretPath,
|
||||
isVisible,
|
||||
secretId,
|
||||
isRotatedSecret
|
||||
isRotatedSecret,
|
||||
importedBy
|
||||
}: Props) => {
|
||||
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
||||
"editSecret"
|
||||
] as const);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
@@ -115,6 +129,17 @@ export const SecretEditRow = ({
|
||||
if (isCreatable) {
|
||||
await onSecretCreate(environment, secretName, value);
|
||||
} else {
|
||||
if (
|
||||
importedBy &&
|
||||
importedBy.some(({ folders }) =>
|
||||
folders?.some(({ secrets }) =>
|
||||
secrets?.some(({ referencedSecretId }) => referencedSecretId === secretId)
|
||||
)
|
||||
)
|
||||
) {
|
||||
handlePopUpOpen("editSecret", { secretValue: value });
|
||||
return;
|
||||
}
|
||||
await onSecretUpdate(
|
||||
environment,
|
||||
secretName,
|
||||
@@ -127,6 +152,18 @@ export const SecretEditRow = ({
|
||||
reset({ value });
|
||||
};
|
||||
|
||||
const handleEditSecret = async ({ secretValue }: { secretValue: string }) => {
|
||||
await onSecretUpdate(
|
||||
environment,
|
||||
secretName,
|
||||
secretValue,
|
||||
isOverride ? SecretType.Personal : SecretType.Shared,
|
||||
secretId
|
||||
);
|
||||
reset({ value: secretValue });
|
||||
handlePopUpClose("editSecret");
|
||||
};
|
||||
|
||||
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.ReadValue
|
||||
@@ -306,6 +343,26 @@ export const SecretEditRow = ({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.editSecret.isOpen}
|
||||
deleteKey="confirm"
|
||||
buttonColorSchema="secondary"
|
||||
buttonText="Save"
|
||||
subTitle=""
|
||||
title="Do you want to edit this secret?"
|
||||
onChange={(isOpen) => handlePopUpToggle("editSecret", isOpen)}
|
||||
onDeleteApproved={() => handleEditSecret(popUp?.editSecret?.data)}
|
||||
formContent={
|
||||
importedBy &&
|
||||
importedBy.length > 0 && (
|
||||
<CollapsibleSecretImports
|
||||
importedBy={importedBy}
|
||||
secretsToDelete={[secretName]}
|
||||
onlyReferences
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-1
@@ -44,6 +44,14 @@ type Props = {
|
||||
secretName: string
|
||||
) => { secret?: SecretV3RawSanitized; environmentInfo?: WorkspaceEnv } | undefined;
|
||||
scrollOffset: number;
|
||||
importedBy?: {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export const SecretOverviewTableRow = ({
|
||||
@@ -58,7 +66,8 @@ export const SecretOverviewTableRow = ({
|
||||
getImportedSecretByKey,
|
||||
scrollOffset,
|
||||
onToggleSecretSelect,
|
||||
isSelected
|
||||
isSelected,
|
||||
importedBy
|
||||
}: Props) => {
|
||||
const [isFormExpanded, setIsFormExpanded] = useToggle();
|
||||
const totalCols = environments.length + 1; // secret key row
|
||||
@@ -244,6 +253,7 @@ export const SecretOverviewTableRow = ({
|
||||
onSecretUpdate={onSecretUpdate}
|
||||
environment={slug}
|
||||
isRotatedSecret={secret?.isRotatedSecret}
|
||||
importedBy={importedBy}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+4
-77
@@ -37,15 +37,17 @@ type Props = {
|
||||
[EntryType.FOLDER]: Record<string, Record<string, TSecretFolder>>;
|
||||
[EntryType.SECRET]: Record<string, Record<string, SecretV3RawSanitized>>;
|
||||
};
|
||||
importedByEnvs?: { environment: string; importedBy: ProjectSecretsImportedBy[] }[];
|
||||
importedBy?: ProjectSecretsImportedBy[] | null;
|
||||
usedBySecretSyncs?: { name: string; destination: string; environment: string }[];
|
||||
secretsToDeleteKeys: string[];
|
||||
};
|
||||
|
||||
export const SelectionPanel = ({
|
||||
secretPath,
|
||||
resetSelectedEntries,
|
||||
selectedEntries,
|
||||
importedByEnvs,
|
||||
importedBy,
|
||||
secretsToDeleteKeys,
|
||||
usedBySecretSyncs
|
||||
}: Props) => {
|
||||
const { permission } = useProjectPermission();
|
||||
@@ -83,81 +85,6 @@ export const SelectionPanel = ({
|
||||
)
|
||||
);
|
||||
|
||||
const secretsToDeleteKeys = useMemo(() => {
|
||||
return Object.values(selectedEntries.secret).flatMap((entries) =>
|
||||
Object.values(entries).map((secret) => secret.key)
|
||||
);
|
||||
}, [selectedEntries]);
|
||||
|
||||
const filterAndMergeEnvironments = (
|
||||
envNames: string[],
|
||||
envs: { environment: string; importedBy: ProjectSecretsImportedBy[] }[]
|
||||
): ProjectSecretsImportedBy[] => {
|
||||
const filteredEnvs = envs.filter((env) => envNames.includes(env.environment));
|
||||
|
||||
if (filteredEnvs.length === 0) return [];
|
||||
|
||||
const allImportedBy = filteredEnvs.flatMap((env) => env.importedBy);
|
||||
const groupedBySlug: Record<string, ProjectSecretsImportedBy[]> = {};
|
||||
|
||||
allImportedBy.forEach((item) => {
|
||||
const { slug } = item.environment;
|
||||
if (!groupedBySlug[slug]) groupedBySlug[slug] = [];
|
||||
groupedBySlug[slug].push(item);
|
||||
});
|
||||
|
||||
const mergedImportedBy = Object.values(groupedBySlug).map((group) => {
|
||||
const { environment } = group[0];
|
||||
const allFolders = group.flatMap((item) => item.folders);
|
||||
|
||||
const foldersByName: Record<string, (typeof allFolders)[number][]> = {};
|
||||
allFolders.forEach((folder) => {
|
||||
if (!foldersByName[folder.name]) foldersByName[folder.name] = [];
|
||||
foldersByName[folder.name].push(folder);
|
||||
});
|
||||
|
||||
const mergedFolders = Object.entries(foldersByName).map(([name, folders]) => {
|
||||
const isImported = folders.some((folder) => folder.isImported);
|
||||
const allSecrets = folders.flatMap((folder) => folder.secrets || []);
|
||||
|
||||
const uniqueSecrets: { secretId: string; referencedSecretKey: string }[] = [];
|
||||
const secretIds = new Set<string>();
|
||||
|
||||
allSecrets
|
||||
.filter((secret) => secretsToDeleteKeys.includes(secret.referencedSecretKey))
|
||||
.forEach((secret) => {
|
||||
if (!secretIds.has(secret.secretId)) {
|
||||
secretIds.add(secret.secretId);
|
||||
uniqueSecrets.push(secret);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
name,
|
||||
isImported,
|
||||
...(uniqueSecrets.length > 0 ? { secrets: uniqueSecrets } : {})
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
environment,
|
||||
folders: mergedFolders.filter(
|
||||
(folder) => folder.isImported || (folder.secrets && folder.secrets.length > 0)
|
||||
)
|
||||
};
|
||||
});
|
||||
|
||||
return mergedImportedBy;
|
||||
};
|
||||
|
||||
const importedBy = useMemo(() => {
|
||||
if (selectedKeysCount === 0 || !importedByEnvs) return null;
|
||||
return filterAndMergeEnvironments(
|
||||
Object.values(selectedEntries.secret).flatMap((entries) => Object.keys(entries)),
|
||||
importedByEnvs
|
||||
);
|
||||
}, [importedByEnvs, selectedEntries, selectedKeysCount]);
|
||||
|
||||
const usedBySecretSyncsFiltered = useMemo(() => {
|
||||
if (selectedKeysCount === 0 || !usedBySecretSyncs) return null;
|
||||
const envs = Object.values(selectedEntries.secret).flatMap((entries) => Object.keys(entries));
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ type Props = {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string }[];
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
|
||||
+5
-3
@@ -26,7 +26,7 @@ interface CollapsibleSecretImportsProps {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string }[];
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
@@ -38,12 +38,14 @@ interface CollapsibleSecretImportsProps {
|
||||
}[]
|
||||
| null;
|
||||
secretsToDelete: string[];
|
||||
onlyReferences?: boolean;
|
||||
}
|
||||
|
||||
export const CollapsibleSecretImports: React.FC<CollapsibleSecretImportsProps> = ({
|
||||
importedBy = [],
|
||||
usedBySecretSyncs = [],
|
||||
secretsToDelete
|
||||
secretsToDelete,
|
||||
onlyReferences
|
||||
}) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
@@ -88,7 +90,7 @@ export const CollapsibleSecretImports: React.FC<CollapsibleSecretImportsProps> =
|
||||
|
||||
importedBy.forEach((env) => {
|
||||
env.folders.forEach((folder) => {
|
||||
if (folder.isImported) {
|
||||
if (folder.isImported && !onlyReferences) {
|
||||
items.push({
|
||||
type: ItemType.Folder,
|
||||
path: folder.name,
|
||||
|
||||
+52
-2
@@ -4,6 +4,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
DeleteActionModal,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -31,7 +32,7 @@ import {
|
||||
useProjectPermission,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { usePopUp, useToggle } from "@app/hooks";
|
||||
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
|
||||
import { WsTag } from "@app/hooks/api/types";
|
||||
import { subject } from "@casl/ability";
|
||||
@@ -56,6 +57,7 @@ import {
|
||||
SecretActionType,
|
||||
TFormSchema
|
||||
} from "./SecretListView.utils";
|
||||
import { CollapsibleSecretImports } from "./CollapsibleSecretImports";
|
||||
|
||||
type Props = {
|
||||
secret: SecretV3RawSanitized;
|
||||
@@ -74,6 +76,14 @@ type Props = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
handleSecretShare: () => void;
|
||||
importedBy?: {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export const SecretItem = memo(
|
||||
@@ -89,8 +99,12 @@ export const SecretItem = memo(
|
||||
onToggleSecretSelect,
|
||||
environment,
|
||||
secretPath,
|
||||
handleSecretShare
|
||||
handleSecretShare,
|
||||
importedBy
|
||||
}: Props) => {
|
||||
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
||||
"editSecret"
|
||||
] as const);
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { permission } = useProjectPermission();
|
||||
const { isRotatedSecret } = secret;
|
||||
@@ -194,9 +208,25 @@ export const SecretItem = memo(
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (data: TFormSchema) => {
|
||||
if (
|
||||
importedBy &&
|
||||
importedBy.some(({ folders }) =>
|
||||
folders?.some(({ secrets }) =>
|
||||
secrets?.some(({ referencedSecretId }) => referencedSecretId === secret.id)
|
||||
)
|
||||
)
|
||||
) {
|
||||
handlePopUpOpen("editSecret", data);
|
||||
return;
|
||||
}
|
||||
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
||||
};
|
||||
|
||||
const handleEditSecret = async (data: TFormSchema) => {
|
||||
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
||||
handlePopUpClose("editSecret");
|
||||
};
|
||||
|
||||
const handleTagSelect = (tag: WsTag) => {
|
||||
if (selectedTagsGroupById?.[tag.id]) {
|
||||
const tagPos = selectedTags.findIndex(({ id }) => id === tag.id);
|
||||
@@ -682,6 +712,26 @@ export const SecretItem = memo(
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.editSecret.isOpen}
|
||||
deleteKey="confirm"
|
||||
buttonColorSchema="secondary"
|
||||
buttonText="Save"
|
||||
subTitle=""
|
||||
title="Do you want to edit this secret?"
|
||||
onChange={(isOpen) => handlePopUpToggle("editSecret", isOpen)}
|
||||
onDeleteApproved={() => handleEditSecret(popUp?.editSecret?.data)}
|
||||
formContent={
|
||||
importedBy &&
|
||||
importedBy.length > 0 && (
|
||||
<CollapsibleSecretImports
|
||||
importedBy={importedBy}
|
||||
secretsToDelete={[secret.key]}
|
||||
onlyReferences
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
+2
-1
@@ -34,7 +34,7 @@ type Props = {
|
||||
environment: { name: string; slug: string };
|
||||
folders: {
|
||||
name: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string }[];
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretId: string }[];
|
||||
isImported: boolean;
|
||||
}[];
|
||||
}[];
|
||||
@@ -354,6 +354,7 @@ export const SecretListView = ({
|
||||
onSaveSecret={handleSaveSecret}
|
||||
onDeleteSecret={onDeleteSecret}
|
||||
onDetailViewSecret={onDetailViewSecret}
|
||||
importedBy={importedBy}
|
||||
onCreateTag={onCreateTag}
|
||||
handleSecretShare={() =>
|
||||
handlePopUpOpen("createSharedSecret", {
|
||||
|
||||
Reference in New Issue
Block a user