mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 05:19:49 +00:00
chore: Refactor and improve UI
This commit is contained in:
@@ -6,7 +6,6 @@ import { apiRequest } from "@app/config/request";
|
||||
import {
|
||||
TDetailsDynamicSecretDTO,
|
||||
TDynamicSecret,
|
||||
TDynamicSecretProvider,
|
||||
TGetDynamicSecretsByEnvsDTO,
|
||||
TListDynamicSecretDTO
|
||||
} from "./types";
|
||||
@@ -22,12 +21,6 @@ export const dynamicSecretKeys = {
|
||||
[{ projectSlug, path, environmentSlug, name }, "dynamic-secret-details"] as const
|
||||
};
|
||||
|
||||
type EntraIDUser = {
|
||||
name: string;
|
||||
id: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
export const useGetDynamicSecrets = ({
|
||||
projectSlug,
|
||||
environmentSlug,
|
||||
@@ -79,25 +72,28 @@ export const useGetDynamicSecretDetails = ({
|
||||
};
|
||||
|
||||
export const useGetDynamicSecretProviderData = ({
|
||||
provider,
|
||||
dataFetchType,
|
||||
tenantId,
|
||||
applicationId,
|
||||
clientSecret,
|
||||
enabled
|
||||
}: {
|
||||
provider: TDynamicSecretProvider,
|
||||
dataFetchType: "Users",
|
||||
tenantId: string;
|
||||
applicationId: string;
|
||||
clientSecret: string;
|
||||
enabled: boolean
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: ["users"],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.post<{ data: { users: [EntraIDUser] } }>(
|
||||
"/api/v1/dynamic-secrets/fetch-provider-data",
|
||||
const { data } = await apiRequest.post<{id:string, email: string, name:string}[]>(
|
||||
"/api/v1/dynamic-secrets/entra-id/users",
|
||||
{
|
||||
provider,
|
||||
dataFetchType
|
||||
tenantId,
|
||||
applicationId,
|
||||
clientSecret
|
||||
}
|
||||
);
|
||||
return data.data.users;
|
||||
return data;
|
||||
},
|
||||
enabled
|
||||
});
|
||||
|
||||
+94
-62
@@ -11,10 +11,10 @@ import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Spinner,
|
||||
Input
|
||||
} from "@app/components/v2";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@app/components/v2/Dropdown/Dropdown";
|
||||
import { Tooltip } from "@app/components/v2/Tooltip";
|
||||
import { useCreateDynamicSecret } from "@app/hooks/api";
|
||||
import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries";
|
||||
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||
@@ -81,8 +81,10 @@ export const AzureEntraIdInputForm = ({
|
||||
const applicationId = watch("provider.applicationId");
|
||||
const clientSecret = watch("provider.clientSecret");
|
||||
|
||||
const configurationComplete = tenantId && applicationId && clientSecret;
|
||||
const { data, isLoading, isFetched, isError, isFetching } = useGetDynamicSecretProviderData({ dataFetchType: "Users", provider: { type: DynamicSecretProviders.AzureEntraId, inputs: { userId: "unused", email: "unused", tenantId, applicationId, clientSecret } }, enabled: !!configurationComplete });
|
||||
const configurationComplete = !!(tenantId && applicationId && clientSecret);
|
||||
const { data, isLoading, isError, isFetching } = useGetDynamicSecretProviderData({ tenantId, applicationId, clientSecret, enabled: !!configurationComplete });
|
||||
const loading = configurationComplete && isFetching;
|
||||
const errored = configurationComplete && !isFetching && isError;
|
||||
const createDynamicSecret = useCreateDynamicSecret();
|
||||
|
||||
const handleCreateDynamicSecret = async ({ name, selectedUsers, provider, maxTTL, defaultTTL }: TForm) => {
|
||||
@@ -165,7 +167,7 @@ export const AzureEntraIdInputForm = ({
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
|
||||
Configuration
|
||||
Configuration
|
||||
<Link href="https://infisical.com/docs/documentation/platform/dynamic-secrets/azure-entra-id" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
@@ -247,64 +249,94 @@ export const AzureEntraIdInputForm = ({
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center space-x-4">
|
||||
{
|
||||
configurationComplete && !isError && !isFetching && isFetched && data &&
|
||||
<Controller
|
||||
control={control}
|
||||
name="selectedUsers"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isRequired
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="w-72">
|
||||
<Input
|
||||
isReadOnly
|
||||
value={value?.length ? `${value.length} selected` : "None"}
|
||||
className="text-left"
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start"
|
||||
style={{ width: "var(--radix-dropdown-menu-trigger-width)" }}
|
||||
<Controller
|
||||
control={control}
|
||||
name="selectedUsers"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isRequired
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<DropdownMenu >
|
||||
<DropdownMenuTrigger
|
||||
className="w-72"
|
||||
disabled={loading || errored || !configurationComplete}
|
||||
>
|
||||
<Tooltip
|
||||
hidden={!loading && !errored && configurationComplete}
|
||||
content=
|
||||
{
|
||||
<div>
|
||||
{(() => {
|
||||
let icon;
|
||||
if (errored) {
|
||||
icon = <FontAwesomeIcon icon={faWarning} color="red" />;
|
||||
} else if (loading || !configurationComplete) {
|
||||
icon = <FontAwesomeIcon icon={faWarning} color="yellow" />;
|
||||
} else {
|
||||
icon = null;
|
||||
}
|
||||
return icon;
|
||||
})()}
|
||||
<span className="ml-4 cursor-default text-mineshaft-300 hover:text-mineshaft-200">
|
||||
{(() => {
|
||||
let message;
|
||||
if (loading) {
|
||||
message = "Loading, please wait...";
|
||||
} else if (errored) {
|
||||
message = "Check the configuration";
|
||||
} else if (!configurationComplete) {
|
||||
message = "Configuration incomplete";
|
||||
} else {
|
||||
message = ""; // or you can leave it undefined
|
||||
}
|
||||
return message;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{data.map((user) => {
|
||||
const ids = value?.map((selectedUser) => selectedUser.id)
|
||||
const isChecked = ids?.includes(user.id);
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
onClick={(evt) => {
|
||||
evt.preventDefault();
|
||||
onChange(
|
||||
isChecked
|
||||
? value?.filter((el) => el.id !== user.id)
|
||||
: [...(value || []), user]
|
||||
);
|
||||
}}
|
||||
key={`create-policy-members-${user.id}`}
|
||||
iconPos="right"
|
||||
icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||
>
|
||||
{user.name} <br /> {`(${user.email})`}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
{
|
||||
configurationComplete && isFetching && (<div className="pl-3 pb-2 w-full flex items-center" ><Spinner size="xs" /><p> Loading </p></div>)
|
||||
}
|
||||
{
|
||||
configurationComplete && !isFetching && isError && (<div className="pl-3 pb-2 w-full flex items-center"><FontAwesomeIcon icon={faWarning} /> <p> Error loading users please ensure Entra Id app is installed and configuration is correct</p></div>)
|
||||
}
|
||||
{
|
||||
!configurationComplete && (<div className="pl-3 pb-2 w-full flex items-center" ><FontAwesomeIcon icon={faWarning} /><p> Complete configuration to fetch users</p></div>)
|
||||
}
|
||||
<div>
|
||||
|
||||
<Input
|
||||
isReadOnly
|
||||
value={value?.length ? `${value.length} selected` : ""}
|
||||
className={`text-left ${loading || errored || !configurationComplete ? "cursor-not-allowed" : ""}`}
|
||||
placeholder="Select users"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start"
|
||||
style={{ width: "var(--radix-dropdown-menu-trigger-width)" }}
|
||||
>
|
||||
{data && data.map((user) => {
|
||||
const ids = value?.map((selectedUser) => selectedUser.id)
|
||||
const isChecked = ids?.includes(user.id);
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
onClick={(evt) => {
|
||||
evt.preventDefault();
|
||||
onChange(
|
||||
isChecked
|
||||
? value?.filter((el) => el.id !== user.id)
|
||||
: [...(value || []), user]
|
||||
);
|
||||
}}
|
||||
key={`create-policy-members-${user.id}`}
|
||||
iconPos="right"
|
||||
icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||
>
|
||||
{user.name} <br /> {`(${user.email})`}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/router";
|
||||
import { faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { IconButton } from "@app/components/v2";
|
||||
|
||||
export const AzureEntraIdCallbackPage = () => {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<div className="flex h-screen flex-col justify-between overflow-auto bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200 dark:[color-scheme:dark]">
|
||||
<div />
|
||||
<div className="mx-auto w-full max-w-xl px-4 py-4 md:px-0">
|
||||
<div className="mb-8 text-center">
|
||||
<div className="mb-4 flex justify-center pt-8">
|
||||
<Link href="https://infisical.com">
|
||||
<Image
|
||||
src="/images/gradientLogo.svg"
|
||||
height={90}
|
||||
width={120}
|
||||
alt="Infisical logo"
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-auto my-8 flex w-full" />
|
||||
<div className="m-auto mb-2 flex w-full flex-col rounded-md border bg-white/[0.05] border-white-500/30 p-6 pt-5">
|
||||
<div className="flex flex-col items-start sm:flex-row sm:items-center">
|
||||
<p className="md:text-md text-md mr-4 w-full">
|
||||
<p
|
||||
className="text-bold text-white bg-clip-text text-transparent"
|
||||
>
|
||||
Copy Tenant ID add and paste it in the dynamic secret configuration.
|
||||
</p>{" "}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-auto flex w-full flex-col rounded-md border border-primary-500/30 bg-primary/5 p-6 pt-5">
|
||||
<div className="flex flex-col items-start sm:flex-row sm:items-center">
|
||||
<p className="md:text-md text-md mr-4 w-full">
|
||||
<p
|
||||
className="text-bold bg-gradient-to-tr from-yellow-500 to-primary-500 bg-clip-text text-transparent"
|
||||
>
|
||||
Tenant ID
|
||||
</p>{" "}
|
||||
<br />
|
||||
<div className="w-full mr-2 flex items-center rounded-md bg-white/[0.05] p-2 text-base text-gray-400">
|
||||
<p className="mr-4 break-all text-left">{router.query.tenant}</p>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
colorSchema="secondary"
|
||||
className="group relative ml-auto"
|
||||
onClick={() => {
|
||||
if (typeof router.query.tenant === "string") {
|
||||
navigator.clipboard.writeText(router.query.tenant);
|
||||
createNotification({
|
||||
title: "Copied Tenant ID to clipboard succesfully",
|
||||
type: "success",
|
||||
text: ""
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCopy} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full bg-mineshaft-600 p-2" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user