From 5a5142118d210dccec02401f0885ec2b3cee54f5 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 3 Mar 2024 10:09:25 +0300 Subject: [PATCH 01/15] feat: ACL Draft --- .../ACLPage/Form/CustomACL/Form.tsx | 84 ++++++++++++++ .../ACLPage/Form/CustomACL/constants.ts | 49 ++++++++ .../components/ACLPage/Form/CustomACL/lib.ts | 45 ++++++++ .../ACLPage/Form/CustomACL/schema.ts | 13 +++ .../ACLPage/Form/CustomACL/types.ts | 16 +++ .../ACLPage/Form/ForConsumers/Form.tsx | 93 +++++++++++++++ .../ACLPage/Form/ForConsumers/lib.ts | 14 +++ .../ACLPage/Form/ForConsumers/schema.ts | 22 ++++ .../ACLPage/Form/ForConsumers/types.ts | 11 ++ .../ACLPage/Form/ForKafkaStreamApps/Form.tsx | 73 ++++++++++++ .../ACLPage/Form/ForKafkaStreamApps/lib.ts | 13 +++ .../ACLPage/Form/ForKafkaStreamApps/schema.ts | 11 ++ .../ACLPage/Form/ForKafkaStreamApps/types.ts | 9 ++ .../ACLPage/Form/ForProducers/Form.tsx | 87 ++++++++++++++ .../ACLPage/Form/ForProducers/lib.ts | 20 ++++ .../ACLPage/Form/ForProducers/schema.ts | 18 +++ .../ACLPage/Form/ForProducers/types.ts | 12 ++ .../components/ACLPage/Form/Form.styled.ts | 75 ++++++++++++ frontend/src/components/ACLPage/Form/Form.tsx | 75 ++++++++++++ .../src/components/ACLPage/Form/constants.ts | 18 +++ frontend/src/components/ACLPage/Form/types.ts | 20 ++++ .../components/ACLPage/List/ActionsCell.tsx | 20 ++++ .../components/ACLPage/List/List.styled.ts | 2 + frontend/src/components/ACLPage/List/List.tsx | 71 ++++++------ .../components/ClusterPage/ClusterPage.tsx | 10 +- .../ControlledMultiSelect/index.tsx | 31 +++++ .../common/Radio/ControlledRadio.tsx | 34 ++++++ .../components/common/Radio/Radio.styled.tsx | 38 +++++++ .../src/components/common/Radio/Radio.tsx | 34 ++++++ frontend/src/components/common/Radio/types.ts | 15 +++ .../common/Select/ControlledSelect.styled.ts | 3 + .../common/Select/ControlledSelect.tsx | 9 +- frontend/src/lib/__test__/isRegex.spec.ts | 17 +++ frontend/src/lib/constants.ts | 2 +- frontend/src/lib/hooks/api/acl.ts | 107 +++++++++++++++--- frontend/src/lib/isRegex.ts | 11 ++ 36 files changed, 1125 insertions(+), 57 deletions(-) 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/constants.ts create mode 100644 frontend/src/components/ACLPage/Form/types.ts create mode 100644 frontend/src/components/ACLPage/List/ActionsCell.tsx 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/components/common/Select/ControlledSelect.styled.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/CustomACL/Form.tsx b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx new file mode 100644 index 000000000..4869121b6 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx @@ -0,0 +1,84 @@ +import React, { FC } 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 { prefixOptions } from 'components/ACLPage/Form/constants'; +import { AclFormProps } from 'components/ACLPage/Form//types'; +import useAppParams from 'lib/hooks/useAppParams'; +import { ClusterName } from 'redux/interfaces'; +import * as S from 'components/ACLPage/Form/Form.styled'; + +import formSchema from './schema'; +import { FormValues } from './types'; +import { toFormValue, toRequest } from './lib'; +import { + defaultValues, + operations, + permissions, + resourceTypes, +} from './constants'; + +const CustomACLForm: FC = ({ formRef, acl, closeForm }) => { + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateCustomAcl(clusterName); + + const methods = useForm({ + mode: 'all', + resolver: yupResolver(formSchema), + defaultValues: { ...defaultValues, ...(acl ? toFormValue(acl) : {}) }, + }); + + const onSubmit = async (data: FormValues) => { + try { + const resource = toRequest(data); + await create.createResource(resource); + closeForm(); + } catch (e) { + // 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..2177f2f0e --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts @@ -0,0 +1,49 @@ +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( + v: T, + unknown: O +): Array { + return Object.values(v).reduce((acc, cur) => { + if (cur !== unknown) { + const option: SelectOption = { label: cur, value: cur }; + acc.push(option); + } + + return acc; + }, []); +} + +export const resourceTypes: Array = toOptionsArray( + KafkaAclResourceType, + KafkaAclResourceType.UNKNOWN +); + +export const operations = toOptionsArray( + KafkaAclOperationEnum, + KafkaAclOperationEnum.UNKNOWN +); + +export const permissions: RadioOption[] = [ + { + value: KafkaAclPermissionEnum.ALLOW, + activeColor: { background: '#33CC66', color: 'white' }, + }, + { + value: KafkaAclPermissionEnum.DENY, + activeColor: { background: 'red', color: 'white' }, + }, +]; + +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..bbac8c4d4 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/lib.ts @@ -0,0 +1,45 @@ +import { KafkaAcl, KafkaAclNamePatternType } from 'generated-sources'; +import isRegex from 'lib/isRegex'; +import { PrefixType } from 'components/ACLPage/Form/types'; + +import { FormValues } from './types'; + +export function toRequest(formValue: FormValues): KafkaAcl { + let namePatternType: KafkaAclNamePatternType; + if (formValue.namePatternType === PrefixType.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, + }; +} + +export function toFormValue(acl: KafkaAcl): FormValues { + let namePatternType: PrefixType; + if (acl.namePatternType === KafkaAclNamePatternType.PREFIXED) { + namePatternType = PrefixType.PREFIXED; + } else { + namePatternType = PrefixType.EXACT; + } + + return { + resourceType: acl.resourceType, + resourceName: acl.resourceName, + principal: acl.principal, + host: acl.host, + operation: acl.operation, + permission: acl.permission, + namePatternType, + }; +} 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..766673c90 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/CustomACL/types.ts @@ -0,0 +1,16 @@ +import { + KafkaAclOperationEnum, + KafkaAclPermissionEnum, + KafkaAclResourceType, +} from 'generated-sources'; +import { PrefixType } from 'components/ACLPage/Form/types'; + +export type FormValues = { + resourceType: KafkaAclResourceType; + resourceName: string; + namePatternType: PrefixType; + 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..339000958 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx @@ -0,0 +1,93 @@ +import React, { FC } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { FormProvider, useForm } from 'react-hook-form'; +import { ClusterName } from 'redux/interfaces'; +import { consumerGroupPayload } from 'lib/fixtures/consumerGroups'; +import { topicsPayload } from 'lib/fixtures/topics'; +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 ControlledRadio from 'components/common/Radio/ControlledRadio'; +import * as S from 'components/ACLPage/Form/Form.styled'; +import { prefixOptions } from 'components/ACLPage/Form/constants'; +import { AclFormProps } from 'components/ACLPage/Form/types'; + +import { FormValues } from './types'; +import { toRequest } from './lib'; +import formSchema from './schema'; + +const ForConsumersForm: FC = ({ formRef, closeForm }) => { + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateConsumersAcl(clusterName); + const methods = useForm({ + mode: 'all', + resolver: yupResolver(formSchema), + }); + + const onSubmit = async (data: FormValues) => { + try { + await create.createResource(toRequest(data)); + closeForm(); + } catch (e) { + // exception handle + } + }; + + // const { data } = useTopics({ clusterName }); // TODO: exclude internal + const topics = topicsPayload.map((topic) => { + return { + label: topic.name, + value: topic.name, + }; + }); + // const consumers = useConsumerGroups({ clusterName, search: '' }); // TODO: WTF + const consumerGroups = [consumerGroupPayload].map((cg) => { + return { + value: cg.groupId, + label: cg.groupId, + }; + }); + + return ( + + +
+ + Principal + + + + + Host restriction + + +
+ + + From Topic(s) + + + + + + + + Consumer group(s) + + + + + +
+
+ ); +}; + +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..9046296a9 --- /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..bfeb9c63f --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForConsumers/types.ts @@ -0,0 +1,11 @@ +import { Option } from 'react-multi-select-component'; +import { PrefixType } from 'components/ACLPage/Form/types'; + +export type FormValues = { + principal: string; + host: string; + topics: Option[]; + topicsPrefix: PrefixType; + consumerGroups: Option[]; + consumerGroupsPrefix: PrefixType; +}; 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..51c5e0a77 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx @@ -0,0 +1,73 @@ +import React, { FC } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { ClusterName } from 'redux/interfaces'; +import { topicsPayload } from 'lib/fixtures/topics'; +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 { AclFormProps } from 'components/ACLPage/Form/types'; + +import { toRequest } from './lib'; +import formSchema from './schema'; +import { FormValues } from './types'; + +const ForKafkaStreamAppsForm: FC = ({ formRef, closeForm }) => { + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateStreamAppAcl(clusterName); + const methods = useForm({ + mode: 'all', + resolver: yupResolver(formSchema), + }); + + const onSubmit = async (data: FormValues) => { + try { + const resource = toRequest(data); + await create.createResource(resource); + closeForm(); + } catch (e) { + // exception + } + }; + + const topics = topicsPayload.map((topic) => { + return { + label: topic.name, + value: topic.name, + }; + }); + + 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..a1ece386b --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts @@ -0,0 +1,9 @@ +import { Option } from 'react-multi-select-component'; + +export type 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..8c3e66230 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx @@ -0,0 +1,87 @@ +import React, { FC } 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 { topicsPayload } from 'lib/fixtures/topics'; +import Input from 'components/common/Input/Input'; +import ControlledRadio from 'components/common/Radio/ControlledRadio'; +import ControlledMultiSelect from 'components/common/MultiSelect/ControlledMultiSelect'; +import Checkbox from 'components/common/Checkbox/Checkbox'; +import * as S from 'components/ACLPage/Form/Form.styled'; +import { prefixOptions } from 'components/ACLPage/Form/constants'; +import { AclFormProps } from 'components/ACLPage/Form/types'; + +import { toRequest } from './lib'; +import { FormValues } from './types'; +import formSchema from './schema'; + +const ForProducersForm: FC = ({ formRef, closeForm }) => { + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateProducerAcl(clusterName); + const methods = useForm({ + mode: 'all', + resolver: yupResolver(formSchema), + }); + + const topics = topicsPayload.map((topic) => { + return { + label: topic.name, + value: topic.name, + }; + }); + + const onSubmit = async (data: FormValues) => { + try { + await create.createResource(toRequest(data)); + closeForm(); + } catch (e) { + // exception + } + }; + + return ( + + +
+ + Principal + + + + + Host restriction + + +
+ + To Topic(s) + + + + + + + + Transaction ID + + + + + +
+ +
+
+ ); +}; + +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..272328363 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForProducers/lib.ts @@ -0,0 +1,20 @@ +import { CreateProducerAcl } from 'generated-sources/models/CreateProducerAcl'; +import { Option } from 'react-multi-select-component'; + +import { FormValues } from './types'; + +const prepareOptions = (options: Option[]): string[] => { + return options.map((opt) => opt.value); +}; + +export const toRequest = (formValues: FormValues): CreateProducerAcl => { + return { + principal: formValues.principal, + host: formValues.host, + topics: prepareOptions(formValues.topics), + 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..16de1d54d --- /dev/null +++ b/frontend/src/components/ACLPage/Form/ForProducers/types.ts @@ -0,0 +1,12 @@ +import { Option } from 'react-multi-select-component'; +import { PrefixType } from 'components/ACLPage/Form/types'; + +export type FormValues = { + principal: string; + host: string; + topics: Option[]; + topicsPrefix: PrefixType; + 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..baa0a464a --- /dev/null +++ b/frontend/src/components/ACLPage/Form/Form.styled.ts @@ -0,0 +1,75 @@ +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` + 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` + border-top: 1px solid #e3e6e8; + display: flex; + justify-content: end; + gap: 8px; + padding: 16px; +`; +export const Content = styled.div` + flex: auto; + padding: 16px; +`; + +export const CloseSidebar = styled.div``; +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..d88e82218 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/Form.tsx @@ -0,0 +1,75 @@ +import React, { FC, ReactNode, 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 { KafkaAcl } from 'generated-sources'; + +import { ACLType } from './types'; +import { ACLTypeOptions } from './constants'; +import * as S from './Form.styled'; +import CustomACLForm from './CustomACL/Form'; +import ForConsumersForm from './ForConsumers/Form'; +import ForProducersForm from './ForProducers/Form'; +import ForKafkaStreamAppsForm from './ForKafkaStreamApps/Form'; + +interface ACLFormProps { + open: boolean; + onClose: () => void; + acl: KafkaAcl | null; +} + +const ACLForm: FC = ({ open, onClose, acl }) => { + const [aclType, setAclType] = useState(ACLType.CUSTOM_ACL); + const formRef = useRef(null); + + let content: ReactNode; + if (aclType === ACLType.CUSTOM_ACL) { + content = ; + } else if (aclType === ACLType.FOR_CONSUMERS) { + content = ; + } else if (aclType === ACLType.FOR_PRODUCERS) { + content = ; + } else { + content = ; + } + + return ( + + + Create ACL + + + + + + + Select ACL type + + )} Consumer group(s) - - + {cgType === PrefixType.EXACT ? ( + + ) : ( + + )} diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/lib.ts b/frontend/src/components/ACLPage/Form/ForConsumers/lib.ts index 9046296a9..b7980e52b 100644 --- a/frontend/src/components/ACLPage/Form/ForConsumers/lib.ts +++ b/frontend/src/components/ACLPage/Form/ForConsumers/lib.ts @@ -6,9 +6,9 @@ export const toRequest = (formValues: FormValues): CreateConsumerAcl => { return { principal: formValues.principal, host: formValues.host, - consumerGroups: formValues.consumerGroups.map((opt) => opt.value), + consumerGroups: formValues.consumerGroups?.map((opt) => opt.value), consumerGroupsPrefix: formValues.consumerGroupsPrefix, - topics: formValues.topics.map((opt) => opt.value), + topics: formValues.topics?.map((opt) => opt.value), topicsPrefix: formValues.topicsPrefix, }; }; diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/types.ts b/frontend/src/components/ACLPage/Form/ForConsumers/types.ts index bfeb9c63f..0c5b2635f 100644 --- a/frontend/src/components/ACLPage/Form/ForConsumers/types.ts +++ b/frontend/src/components/ACLPage/Form/ForConsumers/types.ts @@ -4,8 +4,8 @@ import { PrefixType } from 'components/ACLPage/Form/types'; export type FormValues = { principal: string; host: string; - topics: Option[]; - topicsPrefix: PrefixType; - consumerGroups: Option[]; - consumerGroupsPrefix: PrefixType; + 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 index 51c5e0a77..0da597b5b 100644 --- a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx @@ -1,8 +1,7 @@ -import React, { FC } from 'react'; +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 { topicsPayload } from 'lib/fixtures/topics'; import { useCreateStreamAppAcl } from 'lib/hooks/api/acl'; import useAppParams from 'lib/hooks/useAppParams'; import Input from 'components/common/Input/Input'; @@ -13,32 +12,30 @@ import { AclFormProps } from 'components/ACLPage/Form/types'; import { toRequest } from './lib'; import formSchema from './schema'; import { FormValues } from './types'; +import useTopicsOptions from 'components/ACLPage/lib/useTopicsOptions'; +import ACLFormContext from '../AclFormContext'; -const ForKafkaStreamAppsForm: FC = ({ formRef, closeForm }) => { - const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); - const create = useCreateStreamAppAcl(clusterName); +const ForKafkaStreamAppsForm: FC = ({ formRef }) => { + const { onClose: closeForm } = 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); closeForm(); } catch (e) { - // exception + console.error(e); } }; - const topics = topicsPayload.map((topic) => { - return { - label: topic.name, - value: topic.name, - }; - }); - return ( @@ -55,11 +52,11 @@ const ForKafkaStreamAppsForm: FC = ({ formRef, closeForm }) => {
From topic(s) - + To topic(s) - + Application.id diff --git a/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx index 8c3e66230..8bcd4baee 100644 --- a/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, useContext, useState } from 'react'; import { yupResolver } from '@hookform/resolvers/yup'; import { useCreateProducerAcl } from 'lib/hooks/api/acl'; import { FormProvider, useForm } from 'react-hook-form'; @@ -11,26 +11,46 @@ import ControlledMultiSelect from 'components/common/MultiSelect/ControlledMulti import Checkbox from 'components/common/Checkbox/Checkbox'; import * as S from 'components/ACLPage/Form/Form.styled'; import { prefixOptions } from 'components/ACLPage/Form/constants'; -import { AclFormProps } from 'components/ACLPage/Form/types'; +import { AclFormProps, PrefixType } from 'components/ACLPage/Form/types'; import { toRequest } from './lib'; import { FormValues } from './types'; import formSchema from './schema'; +import Radio from 'components/common/Radio/Radio'; +import useTopicsOptions from 'components/ACLPage/lib/useTopicsOptions'; +import ACLFormContext from '../AclFormContext'; -const ForProducersForm: FC = ({ formRef, closeForm }) => { - const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); - const create = useCreateProducerAcl(clusterName); +const ForProducersForm: FC = ({ formRef }) => { + const { onClose: closeForm } = useContext(ACLFormContext); const methods = useForm({ mode: 'all', resolver: yupResolver(formSchema), }); + const { setValue } = methods; - const topics = topicsPayload.map((topic) => { - return { - label: topic.name, - value: topic.name, - }; - }); + const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); + const create = useCreateProducerAcl(clusterName); + const topics = useTopicsOptions(clusterName); + const [topicType, setTopicType] = useState(PrefixType.EXACT); + const [transactionIdType, setTransactionIdType] = useState(PrefixType.EXACT); + + const onTopicTypeChange = (value: string) => { + if (value == PrefixType.EXACT) { + setValue('topicsPrefix', undefined); + } else { + setValue('topics', undefined); + } + setTopicType(value as PrefixType); + }; + + const onTransactionIdTypeChange = (value: string) => { + if (value == PrefixType.EXACT) { + setValue('transactionsIdPrefix', undefined); + } else { + setValue('transactionalId', undefined); + } + setTransactionIdType(value as PrefixType); + }; const onSubmit = async (data: FormValues) => { try { @@ -58,19 +78,32 @@ const ForProducersForm: FC = ({ formRef, closeForm }) => { To Topic(s) - - + + {topicType === PrefixType.EXACT ? ( + + ) : ( + + )} Transaction ID - - + {transactionIdType === PrefixType.EXACT ? ( + + ) : ( + + )}
diff --git a/frontend/src/components/ACLPage/Form/ForProducers/lib.ts b/frontend/src/components/ACLPage/Form/ForProducers/lib.ts index 272328363..debad2981 100644 --- a/frontend/src/components/ACLPage/Form/ForProducers/lib.ts +++ b/frontend/src/components/ACLPage/Form/ForProducers/lib.ts @@ -1,17 +1,11 @@ import { CreateProducerAcl } from 'generated-sources/models/CreateProducerAcl'; -import { Option } from 'react-multi-select-component'; - import { FormValues } from './types'; -const prepareOptions = (options: Option[]): string[] => { - return options.map((opt) => opt.value); -}; - export const toRequest = (formValues: FormValues): CreateProducerAcl => { return { principal: formValues.principal, host: formValues.host, - topics: prepareOptions(formValues.topics), + topics: formValues.topics?.map((opt) => opt.value), topicsPrefix: formValues.topicsPrefix, transactionalId: formValues.transactionalId, transactionsIdPrefix: formValues.transactionsIdPrefix, diff --git a/frontend/src/components/ACLPage/Form/ForProducers/types.ts b/frontend/src/components/ACLPage/Form/ForProducers/types.ts index 16de1d54d..161bb4add 100644 --- a/frontend/src/components/ACLPage/Form/ForProducers/types.ts +++ b/frontend/src/components/ACLPage/Form/ForProducers/types.ts @@ -4,8 +4,8 @@ import { PrefixType } from 'components/ACLPage/Form/types'; export type FormValues = { principal: string; host: string; - topics: Option[]; - topicsPrefix: PrefixType; + topics?: Option[]; + topicsPrefix?: string; transactionalId?: string; transactionsIdPrefix?: string; indemponent: boolean; diff --git a/frontend/src/components/ACLPage/Form/Form.tsx b/frontend/src/components/ACLPage/Form/Form.tsx index d88e82218..60c09c946 100644 --- a/frontend/src/components/ACLPage/Form/Form.tsx +++ b/frontend/src/components/ACLPage/Form/Form.tsx @@ -1,38 +1,37 @@ -import React, { FC, ReactNode, useRef, useState } from 'react'; +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 { KafkaAcl } from 'generated-sources'; -import { ACLType } from './types'; +import { ACLType, AclFormProps } from './types'; import { ACLTypeOptions } from './constants'; import * as S from './Form.styled'; -import CustomACLForm from './CustomACL/Form'; -import ForConsumersForm from './ForConsumers/Form'; -import ForProducersForm from './ForProducers/Form'; -import ForKafkaStreamAppsForm from './ForKafkaStreamApps/Form'; +import ACLFormContext from './AclFormContext'; +import PageLoader from 'components/common/PageLoader/PageLoader'; interface ACLFormProps { - open: boolean; - onClose: () => void; - acl: KafkaAcl | null; + isOpen: boolean; } -const ACLForm: FC = ({ open, onClose, acl }) => { +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 formRef = useRef(null); + const { onClose } = useContext(ACLFormContext); - let content: ReactNode; - if (aclType === ACLType.CUSTOM_ACL) { - content = ; - } else if (aclType === ACLType.FOR_CONSUMERS) { - content = ; - } else if (aclType === ACLType.FOR_PRODUCERS) { - content = ; - } else { - content = ; - } + const formRef = useRef(null); + const DetailedForm = DETAILED_FORM_COMPONENTS[aclType]; return ( @@ -53,7 +52,9 @@ const ACLForm: FC = ({ open, onClose, acl }) => { onChange={(option) => setAclType(option as ACLType)} />
- {content} + }> + +
diff --git a/frontend/src/components/ACLPage/Form/types.ts b/frontend/src/components/ACLPage/Form/types.ts index 49533bb1e..e93073be4 100644 --- a/frontend/src/components/ACLPage/Form/types.ts +++ b/frontend/src/components/ACLPage/Form/types.ts @@ -1,10 +1,7 @@ -import { KafkaAcl } from 'generated-sources'; import { RefObject } from 'react'; export type AclFormProps = { - formRef: RefObject; - closeForm: () => void; - acl?: KafkaAcl | null; + formRef: RefObject | null; }; export enum PrefixType { diff --git a/frontend/src/components/ACLPage/List/ActionsCell.tsx b/frontend/src/components/ACLPage/List/ActionsCell.tsx deleted file mode 100644 index 537714329..000000000 --- a/frontend/src/components/ACLPage/List/ActionsCell.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { DropdownItem } from 'components/common/Dropdown'; -import Dropdown from 'components/common/Dropdown/Dropdown'; -import React from 'react'; - -type Props = { - onDelete: () => void; - onEdit: () => void; -}; -const ActionsCell: React.FC = ({ onDelete, onEdit }) => { - return ( - - Edit ACL - - Delete ACL - - - ); -}; - -export default ActionsCell; diff --git a/frontend/src/components/ACLPage/List/List.tsx b/frontend/src/components/ACLPage/List/List.tsx index 1b58826c8..b41ddee36 100644 --- a/frontend/src/components/ACLPage/List/List.tsx +++ b/frontend/src/components/ACLPage/List/List.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { ColumnDef } from '@tanstack/react-table'; +import React from 'react'; +import { ColumnDef, Row } from '@tanstack/react-table'; import PageHeading from 'components/common/PageHeading/PageHeading'; import Table from 'components/common/NewTable'; import { useConfirm } from 'lib/hooks/useConfirm'; @@ -16,33 +16,35 @@ import { Button } from 'components/common/Button/Button'; import ACLForm from 'components/ACLPage/Form/Form'; import * as S from './List.styled'; -import ActionsCell from './ActionsCell'; +import DeleteIcon from 'components/common/Icons/DeleteIcon'; +import { useTheme } from 'styled-components'; +import ACLFormContext from '../Form/AclFormContext'; const ACList: React.FC = () => { const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); const { data: aclList } = useAcls(clusterName); const { deleteResource } = useDeleteAcl(clusterName); const modal = useConfirm(true); + const theme = useTheme(); const { value: isFormOpen, setFalse: closeForm, setTrue: openFrom, } = useBoolean(); + const [rowId, setRowId] = React.useState(''); - const handleDeleteClick = (acl: KafkaAcl) => { - modal('Are you sure want to delete this ACL record?', () => - deleteResource(acl) - ); + const handleDeleteClick = (acl: KafkaAcl | null) => { + if (acl) { + modal('Are you sure want to delete this ACL record?', () => + deleteResource(acl) + ); + } }; - const [currentAcl, setAcl] = useState(null); - const handleEditClick = (acl: KafkaAcl) => { - setAcl(acl); - openFrom(); - }; - const handleCreateClick = () => { - setAcl(null); - openFrom(); + const handleRowHover = (value: Row) => { + if (value) { + setRowId(value.id); + } }; const columns = React.useMemo[]>( @@ -123,16 +125,20 @@ const ACList: React.FC = () => { size: 111, }, { - id: 'actions', + id: 'delete', // eslint-disable-next-line react/no-unstable-nested-components cell: ({ row }) => { return ( - handleDeleteClick(row.original)} - onEdit={() => handleEditClick(row.original)} - /> + handleDeleteClick(row.original)}> + + ); }, + size: 76, }, ], [] @@ -141,7 +147,7 @@ const ACList: React.FC = () => { return ( - @@ -149,8 +155,12 @@ const ACList: React.FC = () => { columns={columns} data={aclList ?? []} emptyMessage="No ACL items found" + onRowHover={handleRowHover} + onMouseLeave={() => setRowId('')} /> - + + {isFormOpen && } + ); }; diff --git a/frontend/src/components/ACLPage/lib/useConsumerGroupsOptions.ts b/frontend/src/components/ACLPage/lib/useConsumerGroupsOptions.ts new file mode 100644 index 000000000..4cda08386 --- /dev/null +++ b/frontend/src/components/ACLPage/lib/useConsumerGroupsOptions.ts @@ -0,0 +1,20 @@ +import { useConsumerGroups } from 'lib/hooks/api/consumers'; +import { useMemo } from 'react'; + +const useConsumerGroupsOptions = (clusterName: string, search: string) => { + const { data } = useConsumerGroups({ clusterName, search }); + const consumerGroups = useMemo(() => { + return ( + data?.consumerGroups?.map((cg) => { + return { + value: cg.groupId, + label: cg.groupId, + }; + }) || [] + ); + }, [clusterName, search]); + + return consumerGroups; +}; + +export default useConsumerGroupsOptions; diff --git a/frontend/src/components/ACLPage/lib/useTopicsOptions.ts b/frontend/src/components/ACLPage/lib/useTopicsOptions.ts new file mode 100644 index 000000000..89db10b23 --- /dev/null +++ b/frontend/src/components/ACLPage/lib/useTopicsOptions.ts @@ -0,0 +1,20 @@ +import { useTopics } from 'lib/hooks/api/topics'; +import { useMemo } from 'react'; + +const useTopicsOptions = (clusterName: string) => { + const { data } = useTopics({ clusterName }); + const topics = useMemo(() => { + return ( + data?.topics?.map((topic) => { + return { + label: topic.name, + value: topic.name, + }; + }) || [] + ); + }, [clusterName]); + + return topics; +}; + +export default useTopicsOptions; diff --git a/frontend/src/lib/constants.ts b/frontend/src/lib/constants.ts index 4a4771f6a..833ad2faf 100644 --- a/frontend/src/lib/constants.ts +++ b/frontend/src/lib/constants.ts @@ -8,7 +8,7 @@ declare global { } export const BASE_PARAMS: ConfigurationParameters = { - basePath: 'https://test.kafbat.dev' || '', + basePath: window.basePath || '', credentials: 'include', headers: { 'Content-Type': 'application/json', From a17917b2b6bddae1dc58d944320b2eebbf0f04ae Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 3 Mar 2024 16:58:52 +0300 Subject: [PATCH 03/15] Shared Match type selector --- .../components/ACLPage/Form/AclFormContext.ts | 2 +- .../ACLPage/Form/CustomACL/Form.tsx | 15 +++-- .../ACLPage/Form/CustomACL/constants.ts | 4 +- .../components/ACLPage/Form/CustomACL/lib.ts | 10 +-- .../ACLPage/Form/CustomACL/types.ts | 8 +-- .../ACLPage/Form/ForConsumers/Form.tsx | 66 ++++++++----------- .../ACLPage/Form/ForConsumers/types.ts | 5 +- .../ACLPage/Form/ForKafkaStreamApps/Form.tsx | 8 +-- .../ACLPage/Form/ForKafkaStreamApps/types.ts | 4 +- .../ACLPage/Form/ForProducers/Form.tsx | 45 ++++--------- .../ACLPage/Form/ForProducers/lib.ts | 1 + .../ACLPage/Form/ForProducers/types.ts | 5 +- frontend/src/components/ACLPage/Form/Form.tsx | 13 ++-- .../Form/components/MatchTypeSelector.tsx | 38 +++++++++++ .../src/components/ACLPage/Form/constants.ts | 8 +-- frontend/src/components/ACLPage/Form/types.ts | 10 ++- .../common/Radio/ControlledRadio.tsx | 11 +--- frontend/src/components/common/Radio/types.ts | 5 ++ 18 files changed, 132 insertions(+), 126 deletions(-) create mode 100644 frontend/src/components/ACLPage/Form/components/MatchTypeSelector.tsx diff --git a/frontend/src/components/ACLPage/Form/AclFormContext.ts b/frontend/src/components/ACLPage/Form/AclFormContext.ts index 67577642f..5f80fef6f 100644 --- a/frontend/src/components/ACLPage/Form/AclFormContext.ts +++ b/frontend/src/components/ACLPage/Form/AclFormContext.ts @@ -1,4 +1,4 @@ -import { RefObject, createContext } from 'react'; +import { createContext } from 'react'; interface ACLFormContextProps { onClose: () => void; diff --git a/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx index c13a2bc33..3720462e7 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx +++ b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx @@ -5,24 +5,24 @@ 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 { prefixOptions } from 'components/ACLPage/Form/constants'; -import { AclFormProps } from 'components/ACLPage/Form//types'; +import { matchTypeOptions } from 'components/ACLPage/Form/constants'; +import { AclDetailedFormProps } from 'components/ACLPage/Form//types'; 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 formSchema from './schema'; import { FormValues } from './types'; -import { toFormValue, toRequest } from './lib'; +import { toRequest } from './lib'; import { defaultValues, operations, permissions, resourceTypes, } from './constants'; -import ACLFormContext from '../AclFormContext'; -const CustomACLForm: FC = ({ formRef }) => { +const CustomACLForm: FC = ({ formRef }) => { const { onClose: closeForm } = useContext(ACLFormContext); const methods = useForm({ mode: 'all', @@ -74,7 +74,10 @@ const CustomACLForm: FC = ({ formRef }) => { Matching pattern - + diff --git a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts index 2177f2f0e..e0c572a93 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts +++ b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts @@ -9,10 +9,10 @@ import { RadioOption } from 'components/common/Radio/types'; import { FormValues } from './types'; function toOptionsArray( - v: T, + enumerable: T, unknown: O ): Array { - return Object.values(v).reduce((acc, cur) => { + return Object.values(enumerable).reduce((acc, cur) => { if (cur !== unknown) { const option: SelectOption = { label: cur, value: cur }; acc.push(option); diff --git a/frontend/src/components/ACLPage/Form/CustomACL/lib.ts b/frontend/src/components/ACLPage/Form/CustomACL/lib.ts index bbac8c4d4..f68d96a24 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/lib.ts +++ b/frontend/src/components/ACLPage/Form/CustomACL/lib.ts @@ -1,12 +1,12 @@ import { KafkaAcl, KafkaAclNamePatternType } from 'generated-sources'; import isRegex from 'lib/isRegex'; -import { PrefixType } from 'components/ACLPage/Form/types'; +import { MatchType } from 'components/ACLPage/Form/types'; import { FormValues } from './types'; export function toRequest(formValue: FormValues): KafkaAcl { let namePatternType: KafkaAclNamePatternType; - if (formValue.namePatternType === PrefixType.PREFIXED) { + if (formValue.namePatternType === MatchType.PREFIXED) { namePatternType = KafkaAclNamePatternType.PREFIXED; } else if (isRegex(formValue.resourceName)) { namePatternType = KafkaAclNamePatternType.MATCH; @@ -26,11 +26,11 @@ export function toRequest(formValue: FormValues): KafkaAcl { } export function toFormValue(acl: KafkaAcl): FormValues { - let namePatternType: PrefixType; + let namePatternType: MatchType; if (acl.namePatternType === KafkaAclNamePatternType.PREFIXED) { - namePatternType = PrefixType.PREFIXED; + namePatternType = MatchType.PREFIXED; } else { - namePatternType = PrefixType.EXACT; + namePatternType = MatchType.EXACT; } return { diff --git a/frontend/src/components/ACLPage/Form/CustomACL/types.ts b/frontend/src/components/ACLPage/Form/CustomACL/types.ts index 766673c90..fa40f955c 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/types.ts +++ b/frontend/src/components/ACLPage/Form/CustomACL/types.ts @@ -3,14 +3,14 @@ import { KafkaAclPermissionEnum, KafkaAclResourceType, } from 'generated-sources'; -import { PrefixType } from 'components/ACLPage/Form/types'; +import { MatchType } from 'components/ACLPage/Form/types'; -export type FormValues = { +export interface FormValues { resourceType: KafkaAclResourceType; resourceName: string; - namePatternType: PrefixType; + 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 index 2a32c73e1..eb82228b0 100644 --- a/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext, useState } from 'react'; +import React, { FC, useContext } from 'react'; import { yupResolver } from '@hookform/resolvers/yup'; import { FormProvider, useForm } from 'react-hook-form'; import { ClusterName } from 'redux/interfaces'; @@ -7,18 +7,17 @@ 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 { prefixOptions } from 'components/ACLPage/Form/constants'; -import { AclFormProps, PrefixType } from 'components/ACLPage/Form/types'; - -import { FormValues } from './types'; -import { toRequest } from './lib'; -import formSchema from './schema'; -import Radio from 'components/common/Radio/Radio'; +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 '../AclFormContext'; +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 ForConsumersForm: FC = ({ formRef }) => { const { onClose: closeForm } = useContext(ACLFormContext); const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); const create = useCreateConsumersAcl(clusterName); @@ -42,24 +41,20 @@ const ForConsumersForm: FC = ({ formRef }) => { const topics = useTopicsOptions(clusterName); const consumerGroups = useConsumerGroupsOptions(clusterName, ''); - const [topicType, setTopicType] = useState(PrefixType.EXACT); const onTopicTypeChange = (value: string) => { - if (value == PrefixType.EXACT) { + if (value === MatchType.EXACT) { setValue('topicsPrefix', undefined); } else { setValue('topics', undefined); } - setTopicType(value as PrefixType); }; - const [cgType, setCgType] = useState(PrefixType.EXACT); - const onCgTypeChange = (value: string) => { - if (value == PrefixType.EXACT) { + const onConsumerGroupTypeChange = (value: string) => { + if (value === MatchType.EXACT) { setValue('consumerGroupsPrefix', undefined); } else { setValue('consumerGroups', undefined); } - setCgType(value as PrefixType); }; return ( @@ -80,38 +75,29 @@ const ForConsumersForm: FC = ({ formRef }) => { From Topic(s) - } + prefixed={} onChange={onTopicTypeChange} /> - {topicType === PrefixType.EXACT ? ( - - ) : ( - - )} Consumer group(s) - + } + prefixed={ + + } + onChange={onConsumerGroupTypeChange} /> - {cgType === PrefixType.EXACT ? ( - - ) : ( - - )} diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/types.ts b/frontend/src/components/ACLPage/Form/ForConsumers/types.ts index 0c5b2635f..97fc73484 100644 --- a/frontend/src/components/ACLPage/Form/ForConsumers/types.ts +++ b/frontend/src/components/ACLPage/Form/ForConsumers/types.ts @@ -1,11 +1,10 @@ import { Option } from 'react-multi-select-component'; -import { PrefixType } from 'components/ACLPage/Form/types'; -export type FormValues = { +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 index 0da597b5b..78bae82e3 100644 --- a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx @@ -7,15 +7,15 @@ 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 { AclFormProps } from 'components/ACLPage/Form/types'; +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'; -import useTopicsOptions from 'components/ACLPage/lib/useTopicsOptions'; -import ACLFormContext from '../AclFormContext'; -const ForKafkaStreamAppsForm: FC = ({ formRef }) => { +const ForKafkaStreamAppsForm: FC = ({ formRef }) => { const { onClose: closeForm } = useContext(ACLFormContext); const methods = useForm({ mode: 'all', diff --git a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts index a1ece386b..adeee573c 100644 --- a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/types.ts @@ -1,9 +1,9 @@ import { Option } from 'react-multi-select-component'; -export type FormValues = { +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 index 8bcd4baee..d778bb01f 100644 --- a/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx @@ -1,26 +1,23 @@ -import React, { FC, useContext, useState } from 'react'; +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 { topicsPayload } from 'lib/fixtures/topics'; import Input from 'components/common/Input/Input'; -import ControlledRadio from 'components/common/Radio/ControlledRadio'; import ControlledMultiSelect from 'components/common/MultiSelect/ControlledMultiSelect'; import Checkbox from 'components/common/Checkbox/Checkbox'; import * as S from 'components/ACLPage/Form/Form.styled'; -import { prefixOptions } from 'components/ACLPage/Form/constants'; -import { AclFormProps, PrefixType } from 'components/ACLPage/Form/types'; +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'; -import Radio from 'components/common/Radio/Radio'; -import useTopicsOptions from 'components/ACLPage/lib/useTopicsOptions'; -import ACLFormContext from '../AclFormContext'; -const ForProducersForm: FC = ({ formRef }) => { +const ForProducersForm: FC = ({ formRef }) => { const { onClose: closeForm } = useContext(ACLFormContext); const methods = useForm({ mode: 'all', @@ -31,25 +28,21 @@ const ForProducersForm: FC = ({ formRef }) => { const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); const create = useCreateProducerAcl(clusterName); const topics = useTopicsOptions(clusterName); - const [topicType, setTopicType] = useState(PrefixType.EXACT); - const [transactionIdType, setTransactionIdType] = useState(PrefixType.EXACT); const onTopicTypeChange = (value: string) => { - if (value == PrefixType.EXACT) { + if (value === MatchType.EXACT) { setValue('topicsPrefix', undefined); } else { setValue('topics', undefined); } - setTopicType(value as PrefixType); }; const onTransactionIdTypeChange = (value: string) => { - if (value == PrefixType.EXACT) { + if (value === MatchType.EXACT) { setValue('transactionsIdPrefix', undefined); } else { setValue('transactionalId', undefined); } - setTransactionIdType(value as PrefixType); }; const onSubmit = async (data: FormValues) => { @@ -78,32 +71,22 @@ const ForProducersForm: FC = ({ formRef }) => { To Topic(s) - } + prefixed={} onChange={onTopicTypeChange} /> - {topicType === PrefixType.EXACT ? ( - - ) : ( - - )} Transaction ID - } + prefixed={} onChange={onTransactionIdTypeChange} /> - {transactionIdType === PrefixType.EXACT ? ( - - ) : ( - - )}
diff --git a/frontend/src/components/ACLPage/Form/ForProducers/lib.ts b/frontend/src/components/ACLPage/Form/ForProducers/lib.ts index debad2981..ccfc57f65 100644 --- a/frontend/src/components/ACLPage/Form/ForProducers/lib.ts +++ b/frontend/src/components/ACLPage/Form/ForProducers/lib.ts @@ -1,4 +1,5 @@ import { CreateProducerAcl } from 'generated-sources/models/CreateProducerAcl'; + import { FormValues } from './types'; export const toRequest = (formValues: FormValues): CreateProducerAcl => { diff --git a/frontend/src/components/ACLPage/Form/ForProducers/types.ts b/frontend/src/components/ACLPage/Form/ForProducers/types.ts index 161bb4add..b6a4e38e0 100644 --- a/frontend/src/components/ACLPage/Form/ForProducers/types.ts +++ b/frontend/src/components/ACLPage/Form/ForProducers/types.ts @@ -1,7 +1,6 @@ import { Option } from 'react-multi-select-component'; -import { PrefixType } from 'components/ACLPage/Form/types'; -export type FormValues = { +export interface FormValues { principal: string; host: string; topics?: Option[]; @@ -9,4 +8,4 @@ export type FormValues = { transactionalId?: string; transactionsIdPrefix?: string; indemponent: boolean; -}; +} diff --git a/frontend/src/components/ACLPage/Form/Form.tsx b/frontend/src/components/ACLPage/Form/Form.tsx index 60c09c946..4e9b6dfce 100644 --- a/frontend/src/components/ACLPage/Form/Form.tsx +++ b/frontend/src/components/ACLPage/Form/Form.tsx @@ -4,19 +4,14 @@ import Heading from 'components/common/heading/Heading.styled'; import CloseIcon from 'components/common/Icons/CloseIcon'; import { Button } from 'components/common/Button/Button'; -import { ACLType, AclFormProps } from './types'; +import { ACLFormProps, ACLType, AclDetailedFormProps } from './types'; import { ACLTypeOptions } from './constants'; import * as S from './Form.styled'; import ACLFormContext from './AclFormContext'; -import PageLoader from 'components/common/PageLoader/PageLoader'; - -interface ACLFormProps { - isOpen: boolean; -} const DETAILED_FORM_COMPONENTS: Record< keyof typeof ACLType, - FC + FC > = { [ACLType.CUSTOM_ACL]: React.lazy(() => import('./CustomACL/Form')), [ACLType.FOR_CONSUMERS]: React.lazy(() => import('./ForConsumers/Form')), @@ -52,8 +47,8 @@ const ACLForm: FC = ({ isOpen: open }) => { onChange={(option) => setAclType(option as ACLType)} /> - }> - + }> + diff --git a/frontend/src/components/ACLPage/Form/components/MatchTypeSelector.tsx b/frontend/src/components/ACLPage/Form/components/MatchTypeSelector.tsx new file mode 100644 index 000000000..2fccf0a75 --- /dev/null +++ b/frontend/src/components/ACLPage/Form/components/MatchTypeSelector.tsx @@ -0,0 +1,38 @@ +import React, { FC, ReactElement, useState } from 'react'; +import Radio from 'components/common/Radio/Radio'; +import { MatchType } from 'components/ACLPage/Form/types'; +import { matchTypeOptions } from 'components/ACLPage/Form/constants'; + +interface MatchTypeSelectorProps { + exact: ReactElement; + prefixed: ReactElement; + onChange?: (matchType: MatchType) => void; +} + +const MatchTypeSelector: FC = ({ + exact, + prefixed, + onChange, +}) => { + const [matchType, setMatchType] = useState(MatchType.EXACT); + + const handleChange = (value: MatchType) => { + setMatchType(value); + onChange?.(value); + }; + + const content = matchType === MatchType.EXACT ? exact : prefixed; + + return ( + <> + handleChange(v as MatchType)} + /> +
{content}
+ + ); +}; + +export default MatchTypeSelector; diff --git a/frontend/src/components/ACLPage/Form/constants.ts b/frontend/src/components/ACLPage/Form/constants.ts index fa213a2f7..5acbd9206 100644 --- a/frontend/src/components/ACLPage/Form/constants.ts +++ b/frontend/src/components/ACLPage/Form/constants.ts @@ -1,13 +1,13 @@ import { SelectOption } from 'components/common/Select/Select'; import { RadioOption } from 'components/common/Radio/types'; -import { ACLType, PrefixType } from './types'; +import { ACLType, MatchType } from './types'; -export const prefixOptions: RadioOption[] = [ +export const matchTypeOptions: RadioOption[] = [ { - value: PrefixType.EXACT, + value: MatchType.EXACT, }, - { value: PrefixType.PREFIXED }, + { value: MatchType.PREFIXED }, ]; export const ACLTypeOptions: SelectOption[] = [ diff --git a/frontend/src/components/ACLPage/Form/types.ts b/frontend/src/components/ACLPage/Form/types.ts index e93073be4..fcac9682e 100644 --- a/frontend/src/components/ACLPage/Form/types.ts +++ b/frontend/src/components/ACLPage/Form/types.ts @@ -1,10 +1,14 @@ import { RefObject } from 'react'; -export type AclFormProps = { +export interface AclDetailedFormProps { formRef: RefObject | null; -}; +} + +export interface ACLFormProps { + isOpen: boolean; +} -export enum PrefixType { +export enum MatchType { EXACT = 'EXACT', PREFIXED = 'PREFIXED', } diff --git a/frontend/src/components/common/Radio/ControlledRadio.tsx b/frontend/src/components/common/Radio/ControlledRadio.tsx index a0aff05b8..006eff40b 100644 --- a/frontend/src/components/common/Radio/ControlledRadio.tsx +++ b/frontend/src/components/common/Radio/ControlledRadio.tsx @@ -1,14 +1,9 @@ import React, { FC } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; -import { RadioOption } from './types'; +import { ControlledRadioProps } from './types'; import { Radio } from './Radio'; -type ControlledRadioProps = { - name: string; - options: ReadonlyArray; -}; - const ControlledRadio: FC = ({ name, options }) => { const { control } = useFormContext(); @@ -20,9 +15,7 @@ const ControlledRadio: FC = ({ name, options }) => { return ( { - onChange(t); - }} + onChange={onChange} value={value ?? options[0].value} /> ); diff --git a/frontend/src/components/common/Radio/types.ts b/frontend/src/components/common/Radio/types.ts index 86a8f763c..205ffe64d 100644 --- a/frontend/src/components/common/Radio/types.ts +++ b/frontend/src/components/common/Radio/types.ts @@ -13,3 +13,8 @@ export type RadioProps = { options: ReadonlyArray; onChange: (value: string) => void; }; + +export interface ControlledRadioProps { + name: string; + options: ReadonlyArray; +} From d227564230525852cdac5a4315430da5b014e111 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 3 Mar 2024 19:22:15 +0300 Subject: [PATCH 04/15] Add Colors for Radio --- .../components/ACLPage/Form/AclFormContext.ts | 4 +--- .../ACLPage/Form/CustomACL/Form.tsx | 12 +++++++---- .../ACLPage/Form/CustomACL/constants.ts | 13 +++++++++--- .../components/ACLPage/Form/Form.styled.ts | 4 +++- frontend/src/components/ACLPage/Form/Form.tsx | 10 ++++++--- frontend/src/components/ACLPage/List/List.tsx | 12 +++++++---- .../components/common/Radio/Radio.styled.tsx | 21 +++++++++++-------- .../src/components/common/Radio/Radio.tsx | 6 +++--- frontend/src/components/common/Radio/types.ts | 10 +++++---- frontend/src/theme/theme.ts | 17 +++++++++++++++ 10 files changed, 75 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/ACLPage/Form/AclFormContext.ts b/frontend/src/components/ACLPage/Form/AclFormContext.ts index 5f80fef6f..22c859e62 100644 --- a/frontend/src/components/ACLPage/Form/AclFormContext.ts +++ b/frontend/src/components/ACLPage/Form/AclFormContext.ts @@ -3,8 +3,6 @@ import { createContext } from 'react'; interface ACLFormContextProps { onClose: () => void; } -const ACLFormContext = createContext({ - onClose: () => {}, -}); +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 index 3720462e7..42b0420f0 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx +++ b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx @@ -6,11 +6,12 @@ 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 { AclDetailedFormProps } from 'components/ACLPage/Form//types'; 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 { useTheme } from 'styled-components'; +import { AclDetailedFormProps } from 'components/ACLPage/Form/types'; import formSchema from './schema'; import { FormValues } from './types'; @@ -23,7 +24,10 @@ import { } from './constants'; const CustomACLForm: FC = ({ formRef }) => { - const { onClose: closeForm } = useContext(ACLFormContext); + const context = useContext(ACLFormContext); + + const theme = useTheme(); + const methods = useForm({ mode: 'all', resolver: yupResolver(formSchema), @@ -37,7 +41,7 @@ const CustomACLForm: FC = ({ formRef }) => { try { const resource = toRequest(data); await create.createResource(resource); - closeForm(); + context?.onClose(); } catch (e) { // error } @@ -66,7 +70,7 @@ const CustomACLForm: FC = ({ formRef }) => { Operations - + diff --git a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts index e0c572a93..54fd9993d 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts +++ b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts @@ -5,6 +5,7 @@ import { KafkaAclResourceType, } from 'generated-sources'; import { RadioOption } from 'components/common/Radio/types'; +import { DefaultTheme } from 'styled-components'; import { FormValues } from './types'; @@ -32,14 +33,20 @@ export const operations = toOptionsArray( KafkaAclOperationEnum.UNKNOWN ); -export const permissions: RadioOption[] = [ +export const permissions = (theme: DefaultTheme): RadioOption[] => [ { value: KafkaAclPermissionEnum.ALLOW, - activeColor: { background: '#33CC66', color: 'white' }, + activeState: { + background: theme.radio.allow.backgroundColor, + color: theme.radio.allow.color, + }, }, { value: KafkaAclPermissionEnum.DENY, - activeColor: { background: 'red', color: 'white' }, + activeState: { + background: theme.radio.deny.backgroundColor, + color: theme.radio.deny.color, + }, }, ]; diff --git a/frontend/src/components/ACLPage/Form/Form.styled.ts b/frontend/src/components/ACLPage/Form/Form.styled.ts index baa0a464a..ef0c75f0f 100644 --- a/frontend/src/components/ACLPage/Form/Form.styled.ts +++ b/frontend/src/components/ACLPage/Form/Form.styled.ts @@ -71,5 +71,7 @@ export const Content = styled.div` padding: 16px; `; -export const CloseSidebar = styled.div``; +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 index 4e9b6dfce..ce0d2e17a 100644 --- a/frontend/src/components/ACLPage/Form/Form.tsx +++ b/frontend/src/components/ACLPage/Form/Form.tsx @@ -23,7 +23,7 @@ const DETAILED_FORM_COMPONENTS: Record< const ACLForm: FC = ({ isOpen: open }) => { const [aclType, setAclType] = useState(ACLType.CUSTOM_ACL); - const { onClose } = useContext(ACLFormContext); + const formContext = useContext(ACLFormContext); const formRef = useRef(null); const DetailedForm = DETAILED_FORM_COMPONENTS[aclType]; @@ -32,7 +32,7 @@ const ACLForm: FC = ({ isOpen: open }) => { Create ACL - + @@ -53,7 +53,11 @@ const ACLForm: FC = ({ isOpen: open }) => { - diff --git a/frontend/src/components/ACLPage/List/List.tsx b/frontend/src/components/ACLPage/List/List.tsx index e96778394..6cafd6c55 100644 --- a/frontend/src/components/ACLPage/List/List.tsx +++ b/frontend/src/components/ACLPage/List/List.tsx @@ -160,7 +160,7 @@ const ACList: React.FC = () => { /> {isFormOpen && } diff --git a/frontend/src/components/common/Radio/Radio.tsx b/frontend/src/components/common/Radio/Radio.tsx index b203a3196..065a19e92 100644 --- a/frontend/src/components/common/Radio/Radio.tsx +++ b/frontend/src/components/common/Radio/Radio.tsx @@ -17,14 +17,14 @@ export const Radio: FC = ({ options, onChange, value }) => { return ( - {options.map((tab) => ( + {options.map((option) => ( handleChange(tab.value)} - $isActive={selectedValue === tab.value} - $activeState={tab.activeState} + key={option.value} + onClick={() => handleChange(option.value)} + $isActive={selectedValue === option.value} + $activeState={option.activeState} > - {tab.value} + {option.value} ))} From 9f7e1b0f65eaa496c886c2a26d80b65def6e0e55 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 3 Mar 2024 19:53:46 +0300 Subject: [PATCH 06/15] Fix delete icon bug --- frontend/src/components/ACLPage/List/List.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ACLPage/List/List.tsx b/frontend/src/components/ACLPage/List/List.tsx index 6cafd6c55..a6d471c47 100644 --- a/frontend/src/components/ACLPage/List/List.tsx +++ b/frontend/src/components/ACLPage/List/List.tsx @@ -141,7 +141,7 @@ const ACList: React.FC = () => { size: 76, }, ], - [] + [rowId] ); return ( From ef283d32b55beb7fd55dd651deeb0b9837d14507 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 3 Mar 2024 19:56:09 +0300 Subject: [PATCH 07/15] Return ACL guard --- frontend/src/components/ClusterPage/ClusterPage.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ClusterPage/ClusterPage.tsx b/frontend/src/components/ClusterPage/ClusterPage.tsx index 06628ba3b..29d2015f6 100644 --- a/frontend/src/components/ClusterPage/ClusterPage.tsx +++ b/frontend/src/components/ClusterPage/ClusterPage.tsx @@ -100,10 +100,12 @@ const ClusterPage: React.FC = () => { element={} /> )} - } - /> + {contextValue.hasAclViewConfigured && ( + } + /> + )} {appInfo.hasDynamicConfig && ( Date: Sun, 3 Mar 2024 19:57:08 +0300 Subject: [PATCH 08/15] No custom error on mutation --- frontend/src/components/ACLPage/Form/CustomACL/Form.tsx | 2 +- frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx | 3 +-- .../src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx | 2 +- frontend/src/components/ACLPage/Form/ForProducers/Form.tsx | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx index 818331890..23bb5b8ea 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx +++ b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx @@ -43,7 +43,7 @@ const CustomACLForm: FC = ({ formRef }) => { await create.createResource(resource); context?.close(); } catch (e) { - // error + // no custom error } }; diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx index e62522187..eb6652115 100644 --- a/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx @@ -33,8 +33,7 @@ const ForConsumersForm: FC = ({ formRef }) => { await create.createResource(toRequest(data)); context?.close(); } catch (e) { - console.error(e); - // exception handle + // no custom error } }; diff --git a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx index 6090b3dce..8d67c7e45 100644 --- a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx @@ -32,7 +32,7 @@ const ForKafkaStreamAppsForm: FC = ({ formRef }) => { await create.createResource(resource); context?.close(); } catch (e) { - console.error(e); + // no custom error } }; diff --git a/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx index f762067a1..f87ec9307 100644 --- a/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx @@ -50,7 +50,7 @@ const ForProducersForm: FC = ({ formRef }) => { await create.createResource(toRequest(data)); context?.close(); } catch (e) { - // exception + // no custom error } }; From 76a593b14897d4392d964c559558b1eafa1ac91a Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 3 Mar 2024 20:05:59 +0300 Subject: [PATCH 09/15] Radio value invariant --- frontend/src/components/common/Radio/ControlledRadio.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/common/Radio/ControlledRadio.tsx b/frontend/src/components/common/Radio/ControlledRadio.tsx index 006eff40b..044816c84 100644 --- a/frontend/src/components/common/Radio/ControlledRadio.tsx +++ b/frontend/src/components/common/Radio/ControlledRadio.tsx @@ -16,7 +16,7 @@ const ControlledRadio: FC = ({ name, options }) => { ); }} From 47482b252b5c2ef6f78ab464b2c0aef0c9136761 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 3 Mar 2024 21:17:04 +0300 Subject: [PATCH 10/15] Correct state change --- frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx | 2 +- .../src/components/ACLPage/lib/useConsumerGroupsOptions.ts | 6 +++--- frontend/src/components/ACLPage/lib/useTopicsOptions.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx index eb6652115..41624ed33 100644 --- a/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx @@ -38,7 +38,7 @@ const ForConsumersForm: FC = ({ formRef }) => { }; const topics = useTopicsOptions(clusterName); - const consumerGroups = useConsumerGroupsOptions(clusterName, ''); + const consumerGroups = useConsumerGroupsOptions(clusterName); const onTopicTypeChange = (value: string) => { if (value === MatchType.EXACT) { diff --git a/frontend/src/components/ACLPage/lib/useConsumerGroupsOptions.ts b/frontend/src/components/ACLPage/lib/useConsumerGroupsOptions.ts index 4cda08386..335dfc585 100644 --- a/frontend/src/components/ACLPage/lib/useConsumerGroupsOptions.ts +++ b/frontend/src/components/ACLPage/lib/useConsumerGroupsOptions.ts @@ -1,8 +1,8 @@ import { useConsumerGroups } from 'lib/hooks/api/consumers'; import { useMemo } from 'react'; -const useConsumerGroupsOptions = (clusterName: string, search: string) => { - const { data } = useConsumerGroups({ clusterName, search }); +const useConsumerGroupsOptions = (clusterName: string) => { + const { data } = useConsumerGroups({ clusterName, search: '' }); const consumerGroups = useMemo(() => { return ( data?.consumerGroups?.map((cg) => { @@ -12,7 +12,7 @@ const useConsumerGroupsOptions = (clusterName: string, search: string) => { }; }) || [] ); - }, [clusterName, search]); + }, [data]); return consumerGroups; }; diff --git a/frontend/src/components/ACLPage/lib/useTopicsOptions.ts b/frontend/src/components/ACLPage/lib/useTopicsOptions.ts index 89db10b23..441686e4b 100644 --- a/frontend/src/components/ACLPage/lib/useTopicsOptions.ts +++ b/frontend/src/components/ACLPage/lib/useTopicsOptions.ts @@ -12,7 +12,7 @@ const useTopicsOptions = (clusterName: string) => { }; }) || [] ); - }, [clusterName]); + }, [data]); return topics; }; From a581733872d07db273854247a5899918f17574ad Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 3 Mar 2024 21:42:37 +0300 Subject: [PATCH 11/15] Test form on list page --- frontend/src/components/ACLPage/Form/Form.tsx | 2 +- .../ACLPage/List/__test__/List.spec.tsx | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ACLPage/Form/Form.tsx b/frontend/src/components/ACLPage/Form/Form.tsx index 6f928fbc1..d47eb7235 100644 --- a/frontend/src/components/ACLPage/Form/Form.tsx +++ b/frontend/src/components/ACLPage/Form/Form.tsx @@ -29,7 +29,7 @@ const ACLForm: FC = ({ isOpen: open }) => { const DetailedForm = DETAILED_FORM_COMPONENTS[aclType]; return ( - + Create ACL diff --git a/frontend/src/components/ACLPage/List/__test__/List.spec.tsx b/frontend/src/components/ACLPage/List/__test__/List.spec.tsx index 0c39681bb..e1b22e86f 100644 --- a/frontend/src/components/ACLPage/List/__test__/List.spec.tsx +++ b/frontend/src/components/ACLPage/List/__test__/List.spec.tsx @@ -4,12 +4,13 @@ import { screen } from '@testing-library/dom'; import userEvent from '@testing-library/user-event'; import { clusterACLPath } from 'lib/paths'; import ACList from 'components/ACLPage/List/List'; -import { useAcls, useDeleteAcl } from 'lib/hooks/api/acl'; +import { useAcls, useCreateCustomAcl, useDeleteAcl } from 'lib/hooks/api/acl'; import { aclPayload } from 'lib/fixtures/acls'; jest.mock('lib/hooks/api/acl', () => ({ useAcls: jest.fn(), useDeleteAcl: jest.fn(), + useCreateCustomAcl: jest.fn(), })); describe('ACLList Component', () => { @@ -33,6 +34,9 @@ describe('ACLList Component', () => { (useDeleteAcl as jest.Mock).mockImplementation(() => ({ deleteResource: jest.fn(), })); + (useCreateCustomAcl as jest.Mock).mockImplementation(() => ({ + createResource: jest.fn(), + })); }); it('renders ACLList with records', async () => { @@ -50,6 +54,28 @@ describe('ACLList Component', () => { fill: 'transparent', }); }); + + it('header has button for create ACL', () => { + renderComponent(); + const button = screen.getByText('+ Create ACL'); + expect(button).toBeInTheDocument(); + }); + + it('form not in the document', async () => { + renderComponent(); + const form = screen.queryByTestId('aclForm'); + expect(form).not.toBeInTheDocument(); + }); + + describe('after acl button click', () => { + it('form is in the document', async () => { + renderComponent(); + const button = screen.getByText('+ Create ACL'); + await userEvent.click(button); + const form = screen.queryByTestId('aclForm'); + expect(form).toBeInTheDocument(); + }); + }); }); describe('when it has no acls', () => { From 14cca668f7c82ccd3855fd7e3c86668fc4aa66b4 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Mon, 4 Mar 2024 22:35:34 +0300 Subject: [PATCH 12/15] Style fixes --- .../ACLPage/Form/CustomACL/Form.tsx | 26 +++--- .../ACLPage/Form/CustomACL/constants.ts | 13 +-- .../ACLPage/Form/ForConsumers/Form.tsx | 9 +- .../ACLPage/Form/ForKafkaStreamApps/Form.tsx | 11 ++- .../ACLPage/Form/ForProducers/Form.tsx | 17 +++- .../components/ACLPage/Form/Form.styled.ts | 2 +- frontend/src/components/ACLPage/Form/Form.tsx | 2 +- frontend/src/components/ACLPage/List/List.tsx | 4 +- .../common/Input/InputLabel.styled.ts | 3 +- .../components/common/Radio/Radio.styled.tsx | 41 +++++---- .../src/components/common/Radio/Radio.tsx | 2 +- frontend/src/components/common/Radio/types.ts | 4 +- frontend/src/theme/theme.ts | 87 ++++++++++++------- 13 files changed, 139 insertions(+), 82 deletions(-) diff --git a/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx index 23bb5b8ea..a0cf8d0ba 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx +++ b/frontend/src/components/ACLPage/Form/CustomACL/Form.tsx @@ -10,7 +10,6 @@ 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 { useTheme } from 'styled-components'; import { AclDetailedFormProps } from 'components/ACLPage/Form/types'; import formSchema from './schema'; @@ -19,15 +18,13 @@ import { toRequest } from './lib'; import { defaultValues, operations, - getPermissions, + permissions, resourceTypes, } from './constants'; const CustomACLForm: FC = ({ formRef }) => { const context = useContext(ACLFormContext); - const theme = useTheme(); - const methods = useForm({ mode: 'all', resolver: yupResolver(formSchema), @@ -53,12 +50,17 @@ const CustomACLForm: FC = ({ formRef }) => {
Principal - + Host restriction - +
@@ -70,10 +72,7 @@ const CustomACLForm: FC = ({ formRef }) => { Operations - + @@ -85,7 +84,12 @@ const CustomACLForm: FC = ({ formRef }) => { name="namePatternType" options={matchTypeOptions} /> - + diff --git a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts index 61a1510c2..675784afb 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts +++ b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts @@ -5,7 +5,6 @@ import { KafkaAclResourceType, } from 'generated-sources'; import { RadioOption } from 'components/common/Radio/types'; -import { DefaultTheme } from 'styled-components'; import { FormValues } from './types'; @@ -33,20 +32,14 @@ export const operations = toOptionsArray( KafkaAclOperationEnum.UNKNOWN ); -export const getPermissions = (theme: DefaultTheme): RadioOption[] => [ +export const permissions: RadioOption[] = [ { value: KafkaAclPermissionEnum.ALLOW, - activeState: { - background: theme.radio.allow.backgroundColor, - color: theme.radio.allow.color, - }, + itemType: 'green', }, { value: KafkaAclPermissionEnum.DENY, - activeState: { - background: theme.radio.deny.backgroundColor, - color: theme.radio.deny.color, - }, + itemType: 'red', }, ]; diff --git a/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx index 41624ed33..febd75e08 100644 --- a/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx @@ -62,12 +62,17 @@ const ForConsumersForm: FC = ({ formRef }) => {
Principal - + Host restriction - +
diff --git a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx index 8d67c7e45..7a747de4e 100644 --- a/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForKafkaStreamApps/Form.tsx @@ -42,12 +42,17 @@ const ForKafkaStreamAppsForm: FC = ({ formRef }) => {
Principal - + Host restriction - +
@@ -60,7 +65,7 @@ const ForKafkaStreamAppsForm: FC = ({ formRef }) => { Application.id - + diff --git a/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx index f87ec9307..cd84aa674 100644 --- a/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx +++ b/frontend/src/components/ACLPage/Form/ForProducers/Form.tsx @@ -60,12 +60,17 @@ const ForProducersForm: FC = ({ formRef }) => {
Principal - + Host restriction - +
@@ -83,8 +88,12 @@ const ForProducersForm: FC = ({ formRef }) => { Transaction ID } - prefixed={} + exact={ + + } + prefixed={ + + } onChange={onTransactionIdTypeChange} /> diff --git a/frontend/src/components/ACLPage/Form/Form.styled.ts b/frontend/src/components/ACLPage/Form/Form.styled.ts index ef0c75f0f..340695981 100644 --- a/frontend/src/components/ACLPage/Form/Form.styled.ts +++ b/frontend/src/components/ACLPage/Form/Form.styled.ts @@ -41,6 +41,7 @@ export const Form = styled(StyledForm)` `; export const Field = styled.div` + ${({ theme }) => theme.input.label}; display: flex; justify-content: space-between; @@ -60,7 +61,6 @@ export const ControlList = styled.div` `; export const Footer = styled.div` - border-top: 1px solid #e3e6e8; display: flex; justify-content: end; gap: 8px; diff --git a/frontend/src/components/ACLPage/Form/Form.tsx b/frontend/src/components/ACLPage/Form/Form.tsx index d47eb7235..1242335d8 100644 --- a/frontend/src/components/ACLPage/Form/Form.tsx +++ b/frontend/src/components/ACLPage/Form/Form.tsx @@ -51,7 +51,7 @@ const ACLForm: FC = ({ isOpen: open }) => {
- +
{ emptyMessage="No ACL items found" onRowHover={handleRowHover} onMouseLeave={() => setRowId('')} + enableSorting /> theme.input.label.color}; + color: ${({ theme }) => theme.input.label}; input[type='checkbox'] { + accent-color: ${({ theme }) => theme.input.icon.color}; display: inline-block; margin-right: 8px; vertical-align: text-top; diff --git a/frontend/src/components/common/Radio/Radio.styled.tsx b/frontend/src/components/common/Radio/Radio.styled.tsx index 454d28321..163b49763 100644 --- a/frontend/src/components/common/Radio/Radio.styled.tsx +++ b/frontend/src/components/common/Radio/Radio.styled.tsx @@ -1,30 +1,39 @@ import styled, { css } from 'styled-components'; -import { ActiveState } from './types'; +import { RadioItemType } from './types'; export const Item = styled.div<{ $isActive?: boolean; - $activeState?: ActiveState; + $itemType: RadioItemType; }>` - background-color: ${({ theme }) => theme.radio.default.backgroundColor}; - border: 1px solid ${({ theme }) => theme.radio.default.borderColor}; - color: ${({ theme }) => theme.radio.default.color}; - padding: 0 16px; - cursor: pointer; - height: 32px; - line-height: 32px; + ${({ theme, $itemType }) => css` + background-color: ${theme.acl.create.radioButtons[$itemType].normal + .background}; + border: 1px solid ${theme.acl.create.radioButtons[$itemType].normal.border}; + color: ${theme.acl.create.radioButtons[$itemType].normal.text}; + padding: 0 16px; + cursor: pointer; + height: 32px; + line-height: 32px; + `} - ${({ $isActive, $activeState, theme }) => { + ${({ $isActive, $itemType, theme }) => { if ($isActive) { return css` - color: ${$activeState?.color || theme.radio.default.activeColor}; - background-color: ${$activeState?.background || - theme.radio.default.activeBackgroundColor}; - border-color: ${$activeState?.background || - theme.radio.default.borderColor}; + color: ${theme.acl.create.radioButtons[$itemType].active.text}; + background-color: ${theme.acl.create.radioButtons[$itemType].active.background}; + border-color: ${theme.acl.create.radioButtons[$itemType].active.background}}; `; } - return css``; + return css` + &:hover { + background: ${theme.acl.create.radioButtons[$itemType].hover + .background}; + border: 1px solid + ${theme.acl.create.radioButtons[$itemType].hover.border}; + color: ${theme.acl.create.radioButtons[$itemType].hover.text}; + } + `; }} `; diff --git a/frontend/src/components/common/Radio/Radio.tsx b/frontend/src/components/common/Radio/Radio.tsx index 065a19e92..d23c61a09 100644 --- a/frontend/src/components/common/Radio/Radio.tsx +++ b/frontend/src/components/common/Radio/Radio.tsx @@ -22,7 +22,7 @@ export const Radio: FC = ({ options, onChange, value }) => { key={option.value} onClick={() => handleChange(option.value)} $isActive={selectedValue === option.value} - $activeState={option.activeState} + $itemType={option.itemType || 'gray'} > {option.value} diff --git a/frontend/src/components/common/Radio/types.ts b/frontend/src/components/common/Radio/types.ts index 064f9fd38..c05347ee0 100644 --- a/frontend/src/components/common/Radio/types.ts +++ b/frontend/src/components/common/Radio/types.ts @@ -1,13 +1,15 @@ import { PropsWithChildren } from 'react'; +import { ThemeType } from 'theme/theme'; export type ActiveState = { background: string; color: string; }; +export type RadioItemType = keyof ThemeType['acl']['create']['radioButtons']; export type RadioOption = { value: string; - activeState?: ActiveState; + itemType?: RadioItemType; }; export interface RadioProps extends PropsWithChildren { diff --git a/frontend/src/theme/theme.ts b/frontend/src/theme/theme.ts index c1be6842d..503dcde57 100644 --- a/frontend/src/theme/theme.ts +++ b/frontend/src/theme/theme.ts @@ -309,23 +309,6 @@ const baseTheme = { color: Colors.neutral[85], }, }, - radio: { - allow: { - backgroundColor: Colors.green[50], - color: Colors.neutral[0], - }, - deny: { - backgroundColor: Colors.red[50], - color: Colors.neutral[0], - }, - default: { - backgroundColor: Colors.neutral[0], - color: Colors.neutral[50], - borderColor: Colors.neutral[10], - activeColor: Colors.neutral[100], - activeBackgroundColor: Colors.neutral[10], - }, - }, }; export const theme = { @@ -742,31 +725,53 @@ export const theme = { normal: { background: Colors.neutral[0], text: Colors.neutral[50], + border: Colors.neutral[10], }, active: { background: Colors.green[50], text: Colors.neutral[0], + border: Colors.green[50], }, hover: { - background: Colors.green[10], - text: Colors.neutral[90], + background: Colors.green[50], + text: Colors.neutral[0], + border: Colors.green[50], }, }, gray: { normal: { background: Colors.neutral[0], text: Colors.neutral[50], + border: Colors.neutral[10], }, active: { background: Colors.neutral[10], text: Colors.neutral[90], + border: Colors.neutral[10], }, hover: { - background: Colors.neutral[5], - text: Colors.neutral[90], + background: Colors.neutral[10], + text: Colors.neutral[50], + border: Colors.neutral[10], + }, + }, + red: { + normal: { + background: Colors.neutral[0], + text: Colors.neutral[50], + border: Colors.neutral[10], + }, + active: { + background: Colors.red[50], + text: Colors.neutral[0], + border: Colors.red[50], + }, + hover: { + background: Colors.red[50], + text: Colors.neutral[0], + border: Colors.red[50], }, }, - red: {}, }, }, }, @@ -1255,33 +1260,55 @@ export const darkTheme: ThemeType = { radioButtons: { green: { normal: { - background: Colors.neutral[0], + background: Colors.neutral[90], text: Colors.neutral[50], + border: Colors.neutral[50], }, active: { background: Colors.green[50], text: Colors.neutral[0], + border: Colors.green[50], }, hover: { - background: Colors.green[10], + background: Colors.green[50], text: Colors.neutral[0], + border: Colors.green[50], }, }, gray: { normal: { - background: Colors.neutral[0], + background: Colors.neutral[90], text: Colors.neutral[50], + border: Colors.neutral[80], }, active: { - background: Colors.neutral[10], - text: Colors.neutral[90], + background: Colors.neutral[80], + text: Colors.neutral[0], + border: Colors.neutral[80], }, hover: { - background: Colors.neutral[5], - text: Colors.neutral[90], + background: Colors.neutral[85], + text: Colors.neutral[0], + border: Colors.neutral[85], + }, + }, + red: { + normal: { + background: Colors.neutral[90], + text: Colors.neutral[50], + border: Colors.neutral[50], + }, + active: { + background: Colors.red[50], + text: Colors.neutral[0], + border: Colors.red[50], + }, + hover: { + background: Colors.red[50], + text: Colors.neutral[0], + border: Colors.red[50], }, }, - red: {}, }, }, }, From d6cd1b00c3d3dc3d2577b3cbbc02891b2f59f33d Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Wed, 6 Mar 2024 12:33:04 +0300 Subject: [PATCH 13/15] Fix input colors --- frontend/src/components/common/Input/Input.styled.ts | 2 +- frontend/src/components/common/Input/InputLabel.styled.ts | 2 +- frontend/src/theme/theme.ts | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/common/Input/Input.styled.ts b/frontend/src/components/common/Input/Input.styled.ts index f21962fe6..2ff36c230 100644 --- a/frontend/src/components/common/Input/Input.styled.ts +++ b/frontend/src/components/common/Input/Input.styled.ts @@ -96,5 +96,5 @@ export const FormError = styled.p` export const InputHint = styled.p` font-size: 0.85rem; margin-top: 0.25rem; - color: ${({ theme }) => theme.clusterConfigForm.inputHintText.secondary}; + color: ${({ theme }) => theme.checkbox.hint}; `; diff --git a/frontend/src/components/common/Input/InputLabel.styled.ts b/frontend/src/components/common/Input/InputLabel.styled.ts index 9eae1e048..19af510ee 100644 --- a/frontend/src/components/common/Input/InputLabel.styled.ts +++ b/frontend/src/components/common/Input/InputLabel.styled.ts @@ -4,7 +4,7 @@ export const InputLabel = styled.label` font-weight: 500; font-size: 12px; line-height: 20px; - color: ${({ theme }) => theme.input.label}; + color: ${({ theme }) => theme.input.label.color}; input[type='checkbox'] { accent-color: ${({ theme }) => theme.input.icon.color}; display: inline-block; diff --git a/frontend/src/theme/theme.ts b/frontend/src/theme/theme.ts index 503dcde57..ddbb835c3 100644 --- a/frontend/src/theme/theme.ts +++ b/frontend/src/theme/theme.ts @@ -120,6 +120,10 @@ const baseTheme = { backgroundColor: Colors.neutral[5], color: Colors.red[55], }, + checkbox: { + label: Colors.neutral[50], + hint: Colors.neutral[50], + }, layout: { minWidth: '1200px', navBarWidth: '201px', @@ -1262,7 +1266,7 @@ export const darkTheme: ThemeType = { normal: { background: Colors.neutral[90], text: Colors.neutral[50], - border: Colors.neutral[50], + border: Colors.neutral[80], }, active: { background: Colors.green[50], @@ -1296,7 +1300,7 @@ export const darkTheme: ThemeType = { normal: { background: Colors.neutral[90], text: Colors.neutral[50], - border: Colors.neutral[50], + border: Colors.neutral[80], }, active: { background: Colors.red[50], From 8ce0ce268a20f09af56f61fe303eff01b82c1df8 Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Wed, 6 Mar 2024 12:50:52 +0300 Subject: [PATCH 14/15] Remove redundan styled component --- frontend/src/components/ACLPage/List/__test__/List.spec.tsx | 4 ++-- .../src/components/common/Select/ControlledSelect.styled.ts | 3 --- frontend/src/components/common/Select/ControlledSelect.tsx | 5 ++--- 3 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 frontend/src/components/common/Select/ControlledSelect.styled.ts diff --git a/frontend/src/components/ACLPage/List/__test__/List.spec.tsx b/frontend/src/components/ACLPage/List/__test__/List.spec.tsx index e1b22e86f..88a5a9575 100644 --- a/frontend/src/components/ACLPage/List/__test__/List.spec.tsx +++ b/frontend/src/components/ACLPage/List/__test__/List.spec.tsx @@ -57,7 +57,7 @@ describe('ACLList Component', () => { it('header has button for create ACL', () => { renderComponent(); - const button = screen.getByText('+ Create ACL'); + const button = screen.getByText('Create ACL'); expect(button).toBeInTheDocument(); }); @@ -70,7 +70,7 @@ describe('ACLList Component', () => { describe('after acl button click', () => { it('form is in the document', async () => { renderComponent(); - const button = screen.getByText('+ Create ACL'); + const button = screen.getByText('Create ACL'); await userEvent.click(button); const form = screen.queryByTestId('aclForm'); expect(form).toBeInTheDocument(); diff --git a/frontend/src/components/common/Select/ControlledSelect.styled.ts b/frontend/src/components/common/Select/ControlledSelect.styled.ts deleted file mode 100644 index dfb989090..000000000 --- a/frontend/src/components/common/Select/ControlledSelect.styled.ts +++ /dev/null @@ -1,3 +0,0 @@ -import styled from 'styled-components'; - -export const StyledControlledSelect = styled.div``; diff --git a/frontend/src/components/common/Select/ControlledSelect.tsx b/frontend/src/components/common/Select/ControlledSelect.tsx index 2c5dea187..0621b49d0 100644 --- a/frontend/src/components/common/Select/ControlledSelect.tsx +++ b/frontend/src/components/common/Select/ControlledSelect.tsx @@ -5,7 +5,6 @@ import { InputLabel } from 'components/common/Input/InputLabel.styled'; import { ErrorMessage } from '@hookform/error-message'; import Select, { SelectOption } from './Select'; -import { StyledControlledSelect } from './ControlledSelect.styled'; interface ControlledSelectProps { name: string; @@ -28,7 +27,7 @@ const ControlledSelect: React.FC = ({ const id = React.useId(); return ( - +
{label && {label}} = ({ - +
); }; From 8a217ea85fc5662c781eee2d96b4fd6a35fabc0f Mon Sep 17 00:00:00 2001 From: Pavel Makarichev Date: Sun, 17 Mar 2024 14:04:28 +0300 Subject: [PATCH 15/15] Use generic SelectOptions --- .../ACLPage/Form/CustomACL/constants.ts | 19 +++++++++---------- .../src/components/ACLPage/Form/constants.ts | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts index 675784afb..ec443da13 100644 --- a/frontend/src/components/ACLPage/Form/CustomACL/constants.ts +++ b/frontend/src/components/ACLPage/Form/CustomACL/constants.ts @@ -8,27 +8,26 @@ import { RadioOption } from 'components/common/Radio/types'; import { FormValues } from './types'; -function toOptionsArray( - enumerable: T, - unknown: O -): Array { - return Object.values(enumerable).reduce((acc, cur) => { +function toOptionsArray( + list: T[], + unknown: T +): SelectOption[] { + return list.reduce[]>((acc, cur) => { if (cur !== unknown) { - const option: SelectOption = { label: cur, value: cur }; - acc.push(option); + acc.push({ label: cur, value: cur }); } return acc; }, []); } -export const resourceTypes: Array = toOptionsArray( - KafkaAclResourceType, +export const resourceTypes = toOptionsArray( + Object.values(KafkaAclResourceType), KafkaAclResourceType.UNKNOWN ); export const operations = toOptionsArray( - KafkaAclOperationEnum, + Object.values(KafkaAclOperationEnum), KafkaAclOperationEnum.UNKNOWN ); diff --git a/frontend/src/components/ACLPage/Form/constants.ts b/frontend/src/components/ACLPage/Form/constants.ts index 5acbd9206..6a6d024ed 100644 --- a/frontend/src/components/ACLPage/Form/constants.ts +++ b/frontend/src/components/ACLPage/Form/constants.ts @@ -10,7 +10,7 @@ export const matchTypeOptions: RadioOption[] = [ { value: MatchType.PREFIXED }, ]; -export const ACLTypeOptions: SelectOption[] = [ +export const ACLTypeOptions: SelectOption[] = [ { label: 'Custom ACL', value: ACLType.CUSTOM_ACL }, { label: 'For Consumers', value: ACLType.FOR_CONSUMERS }, { label: 'For Producers', value: ACLType.FOR_PRODUCERS },