From 6b7fda458bb0d9aadff8d638557fff150aa3f9d0 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Mon, 18 Mar 2024 23:45:44 +0600 Subject: [PATCH] FE: Add forms for an ACL creating (#188) Co-authored-by: Pavel Makarichev Co-authored-by: Roman Zabaluev --- .../components/ACLPage/Form/AclFormContext.ts | 8 ++ .../ACLPage/Form/CustomACL/Form.tsx | 100 ++++++++++++++++ .../ACLPage/Form/CustomACL/constants.ts | 48 ++++++++ .../components/ACLPage/Form/CustomACL/lib.ts | 26 ++++ .../ACLPage/Form/CustomACL/schema.ts | 13 ++ .../ACLPage/Form/CustomACL/types.ts | 16 +++ .../ACLPage/Form/ForConsumers/Form.tsx | 112 ++++++++++++++++++ .../ACLPage/Form/ForConsumers/lib.ts | 14 +++ .../ACLPage/Form/ForConsumers/schema.ts | 22 ++++ .../ACLPage/Form/ForConsumers/types.ts | 10 ++ .../ACLPage/Form/ForKafkaStreamApps/Form.tsx | 75 ++++++++++++ .../ACLPage/Form/ForKafkaStreamApps/lib.ts | 13 ++ .../ACLPage/Form/ForKafkaStreamApps/schema.ts | 11 ++ .../ACLPage/Form/ForKafkaStreamApps/types.ts | 9 ++ .../ACLPage/Form/ForProducers/Form.tsx | 112 ++++++++++++++++++ .../ACLPage/Form/ForProducers/lib.ts | 15 +++ .../ACLPage/Form/ForProducers/schema.ts | 18 +++ .../ACLPage/Form/ForProducers/types.ts | 11 ++ .../components/ACLPage/Form/Form.styled.ts | 77 ++++++++++++ frontend/src/components/ACLPage/Form/Form.tsx | 75 ++++++++++++ .../Form/components/MatchTypeSelector.tsx | 38 ++++++ .../src/components/ACLPage/Form/constants.ts | 18 +++ frontend/src/components/ACLPage/Form/types.ts | 21 ++++ .../components/ACLPage/List/List.styled.ts | 2 + frontend/src/components/ACLPage/List/List.tsx | 53 ++++++--- .../ACLPage/List/__test__/List.spec.tsx | 28 ++++- .../ACLPage/lib/useConsumerGroupsOptions.ts | 20 ++++ .../ACLPage/lib/useTopicsOptions.ts | 20 ++++ .../components/common/Input/Input.styled.ts | 2 +- .../common/Input/InputLabel.styled.ts | 1 + .../ControlledMultiSelect/index.tsx | 31 +++++ .../common/Radio/ControlledRadio.tsx | 27 +++++ .../components/common/Radio/Radio.styled.tsx | 50 ++++++++ .../src/components/common/Radio/Radio.tsx | 34 ++++++ frontend/src/components/common/Radio/types.ts | 24 ++++ .../common/Select/ControlledSelect.tsx | 4 +- frontend/src/lib/__test__/isRegex.spec.ts | 17 +++ frontend/src/lib/hooks/api/acl.ts | 107 +++++++++++++++-- frontend/src/lib/isRegex.ts | 11 ++ frontend/src/theme/theme.ts | 74 ++++++++++-- 40 files changed, 1321 insertions(+), 46 deletions(-) create mode 100644 frontend/src/components/ACLPage/Form/AclFormContext.ts create mode 100644 frontend/src/components/ACLPage/Form/CustomACL/Form.tsx create mode 100644 frontend/src/components/ACLPage/Form/CustomACL/constants.ts create mode 100644 frontend/src/components/ACLPage/Form/CustomACL/lib.ts create mode 100644 frontend/src/components/ACLPage/Form/CustomACL/schema.ts create mode 100644 frontend/src/components/ACLPage/Form/CustomACL/types.ts create mode 100644 frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx create mode 100644 frontend/src/components/ACLPage/Form/ForConsumers/lib.ts create mode 100644 frontend/src/components/ACLPage/Form/ForConsumers/schema.ts create mode 100644 frontend/src/components/ACLPage/Form/ForConsumers/types.ts create mode 100644 frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx create mode 100644 frontend/src/components/ACLPage/Form/ForKafkaStreamApps/lib.ts create mode 100644 frontend/src/components/ACLPage/Form/ForKafkaStreamApps/schema.ts create mode 100644 frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts create mode 100644 frontend/src/components/ACLPage/Form/ForProducers/Form.tsx create mode 100644 frontend/src/components/ACLPage/Form/ForProducers/lib.ts create mode 100644 frontend/src/components/ACLPage/Form/ForProducers/schema.ts create mode 100644 frontend/src/components/ACLPage/Form/ForProducers/types.ts create mode 100644 frontend/src/components/ACLPage/Form/Form.styled.ts create mode 100644 frontend/src/components/ACLPage/Form/Form.tsx create mode 100644 frontend/src/components/ACLPage/Form/components/MatchTypeSelector.tsx create mode 100644 frontend/src/components/ACLPage/Form/constants.ts create mode 100644 frontend/src/components/ACLPage/Form/types.ts create mode 100644 frontend/src/components/ACLPage/lib/useConsumerGroupsOptions.ts create mode 100644 frontend/src/components/ACLPage/lib/useTopicsOptions.ts create mode 100644 frontend/src/components/common/MultiSelect/ControlledMultiSelect/index.tsx create mode 100644 frontend/src/components/common/Radio/ControlledRadio.tsx create mode 100644 frontend/src/components/common/Radio/Radio.styled.tsx create mode 100644 frontend/src/components/common/Radio/Radio.tsx create mode 100644 frontend/src/components/common/Radio/types.ts create mode 100644 frontend/src/lib/__test__/isRegex.spec.ts create mode 100644 frontend/src/lib/isRegex.ts diff --git a/frontend/src/components/ACLPage/Form/AclFormContext.ts b/frontend/src/components/ACLPage/Form/AclFormContext.ts new file mode 100644 index 000000000..dee6bbfb6 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/AclFormContext.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react'; + +interface ACLFormContextProps { + close: () => void; +} +const ACLFormContext = createContext(null); + +export default ACLFormContext; diff --git a/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx new file mode 100644 index 000000000..a0cf8d0ba --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx @@ -0,0 +1,100 @@ +import React, { FC, useContext } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { FormProvider, useForm } from 'react-hook-form'; +import { useCreateCustomAcl } from 'lib/hooks/api/acl'; +import ControlledRadio from 'components/common/Radio/ControlledRadio'; +import Input from 'components/common/Input/Input'; +import ControlledSelect from 'components/common/Select/ControlledSelect'; +import { matchTypeOptions } from 'components/ACLPage/Form/constants'; +import useAppParams from 'lib/hooks/useAppParams'; +import { ClusterName } from 'redux/interfaces'; +import * as S from 'components/ACLPage/Form/Form.styled'; +import ACLFormContext from 'components/ACLPage/Form/AclFormContext'; +import { AclDetailedFormProps } from 'components/ACLPage/Form/types'; + +import formSchema from './schema'; +import { FormValues } from './types'; +import { toRequest } from './lib'; +import { + defaultValues, + operations, + permissions, + resourceTypes, +} from './constants'; + +const CustomACLForm: FC = ({ formRef }) => { + const context = useContext(ACLFormContext); + + const methods = useForm({ + mode: 'all', + resolver: yupResolver(formSchema), + defaultValues: { ...defaultValues }, + }); + + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateCustomAcl(clusterName); + + const onSubmit = async (data: FormValues) => { + try { + const resource = toRequest(data); + await create.createResource(resource); + context?.close(); + } catch (e) { + // no custom error + } + }; + + return ( + + +
+ + Principal + + + + + Host restriction + + +
+ + + Resource type + + + + + Operations + + + + + + + + Matching pattern + + + + + +
+
+ ); +}; + +export default React.memo(CustomACLForm); diff --git a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts new file mode 100644 index 000000000..ec443da13 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts @@ -0,0 +1,48 @@ +import { SelectOption } from 'components/common/Select/Select'; +import { + KafkaAclOperationEnum, + KafkaAclPermissionEnum, + KafkaAclResourceType, +} from 'generated-sources'; +import { RadioOption } from 'components/common/Radio/types'; + +import { FormValues } from './types'; + +function toOptionsArray( + list: T[], + unknown: T +): SelectOption[] { + return list.reduce[]>((acc, cur) => { + if (cur !== unknown) { + acc.push({ label: cur, value: cur }); + } + + return acc; + }, []); +} + +export const resourceTypes = toOptionsArray( + Object.values(KafkaAclResourceType), + KafkaAclResourceType.UNKNOWN +); + +export const operations = toOptionsArray( + Object.values(KafkaAclOperationEnum), + KafkaAclOperationEnum.UNKNOWN +); + +export const permissions: RadioOption[] = [ + { + value: KafkaAclPermissionEnum.ALLOW, + itemType: 'green', + }, + { + value: KafkaAclPermissionEnum.DENY, + itemType: 'red', + }, +]; + +export const defaultValues: Partial = { + resourceType: resourceTypes[0].value as KafkaAclResourceType, + operation: operations[0].value as KafkaAclOperationEnum, +}; diff --git a/frontend/src/components/ACLPage/Form/CustomACL/lib.ts b/frontend/src/components/ACLPage/Form/CustomACL/lib.ts new file mode 100644 index 000000000..3048b6056 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/lib.ts @@ -0,0 +1,26 @@ +import { KafkaAcl, KafkaAclNamePatternType } from 'generated-sources'; +import isRegex from 'lib/isRegex'; +import { MatchType } from 'components/ACLPage/Form/types'; + +import { FormValues } from './types'; + +export function toRequest(formValue: FormValues): KafkaAcl { + let namePatternType: KafkaAclNamePatternType; + if (formValue.namePatternType === MatchType.PREFIXED) { + namePatternType = KafkaAclNamePatternType.PREFIXED; + } else if (isRegex(formValue.resourceName)) { + namePatternType = KafkaAclNamePatternType.MATCH; + } else { + namePatternType = KafkaAclNamePatternType.LITERAL; + } + + return { + resourceType: formValue.resourceType, + resourceName: formValue.resourceName, + namePatternType, + principal: formValue.principal, + host: formValue.host, + operation: formValue.operation, + permission: formValue.permission, + }; +} diff --git a/frontend/src/components/ACLPage/Form/CustomACL/schema.ts b/frontend/src/components/ACLPage/Form/CustomACL/schema.ts new file mode 100644 index 000000000..8bb4e756d --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/schema.ts @@ -0,0 +1,13 @@ +import { object, string } from 'yup'; + +const formSchema = object({ + resourceType: string().required(), + resourceName: string().required(), + namePatternType: string().required(), + principal: string().required(), + host: string().required(), + operation: string().required(), + permission: string().required(), +}); + +export default formSchema; diff --git a/frontend/src/components/ACLPage/Form/CustomACL/types.ts b/frontend/src/components/ACLPage/Form/CustomACL/types.ts new file mode 100644 index 000000000..fa40f955c --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/types.ts @@ -0,0 +1,16 @@ +import { + KafkaAclOperationEnum, + KafkaAclPermissionEnum, + KafkaAclResourceType, +} from 'generated-sources'; +import { MatchType } from 'components/ACLPage/Form/types'; + +export interface FormValues { + resourceType: KafkaAclResourceType; + resourceName: string; + namePatternType: MatchType; + principal: string; + host: string; + operation: KafkaAclOperationEnum; + permission: KafkaAclPermissionEnum; +} diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx new file mode 100644 index 000000000..febd75e08 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx @@ -0,0 +1,112 @@ +import React, { FC, useContext } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { FormProvider, useForm } from 'react-hook-form'; +import { ClusterName } from 'redux/interfaces'; +import { useCreateConsumersAcl } from 'lib/hooks/api/acl'; +import useAppParams from 'lib/hooks/useAppParams'; +import ControlledMultiSelect from 'components/common/MultiSelect/ControlledMultiSelect'; +import Input from 'components/common/Input/Input'; +import * as S from 'components/ACLPage/Form/Form.styled'; +import { AclDetailedFormProps, MatchType } from 'components/ACLPage/Form/types'; +import useTopicsOptions from 'components/ACLPage/lib/useTopicsOptions'; +import useConsumerGroupsOptions from 'components/ACLPage/lib/useConsumerGroupsOptions'; +import ACLFormContext from 'components/ACLPage/Form/AclFormContext'; +import MatchTypeSelector from 'components/ACLPage/Form/components/MatchTypeSelector'; + +import formSchema from './schema'; +import { toRequest } from './lib'; +import { FormValues } from './types'; + +const ForConsumersForm: FC = ({ formRef }) => { + const context = useContext(ACLFormContext); + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateConsumersAcl(clusterName); + const methods = useForm({ + mode: 'all', + resolver: yupResolver(formSchema), + }); + + const { setValue } = methods; + + const onSubmit = async (data: FormValues) => { + try { + await create.createResource(toRequest(data)); + context?.close(); + } catch (e) { + // no custom error + } + }; + + const topics = useTopicsOptions(clusterName); + const consumerGroups = useConsumerGroupsOptions(clusterName); + + const onTopicTypeChange = (value: string) => { + if (value === MatchType.EXACT) { + setValue('topicsPrefix', undefined); + } else { + setValue('topics', undefined); + } + }; + + const onConsumerGroupTypeChange = (value: string) => { + if (value === MatchType.EXACT) { + setValue('consumerGroupsPrefix', undefined); + } else { + setValue('consumerGroups', undefined); + } + }; + + return ( + + +
+ + Principal + + + + + Host restriction + + +
+ + + From Topic(s) + + } + prefixed={} + onChange={onTopicTypeChange} + /> + + + + + Consumer group(s) + + + } + prefixed={ + + } + onChange={onConsumerGroupTypeChange} + /> + + +
+
+ ); +}; + +export default React.memo(ForConsumersForm); diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/lib.ts b/frontend/src/components/ACLPage/Form/ForConsumers/lib.ts new file mode 100644 index 000000000..b7980e52b --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForConsumers/lib.ts @@ -0,0 +1,14 @@ +import { CreateConsumerAcl } from 'generated-sources/models/CreateConsumerAcl'; + +import { FormValues } from './types'; + +export const toRequest = (formValues: FormValues): CreateConsumerAcl => { + return { + principal: formValues.principal, + host: formValues.host, + consumerGroups: formValues.consumerGroups?.map((opt) => opt.value), + consumerGroupsPrefix: formValues.consumerGroupsPrefix, + topics: formValues.topics?.map((opt) => opt.value), + topicsPrefix: formValues.topicsPrefix, + }; +}; diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/schema.ts b/frontend/src/components/ACLPage/Form/ForConsumers/schema.ts new file mode 100644 index 000000000..f9c175c48 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForConsumers/schema.ts @@ -0,0 +1,22 @@ +import { array, object, string } from 'yup'; + +const formSchema = object({ + principal: string().required(), + host: string().required(), + topics: array().of( + object().shape({ + label: string().required(), + value: string().required(), + }) + ), + topicsPrefix: string(), + consumerGroups: array().of( + object().shape({ + label: string().required(), + value: string().required(), + }) + ), + consumerGroupsPrefix: string(), +}); + +export default formSchema; diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/types.ts b/frontend/src/components/ACLPage/Form/ForConsumers/types.ts new file mode 100644 index 000000000..97fc73484 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForConsumers/types.ts @@ -0,0 +1,10 @@ +import { Option } from 'react-multi-select-component'; + +export interface FormValues { + principal: string; + host: string; + topics?: Option[]; + topicsPrefix?: string; + consumerGroups?: Option[]; + consumerGroupsPrefix?: string; +} diff --git a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx new file mode 100644 index 000000000..7a747de4e --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx @@ -0,0 +1,75 @@ +import React, { FC, useContext } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { ClusterName } from 'redux/interfaces'; +import { useCreateStreamAppAcl } from 'lib/hooks/api/acl'; +import useAppParams from 'lib/hooks/useAppParams'; +import Input from 'components/common/Input/Input'; +import ControlledMultiSelect from 'components/common/MultiSelect/ControlledMultiSelect'; +import * as S from 'components/ACLPage/Form/Form.styled'; +import { AclDetailedFormProps } from 'components/ACLPage/Form/types'; +import useTopicsOptions from 'components/ACLPage/lib/useTopicsOptions'; +import ACLFormContext from 'components/ACLPage/Form/AclFormContext'; + +import { toRequest } from './lib'; +import formSchema from './schema'; +import { FormValues } from './types'; + +const ForKafkaStreamAppsForm: FC = ({ formRef }) => { + const context = useContext(ACLFormContext); + const methods = useForm({ + mode: 'all', + resolver: yupResolver(formSchema), + }); + + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateStreamAppAcl(clusterName); + const topics = useTopicsOptions(clusterName); + + const onSubmit = async (data: FormValues) => { + try { + const resource = toRequest(data); + await create.createResource(resource); + context?.close(); + } catch (e) { + // no custom error + } + }; + + return ( + + +
+ + Principal + + + + + Host restriction + + +
+ + From topic(s) + + + + To topic(s) + + + + Application.id + + +
+
+ ); +}; + +export default ForKafkaStreamAppsForm; diff --git a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/lib.ts b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/lib.ts new file mode 100644 index 000000000..dce01c66b --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/lib.ts @@ -0,0 +1,13 @@ +import { CreateStreamAppAcl } from 'generated-sources/models/CreateStreamAppAcl'; + +import { FormValues } from './types'; + +export const toRequest = (formValues: FormValues): CreateStreamAppAcl => { + return { + principal: formValues.principal, + host: formValues.host, + inputTopics: formValues.inputTopics.map((opt) => opt.value), + outputTopics: formValues.outputTopics.map((opt) => opt.value), + applicationId: formValues.applicationId, + }; +}; diff --git a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/schema.ts b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/schema.ts new file mode 100644 index 000000000..4d6495889 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/schema.ts @@ -0,0 +1,11 @@ +import { array, object, string } from 'yup'; + +const formSchema = object({ + principal: string().required(), + host: string().required(), + inputTopics: array().of(string()).required(), + outputTopics: array().of(string()).required(), + applicationId: string().required(), +}); + +export default formSchema; diff --git a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts new file mode 100644 index 000000000..adeee573c --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts @@ -0,0 +1,9 @@ +import { Option } from 'react-multi-select-component'; + +export interface FormValues { + principal: string; + host: string; + inputTopics: Option[]; + outputTopics: Option[]; + applicationId: string; +} diff --git a/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx new file mode 100644 index 000000000..cd84aa674 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx @@ -0,0 +1,112 @@ +import React, { FC, useContext } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { useCreateProducerAcl } from 'lib/hooks/api/acl'; +import { FormProvider, useForm } from 'react-hook-form'; +import useAppParams from 'lib/hooks/useAppParams'; +import { ClusterName } from 'redux/interfaces'; +import Input from 'components/common/Input/Input'; +import ControlledMultiSelect from 'components/common/MultiSelect/ControlledMultiSelect'; +import Checkbox from 'components/common/Checkbox/Checkbox'; +import * as S from 'components/ACLPage/Form/Form.styled'; +import { AclDetailedFormProps, MatchType } from 'components/ACLPage/Form/types'; +import useTopicsOptions from 'components/ACLPage/lib/useTopicsOptions'; +import ACLFormContext from 'components/ACLPage/Form/AclFormContext'; +import MatchTypeSelector from 'components/ACLPage/Form/components/MatchTypeSelector'; + +import { toRequest } from './lib'; +import { FormValues } from './types'; +import formSchema from './schema'; + +const ForProducersForm: FC = ({ formRef }) => { + const context = useContext(ACLFormContext); + const methods = useForm({ + mode: 'all', + resolver: yupResolver(formSchema), + }); + const { setValue } = methods; + + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateProducerAcl(clusterName); + const topics = useTopicsOptions(clusterName); + + const onTopicTypeChange = (value: string) => { + if (value === MatchType.EXACT) { + setValue('topicsPrefix', undefined); + } else { + setValue('topics', undefined); + } + }; + + const onTransactionIdTypeChange = (value: string) => { + if (value === MatchType.EXACT) { + setValue('transactionsIdPrefix', undefined); + } else { + setValue('transactionalId', undefined); + } + }; + + const onSubmit = async (data: FormValues) => { + try { + await create.createResource(toRequest(data)); + context?.close(); + } catch (e) { + // no custom error + } + }; + + return ( + + +
+ + Principal + + + + + Host restriction + + +
+ + To Topic(s) + + } + prefixed={} + onChange={onTopicTypeChange} + /> + + + + + Transaction ID + + + } + prefixed={ + + } + onChange={onTransactionIdTypeChange} + /> + + +
+ +
+
+ ); +}; + +export default ForProducersForm; diff --git a/frontend/src/components/ACLPage/Form/ForProducers/lib.ts b/frontend/src/components/ACLPage/Form/ForProducers/lib.ts new file mode 100644 index 000000000..ccfc57f65 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForProducers/lib.ts @@ -0,0 +1,15 @@ +import { CreateProducerAcl } from 'generated-sources/models/CreateProducerAcl'; + +import { FormValues } from './types'; + +export const toRequest = (formValues: FormValues): CreateProducerAcl => { + return { + principal: formValues.principal, + host: formValues.host, + topics: formValues.topics?.map((opt) => opt.value), + topicsPrefix: formValues.topicsPrefix, + transactionalId: formValues.transactionalId, + transactionsIdPrefix: formValues.transactionsIdPrefix, + idempotent: formValues.indemponent, + }; +}; diff --git a/frontend/src/components/ACLPage/Form/ForProducers/schema.ts b/frontend/src/components/ACLPage/Form/ForProducers/schema.ts new file mode 100644 index 000000000..b8bb3e526 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForProducers/schema.ts @@ -0,0 +1,18 @@ +import { array, boolean, object, string } from 'yup'; + +const formSchema = object({ + principal: string().required(), + host: string().required(), + topics: array().of( + object().shape({ + label: string().required(), + value: string().required(), + }) + ), + topicsPrefix: string(), + transactionalId: string(), + transactionsIdPrefix: string(), + idempotent: boolean(), +}); + +export default formSchema; diff --git a/frontend/src/components/ACLPage/Form/ForProducers/types.ts b/frontend/src/components/ACLPage/Form/ForProducers/types.ts new file mode 100644 index 000000000..b6a4e38e0 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForProducers/types.ts @@ -0,0 +1,11 @@ +import { Option } from 'react-multi-select-component'; + +export interface FormValues { + principal: string; + host: string; + topics?: Option[]; + topicsPrefix?: string; + transactionalId?: string; + transactionsIdPrefix?: string; + indemponent: boolean; +} diff --git a/frontend/src/components/ACLPage/Form/Form.styled.ts b/frontend/src/components/ACLPage/Form/Form.styled.ts new file mode 100644 index 000000000..340695981 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/Form.styled.ts @@ -0,0 +1,77 @@ +import { StyledForm } from 'components/common/Form/Form.styled'; +import { Input } from 'components/common/Input/Input.styled'; +import MultiSelect from 'components/common/MultiSelect/MultiSelect.styled'; +import styled from 'styled-components'; + +export const Wrapper = styled.div<{ $open?: boolean }>( + ({ theme, $open }) => ` + background-color: ${theme.default.backgroundColor}; + position: fixed; + top: ${theme.layout.navBarHeight}; + bottom: 0; + width: 37vw; + right: calc(${$open ? '0px' : theme.layout.rightSidebarWidth} * -1); + box-shadow: -1px 0px 10px 0px rgba(0, 0, 0, 0.2); + transition: right 0.3s linear; + z-index: 200; + display: flex; + flex-direction: column; + + h3 { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid ${theme.layout.stuffBorderColor}; + padding: 16px; + } +` +); + +export const Form = styled(StyledForm)` + margin-top: 16px; + padding: 0px; + + ${MultiSelect} { + min-width: 270px; + } + + ${Input} { + min-width: 270px; + } +`; + +export const Field = styled.div` + ${({ theme }) => theme.input.label}; + display: flex; + justify-content: space-between; + + & ul { + width: 100%; + } +`; + +export const Label = styled.label` + line-height: 32px; +`; + +export const ControlList = styled.div` + display: flex; + flex-direction: column; + gap: 10px; +`; + +export const Footer = styled.div` + display: flex; + justify-content: end; + gap: 8px; + padding: 16px; +`; +export const Content = styled.div` + flex: auto; + padding: 16px; +`; + +export const CloseSidebar = styled.div` + cursor: pointer; +`; +export const Title = styled.span``; diff --git a/frontend/src/components/ACLPage/Form/Form.tsx b/frontend/src/components/ACLPage/Form/Form.tsx new file mode 100644 index 000000000..1242335d8 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/Form.tsx @@ -0,0 +1,75 @@ +import React, { FC, Suspense, useContext, useRef, useState } from 'react'; +import Select from 'components/common/Select/Select'; +import Heading from 'components/common/heading/Heading.styled'; +import CloseIcon from 'components/common/Icons/CloseIcon'; +import { Button } from 'components/common/Button/Button'; + +import { ACLFormProps, ACLType, AclDetailedFormProps } from './types'; +import { ACLTypeOptions } from './constants'; +import * as S from './Form.styled'; +import ACLFormContext from './AclFormContext'; + +const DETAILED_FORM_COMPONENTS: Record< + keyof typeof ACLType, + FC +> = { + [ACLType.CUSTOM_ACL]: React.lazy(() => import('./CustomACL/Form')), + [ACLType.FOR_CONSUMERS]: React.lazy(() => import('./ForConsumers/Form')), + [ACLType.FOR_PRODUCERS]: React.lazy(() => import('./ForProducers/Form')), + [ACLType.FOR_KAFKA_STREAM_APPS]: React.lazy( + () => import('./ForKafkaStreamApps/Form') + ), +}; + +const ACLForm: FC = ({ isOpen: open }) => { + const [aclType, setAclType] = useState(ACLType.CUSTOM_ACL); + const formContext = useContext(ACLFormContext); + + const formRef = useRef(null); + const DetailedForm = DETAILED_FORM_COMPONENTS[aclType]; + + return ( + + + Create ACL + + + + + + + Select ACL type +