feat: added dynamic secret to overview page

This commit is contained in:
Akhil Mohan
2024-03-26 23:40:41 +05:30
parent d735ec71b8
commit aef8d79101
8 changed files with 161 additions and 7 deletions

View File

@@ -1,2 +1,6 @@
export { useCreateDynamicSecret, useDeleteDynamicSecret, useUpdateDynamicSecret } from "./mutation";
export { useGetDynamicSecretDetails,useGetDynamicSecrets } from "./queries";
export {
useGetDynamicSecretDetails,
useGetDynamicSecrets,
useGetDynamicSecretsOfAllEnv
} from "./queries";

View File

@@ -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 { TDetailsDynamicSecretDTO, TDynamicSecret, TListDynamicSecretDTO } from "./types";
import {
TDetailsDynamicSecretDTO,
TDynamicSecret,
TGetDynamicSecretsByEnvsDTO,
TListDynamicSecretDTO
} from "./types";
export const dynamicSecretKeys = {
list: ({
@@ -15,7 +21,11 @@ export const dynamicSecretKeys = {
[{ 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({
queryKey: dynamicSecretKeys.list({ path, environmentSlug, projectSlug }),
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 };
};

View File

@@ -82,3 +82,9 @@ export type TDetailsDynamicSecretDTO = {
environmentSlug: string;
name: string;
};
export type TGetDynamicSecretsByEnvsDTO = {
projectSlug: string;
path: string;
environmentSlugs: string[];
};

View File

@@ -35,3 +35,5 @@ export const useGetDynamicSecretLeases = ({
}
});
};

View File

@@ -46,3 +46,4 @@ export type TRevokeDynamicSecretLeaseDTO = {
environmentSlug: string;
isForced?: boolean;
};

View File

@@ -54,6 +54,7 @@ import {
useCreateFolder,
useCreateSecretV3,
useDeleteSecretV3,
useGetDynamicSecretsOfAllEnv,
useGetFoldersByEnv,
useGetImportedSecretsAllEnvs,
useGetProjectSecretsAllEnv,
@@ -66,12 +67,13 @@ import { FolderForm } from "../SecretMainPage/components/ActionBar/FolderForm";
import { CreateSecretForm } from "./components/CreateSecretForm";
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
import { ProjectIndexSecretsSection } from "./components/ProjectIndexSecretsSection";
import { SecretOverviewDynamicSecretRow } from "./components/SecretOverviewDynamicSecretRow";
import { SecretOverviewFolderRow } from "./components/SecretOverviewFolderRow";
import { SecretOverviewTableRow } from "./components/SecretOverviewTableRow";
export const SecretOverviewPage = () => {
const { t } = useTranslation();
const router = useRouter();
// this is to set expandable table width
@@ -98,6 +100,7 @@ export const SecretOverviewPage = () => {
const { currentWorkspace, isLoading: isWorkspaceLoading } = useWorkspace();
const { currentOrg } = useOrganization();
const workspaceId = currentWorkspace?.id as string;
const projectSlug = currentWorkspace?.slug as string;
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
const [searchFilter, setSearchFilter] = useState("");
const secretPath = (router.query?.secretPath as string) || "/";
@@ -138,6 +141,13 @@ export const SecretOverviewPage = () => {
environments: userAvailableEnvs.map(({ slug }) => slug)
});
const { dynamicSecretNames, dynamicSecrets, isDynamicSecretPresentInEnv } =
useGetDynamicSecretsOfAllEnv({
projectSlug,
environmentSlugs: userAvailableEnvs.map(({ slug }) => slug),
path: secretPath
});
const { mutateAsync: createSecretV3 } = useCreateSecretV3();
const { mutateAsync: updateSecretV3 } = useUpdateSecretV3();
const { mutateAsync: deleteSecretV3 } = useDeleteSecretV3();
@@ -336,12 +346,19 @@ export const SecretOverviewPage = () => {
const filteredFolderNames = folderNames?.filter((name) =>
name.toLowerCase().includes(searchFilter.toLowerCase())
);
const filteredDynamicSecrets = dynamicSecretNames?.filter((name) =>
name.toLowerCase().includes(searchFilter.toLowerCase())
);
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 &&
filteredFolderNames?.length === 0;
filteredFolderNames?.length === 0 &&
filteredDynamicSecrets?.length === 0;
return (
<>
@@ -651,6 +668,15 @@ export const SecretOverviewPage = () => {
onClick={handleFolderClick}
/>
))}
{!isTableLoading &&
filteredDynamicSecrets.map((dynamicSecretName, index) => (
<SecretOverviewDynamicSecretRow
dynamicSecretName={dynamicSecretName}
isDynamicSecretInEnv={isDynamicSecretPresentInEnv}
environments={visibleEnvs}
key={`overview-${dynamicSecretName}-${index + 1}`}
/>
))}
{!isTableLoading &&
visibleEnvs?.length > 0 &&
filteredSecretNames.map((key, index) => (

View File

@@ -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>
);
};

View File

@@ -0,0 +1 @@
export { SecretOverviewDynamicSecretRow } from "./SecretOverviewDynamicSecretRow";