Skip to content

Commit 33a2f67

Browse files
committed
Add all frontend changes
1 parent e13808a commit 33a2f67

File tree

23 files changed

+285
-87
lines changed

23 files changed

+285
-87
lines changed

frontend/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
#
1+
VITE_DEV_PROXY= http://localhost:8080 # your API server

frontend/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66

77
<!-- Favicons -->
8+
<script src="<%= PUBLIC_PATH %>/env.js"></script>
89
<link rel="icon" href="<%= PUBLIC_PATH %>/favicon/favicon.svg" type="image/svg+xml" />
910
<link rel="apple-touch-icon" href="<%= PUBLIC_PATH %>/favicon/apple-touch-icon.png" />
1011
<link rel="manifest" href="<%= PUBLIC_PATH %>/manifest.json" />

frontend/public/env.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
window._env_ = {
2+
REACT_APP_SUPPORT_URL: "https://chatgpt.com"
3+
};

frontend/src/components/ACLPage/Form/ForConsumers/Form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const ForConsumersForm: FC<AclDetailedFormProps> = ({ formRef }) => {
5757
const data = { ...formData }; // Create a copy of formData
5858

5959
const selectedTopics = data.topics || [];
60-
const { topicsPrefix: topicsPrefix} = data;
60+
const { topicsPrefix } = data;
6161
const allTopicsSelected = selectedTopics.length === topics.length && topics.length !== 0;
6262

6363
const { consumerGroups: selectedConsumerGroup } = data;

frontend/src/components/ACLPage/Form/ForProducers/Form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { FC, useContext, useState, useEffect } from 'react';
1+
import React, { FC, useContext, useState } from 'react';
22
import { yupResolver } from '@hookform/resolvers/yup';
33
import { useCreateProducerAcl } from 'lib/hooks/api/acl';
44
import { FormProvider, useForm } from 'react-hook-form';
@@ -63,7 +63,7 @@ const ForProducersForm: FC<AclDetailedFormProps> = ({ formRef }) => {
6363
const data = { ...formData }; // Create a copy of formData
6464

6565
const selectedTopics = data.topics || [];
66-
const topicsPrefix = data.topicsPrefix;
66+
const { topicsPrefix } = data;
6767
const allTopicsSelected = selectedTopics.length === topics.length && selectedTopics.length !== 0;
6868

6969
if (selectedTopics.length === 0 && !addAllTopicsChecked && !topicsPrefix) {

frontend/src/components/ACLPage/Form/Form.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import Heading from 'components/common/heading/Heading.styled';
44
import CloseIcon from 'components/common/Icons/CloseIcon';
55
import { Button } from 'components/common/Button/Button';
66

7+
import PageLoader from 'components/common/PageLoader/PageLoader';
78
import { ACLFormProps, ACLType, AclDetailedFormProps } from './types';
89
import { ACLTypeOptions } from './constants';
910
import * as S from './Form.styled';
11+
1012
import ACLFormContext from './AclFormContext';
11-
import PageLoader from 'components/common/PageLoader/PageLoader';
1213

1314
const DETAILED_FORM_COMPONENTS: Record<
1415
keyof typeof ACLType,

frontend/src/components/ACLPage/List/List.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import React from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { ColumnDef, Row } from '@tanstack/react-table';
33
import PageHeading from 'components/common/PageHeading/PageHeading';
44
import Table from 'components/common/NewTable';
55
import { useConfirm } from 'lib/hooks/useConfirm';
66
import useAppParams from 'lib/hooks/useAppParams';
77
import { useAcls, useDeleteAcl } from 'lib/hooks/api/acl';
88
import { ClusterName } from 'lib/interfaces/cluster';
9+
import { useSearchParams } from 'react-router-dom';
910
import {
1011
Action,
1112
KafkaAcl,
@@ -14,18 +15,30 @@ import {
1415
ResourceType,
1516
} from 'generated-sources';
1617
import useBoolean from 'lib/hooks/useBoolean';
18+
import { PER_PAGE } from 'lib/constants';
1719
import ACLForm from 'components/ACLPage/Form/Form';
1820
import DeleteIcon from 'components/common/Icons/DeleteIcon';
1921
import { useTheme } from 'styled-components';
2022
import ACLFormContext from 'components/ACLPage/Form/AclFormContext';
2123
import PlusIcon from 'components/common/Icons/PlusIcon';
2224
import ActionButton from 'components/common/ActionComponent/ActionButton/ActionButton';
25+
import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled';
26+
import Search from 'components/common/Search/Search';
2327

2428
import * as S from './List.styled';
2529

2630
const ACList: React.FC = () => {
2731
const { clusterName } = useAppParams<{ clusterName: ClusterName }>();
28-
const { data: aclList } = useAcls(clusterName);
32+
const [searchParams, setSearchParams] = useSearchParams();
33+
34+
// Set default values for pagination
35+
const page = 1; // or fetch from state/context
36+
const perPage = 1; // or a constant from your constants file
37+
const [search, setSearch] = useState(searchParams.get('q') || '');
38+
39+
40+
const { data: aclList } = useAcls({ clusterName, search });
41+
2942
const { deleteResource } = useDeleteAcl(clusterName);
3043
const modal = useConfirm(true);
3144
const theme = useTheme();
@@ -36,6 +49,12 @@ const ACList: React.FC = () => {
3649
} = useBoolean();
3750
const [rowId, setRowId] = React.useState('');
3851

52+
53+
// Set the search params to the url based on the localStorage value
54+
useEffect(() => {
55+
setSearch(searchParams.get('q') || '');
56+
}, [searchParams]);
57+
3958
const handleDeleteClick = (acl: KafkaAcl | null) => {
4059
if (acl) {
4160
modal('Are you sure want to delete this ACL record?', () =>
@@ -162,6 +181,9 @@ const ACList: React.FC = () => {
162181
<PlusIcon /> Create ACL
163182
</ActionButton>
164183
</PageHeading>
184+
<ControlPanelWrapper hasInput>
185+
<Search placeholder="Search by Principle Name" />
186+
</ControlPanelWrapper>
165187
<Table
166188
columns={columns}
167189
data={aclList ?? []}

frontend/src/components/Brokers/Broker/Configs/TableComponents/InputCell/styled.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const Value = styled.span`
1616
text-overflow: ellipsis;
1717
max-width: 400px;
1818
overflow: hidden;
19-
white-space: nowrap;
19+
white-space: pre;
2020
`;
2121

2222
export const ButtonsWrapper = styled.div`
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React, { useMemo } from 'react';
2+
import { Row } from '@tanstack/react-table';
3+
import { Action, ConsumerGroupState, ConsumerGroup, ResourceType } from 'generated-sources';
4+
import useAppParams from 'lib/hooks/useAppParams';
5+
import { ClusterName } from 'lib/interfaces/cluster';
6+
import { useConfirm } from 'lib/hooks/useConfirm';
7+
import { ActionCanButton } from 'components/common/ActionComponent';
8+
import { isPermitted } from 'lib/permissions';
9+
import { useUserInfo } from 'lib/hooks/useUserInfo';
10+
import { showServerError, showAlert } from 'lib/errorHandling';
11+
import { useDeleteMultipleConsumerGroupsMutation } from 'lib/hooks/api/consumers';
12+
13+
interface BatchActionsbarProps {
14+
rows: Row<ConsumerGroup>[]; // Ensure that each row's original contains `groupId` and `state`
15+
resetRowSelection(): void;
16+
}
17+
18+
const BatchActionsbar: React.FC<BatchActionsbarProps> = ({
19+
rows,
20+
resetRowSelection,
21+
}) => {
22+
const { clusterName } = useAppParams<{ clusterName: ClusterName }>();
23+
const confirm = useConfirm();
24+
const { roles, rbacFlag } = useUserInfo();
25+
26+
// Map to include both groupId and state
27+
const selectedConsumerGroups = rows.map(({ original }) => ({
28+
groupId: original.groupId,
29+
state: original.state as ConsumerGroupState,
30+
}));
31+
32+
const deleteMultipleConsumerGroupsMutation = useDeleteMultipleConsumerGroupsMutation();
33+
34+
const deleteConsumerGroupsHandler = () => {
35+
const nonEmptyGroups = selectedConsumerGroups.filter(
36+
(group: ConsumerGroup) => group.state !== ConsumerGroupState.EMPTY
37+
);
38+
39+
if (nonEmptyGroups.length > 0) {
40+
showAlert('error', {
41+
title: 'Deletion Failed',
42+
message: 'All selected consumer groups must be in EMPTY state to be deleted.',
43+
});
44+
return;
45+
}
46+
47+
confirm(
48+
'Are you sure you want to delete the selected consumer groups?',
49+
async () => {
50+
try {
51+
await deleteMultipleConsumerGroupsMutation.mutateAsync({
52+
clusterName,
53+
consumerGroups: selectedConsumerGroups.map(group => group.groupId),
54+
});
55+
resetRowSelection();
56+
} catch (e) {
57+
// do nothing
58+
}
59+
}
60+
);
61+
};
62+
63+
const canDeleteSelectedConsumerGroups = useMemo(() => {
64+
return selectedConsumerGroups.every((value) =>
65+
isPermitted({
66+
roles,
67+
resource: ResourceType.CONSUMER,
68+
action: Action.DELETE,
69+
value: value.groupId,
70+
clusterName,
71+
rbacFlag,
72+
})
73+
);
74+
}, [selectedConsumerGroups, clusterName, roles]);
75+
76+
return (
77+
<>
78+
<ActionCanButton
79+
buttonSize="M"
80+
buttonType="secondary"
81+
onClick={deleteConsumerGroupsHandler}
82+
disabled={!selectedConsumerGroups.length}
83+
canDoAction={canDeleteSelectedConsumerGroups}
84+
>
85+
Delete selected consumer groups
86+
</ActionCanButton>
87+
</>
88+
);
89+
};
90+
91+
export default BatchActionsbar;

frontend/src/components/ConsumerGroups/List.tsx

Lines changed: 53 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
1616
import { CONSUMER_GROUP_STATE_TOOLTIPS, PER_PAGE } from 'lib/constants';
1717
import { useConsumerGroups } from 'lib/hooks/api/consumers';
1818
import Tooltip from 'components/common/Tooltip/Tooltip';
19+
import BatchActionsBar from './BatchActionsBar'; // Import the new batch actions bar
1920

2021
const List = () => {
2122
const { clusterName } = useAppParams<ClusterNameRoute>();
@@ -33,62 +34,55 @@ const List = () => {
3334
search: searchParams.get('q') || '',
3435
});
3536

36-
const columns = React.useMemo<ColumnDef<ConsumerGroup>[]>(
37-
() => [
38-
{
39-
id: ConsumerGroupOrdering.NAME,
40-
header: 'Group ID',
41-
accessorKey: 'groupId',
42-
// eslint-disable-next-line react/no-unstable-nested-components
43-
cell: ({ getValue }) => (
44-
<LinkCell
45-
value={`${getValue<string | number>()}`}
46-
to={encodeURIComponent(`${getValue<string | number>()}`)}
37+
const columns = React.useMemo<ColumnDef<ConsumerGroup>[]>(() => [
38+
{
39+
id: ConsumerGroupOrdering.NAME,
40+
header: 'Group ID',
41+
accessorKey: 'groupId',
42+
cell: ({ getValue }) => (
43+
<LinkCell
44+
value={`${getValue<string | number>()}`}
45+
to={encodeURIComponent(`${getValue<string | number>()}`)}
46+
/>
47+
),
48+
},
49+
{
50+
id: ConsumerGroupOrdering.MEMBERS,
51+
header: 'Num Of Members',
52+
accessorKey: 'members',
53+
},
54+
{
55+
id: ConsumerGroupOrdering.TOPIC_NUM,
56+
header: 'Num Of Topics',
57+
accessorKey: 'topics',
58+
},
59+
{
60+
id: ConsumerGroupOrdering.MESSAGES_BEHIND,
61+
header: 'Consumer Lag',
62+
accessorKey: 'consumerLag',
63+
cell: (args) => args.getValue() || 'N/A',
64+
},
65+
{
66+
header: 'Coordinator',
67+
accessorKey: 'coordinator.id',
68+
enableSorting: false,
69+
},
70+
{
71+
id: ConsumerGroupOrdering.STATE,
72+
header: 'State',
73+
accessorKey: 'state',
74+
cell: (args) => {
75+
const value = args.getValue() as ConsumerGroupState;
76+
return (
77+
<Tooltip
78+
value={<TagCell {...args} />}
79+
content={CONSUMER_GROUP_STATE_TOOLTIPS[value]}
80+
placement="bottom-end"
4781
/>
48-
),
82+
);
4983
},
50-
{
51-
id: ConsumerGroupOrdering.MEMBERS,
52-
header: 'Num Of Members',
53-
accessorKey: 'members',
54-
},
55-
{
56-
id: ConsumerGroupOrdering.TOPIC_NUM,
57-
header: 'Num Of Topics',
58-
accessorKey: 'topics',
59-
},
60-
{
61-
id: ConsumerGroupOrdering.MESSAGES_BEHIND,
62-
header: 'Consumer Lag',
63-
accessorKey: 'consumerLag',
64-
cell: (args) => {
65-
return args.getValue() || 'N/A';
66-
},
67-
},
68-
{
69-
header: 'Coordinator',
70-
accessorKey: 'coordinator.id',
71-
enableSorting: false,
72-
},
73-
{
74-
id: ConsumerGroupOrdering.STATE,
75-
header: 'State',
76-
accessorKey: 'state',
77-
// eslint-disable-next-line react/no-unstable-nested-components
78-
cell: (args) => {
79-
const value = args.getValue() as ConsumerGroupState;
80-
return (
81-
<Tooltip
82-
value={<TagCell {...args} />}
83-
content={CONSUMER_GROUP_STATE_TOOLTIPS[value]}
84-
placement="bottom-end"
85-
/>
86-
);
87-
},
88-
},
89-
],
90-
[]
91-
);
84+
},
85+
], []);
9286

9387
return (
9488
<>
@@ -100,22 +94,18 @@ const List = () => {
10094
columns={columns}
10195
pageCount={consumerGroups.data?.pageCount || 0}
10296
data={consumerGroups.data?.consumerGroups || []}
103-
emptyMessage={
104-
consumerGroups.isSuccess
105-
? 'No active consumer groups found'
106-
: 'Loading...'
107-
}
97+
emptyMessage={consumerGroups.isSuccess ? 'No active consumer groups found' : 'Loading...'}
10898
serverSideProcessing
10999
enableSorting
100+
enableRowSelection
101+
batchActionsBar={BatchActionsBar}
110102
onRowClick={({ original }) =>
111-
navigate(
112-
clusterConsumerGroupDetailsPath(clusterName, original.groupId)
113-
)
103+
navigate(clusterConsumerGroupDetailsPath(clusterName, original.groupId))
114104
}
115105
disabled={consumerGroups.isFetching}
116106
/>
117107
</>
118108
);
119109
};
120110

121-
export default List;
111+
export default List;

0 commit comments

Comments
 (0)