feat: secret reference graph for understanding how its pulled

This commit is contained in:
=
2024-10-25 13:09:49 +05:30
parent 0fb5fa0c8b
commit 104bff0586
14 changed files with 568 additions and 155 deletions
+5 -1
View File
@@ -8,4 +8,8 @@ export {
useUpdateSecretBatch,
useUpdateSecretV3
} from "./mutations";
export { useGetProjectSecrets, useGetProjectSecretsAllEnv, useGetSecretVersion } from "./queries";
export {
useGetProjectSecrets,
useGetProjectSecretsAllEnv,
useGetSecretReferenceTree,
useGetSecretVersion} from "./queries";
+35 -2
View File
@@ -17,14 +17,17 @@ import {
SecretVersions,
TGetProjectSecretsAllEnvDTO,
TGetProjectSecretsDTO,
TGetProjectSecretsKey
TGetProjectSecretsKey,
TGetSecretReferenceTreeDTO,
TSecretReferenceTraceNode
} from "./types";
export const secretKeys = {
// this is also used in secretSnapshot part
getProjectSecret: ({ workspaceId, environment, secretPath }: TGetProjectSecretsKey) =>
[{ workspaceId, environment, secretPath }, "secrets"] as const,
getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const
getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const,
getSecretReferenceTree: (dto: TGetSecretReferenceTreeDTO) => ["secret-reference-tree", dto]
};
export const fetchProjectSecrets = async ({
@@ -227,3 +230,33 @@ export const useGetSecretVersion = (dto: GetSecretVersionsDTO) =>
return data.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
}, [])
});
const fetchSecretReferenceTree = async ({
secretPath,
projectId,
secretKey,
environmentSlug
}: TGetSecretReferenceTreeDTO) => {
const { data } = await apiRequest.get<{ tree: TSecretReferenceTraceNode; value: string }>(
`/api/v3/secrets/raw/${secretKey}/secret-reference-tree`,
{
params: {
secretPath,
workspaceId: projectId,
environment: environmentSlug
}
}
);
return data;
};
export const useGetSecretReferenceTree = (dto: TGetSecretReferenceTreeDTO) =>
useQuery({
enabled:
Boolean(dto.environmentSlug) &&
Boolean(dto.secretPath) &&
Boolean(dto.projectId) &&
Boolean(dto.secretKey),
queryKey: secretKeys.getSecretReferenceTree(dto),
queryFn: () => fetchSecretReferenceTree(dto)
});
+15
View File
@@ -210,3 +210,18 @@ export type TMoveSecretsDTO = {
secretIds: string[];
shouldOverwrite: boolean;
};
export type TGetSecretReferenceTreeDTO = {
secretKey: string;
secretPath: string;
environmentSlug: string;
projectId: string;
};
export type TSecretReferenceTraceNode = {
key: string;
value?: string;
environment: string;
secretPath: string;
children: TSecretReferenceTraceNode[];
};
@@ -7,6 +7,7 @@ import {
faCircleDot,
faClock,
faPlus,
faRoad,
faShare,
faTag
} from "@fortawesome/free-solid-svg-icons";
@@ -28,6 +29,9 @@ import {
FormControl,
IconButton,
Input,
Modal,
ModalContent,
ModalTrigger,
Switch,
Tag,
TextArea,
@@ -41,6 +45,7 @@ import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
import { CreateReminderForm } from "./CreateReminderForm";
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
import { SecretReferenceTree } from "./SecretReferenceDetails";
type Props = {
isOpen?: boolean;
@@ -448,9 +453,38 @@ export const SecretDetailSidebar = ({
)}
/>
</div>
<div className="ml-1 flex items-center space-x-2">
<div className="ml-1 flex items-center space-x-4">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
>
{(isAllowed) => (
<Modal>
<ModalTrigger asChild>
<Button
variant="outline_bg"
className="w-full px-2 py-1"
leftIcon={<FontAwesomeIcon icon={faRoad} />}
isDisabled={!isAllowed}
>
Reference tree
</Button>
</ModalTrigger>
<ModalContent
title="Secret Reference Tree"
subTitle="Shows the actual value and reference tree"
>
<SecretReferenceTree
secretPath={secretPath}
environment={environment}
secret={secret}
/>
</ModalContent>
</Modal>
)}
</ProjectPermissionCan>
<Button
className="px-2 py-1"
className="w-full px-2 py-1"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faShare} />}
onClick={() => {
@@ -16,7 +16,7 @@ import { WsTag } from "@app/hooks/api/types";
import { AddShareSecretModal } from "@app/views/ShareSecretPage/components/AddShareSecretModal";
import { useSelectedSecretActions, useSelectedSecrets } from "../../SecretMainPage.store";
import { SecretDetailSidebar } from "./SecretDetaiSidebar";
import { SecretDetailSidebar } from "./SecretDetailSidebar";
import { SecretItem } from "./SecretItem";
import { FontAwesomeSpriteSymbols } from "./SecretListView.utils";
@@ -0,0 +1,124 @@
import { twMerge } from "tailwind-merge";
import { FormControl, SecretInput, Tag, Tooltip } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useGetSecretReferenceTree } from "@app/hooks/api";
import { SecretV3RawSanitized, TSecretReferenceTraceNode } from "@app/hooks/api/types";
type Props = {
environment: string;
secretPath: string;
secret: SecretV3RawSanitized;
};
export const SecretReferenceNode = ({
node,
isRoot
}: {
node: TSecretReferenceTraceNode;
isRoot?: boolean;
}) => (
<div>
{isRoot ? (
<FormControl label="Reference value" className="sticky top-0 mb-0 bg-mineshaft-800">
<SecretInput
key="value-overriden"
isReadOnly
value={node.value}
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
/>
</FormControl>
) : (
<div className="py-2 text-sm">
<div>
{node.key}
<Tooltip content={node.value}>
<span
className={twMerge(
"ml-2 rounded border border-gray-400 px-1 text-xs text-gray-400",
!node.value && "border-red-400 text-red-400"
)}
>
{node.value ? "value" : "empty"}
</span>
</Tooltip>
</div>
<div className="mt-2">
<div className="flex items-center space-x-2">
<div className="flex items-center">
<Tag
size="xs"
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<div>Environment</div>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{node.environment}
</div>
</Tag>
</div>
<div className="flex items-center">
<Tag
size="xs"
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<div>Secret Path</div>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{node.secretPath}
</div>
</Tag>
</div>
</div>
</div>
<div className="w-full border-b border-gray-800 py-1" />
</div>
)}
{node.children.length > 0 && (
<div className="border-l border-gray-600 pl-6 transition-all hover:border-primary-600">
{node.children.map((el, index) => (
<SecretReferenceNode node={el} key={`node-${node.key}-${index + 1}`} />
))}
</div>
)}
</div>
);
export const SecretReferenceTree = ({ secretPath, environment, secret }: Props) => {
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace?.id || "";
const { data } = useGetSecretReferenceTree({
secretPath,
environmentSlug: environment,
projectId,
secretKey: secret?.key
});
const tree = data?.tree;
const secretValue = data?.value;
return (
<div>
<FormControl label="Expanded value">
<SecretInput
key="value-overriden"
isReadOnly
value={secretValue}
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
/>
</FormControl>
<div className="max-h-80 overflow-auto">
{tree && <SecretReferenceNode node={tree} isRoot />}
</div>
</div>
);
};