mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 05:25:56 +00:00
Feat: Visualize imported folders in overview page
This commit is contained in:
@@ -13,9 +13,9 @@ import { secretSnapshotKeys } from "../secretSnapshots/queries";
|
|||||||
import {
|
import {
|
||||||
TCreateFolderDTO,
|
TCreateFolderDTO,
|
||||||
TDeleteFolderDTO,
|
TDeleteFolderDTO,
|
||||||
|
TFetchProjectFoldersResponse,
|
||||||
TGetFoldersByEnvDTO,
|
TGetFoldersByEnvDTO,
|
||||||
TGetProjectFoldersDTO,
|
TGetProjectFoldersDTO,
|
||||||
TSecretFolder,
|
|
||||||
TUpdateFolderDTO
|
TUpdateFolderDTO
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@@ -25,14 +25,14 @@ export const folderQueryKeys = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => {
|
const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => {
|
||||||
const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", {
|
const { data } = await apiRequest.get<TFetchProjectFoldersResponse>("/api/v1/folders", {
|
||||||
params: {
|
params: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data.folders;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetProjectFolders = ({
|
export const useGetProjectFolders = ({
|
||||||
@@ -43,9 +43,9 @@ export const useGetProjectFolders = ({
|
|||||||
}: TGetProjectFoldersDTO & {
|
}: TGetProjectFoldersDTO & {
|
||||||
options?: Omit<
|
options?: Omit<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
TSecretFolder[],
|
TFetchProjectFoldersResponse,
|
||||||
unknown,
|
unknown,
|
||||||
TSecretFolder[],
|
TFetchProjectFoldersResponse,
|
||||||
ReturnType<typeof folderQueryKeys.getSecretFolders>
|
ReturnType<typeof folderQueryKeys.getSecretFolders>
|
||||||
>,
|
>,
|
||||||
"queryKey" | "queryFn"
|
"queryKey" | "queryFn"
|
||||||
@@ -74,7 +74,7 @@ export const useGetFoldersByEnv = ({
|
|||||||
const folderNames = useMemo(() => {
|
const folderNames = useMemo(() => {
|
||||||
const names = new Set<string>();
|
const names = new Set<string>();
|
||||||
folders?.forEach(({ data }) => {
|
folders?.forEach(({ data }) => {
|
||||||
data?.forEach(({ name }) => {
|
data?.folders?.forEach(({ name }) => {
|
||||||
names.add(name);
|
names.add(name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -85,16 +85,34 @@ export const useGetFoldersByEnv = ({
|
|||||||
(name: string, env: string) => {
|
(name: string, env: string) => {
|
||||||
const selectedEnvIndex = environments.indexOf(env);
|
const selectedEnvIndex = environments.indexOf(env);
|
||||||
if (selectedEnvIndex !== -1) {
|
if (selectedEnvIndex !== -1) {
|
||||||
return Boolean(
|
const isPresent = folders?.[selectedEnvIndex]?.data?.folders.find(
|
||||||
folders?.[selectedEnvIndex]?.data?.find(({ name: folderName }) => folderName === name)
|
({ name: folderName }) => folderName === name
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return Boolean(isPresent);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
[(folders || []).map((folder) => folder.data)]
|
[(folders || []).map((folder) => folder.data)]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { folders, folderNames, isFolderPresentInEnv };
|
const isImportedFolderPresentInEnv = useCallback(
|
||||||
|
(name: string, env: string) => {
|
||||||
|
const selectedEnvIndex = environments.indexOf(env);
|
||||||
|
|
||||||
|
if (selectedEnvIndex !== -1) {
|
||||||
|
const isPresent = folders?.[selectedEnvIndex]?.data?.importedFolders.find(
|
||||||
|
({ importPath }) => importPath === `/${name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return Boolean(isPresent);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[(folders || []).map((folder) => folder.data)]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { folders, folderNames, isFolderPresentInEnv, isImportedFolderPresentInEnv };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCreateFolder = () => {
|
export const useCreateFolder = () => {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export const SecretMainPage = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// fetch folders
|
// fetch folders
|
||||||
const { data: folders, isLoading: isFoldersLoading } = useGetProjectFolders({
|
const { data: folderData, isLoading: isFoldersLoading } = useGetProjectFolders({
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
path: secretPath
|
path: secretPath
|
||||||
@@ -163,7 +163,9 @@ export const SecretMainPage = () => {
|
|||||||
isPaused: !canDoReadRollback
|
isPaused: !canDoReadRollback
|
||||||
});
|
});
|
||||||
|
|
||||||
const isNotEmtpy = Boolean(secrets?.length || folders?.length || secretImports?.length);
|
const isNotEmtpy = Boolean(
|
||||||
|
secrets?.length || folderData?.folders?.length || secretImports?.length
|
||||||
|
);
|
||||||
|
|
||||||
const handleSortToggle = () =>
|
const handleSortToggle = () =>
|
||||||
setSortDir((state) => (state === SortDir.ASC ? SortDir.DESC : SortDir.ASC));
|
setSortDir((state) => (state === SortDir.ASC ? SortDir.DESC : SortDir.ASC));
|
||||||
@@ -292,7 +294,7 @@ export const SecretMainPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<FolderListView
|
<FolderListView
|
||||||
folders={folders}
|
folders={folderData?.folders}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
workspaceId={workspaceId}
|
workspaceId={workspaceId}
|
||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
@@ -312,7 +314,7 @@ export const SecretMainPage = () => {
|
|||||||
isProtectedBranch={isProtectedBranch}
|
isProtectedBranch={isProtectedBranch}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!canReadSecret && folders?.length === 0 && <PermissionDeniedBanner />}
|
{!canReadSecret && folderData?.folders?.length === 0 && <PermissionDeniedBanner />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CreateSecretForm
|
<CreateSecretForm
|
||||||
@@ -352,7 +354,7 @@ export const SecretMainPage = () => {
|
|||||||
workspaceId={workspaceId}
|
workspaceId={workspaceId}
|
||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
secrets={secrets}
|
secrets={secrets}
|
||||||
folders={folders}
|
folders={folderData?.folders}
|
||||||
snapshotCount={snapshotCount}
|
snapshotCount={snapshotCount}
|
||||||
onGoBack={handleResetSnapshot}
|
onGoBack={handleResetSnapshot}
|
||||||
onClickListSnapshot={() => handlePopUpToggle("snapshots", true)}
|
onClickListSnapshot={() => handlePopUpToggle("snapshots", true)}
|
||||||
|
|||||||
@@ -125,11 +125,12 @@ export const SecretOverviewPage = () => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
decryptFileKey: latestFileKey!
|
decryptFileKey: latestFileKey!
|
||||||
});
|
});
|
||||||
const { folders, folderNames, isFolderPresentInEnv } = useGetFoldersByEnv({
|
const { folders, folderNames, isFolderPresentInEnv, isImportedFolderPresentInEnv } =
|
||||||
projectId: workspaceId,
|
useGetFoldersByEnv({
|
||||||
path: secretPath,
|
projectId: workspaceId,
|
||||||
environments: userAvailableEnvs.map(({ slug }) => slug)
|
path: secretPath,
|
||||||
});
|
environments: userAvailableEnvs.map(({ slug }) => slug)
|
||||||
|
});
|
||||||
|
|
||||||
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
||||||
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
|
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
|
||||||
@@ -639,6 +640,7 @@ export const SecretOverviewPage = () => {
|
|||||||
<SecretOverviewFolderRow
|
<SecretOverviewFolderRow
|
||||||
folderName={folderName}
|
folderName={folderName}
|
||||||
isFolderPresentInEnv={isFolderPresentInEnv}
|
isFolderPresentInEnv={isFolderPresentInEnv}
|
||||||
|
isImportedFolderPresentInEnv={isImportedFolderPresentInEnv}
|
||||||
environments={visibleEnvs}
|
environments={visibleEnvs}
|
||||||
key={`overview-${folderName}-${index + 1}`}
|
key={`overview-${folderName}-${index + 1}`}
|
||||||
onClick={handleFolderClick}
|
onClick={handleFolderClick}
|
||||||
|
|||||||
+15
-6
@@ -1,13 +1,14 @@
|
|||||||
import { faCheck, faFolder, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faFileImport, faFolder, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { Td, Tr } from "@app/components/v2";
|
import { Td, Tooltip, Tr } from "@app/components/v2";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
folderName: string;
|
folderName: string;
|
||||||
environments: { name: string; slug: string }[];
|
environments: { name: string; slug: string }[];
|
||||||
isFolderPresentInEnv: (name: string, env: string) => boolean;
|
isFolderPresentInEnv: (name: string, env: string) => boolean;
|
||||||
|
isImportedFolderPresentInEnv: (name: string, env: string) => boolean;
|
||||||
onClick: (path: string) => void;
|
onClick: (path: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ export const SecretOverviewFolderRow = ({
|
|||||||
folderName,
|
folderName,
|
||||||
environments = [],
|
environments = [],
|
||||||
isFolderPresentInEnv,
|
isFolderPresentInEnv,
|
||||||
|
isImportedFolderPresentInEnv,
|
||||||
onClick
|
onClick
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
@@ -29,17 +31,24 @@ export const SecretOverviewFolderRow = ({
|
|||||||
</Td>
|
</Td>
|
||||||
{environments.map(({ slug }, i) => {
|
{environments.map(({ slug }, i) => {
|
||||||
const isPresent = isFolderPresentInEnv(folderName, slug);
|
const isPresent = isFolderPresentInEnv(folderName, slug);
|
||||||
|
const isImportPresent = isImportedFolderPresentInEnv(folderName, slug);
|
||||||
return (
|
return (
|
||||||
<Td
|
<Td
|
||||||
key={`sec-overview-${slug}-${i + 1}-folder`}
|
key={`sec-overview-${slug}-${i + 1}-folder`}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"border-r border-mineshaft-600 py-3 group-hover:bg-mineshaft-700",
|
"border-r border-mineshaft-600 py-3 group-hover:bg-mineshaft-700",
|
||||||
isPresent ? "text-green-600" : "text-red-600"
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
isPresent || isImportPresent ? "text-green-600" : "text-red-600"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex justify-center">
|
<Tooltip isDisabled={!isImportPresent} content="Folder is imported">
|
||||||
<FontAwesomeIcon icon={isPresent ? faCheck : faXmark} />
|
<div className="flex justify-center">
|
||||||
</div>
|
<FontAwesomeIcon
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
icon={isPresent ? faCheck : isImportPresent ? faFileImport : faXmark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</Td>
|
</Td>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user