This is an automated email from the ASF dual-hosted git repository.
arafat2198 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 159fb646672 HDDS-12710. UI changes for fixing counts and container
pagination. (#8862).
159fb646672 is described below
commit 159fb646672b626548dcd64193c4fab435252d1d
Author: Arafat2198 <[email protected]>
AuthorDate: Sun Aug 17 17:14:11 2025 +0530
HDDS-12710. UI changes for fixing counts and container pagination. (#8862).
---
.../views/missingContainers/missingContainers.tsx | 333 +++++++++++++--------
1 file changed, 205 insertions(+), 128 deletions(-)
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx
index db30871a5a4..9156d68326e 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/missingContainers/missingContainers.tsx
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,6 +22,7 @@ import filesize from 'filesize';
import {Table, Tabs, Tooltip} from 'antd';
import {TablePaginationConfig} from 'antd/es/table';
import {InfoCircleOutlined} from '@ant-design/icons';
+import { Link } from 'react-router-dom';
import {ColumnSearch} from '@/utils/columnSearch';
import {showDataFetchError, timeFormat} from '@/utils/common';
@@ -67,6 +68,8 @@ interface IUnhealthyContainersResponse {
overReplicatedCount: number;
misReplicatedCount: number;
containers: IContainerResponse[];
+ lastKey: string;
+ firstKey: string;
}
interface IKeyResponse {
@@ -149,7 +152,7 @@ const CONTAINER_TAB_COLUMNS = [
render: (expectedReplicaCount: number, record: IContainerResponse) => {
const actualReplicaCount = record.actualReplicaCount;
return (
- <span>
+ <span>
{actualReplicaCount} / {expectedReplicaCount}
</span>
);
@@ -160,30 +163,30 @@ const CONTAINER_TAB_COLUMNS = [
dataIndex: 'replicas',
key: 'replicas',
render: (replicas: IContainerReplicas[]) => (
- <div>
- {replicas && replicas.map(replica => {
- const tooltip = (
- <div>
- <div>First Report Time: {timeFormat(replica.firstSeenTime)}</div>
- <div>Last Report Time: {timeFormat(replica.lastSeenTime)}</div>
- </div>
- );
- return (
- <div key={replica.datanodeHost}>
- <Tooltip
- placement='left'
- title={tooltip}
- >
- <InfoCircleOutlined className='icon-small' />
- </Tooltip>
- <span className='pl-5'>
+ <div>
+ {replicas && replicas.map(replica => {
+ const tooltip = (
+ <div>
+ <div>First Report Time:
{timeFormat(replica.firstSeenTime)}</div>
+ <div>Last Report Time:
{timeFormat(replica.lastSeenTime)}</div>
+ </div>
+ );
+ return (
+ <div key={replica.datanodeHost}>
+ <Tooltip
+ placement='left'
+ title={tooltip}
+ >
+ <InfoCircleOutlined className='icon-small' />
+ </Tooltip>
+ <span className='pl-5'>
{replica.datanodeHost}
</span>
- </div>
- );
- }
- )}
- </div>
+ </div>
+ );
+ }
+ )}
+ </div>
)
},
{
@@ -214,11 +217,16 @@ interface IExpandedRowState {
interface IMissingContainersState {
loading: boolean;
- missingDataSource: IContainerResponse[];
- underReplicatedDataSource: IContainerResponse[];
- overReplicatedDataSource: IContainerResponse[];
- misReplicatedDataSource: IContainerResponse[];
+ containerDataSource: IContainerResponse[];
+ missingCount: number;
+ underReplicatedCount: number;
+ overReplicatedCount: number;
+ misReplicatedCount: number;
expandedRowData: IExpandedRow;
+ currentState: string;
+ pageSize: number;
+ firstSeenKey: number;
+ lastSeenKey: number;
}
let cancelContainerSignal: AbortController;
@@ -229,47 +237,50 @@ export class MissingContainers extends
React.Component<Record<string, object>, I
super(props);
this.state = {
loading: false,
- missingDataSource: [],
- underReplicatedDataSource: [],
- overReplicatedDataSource: [],
- misReplicatedDataSource: [],
- expandedRowData: {}
+ containerDataSource: [],
+ expandedRowData: {},
+ missingCount: 0,
+ underReplicatedCount: 0,
+ overReplicatedCount: 0,
+ misReplicatedCount: 0,
+ currentState: "MISSING",
+ pageSize: 10,
+ firstSeenKey: 0,
+ lastSeenKey: 0
};
}
- componentDidMount(): void {
- // Fetch missing containers on component mount
- this.setState({
- loading: true
- });
-
- const { request, controller } =
AxiosGetHelper('/api/v1/containers/unhealthy', cancelContainerSignal);
- cancelContainerSignal = controller;
-
- request.then(allContainersResponse => {
+ // --- MODIFIED FUNCTION ---
+ // This function now accepts its parameters instead of reading from
`this.state`
+ fetchUnhealthyContainers = (direction: boolean, options: { pageSize: number,
lastSeenKey: number, firstSeenKey: number, currentState: string }) => {
+ this.setState({ loading: true });
- const allContainersResponseData: IUnhealthyContainersResponse =
allContainersResponse.data;
- const allContainers: IContainerResponse[] =
allContainersResponseData.containers;
+ const { pageSize, lastSeenKey, firstSeenKey, currentState } = options;
- const missingContainersResponseData = allContainers &&
allContainers.filter(item => item.containerState === 'MISSING');
- const mContainers: IContainerResponse[] = missingContainersResponseData;
+ let baseUrl =
`/api/v1/containers/unhealthy/${encodeURIComponent(currentState)}?limit=${pageSize}`;
+ let queryParam = direction
+ ? `&minContainerId=${encodeURIComponent(lastSeenKey)}`
+ : `&maxContainerId=${encodeURIComponent(firstSeenKey)}`;
+ let urlVal = baseUrl + queryParam;
- const underReplicatedResponseData = allContainers &&
allContainers.filter(item => item.containerState === 'UNDER_REPLICATED');
- const uContainers: IContainerResponse[] = underReplicatedResponseData;
-
- const overReplicatedResponseData = allContainers &&
allContainers.filter(item => item.containerState === 'OVER_REPLICATED');
- const oContainers: IContainerResponse[] = overReplicatedResponseData;
+ const { request, controller } = AxiosGetHelper(urlVal,
cancelContainerSignal);
+ cancelContainerSignal = controller;
- const misReplicatedResponseData = allContainers &&
allContainers.filter(item => item.containerState === 'MIS_REPLICATED');
- const mrContainers: IContainerResponse[] = misReplicatedResponseData;
+ request.then(allContainersResponse => {
+ const responseData: IUnhealthyContainersResponse =
allContainersResponse.data;
+ const newContainers = responseData.containers;
- this.setState({
+ // Use functional setState to avoid race conditions and correctly handle
empty responses
+ this.setState(prevState => ({
loading: false,
- missingDataSource: mContainers,
- underReplicatedDataSource: uContainers,
- overReplicatedDataSource: oContainers,
- misReplicatedDataSource: mrContainers
- });
+ containerDataSource: newContainers,
+ missingCount: responseData.missingCount,
+ misReplicatedCount: responseData.misReplicatedCount,
+ underReplicatedCount: responseData.underReplicatedCount,
+ overReplicatedCount: responseData.overReplicatedCount,
+ firstSeenKey: newContainers.length > 0 ? Number(responseData.firstKey)
: prevState.firstSeenKey,
+ lastSeenKey: newContainers.length > 0 ? Number(responseData.lastKey) :
prevState.lastSeenKey
+ }));
}).catch(error => {
this.setState({
loading: false
@@ -278,6 +289,10 @@ export class MissingContainers extends
React.Component<Record<string, object>, I
});
}
+ componentDidMount(): void {
+ this.changeTab('1');
+ }
+
componentWillUnmount(): void {
cancelRequests([
cancelContainerSignal,
@@ -285,23 +300,19 @@ export class MissingContainers extends
React.Component<Record<string, object>, I
]);
}
- onShowSizeChange = (current: number, pageSize: number) => {
- console.log(current, pageSize);
- };
-
onRowExpandClick = (expanded: boolean, record: IContainerResponse) => {
if (expanded) {
this.setState(({ expandedRowData }) => {
const expandedRowState: IExpandedRowState =
expandedRowData[record.containerID]
- ? Object.assign({}, expandedRowData[record.containerID], { loading:
true })
- : {
- containerId: record.containerID,
- loading: true,
- dataSource: [],
- totalCount: 0
- };
+ ? { ...expandedRowData[record.containerID], loading: true }
+ : {
+ containerId: record.containerID,
+ loading: true,
+ dataSource: [],
+ totalCount: 0
+ };
return {
- expandedRowData: Object.assign({}, expandedRowData, {
[record.containerID]: expandedRowState })
+ expandedRowData: { ...expandedRowData, [record.containerID]:
expandedRowState }
};
});
@@ -311,20 +322,24 @@ export class MissingContainers extends
React.Component<Record<string, object>, I
request.then(response => {
const containerKeysResponse: IContainerKeysResponse = response.data;
this.setState(({ expandedRowData }) => {
- const expandedRowState: IExpandedRowState =
- Object.assign({}, expandedRowData[record.containerID],
- { loading: false, dataSource: containerKeysResponse.keys,
totalCount: containerKeysResponse.totalCount });
+ const expandedRowState: IExpandedRowState = {
+ ...expandedRowData[record.containerID],
+ loading: false,
+ dataSource: containerKeysResponse.keys,
+ totalCount: containerKeysResponse.totalCount
+ };
return {
- expandedRowData: Object.assign({}, expandedRowData, {
[record.containerID]: expandedRowState })
+ expandedRowData: { ...expandedRowData, [record.containerID]:
expandedRowState }
};
});
}).catch(error => {
this.setState(({ expandedRowData }) => {
- const expandedRowState: IExpandedRowState =
- Object.assign({}, expandedRowData[record.containerID],
- { loading: false });
+ const expandedRowState: IExpandedRowState = {
+ ...expandedRowData[record.containerID],
+ loading: false
+ };
return {
- expandedRowData: Object.assign({}, expandedRowData, {
[record.containerID]: expandedRowState })
+ expandedRowData: { ...expandedRowData, [record.containerID]:
expandedRowState }
};
});
showDataFetchError(error.toString());
@@ -341,23 +356,32 @@ export class MissingContainers extends
React.Component<Record<string, object>, I
if (expandedRowData[containerId]) {
const containerKeys: IExpandedRowState = expandedRowData[containerId];
const dataSource = containerKeys.dataSource.map(record => (
- { ...record, uid: `${record.Volume}/${record.Bucket}/${record.Key}` }
+ { ...record, uid: `${record.Volume}/${record.Bucket}/${record.Key}` }
));
const paginationConfig: TablePaginationConfig = {
showTotal: (total: number, range) => `${range[0]}-${range[1]} of
${total} keys`
};
return (
- <Table
- loading={containerKeys.loading} dataSource={dataSource}
- columns={KEY_TABLE_COLUMNS} pagination={paginationConfig}
- rowKey='uid'
- locale={{ filterTitle: '' }} />
+ <Table
+ loading={containerKeys.loading} dataSource={dataSource}
+ columns={KEY_TABLE_COLUMNS} pagination={paginationConfig}
+ rowKey='uid'
+ locale={{ filterTitle: '' }} />
);
}
return <div>Loading...</div>;
};
+ onShowSizeChange = (_: number, pageSize: number) => {
+ this.setState((prevState) => ({
+ pageSize: pageSize,
+ lastSeenKey: prevState.firstSeenKey - 1 // We want to stay on the
current page range and only change size
+ }), () => {
+ this.fetchNextRecords();
+ });
+ }
+
searchColumn = () => {
return CONTAINER_TAB_COLUMNS.reduce<any[]>((filtered, column) => {
if (column.isSearchable) {
@@ -374,58 +398,111 @@ export class MissingContainers extends
React.Component<Record<string, object>, I
}, [])
};
+
+ fetchPreviousRecords = () => {
+ const { pageSize, lastSeenKey, firstSeenKey, currentState } = this.state;
+ this.fetchUnhealthyContainers(false, { pageSize, lastSeenKey,
firstSeenKey, currentState });
+ }
+
+ fetchNextRecords = () => {
+ const { pageSize, lastSeenKey, firstSeenKey, currentState } = this.state;
+ this.fetchUnhealthyContainers(true, { pageSize, lastSeenKey, firstSeenKey,
currentState });
+ }
+
+ itemRender = (_: any, type: string, originalElement: any) => {
+ if (type === 'prev') {
+ return <div><Link to="#"
onClick={this.fetchPreviousRecords}>{'<'}</Link></div>;
+ }
+ if (type === 'next') {
+ return <div><Link to="#" onClick={this.fetchNextRecords}> {'>'}
</Link></div>;
+ }
+ return originalElement;
+ };
+
+
+ changeTab = (activeKey: any) => {
+ let currentState = "MISSING";
+ if (activeKey === '2') {
+ currentState = "UNDER_REPLICATED";
+ } else if (activeKey === '3') {
+ currentState = "OVER_REPLICATED";
+ } else if (activeKey === '4') {
+ currentState = "MIS_REPLICATED";
+ }
+ this.setState({
+ containerDataSource: [],
+ expandedRowData: {},
+ missingCount: 0,
+ underReplicatedCount: 0,
+ overReplicatedCount: 0,
+ misReplicatedCount: 0,
+ currentState: currentState,
+ firstSeenKey: 0,
+ lastSeenKey: 0
+ }, () => {
+ this.fetchNextRecords();
+ });
+ }
+
+
render() {
- const { missingDataSource, loading, underReplicatedDataSource,
overReplicatedDataSource, misReplicatedDataSource } = this.state;
+ const {containerDataSource, loading,
+ missingCount, misReplicatedCount, overReplicatedCount,
underReplicatedCount} = this.state;
+
+ // --- MODIFIED PAGINATION CONFIG ---
const paginationConfig: TablePaginationConfig = {
- showTotal: (total: number, range) => `${range[0]}-${range[1]} of
${total} missing containers`,
+ pageSize: this.state.pageSize,
+ pageSizeOptions: ['10', '20', '30', '50'],
showSizeChanger: true,
- onShowSizeChange: this.onShowSizeChange
+ itemRender: this.itemRender,
+ onShowSizeChange: this.onShowSizeChange,
+ // Removed current and onChange as they are not needed for cursor-based
pagination
};
- const generateTable = (dataSource) => {
+ const generateTable = (dataSource: IContainerResponse[]) => {
return <Table
- expandable={{
- expandRowByClick: true,
- expandedRowRender: this.expandedRowRender,
- onExpand: this.onRowExpandClick
- }}
- dataSource={dataSource}
- columns={this.searchColumn()}
- loading={loading}
- pagination={paginationConfig} rowKey='containerID'
- locale={{ filterTitle: "" }} />
+ expandable={{
+ expandRowByClick: true,
+ expandedRowRender: this.expandedRowRender,
+ onExpand: this.onRowExpandClick
+ }}
+ dataSource={dataSource}
+ columns={this.searchColumn()}
+ loading={loading}
+ pagination={paginationConfig} rowKey='containerID'
+ locale={{ filterTitle: "" }} />
}
return (
- <div className='missing-containers-container'>
- <div className='page-header'>
- Containers
- </div>
- <div className='content-div'>
- <Tabs defaultActiveKey='1'>
- <Tabs.TabPane
- key='1'
- tab={`Missing (${missingDataSource?.length ?? 0})`}>
- {generateTable(missingDataSource)}
- </Tabs.TabPane>
- <Tabs.TabPane
- key='2'
- tab={`Under-Replicated (${underReplicatedDataSource?.length ??
0})`}>
- {generateTable(underReplicatedDataSource)}
- </Tabs.TabPane>
- <Tabs.TabPane
- key='3'
- tab={`Over-Replicated (${overReplicatedDataSource?.length ??
0})`}>
- {generateTable(overReplicatedDataSource)}
- </Tabs.TabPane>
- <Tabs.TabPane
- key='4'
- tab={`Mis-Replicated (${misReplicatedDataSource?.length ?? 0})`}>
- {generateTable(misReplicatedDataSource)}
- </Tabs.TabPane>
- </Tabs>
+ <div className='missing-containers-container'>
+ <div className='page-header'>
+ Containers
+ </div>
+ <div className='content-div'>
+ <Tabs defaultActiveKey='1' onChange={this.changeTab}>
+ <Tabs.TabPane
+ key='1'
+ tab={`Missing${(missingCount > 0) ? ` (${missingCount})` :
''}`}>
+ {generateTable(containerDataSource)}
+ </Tabs.TabPane>
+ <Tabs.TabPane
+ key='2'
+ tab={`Under-Replicated${(underReplicatedCount > 0) ? `
(${underReplicatedCount})` : ''}`}>
+ {generateTable(containerDataSource)}
+ </Tabs.TabPane>
+ <Tabs.TabPane
+ key='3'
+ tab={`Over-Replicated${(overReplicatedCount > 0) ? `
(${overReplicatedCount})` : ''}`}>
+ {generateTable(containerDataSource)}
+ </Tabs.TabPane>
+ <Tabs.TabPane
+ key='4'
+ tab={`Mis-Replicated${(misReplicatedCount > 0) ? `
(${misReplicatedCount})` : ''}`}>
+ {generateTable(containerDataSource)}
+ </Tabs.TabPane>
+ </Tabs>
+ </div>
</div>
- </div>
);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]