chore: refactor frontend and UI improvements

This commit is contained in:
Meet
2024-09-19 01:01:18 +05:30
parent 0fd193f8e0
commit a02e73e2a4
4 changed files with 115 additions and 95 deletions

View 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>
);

View File

@@ -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>
);

View File

@@ -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>
);
};

View File

@@ -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