mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
chore: refactor frontend and UI improvements
This commit is contained in:
36
frontend/src/components/features/FormLabelToolTip.tsx
Normal file
36
frontend/src/components/features/FormLabelToolTip.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { FormLabel, Tooltip } from "../v2";
|
||||
|
||||
// To give users example of possible values of TTL
|
||||
export const FormLabelToolTip = ({ label, linkToMore, content }: { label: string, linkToMore: string, content: string }) => (
|
||||
<div>
|
||||
<FormLabel
|
||||
label={label}
|
||||
icon={
|
||||
<Tooltip
|
||||
content={
|
||||
<span>
|
||||
{content}{" "}
|
||||
<a
|
||||
href={linkToMore}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary-700"
|
||||
>
|
||||
More
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faQuestionCircle}
|
||||
size="sm"
|
||||
className="relative bottom-1 right-1"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -1,36 +1,12 @@
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { FormLabel, Tooltip } from "../v2";
|
||||
import { FormLabelToolTip } from "./FormLabelToolTip";
|
||||
|
||||
// To give users example of possible values of TTL
|
||||
export const TtlFormLabel = ({ label }: { label: string }) => (
|
||||
<div>
|
||||
<FormLabel
|
||||
<FormLabelToolTip
|
||||
label={label}
|
||||
icon={
|
||||
<Tooltip
|
||||
content={
|
||||
<span>
|
||||
1m, 2h, 3d.{" "}
|
||||
<a
|
||||
href="https://github.com/vercel/ms?tab=readme-ov-file#examples"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary-700"
|
||||
>
|
||||
More
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faQuestionCircle}
|
||||
size="sm"
|
||||
className="relative bottom-1 right-1"
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
content="1m, 2h, 3d. "
|
||||
linkToMore="https://github.com/vercel/ms?tab=readme-ov-file#examples"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCheckCircle, faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import ms from "ms";
|
||||
import { z } from "zod";
|
||||
|
||||
import ListBoxMultiple from "@app/components/basic/ListboxMultiple";
|
||||
import { TtlFormLabel } from "@app/components/features";
|
||||
import { FormLabelToolTip } from "@app/components/features/FormLabelToolTip";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
@@ -15,11 +15,19 @@ import {
|
||||
Input,
|
||||
Spinner,
|
||||
} from "@app/components/v2";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@app/components/v2/Dropdown/Dropdown";
|
||||
import { useCreateDynamicSecret } from "@app/hooks/api";
|
||||
import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries";
|
||||
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||
|
||||
import { AzureEntraIdSetup } from "./AzureEntraIdSetup";
|
||||
|
||||
const formSchema = z.object({
|
||||
selectedUsers: z.array(z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
email: z.string().min(1),
|
||||
})),
|
||||
provider: z.object({
|
||||
tenantId: z.string().min(1),
|
||||
}),
|
||||
@@ -55,12 +63,6 @@ type Props = {
|
||||
environment: string;
|
||||
};
|
||||
|
||||
type EntraIDUser = {
|
||||
name: string;
|
||||
id: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
export const AzureEntraIdInputForm = ({
|
||||
onCompleted,
|
||||
onCancel,
|
||||
@@ -77,25 +79,11 @@ export const AzureEntraIdInputForm = ({
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
const tenantId = watch("provider.tenantId");
|
||||
const [selectedUsers, setSelectedUsers] = useState<EntraIDUser[]>([]);
|
||||
const { data, isLoading, isFetched, isError, isFetching } = useGetDynamicSecretProviderData({ dataFetchType: "Users", provider: {type: DynamicSecretProviders.AzureEntraId, inputs: { userId : "unused", email: "unused", tenantId }}, enabled: !!tenantId });
|
||||
const mappedUserList = data?.map((user) => ({
|
||||
primaryText: user.name,
|
||||
secondaryText: user.email,
|
||||
id: user.id,
|
||||
}));
|
||||
const handleChange = (selected: { primaryText: string; secondaryText: string; id: string }[]) => {
|
||||
// Map back to EntraIDUser based on the id or other attributes
|
||||
const selectedMappedUsers = data?.filter(user =>
|
||||
selected.some(selectedItem => selectedItem.id === user.id)
|
||||
);
|
||||
if(selectedMappedUsers){
|
||||
setSelectedUsers(selectedMappedUsers);
|
||||
}
|
||||
};
|
||||
const [onSetup, setOnSetup] = useState(true);
|
||||
const { data, isLoading, isFetched, isError, isFetching } = useGetDynamicSecretProviderData({ dataFetchType: "Users", provider: { type: DynamicSecretProviders.AzureEntraId, inputs: { userId: "unused", email: "unused", tenantId } }, enabled: !!tenantId });
|
||||
const createDynamicSecret = useCreateDynamicSecret();
|
||||
|
||||
const handleCreateDynamicSecret = async ({ name, provider, maxTTL, defaultTTL }: TForm) => {
|
||||
const handleCreateDynamicSecret = async ({ name, selectedUsers, provider, maxTTL, defaultTTL }: TForm) => {
|
||||
// wait till previous request is finished
|
||||
if (createDynamicSecret.isLoading) return;
|
||||
try {
|
||||
@@ -121,7 +109,12 @@ export const AzureEntraIdInputForm = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={handleSubmit(handleCreateDynamicSecret)} autoComplete="off">
|
||||
{onSetup && <AzureEntraIdSetup
|
||||
onCompleted={() => { setOnSetup(false); }}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
}
|
||||
{!onSetup && <form onSubmit={handleSubmit(handleCreateDynamicSecret)} autoComplete="off">
|
||||
<div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex-grow">
|
||||
@@ -192,7 +185,7 @@ export const AzureEntraIdInputForm = ({
|
||||
<Input {...field} placeholder="Tenant Id from Azure Entra ID App installation" />
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,15 +195,55 @@ export const AzureEntraIdInputForm = ({
|
||||
Select Users
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-4">
|
||||
{
|
||||
tenantId && !isError && !isFetching && isFetched && data && mappedUserList &&
|
||||
<ListBoxMultiple
|
||||
isSelected={mappedUserList.filter(user =>
|
||||
selectedUsers.some(selectedUser => selectedUser.id === user.id)
|
||||
tenantId && !isError && !isFetching && isFetched && data &&
|
||||
<Controller
|
||||
control={control}
|
||||
name="selectedUsers"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label={<FormLabelToolTip content="We create a secret for each user" label="Select Users" linkToMore=""/>}
|
||||
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)" }}
|
||||
>
|
||||
{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>
|
||||
)}
|
||||
onChange={handleChange}
|
||||
data={mappedUserList}
|
||||
/>
|
||||
}
|
||||
{
|
||||
@@ -230,11 +263,15 @@ export const AzureEntraIdInputForm = ({
|
||||
<Button type="submit" isLoading={isSubmitting} isDisabled={isLoading || isError}>
|
||||
Submit
|
||||
</Button>
|
||||
<Button variant="outline_bg" onClick={() => { setOnSetup(true); }}>
|
||||
Back
|
||||
</Button>
|
||||
<Button variant="outline_bg" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { DiRedis } from "react-icons/di";
|
||||
import { SiApachecassandra, SiElasticsearch, SiMongodb, SiRabbitmq } from "react-icons/si";
|
||||
import { SiApachecassandra, SiElasticsearch, SiMicrosoftazure, SiMongodb, SiRabbitmq } from "react-icons/si";
|
||||
import { faAws } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faDatabase } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -12,7 +12,6 @@ import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||
import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm";
|
||||
import { AwsIamInputForm } from "./AwsIamInputForm";
|
||||
import { AzureEntraIdInputForm } from "./AzureEntraIdInputForm";
|
||||
import { AzureEntraIdSetup } from "./AzureEntraIdSetup";
|
||||
import { CassandraInputForm } from "./CassandraInputForm";
|
||||
import { ElasticSearchInputForm } from "./ElasticSearchInputForm";
|
||||
import { MongoAtlasInputForm } from "./MongoAtlasInputForm";
|
||||
@@ -31,7 +30,6 @@ type Props = {
|
||||
|
||||
enum WizardSteps {
|
||||
SelectProvider = "select-provider",
|
||||
ProviderSetup = "provider-setup",
|
||||
ProviderInputs = "provider-inputs"
|
||||
}
|
||||
|
||||
@@ -82,10 +80,9 @@ const DYNAMIC_SECRET_LIST = [
|
||||
title: "RabbitMQ"
|
||||
},
|
||||
{
|
||||
icon: <SiRabbitmq size="1.5rem" />,
|
||||
icon: <SiMicrosoftazure size="1.5rem" />,
|
||||
provider: DynamicSecretProviders.AzureEntraId,
|
||||
title: "Azure Entra ID",
|
||||
hasSetupStep: true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -123,7 +120,7 @@ export const CreateDynamicSecretForm = ({
|
||||
>
|
||||
<div className="mb-4 text-mineshaft-300">Select a service to connect to:</div>
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{DYNAMIC_SECRET_LIST.map(({ icon, provider, title, hasSetupStep }) => (
|
||||
{DYNAMIC_SECRET_LIST.map(({ icon, provider, title }) => (
|
||||
<div
|
||||
key={`dynamic-secret-provider-${provider}`}
|
||||
className="flex h-32 w-32 cursor-pointer flex-col items-center space-y-4 rounded border border-mineshaft-500 bg-bunker-600 p-6 transition-all hover:border-primary/70 hover:bg-primary/10 hover:text-white"
|
||||
@@ -131,22 +128,13 @@ export const CreateDynamicSecretForm = ({
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
setSelectedProvider(provider);
|
||||
if(hasSetupStep){
|
||||
setWizardStep(WizardSteps.ProviderSetup);
|
||||
} else {
|
||||
setWizardStep(WizardSteps.ProviderInputs);
|
||||
}
|
||||
setWizardStep(WizardSteps.ProviderInputs);
|
||||
}}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
setSelectedProvider(provider);
|
||||
setWizardStep(WizardSteps.ProviderInputs);
|
||||
}
|
||||
if(hasSetupStep){
|
||||
setWizardStep(WizardSteps.ProviderSetup);
|
||||
} else {
|
||||
setWizardStep(WizardSteps.ProviderInputs);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
@@ -156,23 +144,6 @@ export const CreateDynamicSecretForm = ({
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
{
|
||||
wizardStep === WizardSteps.ProviderSetup && selectedProvider === DynamicSecretProviders.AzureEntraId
|
||||
&& (
|
||||
<motion.div
|
||||
key="dynamic-azure-entra-id-setup"
|
||||
transition={{ duration: 0.1 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: -30 }}
|
||||
>
|
||||
<AzureEntraIdSetup
|
||||
onCompleted={() => { setWizardStep(WizardSteps.ProviderInputs) }}
|
||||
onCancel={handleFormReset}
|
||||
/>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
{wizardStep === WizardSteps.ProviderInputs &&
|
||||
selectedProvider === DynamicSecretProviders.SqlDatabase && (
|
||||
<motion.div
|
||||
|
||||
Reference in New Issue
Block a user