feat(infisical-pg): completed root folder level secret, secret import and folder

This commit is contained in:
Akhil Mohan
2024-01-27 12:38:23 +05:30
parent 8a6ab7f2f6
commit 0e1191f2ea
70 changed files with 3105 additions and 278 deletions
@@ -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}
@@ -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>
@@ -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 =
(