mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 23:26:42 +00:00
feat: added dynamic secret to overview page
This commit is contained in:
@@ -1,2 +1,6 @@
|
|||||||
export { useCreateDynamicSecret, useDeleteDynamicSecret, useUpdateDynamicSecret } from "./mutation";
|
export { useCreateDynamicSecret, useDeleteDynamicSecret, useUpdateDynamicSecret } from "./mutation";
|
||||||
export { useGetDynamicSecretDetails,useGetDynamicSecrets } from "./queries";
|
export {
|
||||||
|
useGetDynamicSecretDetails,
|
||||||
|
useGetDynamicSecrets,
|
||||||
|
useGetDynamicSecretsOfAllEnv
|
||||||
|
} from "./queries";
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useCallback, useMemo } from "react";
|
||||||
|
import { useQueries, useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { TDetailsDynamicSecretDTO, TDynamicSecret, TListDynamicSecretDTO } from "./types";
|
import {
|
||||||
|
TDetailsDynamicSecretDTO,
|
||||||
|
TDynamicSecret,
|
||||||
|
TGetDynamicSecretsByEnvsDTO,
|
||||||
|
TListDynamicSecretDTO
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export const dynamicSecretKeys = {
|
export const dynamicSecretKeys = {
|
||||||
list: ({
|
list: ({
|
||||||
@@ -15,7 +21,11 @@ export const dynamicSecretKeys = {
|
|||||||
[{ projectSlug, path, environmentSlug, name }, "dynamic-secret-details"] as const
|
[{ projectSlug, path, environmentSlug, name }, "dynamic-secret-details"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetDynamicSecrets = ({ projectSlug, environmentSlug, path }: TListDynamicSecretDTO) => {
|
export const useGetDynamicSecrets = ({
|
||||||
|
projectSlug,
|
||||||
|
environmentSlug,
|
||||||
|
path
|
||||||
|
}: TListDynamicSecretDTO) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: dynamicSecretKeys.list({ path, environmentSlug, projectSlug }),
|
queryKey: dynamicSecretKeys.list({ path, environmentSlug, projectSlug }),
|
||||||
enabled: Boolean(projectSlug && environmentSlug && path),
|
enabled: Boolean(projectSlug && environmentSlug && path),
|
||||||
@@ -60,3 +70,57 @@ export const useGetDynamicSecretDetails = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetDynamicSecretsOfAllEnv = ({
|
||||||
|
path,
|
||||||
|
projectSlug,
|
||||||
|
environmentSlugs
|
||||||
|
}: TGetDynamicSecretsByEnvsDTO) => {
|
||||||
|
const dynamicSecrets = useQueries({
|
||||||
|
queries: environmentSlugs.map((environment) => ({
|
||||||
|
queryKey: dynamicSecretKeys.list({ path, environmentSlug: environment, projectSlug }),
|
||||||
|
enabled: Boolean(projectSlug && environment && path),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{ dynamicSecrets: TDynamicSecret[] }>(
|
||||||
|
"/api/v1/dynamic-secrets",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
projectSlug,
|
||||||
|
environmentSlug: environment,
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.dynamicSecrets;
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
const dynamicSecretNames = useMemo(() => {
|
||||||
|
const names = new Set<string>();
|
||||||
|
dynamicSecrets?.forEach(({ data }) => {
|
||||||
|
data?.forEach(({ name }) => {
|
||||||
|
names.add(name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return [...names];
|
||||||
|
}, [(dynamicSecrets || []).map((dynamicSecret) => dynamicSecret.data)]);
|
||||||
|
|
||||||
|
const isDynamicSecretPresentInEnv = useCallback(
|
||||||
|
(name: string, env: string) => {
|
||||||
|
const selectedEnvIndex = environmentSlugs.indexOf(env);
|
||||||
|
if (selectedEnvIndex !== -1) {
|
||||||
|
return Boolean(
|
||||||
|
dynamicSecrets?.[selectedEnvIndex]?.data?.find(
|
||||||
|
({ name: dynamicSecretName }) => dynamicSecretName === name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[(dynamicSecrets || []).map((el) => el.data)]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { dynamicSecrets, isDynamicSecretPresentInEnv, dynamicSecretNames };
|
||||||
|
};
|
||||||
|
|||||||
@@ -82,3 +82,9 @@ export type TDetailsDynamicSecretDTO = {
|
|||||||
environmentSlug: string;
|
environmentSlug: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TGetDynamicSecretsByEnvsDTO = {
|
||||||
|
projectSlug: string;
|
||||||
|
path: string;
|
||||||
|
environmentSlugs: string[];
|
||||||
|
};
|
||||||
|
|||||||
@@ -35,3 +35,5 @@ export const useGetDynamicSecretLeases = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,3 +46,4 @@ export type TRevokeDynamicSecretLeaseDTO = {
|
|||||||
environmentSlug: string;
|
environmentSlug: string;
|
||||||
isForced?: boolean;
|
isForced?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import {
|
|||||||
useCreateFolder,
|
useCreateFolder,
|
||||||
useCreateSecretV3,
|
useCreateSecretV3,
|
||||||
useDeleteSecretV3,
|
useDeleteSecretV3,
|
||||||
|
useGetDynamicSecretsOfAllEnv,
|
||||||
useGetFoldersByEnv,
|
useGetFoldersByEnv,
|
||||||
useGetImportedSecretsAllEnvs,
|
useGetImportedSecretsAllEnvs,
|
||||||
useGetProjectSecretsAllEnv,
|
useGetProjectSecretsAllEnv,
|
||||||
@@ -66,12 +67,13 @@ import { FolderForm } from "../SecretMainPage/components/ActionBar/FolderForm";
|
|||||||
import { CreateSecretForm } from "./components/CreateSecretForm";
|
import { CreateSecretForm } from "./components/CreateSecretForm";
|
||||||
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
|
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
|
||||||
import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection";
|
import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection";
|
||||||
|
import { SecretOverviewDynamicSecretRow } from "./components/SecretOverviewDynamicSecretRow";
|
||||||
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
|
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
|
||||||
import { SecretOverviewTableRow } from "./components/SecretOverviewTableRow";
|
import { SecretOverviewTableRow } from "./components/SecretOverviewTableRow";
|
||||||
|
|
||||||
export const SecretOverviewPage = () => {
|
export const SecretOverviewPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// this is to set expandable table width
|
// this is to set expandable table width
|
||||||
@@ -98,6 +100,7 @@ export const SecretOverviewPage = () => {
|
|||||||
const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace();
|
const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const workspaceId = currentWorkspace?.id as string;
|
const workspaceId = currentWorkspace?.id as string;
|
||||||
|
const projectSlug = currentWorkspace?.slug as string;
|
||||||
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
|
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
|
||||||
const [searchFilter, setSearchFilter] = useState("");
|
const [searchFilter, setSearchFilter] = useState("");
|
||||||
const secretPath = (router.query?.secretPath as string) || "/";
|
const secretPath = (router.query?.secretPath as string) || "/";
|
||||||
@@ -138,6 +141,13 @@ export const SecretOverviewPage = () => {
|
|||||||
environments: userAvailableEnvs.map(({ slug }) => slug)
|
environments: userAvailableEnvs.map(({ slug }) => slug)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { dynamicSecretNames, dynamicSecrets, isDynamicSecretPresentInEnv } =
|
||||||
|
useGetDynamicSecretsOfAllEnv({
|
||||||
|
projectSlug,
|
||||||
|
environmentSlugs: userAvailableEnvs.map(({ slug }) => slug),
|
||||||
|
path: secretPath
|
||||||
|
});
|
||||||
|
|
||||||
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
|
||||||
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
|
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
|
||||||
const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3();
|
const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3();
|
||||||
@@ -336,12 +346,19 @@ export const SecretOverviewPage = () => {
|
|||||||
const filteredFolderNames = folderNames?.filter((name) =>
|
const filteredFolderNames = folderNames?.filter((name) =>
|
||||||
name.toLowerCase().includes(searchFilter.toLowerCase())
|
name.toLowerCase().includes(searchFilter.toLowerCase())
|
||||||
);
|
);
|
||||||
|
const filteredDynamicSecrets = dynamicSecretNames?.filter((name) =>
|
||||||
|
name.toLowerCase().includes(searchFilter.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
const isTableEmpty =
|
const isTableEmpty =
|
||||||
!(
|
!(
|
||||||
folders?.every(({ isLoading }) => isLoading) && secrets?.every(({ isLoading }) => isLoading)
|
folders?.every(({ isLoading }) => isLoading) &&
|
||||||
|
secrets?.every(({ isLoading }) => isLoading) &&
|
||||||
|
dynamicSecrets?.every(({ isLoading }) => isLoading)
|
||||||
) &&
|
) &&
|
||||||
filteredSecretNames?.length === 0 &&
|
filteredSecretNames?.length === 0 &&
|
||||||
filteredFolderNames?.length === 0;
|
filteredFolderNames?.length === 0 &&
|
||||||
|
filteredDynamicSecrets?.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -651,6 +668,15 @@ export const SecretOverviewPage = () => {
|
|||||||
onClick={handleFolderClick}
|
onClick={handleFolderClick}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{!isTableLoading &&
|
||||||
|
filteredDynamicSecrets.map((dynamicSecretName, index) => (
|
||||||
|
<SecretOverviewDynamicSecretRow
|
||||||
|
dynamicSecretName={dynamicSecretName}
|
||||||
|
isDynamicSecretInEnv={isDynamicSecretPresentInEnv}
|
||||||
|
environments={visibleEnvs}
|
||||||
|
key={`overview-${dynamicSecretName}-${index + 1}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{!isTableLoading &&
|
{!isTableLoading &&
|
||||||
visibleEnvs?.length > 0 &&
|
visibleEnvs?.length > 0 &&
|
||||||
filteredSecretNames.map((key, index) => (
|
filteredSecretNames.map((key, index) => (
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
import { faCheck, faFingerprint, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { Td, Tr } from "@app/components/v2";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dynamicSecretName: string;
|
||||||
|
environments: { name: string; slug: string }[];
|
||||||
|
isDynamicSecretInEnv: (name: string, env: string) => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecretOverviewDynamicSecretRow = ({
|
||||||
|
dynamicSecretName,
|
||||||
|
environments = [],
|
||||||
|
isDynamicSecretInEnv
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<Tr isHoverable isSelectable className="group">
|
||||||
|
<Td className="sticky left-0 z-10 border-0 bg-mineshaft-800 bg-clip-padding p-0 group-hover:bg-mineshaft-700">
|
||||||
|
<div className="flex items-center space-x-5 border-r border-mineshaft-600 px-5 py-2.5">
|
||||||
|
<div className="text-yellow-700">
|
||||||
|
<FontAwesomeIcon icon={faFingerprint} />
|
||||||
|
</div>
|
||||||
|
<div>{dynamicSecretName}</div>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
{environments.map(({ slug }, i) => {
|
||||||
|
const isPresent = isDynamicSecretInEnv(dynamicSecretName, slug);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Td
|
||||||
|
key={`sec-overview-${slug}-${i + 1}-folder`}
|
||||||
|
className={twMerge(
|
||||||
|
"border-r border-mineshaft-600 py-3 group-hover:bg-mineshaft-700",
|
||||||
|
isPresent ? "text-green-600" : "text-red-600"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<FontAwesomeIcon
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
icon={isPresent ? faCheck : faXmark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { SecretOverviewDynamicSecretRow } from "./SecretOverviewDynamicSecretRow";
|
||||||
Reference in New Issue
Block a user