diff --git a/api/src/main/java/io/kafbat/ui/controller/AclsController.java b/api/src/main/java/io/kafbat/ui/controller/AclsController.java index ae30f8bd0..1722a97e7 100644 --- a/api/src/main/java/io/kafbat/ui/controller/AclsController.java +++ b/api/src/main/java/io/kafbat/ui/controller/AclsController.java @@ -68,6 +68,7 @@ public Mono>> listAcls(String clusterName, KafkaAclResourceTypeDTO resourceTypeDto, String resourceName, KafkaAclNamePatternTypeDTO namePatternTypeDto, + String search, ServerWebExchange exchange) { AccessContext context = AccessContext.builder() .cluster(clusterName) @@ -88,7 +89,7 @@ public Mono>> listAcls(String clusterName, return validateAccess(context).then( Mono.just( ResponseEntity.ok( - aclsService.listAcls(getCluster(clusterName), filter) + aclsService.listAcls(getCluster(clusterName), filter, search) .map(ClusterMapper::toKafkaAclDto))) ).doOnEach(sig -> audit(context, sig)); } diff --git a/api/src/main/java/io/kafbat/ui/service/acl/AclsService.java b/api/src/main/java/io/kafbat/ui/service/acl/AclsService.java index 30078d435..c0ee2469a 100644 --- a/api/src/main/java/io/kafbat/ui/service/acl/AclsService.java +++ b/api/src/main/java/io/kafbat/ui/service/acl/AclsService.java @@ -68,10 +68,11 @@ public Mono deleteAcl(KafkaCluster cluster, AclBinding aclBinding) { .doOnSuccess(v -> log.info("ACL DELETED: [{}]", aclString)); } - public Flux listAcls(KafkaCluster cluster, ResourcePatternFilter filter) { + public Flux listAcls(KafkaCluster cluster, ResourcePatternFilter filter, String principalSearch) { return adminClientService.get(cluster) .flatMap(c -> c.listAcls(filter)) .flatMapIterable(acls -> acls) + .filter(acl -> principalSearch == null || acl.entry().principal().contains(principalSearch)) .sort(Comparator.comparing(AclBinding::toString)); //sorting to keep stable order on different calls } diff --git a/contract/src/main/resources/swagger/kafbat-ui-api.yaml b/contract/src/main/resources/swagger/kafbat-ui-api.yaml index 3c9aa703c..120d5638b 100644 --- a/contract/src/main/resources/swagger/kafbat-ui-api.yaml +++ b/contract/src/main/resources/swagger/kafbat-ui-api.yaml @@ -1965,6 +1965,11 @@ paths: required: false schema: $ref: '#/components/schemas/KafkaAclNamePatternType' + - name: search + in: query + required: false + schema: + type: string responses: 200: description: OK diff --git a/frontend/src/components/ACLPage/List/List.tsx b/frontend/src/components/ACLPage/List/List.tsx index 26155172b..41ac2ca64 100644 --- a/frontend/src/components/ACLPage/List/List.tsx +++ b/frontend/src/components/ACLPage/List/List.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; import { ColumnDef, Row } from '@tanstack/react-table'; import PageHeading from 'components/common/PageHeading/PageHeading'; import Table from 'components/common/NewTable'; @@ -20,12 +21,16 @@ import { useTheme } from 'styled-components'; import ACLFormContext from 'components/ACLPage/Form/AclFormContext'; import PlusIcon from 'components/common/Icons/PlusIcon'; import ActionButton from 'components/common/ActionComponent/ActionButton/ActionButton'; +import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled'; +import Search from 'components/common/Search/Search'; import * as S from './List.styled'; const ACList: React.FC = () => { const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); - const { data: aclList } = useAcls(clusterName); + const [searchParams, setSearchParams] = useSearchParams(); + const [search, setSearch] = useState(searchParams.get('q') || ''); + const { data: aclList } = useAcls({ clusterName, search }); const { deleteResource } = useDeleteAcl(clusterName); const modal = useConfirm(true); const theme = useTheme(); @@ -36,6 +41,17 @@ const ACList: React.FC = () => { } = useBoolean(); const [rowId, setRowId] = React.useState(''); + useEffect(() => { + const params = new URLSearchParams(searchParams); + if (search) { + params.set('q', search); + params.set('page', '1'); // reset to first page on new search + } else { + params.delete('q'); + } + setSearchParams(params, { replace: true }); + }, [search]); + const handleDeleteClick = (acl: KafkaAcl | null) => { if (acl) { modal('Are you sure want to delete this ACL record?', () => @@ -162,6 +178,13 @@ const ACList: React.FC = () => { Create ACL + + + api.listAcls({ clusterName }), + ['clusters', clusterName, 'acls', { search }], + () => + api.listAcls({ + clusterName, + search, + }), { + keepPreviousData: true, suspense: false, } );