{flexRender(
@@ -245,6 +271,8 @@ const Table: React.FC
> = ({
> = ({
{row
.getVisibleCells()
.map(({ id, getContext, column: { columnDef } }) => (
-
+ |
{flexRender(columnDef.cell, getContext())}
|
))}
diff --git a/kafka-ui-react-app/src/components/contexts/ConfirmContext.tsx b/kafka-ui-react-app/src/components/contexts/ConfirmContext.tsx
index f0958972ee..d68eda2547 100644
--- a/kafka-ui-react-app/src/components/contexts/ConfirmContext.tsx
+++ b/kafka-ui-react-app/src/components/contexts/ConfirmContext.tsx
@@ -6,6 +6,8 @@ interface ConfirmContextType {
setContent: React.Dispatch>;
setConfirm: React.Dispatch void) | undefined>>;
cancel: () => void;
+ dangerButton: boolean;
+ setDangerButton: React.Dispatch>;
}
export const ConfirmContext = React.createContext(
@@ -17,6 +19,7 @@ export const ConfirmContextProvider: React.FC<
> = ({ children }) => {
const [content, setContent] = useState(null);
const [confirm, setConfirm] = useState<(() => void) | undefined>(undefined);
+ const [dangerButton, setDangerButton] = useState(false);
const cancel = () => {
setContent(null);
@@ -31,6 +34,8 @@ export const ConfirmContextProvider: React.FC<
confirm,
setConfirm,
cancel,
+ dangerButton,
+ setDangerButton,
}}
>
{children}
diff --git a/kafka-ui-react-app/src/lib/api.ts b/kafka-ui-react-app/src/lib/api.ts
index deef9a7c65..19423d2ac3 100644
--- a/kafka-ui-react-app/src/lib/api.ts
+++ b/kafka-ui-react-app/src/lib/api.ts
@@ -10,6 +10,7 @@ import {
ConsumerGroupsApi,
AuthorizationApi,
ApplicationConfigApi,
+ AclsApi,
} from 'generated-sources';
import { BASE_PARAMS } from 'lib/constants';
@@ -25,3 +26,4 @@ export const kafkaConnectApiClient = new KafkaConnectApi(apiClientConf);
export const consumerGroupsApiClient = new ConsumerGroupsApi(apiClientConf);
export const authApiClient = new AuthorizationApi(apiClientConf);
export const appConfigApiClient = new ApplicationConfigApi(apiClientConf);
+export const aclApiClient = new AclsApi(apiClientConf);
diff --git a/kafka-ui-react-app/src/lib/fixtures/acls.ts b/kafka-ui-react-app/src/lib/fixtures/acls.ts
new file mode 100644
index 0000000000..3eecf7cea8
--- /dev/null
+++ b/kafka-ui-react-app/src/lib/fixtures/acls.ts
@@ -0,0 +1,37 @@
+import {
+ KafkaAcl,
+ KafkaAclResourceType,
+ KafkaAclNamePatternType,
+ KafkaAclPermissionEnum,
+ KafkaAclOperationEnum,
+} from 'generated-sources';
+
+export const aclPayload: KafkaAcl[] = [
+ {
+ principal: 'User 1',
+ resourceName: 'Topic',
+ resourceType: KafkaAclResourceType.TOPIC,
+ host: '_host1',
+ namePatternType: KafkaAclNamePatternType.LITERAL,
+ permission: KafkaAclPermissionEnum.ALLOW,
+ operation: KafkaAclOperationEnum.READ,
+ },
+ {
+ principal: 'User 2',
+ resourceName: 'Topic',
+ resourceType: KafkaAclResourceType.TOPIC,
+ host: '_host1',
+ namePatternType: KafkaAclNamePatternType.PREFIXED,
+ permission: KafkaAclPermissionEnum.ALLOW,
+ operation: KafkaAclOperationEnum.READ,
+ },
+ {
+ principal: 'User 3',
+ resourceName: 'Topic',
+ resourceType: KafkaAclResourceType.TOPIC,
+ host: '_host1',
+ namePatternType: KafkaAclNamePatternType.LITERAL,
+ permission: KafkaAclPermissionEnum.DENY,
+ operation: KafkaAclOperationEnum.READ,
+ },
+];
diff --git a/kafka-ui-react-app/src/lib/hooks/api/acl.ts b/kafka-ui-react-app/src/lib/hooks/api/acl.ts
new file mode 100644
index 0000000000..da6a463fff
--- /dev/null
+++ b/kafka-ui-react-app/src/lib/hooks/api/acl.ts
@@ -0,0 +1,67 @@
+import { aclApiClient as api } from 'lib/api';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { ClusterName } from 'redux/interfaces';
+import { showSuccessAlert } from 'lib/errorHandling';
+import { KafkaAcl } from 'generated-sources';
+
+export function useAcls(clusterName: ClusterName) {
+ return useQuery(
+ ['clusters', clusterName, 'acls'],
+ () => api.listAcls({ clusterName }),
+ {
+ suspense: false,
+ }
+ );
+}
+
+export function useCreateAclMutation(clusterName: ClusterName) {
+ return useMutation(
+ (data: KafkaAcl) =>
+ api.createAcl({
+ clusterName,
+ kafkaAcl: data,
+ }),
+ {
+ onSuccess() {
+ showSuccessAlert({
+ message: 'Your ACL was created successfully',
+ });
+ },
+ }
+ );
+}
+
+export function useCreateAcl(clusterName: ClusterName) {
+ const mutate = useCreateAclMutation(clusterName);
+
+ return {
+ createResource: async (param: KafkaAcl) => {
+ return mutate.mutateAsync(param);
+ },
+ ...mutate,
+ };
+}
+
+export function useDeleteAclMutation(clusterName: ClusterName) {
+ const queryClient = useQueryClient();
+ return useMutation(
+ (acl: KafkaAcl) => api.deleteAcl({ clusterName, kafkaAcl: acl }),
+ {
+ onSuccess: () => {
+ showSuccessAlert({ message: 'ACL deleted' });
+ queryClient.invalidateQueries(['clusters', clusterName, 'acls']);
+ },
+ }
+ );
+}
+
+export function useDeleteAcl(clusterName: ClusterName) {
+ const mutate = useDeleteAclMutation(clusterName);
+
+ return {
+ deleteResource: async (param: KafkaAcl) => {
+ return mutate.mutateAsync(param);
+ },
+ ...mutate,
+ };
+}
diff --git a/kafka-ui-react-app/src/lib/hooks/useConfirm.ts b/kafka-ui-react-app/src/lib/hooks/useConfirm.ts
index 1387f7666b..baac856c59 100644
--- a/kafka-ui-react-app/src/lib/hooks/useConfirm.ts
+++ b/kafka-ui-react-app/src/lib/hooks/useConfirm.ts
@@ -1,12 +1,13 @@
import { ConfirmContext } from 'components/contexts/ConfirmContext';
import React, { useContext } from 'react';
-export const useConfirm = () => {
+export const useConfirm = (danger = false) => {
const context = useContext(ConfirmContext);
return (
message: React.ReactNode,
callback: () => void | Promise
) => {
+ context?.setDangerButton(danger);
context?.setContent(message);
context?.setConfirm(() => async () => {
await callback();
diff --git a/kafka-ui-react-app/src/lib/paths.ts b/kafka-ui-react-app/src/lib/paths.ts
index 6571f1684c..9cee7ca285 100644
--- a/kafka-ui-react-app/src/lib/paths.ts
+++ b/kafka-ui-react-app/src/lib/paths.ts
@@ -285,3 +285,10 @@ export const clusterConfigPath = (
const clusterNewConfigRelativePath = 'create-new-cluster';
export const clusterNewConfigPath = `/ui/clusters/${clusterNewConfigRelativePath}`;
+
+// ACL
+export const clusterAclRelativePath = 'acl';
+export const clusterAclNewRelativePath = 'create-new-acl';
+export const clusterACLPath = (
+ clusterName: ClusterName = RouteParams.clusterName
+) => `${clusterPath(clusterName)}/${clusterAclRelativePath}`;
diff --git a/kafka-ui-react-app/src/theme/theme.ts b/kafka-ui-react-app/src/theme/theme.ts
index e7ee0047e6..f42eb8c6d5 100644
--- a/kafka-ui-react-app/src/theme/theme.ts
+++ b/kafka-ui-react-app/src/theme/theme.ts
@@ -31,6 +31,7 @@ const Colors = {
'15': '#C2F0D1',
'30': '#85E0A3',
'40': '#5CD685',
+ '50': '#33CC66',
'60': '#29A352',
},
brand: {
@@ -231,6 +232,7 @@ const baseTheme = {
white: Colors.neutral[10],
red: Colors.red[10],
blue: Colors.blue[10],
+ secondary: Colors.neutral[15],
},
color: Colors.neutral[90],
},
@@ -416,8 +418,8 @@ export const theme = {
disabled: Colors.red[20],
},
color: {
- normal: Colors.neutral[90],
- disabled: Colors.neutral[30],
+ normal: Colors.neutral[0],
+ disabled: Colors.neutral[0],
},
invertedColors: {
normal: Colors.brand[50],
@@ -695,6 +697,44 @@ export const theme = {
textColor: Colors.brand[50],
deleteIconColor: Colors.brand[50],
},
+ acl: {
+ table: {
+ deleteIcon: Colors.neutral[50],
+ },
+ create: {
+ radioButtons: {
+ green: {
+ normal: {
+ background: Colors.neutral[0],
+ text: Colors.neutral[50],
+ },
+ active: {
+ background: Colors.green[50],
+ text: Colors.neutral[0],
+ },
+ hover: {
+ background: Colors.green[10],
+ text: Colors.neutral[90],
+ },
+ },
+ gray: {
+ normal: {
+ background: Colors.neutral[0],
+ text: Colors.neutral[50],
+ },
+ active: {
+ background: Colors.neutral[10],
+ text: Colors.neutral[90],
+ },
+ hover: {
+ background: Colors.neutral[5],
+ text: Colors.neutral[90],
+ },
+ },
+ red: {},
+ },
+ },
+ },
};
export type ThemeType = typeof theme;
@@ -818,8 +858,8 @@ export const darkTheme: ThemeType = {
disabled: Colors.red[20],
},
color: {
- normal: Colors.neutral[90],
- disabled: Colors.neutral[30],
+ normal: Colors.neutral[0],
+ disabled: Colors.neutral[0],
},
invertedColors: {
normal: Colors.brand[50],
@@ -1155,4 +1195,42 @@ export const darkTheme: ThemeType = {
color: Colors.neutral[0],
},
},
+ acl: {
+ table: {
+ deleteIcon: Colors.neutral[50],
+ },
+ create: {
+ radioButtons: {
+ green: {
+ normal: {
+ background: Colors.neutral[0],
+ text: Colors.neutral[50],
+ },
+ active: {
+ background: Colors.green[50],
+ text: Colors.neutral[0],
+ },
+ hover: {
+ background: Colors.green[10],
+ text: Colors.neutral[0],
+ },
+ },
+ gray: {
+ normal: {
+ background: Colors.neutral[0],
+ text: Colors.neutral[50],
+ },
+ active: {
+ background: Colors.neutral[10],
+ text: Colors.neutral[90],
+ },
+ hover: {
+ background: Colors.neutral[5],
+ text: Colors.neutral[90],
+ },
+ },
+ red: {},
+ },
+ },
+ },
};