mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 08:14:06 +00:00
feat(infisical-pg): completed root folder level secret, secret import and folder
This commit is contained in:
@@ -88,9 +88,9 @@ export const SecretMainPage = () => {
|
||||
});
|
||||
// fetch folders
|
||||
const { data: folders, isLoading: isFoldersLoading } = useGetProjectFolders({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
directory: secretPath
|
||||
path: secretPath
|
||||
});
|
||||
// fetch secret imports
|
||||
const {
|
||||
@@ -98,9 +98,9 @@ export const SecretMainPage = () => {
|
||||
isLoading: isSecretImportsLoading,
|
||||
isFetching: isSecretImportsFetching
|
||||
} = useGetSecretImports({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
directory: secretPath,
|
||||
path: secretPath,
|
||||
options: {
|
||||
enabled: canReadSecret
|
||||
}
|
||||
@@ -108,15 +108,15 @@ export const SecretMainPage = () => {
|
||||
|
||||
// fetch imported secrets to show user the overriden ones
|
||||
const { data: importedSecrets } = useGetImportedSecrets({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
decryptFileKey: decryptFileKey!,
|
||||
directory: secretPath,
|
||||
path: secretPath,
|
||||
options: {
|
||||
enabled: canReadSecret
|
||||
}
|
||||
});
|
||||
// fetch tags
|
||||
// fech tags
|
||||
const { data: tags } = useGetWsTags(canReadSecret ? workspaceId : "");
|
||||
|
||||
const { data: boardPolicy } = useGetSecretApprovalPolicyOfABoard({
|
||||
@@ -146,7 +146,7 @@ export const SecretMainPage = () => {
|
||||
isPaused: !canDoReadRollback
|
||||
});
|
||||
|
||||
const isNotEmtpy = Boolean(secrets?.length || folders?.length || secretImports?.imports?.length);
|
||||
const isNotEmtpy = Boolean(secrets?.length || folders?.length || secretImports?.length);
|
||||
|
||||
const handleSortToggle = () =>
|
||||
setSortDir((state) => (state === SortDir.ASC ? SortDir.DESC : SortDir.ASC));
|
||||
|
||||
@@ -112,10 +112,10 @@ export const ActionBar = ({
|
||||
const handleFolderCreate = async (folderName: string) => {
|
||||
try {
|
||||
await createFolder({
|
||||
folderName,
|
||||
directory: secretPath,
|
||||
name: folderName,
|
||||
path: secretPath,
|
||||
environment,
|
||||
workspaceId
|
||||
projectId: workspaceId
|
||||
});
|
||||
handlePopUpClose("addFolder");
|
||||
createNotification({
|
||||
|
||||
@@ -66,11 +66,11 @@ export const CreateSecretImportForm = ({
|
||||
try {
|
||||
await createSecretImport({
|
||||
environment,
|
||||
workspaceId,
|
||||
directory: secretPath,
|
||||
secretImport: {
|
||||
projectId: workspaceId,
|
||||
path: secretPath,
|
||||
import: {
|
||||
environment: importedEnv,
|
||||
secretPath: importedSecPath
|
||||
path: importedSecPath
|
||||
}
|
||||
});
|
||||
onClose();
|
||||
|
||||
@@ -42,12 +42,13 @@ export const FolderListView = ({
|
||||
|
||||
const handleFolderUpdate = async (newFolderName: string) => {
|
||||
try {
|
||||
const { id: folderId } = popUp.updateFolder.data as TSecretFolder;
|
||||
await updateFolder({
|
||||
folderName: popUp.updateFolder.data as string,
|
||||
folderId,
|
||||
name: newFolderName,
|
||||
directory: secretPath,
|
||||
path: secretPath,
|
||||
environment,
|
||||
workspaceId
|
||||
projectId: workspaceId
|
||||
});
|
||||
handlePopUpClose("updateFolder");
|
||||
createNotification({
|
||||
@@ -65,11 +66,12 @@ export const FolderListView = ({
|
||||
|
||||
const handleFolderDelete = async () => {
|
||||
try {
|
||||
const { id: folderId } = popUp.deleteFolder.data as TSecretFolder;
|
||||
await deleteFolder({
|
||||
folderName: popUp.deleteFolder.data as string,
|
||||
directory: secretPath,
|
||||
folderId,
|
||||
path: secretPath,
|
||||
environment,
|
||||
workspaceId
|
||||
projectId: workspaceId
|
||||
});
|
||||
handlePopUpClose("deleteFolder");
|
||||
createNotification({
|
||||
@@ -106,13 +108,13 @@ export const FolderListView = ({
|
||||
.map(({ name, id }) => (
|
||||
<div
|
||||
key={id}
|
||||
className="flex group border-b border-mineshaft-600 hover:bg-mineshaft-700 cursor-pointer"
|
||||
className="group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="w-11 px-5 py-3 text-yellow-700 flex items-center">
|
||||
<div className="flex w-11 items-center px-5 py-3 text-yellow-700">
|
||||
<FontAwesomeIcon icon={faFolder} />
|
||||
</div>
|
||||
<div
|
||||
className="flex-grow px-4 py-3 flex items-center"
|
||||
className="flex flex-grow items-center px-4 py-3"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(evt) => {
|
||||
@@ -122,7 +124,7 @@ export const FolderListView = ({
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
<div className="px-3 py-3 flex items-center space-x-4 border-l border-mineshaft-600">
|
||||
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
@@ -134,8 +136,8 @@ export const FolderListView = ({
|
||||
ariaLabel="edit-folder"
|
||||
variant="plain"
|
||||
size="sm"
|
||||
className="group-hover:opacity-100 opacity-0 p-0"
|
||||
onClick={() => handlePopUpOpen("updateFolder", name)}
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
onClick={() => handlePopUpOpen("updateFolder", { id, name })}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencilSquare} size="lg" />
|
||||
@@ -153,8 +155,8 @@ export const FolderListView = ({
|
||||
ariaLabel="delete-folder"
|
||||
variant="plain"
|
||||
size="md"
|
||||
className="group-hover:opacity-100 opacity-0 p-0"
|
||||
onClick={() => handlePopUpOpen("deleteFolder", name)}
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
onClick={() => handlePopUpOpen("deleteFolder", { id, name })}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} size="lg" />
|
||||
@@ -171,14 +173,14 @@ export const FolderListView = ({
|
||||
<ModalContent title="Edit Folder">
|
||||
<FolderForm
|
||||
isEdit
|
||||
defaultFolderName={popUp.updateFolder.data as string}
|
||||
defaultFolderName={(popUp.updateFolder?.data as TSecretFolder)?.name}
|
||||
onUpdateFolder={handleFolderUpdate}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteFolder.isOpen}
|
||||
deleteKey={popUp.deleteFolder?.data as string}
|
||||
deleteKey={(popUp.deleteFolder?.data as TSecretFolder)?.name}
|
||||
title="Do you want to delete this folder?"
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteFolder", isOpen)}
|
||||
onDeleteApproved={handleFolderDelete}
|
||||
|
||||
+19
-19
@@ -12,17 +12,18 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { EmptyState, IconButton, SecretInput, TableContainer } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
type Props = {
|
||||
onDelete: (environment: string, secretPath: string) => void;
|
||||
onDelete: () => void;
|
||||
environment: string;
|
||||
secretPath?: string;
|
||||
importedEnv: string;
|
||||
importedSecPath: string;
|
||||
importEnvName: string;
|
||||
importEnvPath: string;
|
||||
importedSecrets: { key: string; value: string; overriden: { env: string; secretPath: string } }[];
|
||||
searchTerm: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
// to show the environment and folder icon
|
||||
@@ -31,7 +32,7 @@ export const EnvFolderIcon = ({ env, secretPath }: { env: string; secretPath: st
|
||||
<div style={{ minWidth: "96px" }}>{env || "-"}</div>
|
||||
{secretPath && (
|
||||
<div className="inline-flex items-center space-x-2 border-l border-mineshaft-600 pl-2">
|
||||
<FontAwesomeIcon icon={faFolder} className="text-green-700 text-md" />
|
||||
<FontAwesomeIcon icon={faFolder} className="text-md text-green-700" />
|
||||
<span>{secretPath}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -39,9 +40,10 @@ export const EnvFolderIcon = ({ env, secretPath }: { env: string; secretPath: st
|
||||
);
|
||||
|
||||
export const SecretImportItem = ({
|
||||
importedEnv,
|
||||
importedSecPath,
|
||||
onDelete,
|
||||
id,
|
||||
importEnvName,
|
||||
importEnvPath,
|
||||
importedSecrets = [],
|
||||
searchTerm = "",
|
||||
secretPath,
|
||||
@@ -49,10 +51,8 @@ export const SecretImportItem = ({
|
||||
}: Props) => {
|
||||
const [isExpanded, setIsExpanded] = useToggle();
|
||||
const { attributes, listeners, transform, transition, setNodeRef, isDragging } = useSortable({
|
||||
id: `${importedEnv}-${importedSecPath}`
|
||||
id
|
||||
});
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const rowEnv = currentWorkspace?.environments?.find(({ slug }) => slug === importedEnv);
|
||||
|
||||
useEffect(() => {
|
||||
const filteredSecrets = importedSecrets.filter((secret) =>
|
||||
@@ -80,7 +80,7 @@ export const SecretImportItem = ({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex group border-b border-mineshaft-600 hover:bg-mineshaft-700 cursor-pointer"
|
||||
className="group flex cursor-pointer border-b border-mineshaft-600 hover:bg-mineshaft-700"
|
||||
role="button"
|
||||
ref={setNodeRef}
|
||||
tabIndex={0}
|
||||
@@ -88,13 +88,13 @@ export const SecretImportItem = ({
|
||||
onClick={() => setIsExpanded.toggle()}
|
||||
onKeyDown={() => setIsExpanded.toggle()}
|
||||
>
|
||||
<div className="w-12 px-4 py-2 flex items-center text-green-700">
|
||||
<div className="flex w-12 items-center px-4 py-2 text-green-700">
|
||||
<FontAwesomeIcon icon={faFileImport} />
|
||||
</div>
|
||||
<div className="flex-grow px-4 py-2 flex items-center">
|
||||
<EnvFolderIcon env={rowEnv?.name || ""} secretPath={importedSecPath} />
|
||||
<div className="flex flex-grow items-center px-4 py-2">
|
||||
<EnvFolderIcon env={importEnvName || ""} secretPath={importEnvPath} />
|
||||
</div>
|
||||
<div className="px-4 py-2 flex items-center space-x-4 border-l border-mineshaft-600">
|
||||
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-4 py-2">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
@@ -107,7 +107,7 @@ export const SecretImportItem = ({
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="expand"
|
||||
className="group-hover:opacity-100 opacity-0 p-0"
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
isDisabled={!isAllowed}
|
||||
@@ -128,10 +128,10 @@ export const SecretImportItem = ({
|
||||
variant="plain"
|
||||
colorSchema="danger"
|
||||
ariaLabel="delete"
|
||||
className="group-hover:opacity-100 opacity-0 p-0"
|
||||
className="p-0 opacity-0 group-hover:opacity-100"
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
onDelete(importedEnv, importedSecPath);
|
||||
onDelete();
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
@@ -167,7 +167,7 @@ export const SecretImportItem = ({
|
||||
{importedSecrets
|
||||
.filter((secret) => secret.key.toUpperCase().includes(searchTerm.toUpperCase()))
|
||||
.map(({ key, value, overriden }, index) => (
|
||||
<tr key={`${importedEnv}-${importedSecPath}-${key}-${index + 1}`}>
|
||||
<tr key={`${id}-${key}-${index + 1}`}>
|
||||
<td className="h-10" style={{ padding: "0.25rem 1rem" }}>
|
||||
{key}
|
||||
</td>
|
||||
|
||||
+47
-60
@@ -17,7 +17,7 @@ import { DeleteActionModal } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteSecretImport, useUpdateSecretImport } from "@app/hooks/api";
|
||||
import { TSecretImports } from "@app/hooks/api/secretImports/types";
|
||||
import { TSecretImport } from "@app/hooks/api/secretImports/types";
|
||||
import { DecryptedSecret } from "@app/hooks/api/types";
|
||||
|
||||
import { SecretImportItem } from "./SecretImportItem";
|
||||
@@ -76,17 +76,15 @@ type Props = {
|
||||
environment: string;
|
||||
workspaceId: string;
|
||||
secretPath?: string;
|
||||
secretImports?: TSecretImports;
|
||||
secretImports?: TSecretImport[];
|
||||
isFetching?: boolean;
|
||||
secrets?: DecryptedSecret[];
|
||||
importedSecrets?: TImportedSecrets;
|
||||
searchTerm: string;
|
||||
};
|
||||
|
||||
type TDeleteSecretImport = { environment: string; secretPath: string };
|
||||
|
||||
export const SecretImportListView = ({
|
||||
secretImports,
|
||||
secretImports = [],
|
||||
environment,
|
||||
workspaceId,
|
||||
secretPath,
|
||||
@@ -107,21 +105,11 @@ export const SecretImportListView = ({
|
||||
useSensor(KeyboardSensor, {})
|
||||
);
|
||||
|
||||
const [items, setItems] = useState(
|
||||
(secretImports?.imports || [])?.map((dto) => ({
|
||||
id: `${dto.environment}-${dto.secretPath}`,
|
||||
...dto
|
||||
}))
|
||||
);
|
||||
const [items, setItems] = useState(secretImports);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetching) {
|
||||
setItems(
|
||||
(secretImports?.imports || [])?.map((dto) => ({
|
||||
id: `${dto.environment}-${dto.secretPath}`,
|
||||
...dto
|
||||
}))
|
||||
);
|
||||
setItems(secretImports);
|
||||
}
|
||||
}, [isFetching]);
|
||||
|
||||
@@ -129,24 +117,19 @@ export const SecretImportListView = ({
|
||||
const { mutate: updateSecretImport } = useUpdateSecretImport();
|
||||
|
||||
const handleSecretImportDelete = async () => {
|
||||
const { environment: importEnv, secretPath: impSecPath } = popUp.deleteSecretImport
|
||||
?.data as TDeleteSecretImport;
|
||||
const { id: secretImportId } = popUp.deleteSecretImport?.data as { id: string };
|
||||
try {
|
||||
if (secretImports?.id) {
|
||||
await deleteSecretImport({
|
||||
workspaceId,
|
||||
environment,
|
||||
directory: secretPath,
|
||||
id: secretImports?.id,
|
||||
secretImportEnv: importEnv,
|
||||
secretImportPath: impSecPath
|
||||
});
|
||||
handlePopUpClose("deleteSecretImport");
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully removed secret link"
|
||||
});
|
||||
}
|
||||
await deleteSecretImport({
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
path: secretPath,
|
||||
id: secretImportId
|
||||
});
|
||||
handlePopUpClose("deleteSecretImport");
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully removed secret link"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
@@ -163,11 +146,13 @@ export const SecretImportListView = ({
|
||||
const newImportOrder = arrayMove(items, oldIndex, newIndex);
|
||||
setItems(newImportOrder);
|
||||
updateSecretImport({
|
||||
workspaceId,
|
||||
projectId: workspaceId,
|
||||
environment,
|
||||
directory: secretPath,
|
||||
id: secretImports?.id || "",
|
||||
secretImports: newImportOrder
|
||||
path: secretPath,
|
||||
id: active.id as string,
|
||||
import: {
|
||||
position: newIndex + 1
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -181,26 +166,28 @@ export const SecretImportListView = ({
|
||||
modifiers={[restrictToVerticalAxis]}
|
||||
>
|
||||
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
||||
{items?.map(({ secretPath: importedSecPath, environment: importedEnv }) => (
|
||||
<SecretImportItem
|
||||
searchTerm={searchTerm}
|
||||
key={`${importedEnv}-${importedSecPath}`}
|
||||
importedEnv={importedEnv}
|
||||
importedSecPath={importedSecPath}
|
||||
importedSecrets={computeImportedSecretRows(
|
||||
importedEnv,
|
||||
importedSecPath,
|
||||
importedSecrets,
|
||||
secrets,
|
||||
environments
|
||||
)}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
onDelete={(env, secPath) =>
|
||||
handlePopUpOpen("deleteSecretImport", { environment: env, secretPath: secPath })
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{items?.map((item) => {
|
||||
const { importPath, importEnv, id } = item;
|
||||
return (
|
||||
<SecretImportItem
|
||||
searchTerm={searchTerm}
|
||||
key={`imported-env-${id}`}
|
||||
id={id}
|
||||
importEnvPath={importPath}
|
||||
importEnvName={importEnv.name}
|
||||
importedSecrets={computeImportedSecretRows(
|
||||
importEnv.slug,
|
||||
importPath,
|
||||
importedSecrets,
|
||||
secrets,
|
||||
environments
|
||||
)}
|
||||
secretPath={secretPath}
|
||||
environment={environment}
|
||||
onDelete={() => handlePopUpOpen("deleteSecretImport", item)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
<DeleteActionModal
|
||||
@@ -208,8 +195,8 @@ export const SecretImportListView = ({
|
||||
deleteKey="unlink"
|
||||
title="Do you want to remove this secret import?"
|
||||
subTitle={`This will unlink secrets from environment ${
|
||||
(popUp.deleteSecretImport?.data as TDeleteSecretImport)?.environment
|
||||
} of path ${(popUp.deleteSecretImport?.data as TDeleteSecretImport)?.secretPath}?`}
|
||||
(popUp.deleteSecretImport?.data as TSecretImport)?.importEnv
|
||||
} of path ${(popUp.deleteSecretImport?.data as TSecretImport)?.importPath}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteSecretImport", isOpen)}
|
||||
onDeleteApproved={handleSecretImportDelete}
|
||||
/>
|
||||
|
||||
@@ -208,8 +208,8 @@ export const SecretListView = ({
|
||||
} = modSecret;
|
||||
const hasKeyChanged = oldKey !== key;
|
||||
|
||||
const tagIds = tags.map(({ id }) => id);
|
||||
const oldTagIds = orgSecret.tags.map(({ id }) => id);
|
||||
const tagIds = tags?.map(({ id }) => id);
|
||||
const oldTagIds = (orgSecret?.tags || []).map(({ id }) => id);
|
||||
const isSameTags = JSON.stringify(tagIds) === JSON.stringify(oldTagIds);
|
||||
const isSharedSecUnchanged =
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user