mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 20:27:43 +00:00
chore: Refactor and improve UI
This commit is contained in:
@@ -3,10 +3,7 @@ import ms from "ms";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
||||||
import {
|
import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/providers/models";
|
||||||
DynamicSecretDataFetchTypes,
|
|
||||||
DynamicSecretProviderSchema
|
|
||||||
} from "@app/ee/services/dynamic-secret/providers/models";
|
|
||||||
import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
|
import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
|
||||||
import { daysToMillisecond } from "@app/lib/dates";
|
import { daysToMillisecond } from "@app/lib/dates";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
@@ -82,26 +79,33 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/fetch-provider-data",
|
url: "/entra-id/users",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
dataFetchType: z.string().min(1).describe("Type of data to fetch"),
|
tenantId: z.string().min(1).describe("The tenant ID of the Azure Entra ID"),
|
||||||
provider: DynamicSecretProviderSchema.describe(DYNAMIC_SECRETS.CREATE.provider)
|
applicationId: z.string().min(1).describe("The application ID of the Azure Entra ID App Registration"),
|
||||||
|
clientSecret: z.string().min(1).describe("The client secret of the Azure Entra ID App Registration")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z
|
||||||
data: z.unknown()
|
.object({
|
||||||
})
|
name: z.string().min(1).describe("The name of the user"),
|
||||||
|
id: z.string().min(1).describe("The ID of the user"),
|
||||||
|
email: z.string().min(1).describe("The email of the user")
|
||||||
|
})
|
||||||
|
.array()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const dataFetchType =
|
const data = await server.services.dynamicSecret.fetchAzureEntraIdUsers({
|
||||||
DynamicSecretDataFetchTypes[req.body.dataFetchType as keyof typeof DynamicSecretDataFetchTypes];
|
tenantId: req.body.tenantId,
|
||||||
const data = await server.services.dynamicSecret.fetchData({ provider: req.body.provider, dataFetchType });
|
applicationId: req.body.applicationId,
|
||||||
|
clientSecret: req.body.clientSecret
|
||||||
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ import {
|
|||||||
TCreateDynamicSecretDTO,
|
TCreateDynamicSecretDTO,
|
||||||
TDeleteDynamicSecretDTO,
|
TDeleteDynamicSecretDTO,
|
||||||
TDetailsDynamicSecretDTO,
|
TDetailsDynamicSecretDTO,
|
||||||
TDynamicSecretsFetchDataDTO,
|
|
||||||
TListDynamicSecretsDTO,
|
TListDynamicSecretsDTO,
|
||||||
TUpdateDynamicSecretDTO
|
TUpdateDynamicSecretDTO
|
||||||
} from "./dynamic-secret-types";
|
} from "./dynamic-secret-types";
|
||||||
|
import { AzureEntraIDProvider } from "./providers/azure-entra-id";
|
||||||
import { DynamicSecretProviders, TDynamicProviderFns } from "./providers/models";
|
import { DynamicSecretProviders, TDynamicProviderFns } from "./providers/models";
|
||||||
|
|
||||||
type TDynamicSecretServiceFactoryDep = {
|
type TDynamicSecretServiceFactoryDep = {
|
||||||
@@ -333,12 +333,21 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
return dynamicSecretCfg;
|
return dynamicSecretCfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = async ({ provider, dataFetchType }: TDynamicSecretsFetchDataDTO) => {
|
const fetchAzureEntraIdUsers = async ({
|
||||||
const selectedProvider = dynamicSecretProviders[provider.type];
|
tenantId,
|
||||||
if (selectedProvider.fetchData) {
|
applicationId,
|
||||||
const data = selectedProvider.fetchData(provider.inputs, dataFetchType);
|
clientSecret
|
||||||
return data;
|
}: {
|
||||||
}
|
tenantId: string;
|
||||||
|
applicationId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
}) => {
|
||||||
|
const azureEntraIdUsers = await AzureEntraIDProvider().fetchAzureEntraIdUsers(
|
||||||
|
tenantId,
|
||||||
|
applicationId,
|
||||||
|
clientSecret
|
||||||
|
);
|
||||||
|
return azureEntraIdUsers;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -347,6 +356,6 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
deleteByName,
|
deleteByName,
|
||||||
getDetails,
|
getDetails,
|
||||||
list,
|
list,
|
||||||
fetchData
|
fetchAzureEntraIdUsers
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { customAlphabet } from "nanoid";
|
|||||||
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { AzureEntraIDSchema, DynamicSecretDataFetchTypes, TDynamicProviderFns } from "./models";
|
import { AzureEntraIDSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const MSFT_GRAPH_API_URL = "https://graph.microsoft.com/v1.0/";
|
const MSFT_GRAPH_API_URL = "https://graph.microsoft.com/v1.0/";
|
||||||
const MSFT_LOGIN_URL = "https://login.microsoftonline.com";
|
const MSFT_LOGIN_URL = "https://login.microsoftonline.com";
|
||||||
@@ -13,7 +13,11 @@ const generatePassword = () => {
|
|||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AzureEntraIDProvider = (): TDynamicProviderFns => {
|
type User = { name: string; id: string; email: string };
|
||||||
|
|
||||||
|
export const AzureEntraIDProvider = (): TDynamicProviderFns & {
|
||||||
|
fetchAzureEntraIdUsers: (tenantId: string, applicationId: string, clientSecret: string) => Promise<User[]>;
|
||||||
|
} => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const providerInputs = await AzureEntraIDSchema.parseAsync(inputs);
|
const providerInputs = await AzureEntraIDSchema.parseAsync(inputs);
|
||||||
return providerInputs;
|
return providerInputs;
|
||||||
@@ -93,51 +97,42 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns => {
|
|||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = async (inputs: unknown, toFetch: DynamicSecretDataFetchTypes) => {
|
const fetchAzureEntraIdUsers = async (tenantId: string, applicationId: string, clientSecret: string) => {
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const data = await getToken(tenantId, applicationId, clientSecret);
|
||||||
|
|
||||||
const data = await getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret);
|
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
throw new BadRequestError({ message: "Failed to authorize to Microsoft Entra ID" });
|
throw new BadRequestError({ message: "Failed to authorize to Microsoft Entra ID" });
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (toFetch) {
|
const response = await axios.get<{ value: [{ id: string; displayName: string; userPrincipalName: string }] }>(
|
||||||
case DynamicSecretDataFetchTypes.Users: {
|
`${MSFT_GRAPH_API_URL}/users`,
|
||||||
const response = await axios.get<{ value: [{ displayName: string; id: string; userPrincipalName: string }] }>(
|
{
|
||||||
`${MSFT_GRAPH_API_URL}/users`,
|
headers: {
|
||||||
{
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
headers: {
|
Authorization: `Bearer ${data.token}`
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
}
|
||||||
Authorization: `Bearer ${data.token}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const users = response.data.value.map(
|
|
||||||
(user: { displayName: string; id: string; userPrincipalName: string }) => {
|
|
||||||
return {
|
|
||||||
name: user.displayName,
|
|
||||||
id: user.id,
|
|
||||||
email: user.userPrincipalName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
users
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
if (response.status !== 200) {
|
||||||
throw new BadRequestError({ message: "Unknown data to fetch" });
|
throw new BadRequestError({ message: "Failed to fetch users" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const users = response.data.value.map((user) => {
|
||||||
|
return {
|
||||||
|
name: user.displayName,
|
||||||
|
id: user.id,
|
||||||
|
email: user.userPrincipalName
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return users;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
validateProviderInputs,
|
validateProviderInputs,
|
||||||
validateConnection,
|
validateConnection,
|
||||||
create,
|
create,
|
||||||
revoke,
|
revoke,
|
||||||
renew,
|
renew,
|
||||||
fetchData
|
fetchAzureEntraIdUsers
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -187,10 +187,6 @@ export enum DynamicSecretProviders {
|
|||||||
AzureEntraID = "azure-entra-id"
|
AzureEntraID = "azure-entra-id"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DynamicSecretDataFetchTypes {
|
|
||||||
Users = "users"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }),
|
||||||
@@ -210,5 +206,4 @@ export type TDynamicProviderFns = {
|
|||||||
validateProviderInputs: (inputs: object) => Promise<unknown>;
|
validateProviderInputs: (inputs: object) => Promise<unknown>;
|
||||||
revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>;
|
revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>;
|
||||||
renew: (inputs: unknown, entityId: string, expireAt: number) => Promise<{ entityId: string }>;
|
renew: (inputs: unknown, entityId: string, expireAt: number) => Promise<{ entityId: string }>;
|
||||||
fetchData?: (inputs: unknown, toFetch: DynamicSecretDataFetchTypes) => Promise<{ data: unknown }>;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import {
|
import {
|
||||||
TDetailsDynamicSecretDTO,
|
TDetailsDynamicSecretDTO,
|
||||||
TDynamicSecret,
|
TDynamicSecret,
|
||||||
TDynamicSecretProvider,
|
|
||||||
TGetDynamicSecretsByEnvsDTO,
|
TGetDynamicSecretsByEnvsDTO,
|
||||||
TListDynamicSecretDTO
|
TListDynamicSecretDTO
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@@ -22,12 +21,6 @@ export const dynamicSecretKeys = {
|
|||||||
[{ projectSlug, path, environmentSlug, name }, "dynamic-secret-details"] as const
|
[{ projectSlug, path, environmentSlug, name }, "dynamic-secret-details"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
type EntraIDUser = {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
email: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGetDynamicSecrets = ({
|
export const useGetDynamicSecrets = ({
|
||||||
projectSlug,
|
projectSlug,
|
||||||
environmentSlug,
|
environmentSlug,
|
||||||
@@ -79,25 +72,28 @@ export const useGetDynamicSecretDetails = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useGetDynamicSecretProviderData = ({
|
export const useGetDynamicSecretProviderData = ({
|
||||||
provider,
|
tenantId,
|
||||||
dataFetchType,
|
applicationId,
|
||||||
|
clientSecret,
|
||||||
enabled
|
enabled
|
||||||
}: {
|
}: {
|
||||||
provider: TDynamicSecretProvider,
|
tenantId: string;
|
||||||
dataFetchType: "Users",
|
applicationId: string;
|
||||||
|
clientSecret: string;
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
}) => {
|
}) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["users"],
|
queryKey: ["users"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.post<{ data: { users: [EntraIDUser] } }>(
|
const { data } = await apiRequest.post<{id:string, email: string, name:string}[]>(
|
||||||
"/api/v1/dynamic-secrets/fetch-provider-data",
|
"/api/v1/dynamic-secrets/entra-id/users",
|
||||||
{
|
{
|
||||||
provider,
|
tenantId,
|
||||||
dataFetchType
|
applicationId,
|
||||||
|
clientSecret
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return data.data.users;
|
return data;
|
||||||
},
|
},
|
||||||
enabled
|
enabled
|
||||||
});
|
});
|
||||||
|
|||||||
+93
-61
@@ -11,10 +11,10 @@ import { createNotification } from "@app/components/notifications";
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input
|
||||||
Spinner,
|
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@app/components/v2/Dropdown/Dropdown";
|
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 { useCreateDynamicSecret } from "@app/hooks/api";
|
||||||
import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries";
|
import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries";
|
||||||
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||||
@@ -81,8 +81,10 @@ export const AzureEntraIdInputForm = ({
|
|||||||
const applicationId = watch("provider.applicationId");
|
const applicationId = watch("provider.applicationId");
|
||||||
const clientSecret = watch("provider.clientSecret");
|
const clientSecret = watch("provider.clientSecret");
|
||||||
|
|
||||||
const configurationComplete = tenantId && applicationId && 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 { data, isLoading, isError, isFetching } = useGetDynamicSecretProviderData({ tenantId, applicationId, clientSecret, enabled: !!configurationComplete });
|
||||||
|
const loading = configurationComplete && isFetching;
|
||||||
|
const errored = configurationComplete && !isFetching && isError;
|
||||||
const createDynamicSecret = useCreateDynamicSecret();
|
const createDynamicSecret = useCreateDynamicSecret();
|
||||||
|
|
||||||
const handleCreateDynamicSecret = async ({ name, selectedUsers, provider, maxTTL, defaultTTL }: TForm) => {
|
const handleCreateDynamicSecret = async ({ name, selectedUsers, provider, maxTTL, defaultTTL }: TForm) => {
|
||||||
@@ -247,64 +249,94 @@ export const AzureEntraIdInputForm = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
{
|
<Controller
|
||||||
configurationComplete && !isError && !isFetching && isFetched && data &&
|
control={control}
|
||||||
<Controller
|
name="selectedUsers"
|
||||||
control={control}
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
name="selectedUsers"
|
<FormControl
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
isRequired
|
||||||
<FormControl
|
isError={Boolean(error)}
|
||||||
isRequired
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
>
|
||||||
errorText={error?.message}
|
<DropdownMenu >
|
||||||
>
|
<DropdownMenuTrigger
|
||||||
<DropdownMenu>
|
className="w-72"
|
||||||
<DropdownMenuTrigger asChild className="w-72">
|
disabled={loading || errored || !configurationComplete}
|
||||||
<Input
|
>
|
||||||
isReadOnly
|
<Tooltip
|
||||||
value={value?.length ? `${value.length} selected` : "None"}
|
hidden={!loading && !errored && configurationComplete}
|
||||||
className="text-left"
|
content=
|
||||||
/>
|
{
|
||||||
</DropdownMenuTrigger>
|
<div>
|
||||||
<DropdownMenuContent align="start"
|
{(() => {
|
||||||
style={{ width: "var(--radix-dropdown-menu-trigger-width)" }}
|
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) => {
|
<div>
|
||||||
const ids = value?.map((selectedUser) => selectedUser.id)
|
|
||||||
const isChecked = ids?.includes(user.id);
|
<Input
|
||||||
return (
|
isReadOnly
|
||||||
<DropdownMenuItem
|
value={value?.length ? `${value.length} selected` : ""}
|
||||||
onClick={(evt) => {
|
className={`text-left ${loading || errored || !configurationComplete ? "cursor-not-allowed" : ""}`}
|
||||||
evt.preventDefault();
|
placeholder="Select users"
|
||||||
onChange(
|
/>
|
||||||
isChecked
|
</div>
|
||||||
? value?.filter((el) => el.id !== user.id)
|
</Tooltip>
|
||||||
: [...(value || []), user]
|
</DropdownMenuTrigger>
|
||||||
);
|
<DropdownMenuContent align="start"
|
||||||
}}
|
style={{ width: "var(--radix-dropdown-menu-trigger-width)" }}
|
||||||
key={`create-policy-members-${user.id}`}
|
>
|
||||||
iconPos="right"
|
{data && data.map((user) => {
|
||||||
icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />}
|
const ids = value?.map((selectedUser) => selectedUser.id)
|
||||||
>
|
const isChecked = ids?.includes(user.id);
|
||||||
{user.name} <br /> {`(${user.email})`}
|
return (
|
||||||
</DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
);
|
onClick={(evt) => {
|
||||||
})}
|
evt.preventDefault();
|
||||||
</DropdownMenuContent>
|
onChange(
|
||||||
</DropdownMenu>
|
isChecked
|
||||||
</FormControl>
|
? value?.filter((el) => el.id !== user.id)
|
||||||
)}
|
: [...(value || []), user]
|
||||||
/>
|
);
|
||||||
}
|
}}
|
||||||
{
|
key={`create-policy-members-${user.id}`}
|
||||||
configurationComplete && isFetching && (<div className="pl-3 pb-2 w-full flex items-center" ><Spinner size="xs" /><p> Loading </p></div>)
|
iconPos="right"
|
||||||
}
|
icon={isChecked && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
{
|
>
|
||||||
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>)
|
{user.name} <br /> {`(${user.email})`}
|
||||||
}
|
</DropdownMenuItem>
|
||||||
{
|
);
|
||||||
!configurationComplete && (<div className="pl-3 pb-2 w-full flex items-center" ><FontAwesomeIcon icon={faWarning} /><p> Complete configuration to fetch users</p></div>)
|
})}
|
||||||
}
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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