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 { FormLabelToolTip } from "./FormLabelToolTip";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FormLabel, Tooltip } from "../v2";
// To give users example of possible values of TTL // To give users example of possible values of TTL
export const TtlFormLabel = ({ label }: { label: string }) => ( export const TtlFormLabel = ({ label }: { label: string }) => (
<div> <div>
<FormLabel <FormLabelToolTip
label={label} label={label}
icon={ content="1m, 2h, 3d. "
<Tooltip linkToMore="https://github.com/vercel/ms?tab=readme-ov-file#examples"
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>
}
/> />
</div> </div>
); );

View File

@@ -1,13 +1,13 @@
import { useState } from "react"; import { useState } from "react";
import { Controller, useForm } from "react-hook-form"; 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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import ms from "ms"; import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import ListBoxMultiple from "@app/components/basic/ListboxMultiple";
import { TtlFormLabel } from "@app/components/features"; import { TtlFormLabel } from "@app/components/features";
import { FormLabelToolTip } from "@app/components/features/FormLabelToolTip";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { import {
Button, Button,
@@ -15,11 +15,19 @@ import {
Input, Input,
Spinner, Spinner,
} from "@app/components/v2"; } from "@app/components/v2";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@app/components/v2/Dropdown/Dropdown";
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";
import { AzureEntraIdSetup } from "./AzureEntraIdSetup";
const formSchema = z.object({ 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({ provider: z.object({
tenantId: z.string().min(1), tenantId: z.string().min(1),
}), }),
@@ -55,12 +63,6 @@ type Props = {
environment: string; environment: string;
}; };
type EntraIDUser = {
name: string;
id: string;
email: string;
};
export const AzureEntraIdInputForm = ({ export const AzureEntraIdInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
@@ -77,25 +79,11 @@ export const AzureEntraIdInputForm = ({
resolver: zodResolver(formSchema) resolver: zodResolver(formSchema)
}); });
const tenantId = watch("provider.tenantId"); const tenantId = watch("provider.tenantId");
const [selectedUsers, setSelectedUsers] = useState<EntraIDUser[]>([]); 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 { 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 createDynamicSecret = useCreateDynamicSecret(); 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 // wait till previous request is finished
if (createDynamicSecret.isLoading) return; if (createDynamicSecret.isLoading) return;
try { try {
@@ -121,7 +109,12 @@ export const AzureEntraIdInputForm = ({
return ( return (
<div> <div>
<form onSubmit={handleSubmit(handleCreateDynamicSecret)} autoComplete="off"> {onSetup && <AzureEntraIdSetup
onCompleted={() => { setOnSetup(false); }}
onCancel={onCancel}
/>
}
{!onSetup && <form onSubmit={handleSubmit(handleCreateDynamicSecret)} autoComplete="off">
<div> <div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="flex-grow"> <div className="flex-grow">
@@ -202,15 +195,55 @@ export const AzureEntraIdInputForm = ({
Select Users Select Users
</div> </div>
<div className="flex flex-col"> <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 && tenantId && !isError && !isFetching && isFetched && data &&
<ListBoxMultiple <Controller
isSelected={mappedUserList.filter(user => control={control}
selectedUsers.some(selectedUser => selectedUser.id === user.id) 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}> <Button type="submit" isLoading={isSubmitting} isDisabled={isLoading || isError}>
Submit Submit
</Button> </Button>
<Button variant="outline_bg" onClick={() => { setOnSetup(true); }}>
Back
</Button>
<Button variant="outline_bg" onClick={onCancel}> <Button variant="outline_bg" onClick={onCancel}>
Cancel Cancel
</Button> </Button>
</div> </div>
</form> </form>
}
</div> </div>
); );
}; };

View File

@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { DiRedis } from "react-icons/di"; 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 { faAws } from "@fortawesome/free-brands-svg-icons";
import { faDatabase } from "@fortawesome/free-solid-svg-icons"; import { faDatabase } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -12,7 +12,6 @@ import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm"; import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm";
import { AwsIamInputForm } from "./AwsIamInputForm"; import { AwsIamInputForm } from "./AwsIamInputForm";
import { AzureEntraIdInputForm } from "./AzureEntraIdInputForm"; import { AzureEntraIdInputForm } from "./AzureEntraIdInputForm";
import { AzureEntraIdSetup } from "./AzureEntraIdSetup";
import { CassandraInputForm } from "./CassandraInputForm"; import { CassandraInputForm } from "./CassandraInputForm";
import { ElasticSearchInputForm } from "./ElasticSearchInputForm"; import { ElasticSearchInputForm } from "./ElasticSearchInputForm";
import { MongoAtlasInputForm } from "./MongoAtlasInputForm"; import { MongoAtlasInputForm } from "./MongoAtlasInputForm";
@@ -31,7 +30,6 @@ type Props = {
enum WizardSteps { enum WizardSteps {
SelectProvider = "select-provider", SelectProvider = "select-provider",
ProviderSetup = "provider-setup",
ProviderInputs = "provider-inputs" ProviderInputs = "provider-inputs"
} }
@@ -82,10 +80,9 @@ const DYNAMIC_SECRET_LIST = [
title: "RabbitMQ" title: "RabbitMQ"
}, },
{ {
icon: <SiRabbitmq size="1.5rem" />, icon: <SiMicrosoftazure size="1.5rem" />,
provider: DynamicSecretProviders.AzureEntraId, provider: DynamicSecretProviders.AzureEntraId,
title: "Azure Entra ID", 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="mb-4 text-mineshaft-300">Select a service to connect to:</div>
<div className="flex flex-wrap items-center gap-4"> <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 <div
key={`dynamic-secret-provider-${provider}`} 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" 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} tabIndex={0}
onClick={() => { onClick={() => {
setSelectedProvider(provider); setSelectedProvider(provider);
if(hasSetupStep){ setWizardStep(WizardSteps.ProviderInputs);
setWizardStep(WizardSteps.ProviderSetup);
} else {
setWizardStep(WizardSteps.ProviderInputs);
}
}} }}
onKeyDown={(evt) => { onKeyDown={(evt) => {
if (evt.key === "Enter") { if (evt.key === "Enter") {
setSelectedProvider(provider); setSelectedProvider(provider);
setWizardStep(WizardSteps.ProviderInputs); setWizardStep(WizardSteps.ProviderInputs);
} }
if(hasSetupStep){
setWizardStep(WizardSteps.ProviderSetup);
} else {
setWizardStep(WizardSteps.ProviderInputs);
}
}} }}
> >
{icon} {icon}
@@ -156,23 +144,6 @@ export const CreateDynamicSecretForm = ({
</div> </div>
</motion.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 && {wizardStep === WizardSteps.ProviderInputs &&
selectedProvider === DynamicSecretProviders.SqlDatabase && ( selectedProvider === DynamicSecretProviders.SqlDatabase && (
<motion.div <motion.div