feat: frontend integration

This commit is contained in:
Sheen Capadngan
2025-01-08 18:24:40 +08:00
parent 74b95d92ab
commit d7c3192099
3 changed files with 172 additions and 2 deletions

View File

@@ -10,6 +10,7 @@ import { useToggle } from "@app/hooks/useToggle";
import { ERROR_NOT_ALLOWED_READ_SECRETS } from "./constants";
import {
GetSecretVersionsDTO,
SecretAccessListEntry,
SecretType,
SecretV3Raw,
SecretV3RawResponse,
@@ -18,6 +19,7 @@ import {
TGetProjectSecretsAllEnvDTO,
TGetProjectSecretsDTO,
TGetProjectSecretsKey,
TGetSecretAccessListDTO,
TGetSecretReferenceTreeDTO,
TSecretReferenceTraceNode
} from "./types";
@@ -27,6 +29,13 @@ export const secretKeys = {
getProjectSecret: ({ workspaceId, environment, secretPath }: TGetProjectSecretsKey) =>
[{ workspaceId, environment, secretPath }, "secrets"] as const,
getSecretVersion: (secretId: string) => [{ secretId }, "secret-versions"] as const,
getSecretAccessList: ({
workspaceId,
environment,
secretPath,
secretKey
}: TGetSecretAccessListDTO) =>
["secret-access-list", { workspaceId, environment, secretPath, secretKey }] as const,
getSecretReferenceTree: (dto: TGetSecretReferenceTreeDTO) => ["secret-reference-tree", dto]
};
@@ -231,6 +240,27 @@ export const useGetSecretVersion = (dto: GetSecretVersionsDTO) =>
}, [])
});
export const useGetSecretAccessList = (dto: TGetSecretAccessListDTO) =>
useQuery({
enabled: Boolean(dto.secretKey),
queryKey: secretKeys.getSecretAccessList(dto),
queryFn: async () => {
const { data } = await apiRequest.get<{
groups: SecretAccessListEntry[];
identities: SecretAccessListEntry[];
users: SecretAccessListEntry[];
}>(`/api/v1/secrets/raw/${dto.secretKey}/access-list`, {
params: {
workspaceId: dto.workspaceId,
environment: dto.environment,
secretPath: dto.secretPath
}
});
return data;
}
});
const fetchSecretReferenceTree = async ({
secretPath,
projectId,

View File

@@ -1,3 +1,5 @@
import { ProjectPermissionActions } from "@app/context";
import type { WsTag } from "../tags/types";
export enum SecretType {
@@ -123,6 +125,13 @@ export type GetSecretVersionsDTO = {
offset: number;
};
export type TGetSecretAccessListDTO = {
workspaceId: string;
environment: string;
secretPath: string;
secretKey: string;
};
export type TCreateSecretsV3DTO = {
secretKey: string;
secretValue: string;
@@ -228,3 +237,10 @@ export type TSecretReferenceTraceNode = {
secretPath: string;
children: TSecretReferenceTraceNode[];
};
export type SecretAccessListEntry = {
allowedActions: ProjectPermissionActions[];
id: string;
membershipId: string;
name: string;
};

View File

@@ -12,6 +12,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { Link } from "@tanstack/react-router";
import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
@@ -34,10 +35,17 @@ import {
Tooltip
} from "@app/components/v2";
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { useToggle } from "@app/hooks";
import { useGetSecretVersion } from "@app/hooks/api";
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { CreateReminderForm } from "./CreateReminderForm";
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
@@ -86,6 +94,7 @@ export const SecretDetailSidebar = ({
});
const { permission } = useProjectPermission();
const { currentWorkspace } = useWorkspace();
const { fields, append, remove } = useFieldArray({
control,
@@ -130,6 +139,13 @@ export const SecretDetailSidebar = ({
secretId: secret?.id
});
const { data: secretAccessList, isPending } = useGetSecretAccessList({
workspaceId: currentWorkspace.id,
environment,
secretPath,
secretKey
});
const handleOverrideClick = () => {
if (isOverridden) {
// override need not be flagged delete if it was never saved in server
@@ -482,8 +498,116 @@ export const SecretDetailSidebar = ({
))}
</div>
</div>
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
<div className="mb-2">Access List</div>
{!isPending && secretAccessList === undefined && (
<Button className="w-full px-2 py-1" variant="outline_bg">
Analyze Access
</Button>
)}
{!isPending && secretAccessList && (
<div className="flex max-h-72 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
{secretAccessList.users.length > 0 && (
<div className="pb-3">
<div className="mb-2 font-bold">Users</div>
<div className="flex flex-wrap gap-2">
{secretAccessList.users.map((user) => (
<div className="rounded-md bg-bunker-500 px-1">
<Link
to={
`/${ProjectType.SecretManager}/$projectId/members/$membershipId` as const
}
params={{
projectId: currentWorkspace.id,
membershipId: user.membershipId
}}
className="text-secondary/80 text-sm hover:text-primary"
>
{user.name}
<Tooltip
content={user.allowedActions.join(", ")}
className="z-[100]"
>
<FontAwesomeIcon
icon={faCircleQuestion}
className="ml-1"
size="sm"
/>
</Tooltip>
</Link>
</div>
))}
</div>
</div>
)}
{secretAccessList.identities.length > 0 && (
<div className="pb-3">
<div className="mb-2 font-bold">Identities</div>
<div className="flex flex-wrap gap-2">
{secretAccessList.identities.map((identity) => (
<div className="rounded-md bg-bunker-500 px-1">
<Link
to={
`/${ProjectType.SecretManager}/$projectId/identities/$identityId` as const
}
params={{
projectId: currentWorkspace.id,
identityId: identity.id
}}
className="text-secondary/80 text-sm hover:text-primary"
>
{identity.name}
<Tooltip
content={identity.allowedActions.join(", ")}
className="z-[100]"
>
<FontAwesomeIcon
icon={faCircleQuestion}
className="ml-1"
size="sm"
/>
</Tooltip>
</Link>
</div>
))}
</div>
</div>
)}
{secretAccessList.groups.length > 0 && (
<div className="pb-3">
<div className="mb-2 font-bold">Groups</div>
<div className="flex flex-wrap gap-2">
{secretAccessList.groups.map((group) => (
<div className="rounded-md bg-bunker-500 px-1">
<Link
to={"/organization/groups/$groupId" as const}
params={{
groupId: group.id
}}
className="text-secondary/80 text-sm hover:text-primary"
>
{group.name}
<Tooltip
content={group.allowedActions.join(", ")}
className="z-[100]"
>
<FontAwesomeIcon
icon={faCircleQuestion}
className="ml-1"
size="sm"
/>
</Tooltip>
</Link>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
<div className="flex flex-col space-y-4">
<div className="flex items-center space-x-4">
<div className="mb-2 flex items-center space-x-4">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, {