mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 16:27:40 +00:00
feat: secret reference graph for understanding how its pulled
This commit is contained in:
@@ -8,4 +8,8 @@ export {
|
||||
useUpdateSecretBatch,
|
||||
useUpdateSecretV3
|
||||
} from "./mutations";
|
||||
export { useGetProjectSecrets, useGetProjectSecretsAllEnv, useGetSecretVersion } from "./queries";
|
||||
export {
|
||||
useGetProjectSecrets,
|
||||
useGetProjectSecretsAllEnv,
|
||||
useGetSecretReferenceTree,
|
||||
useGetSecretVersion} from "./queries";
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
+36
-2
@@ -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";
|
||||
|
||||
|
||||
+124
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user