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 daa04de053 HDDS-11154. Improve Overview Page UI (#7017)
daa04de053 is described below

commit daa04de053f6c44eb0df7a171c7ca04659caae02
Author: Abhishek Pal <[email protected]>
AuthorDate: Thu Aug 8 11:03:41 2024 +0530

    HDDS-11154. Improve Overview Page UI (#7017)
---
 .../webapps/recon/ozone-recon-web/api/db.json      |   5 +-
 .../webapps/recon/ozone-recon-web/src/app.less     |   7 +
 .../webapps/recon/ozone-recon-web/src/app.tsx      |  12 +-
 .../src/v2/components/eChart/eChart.tsx            |  89 ++++
 .../v2/components/errorBoundary/errorBoundary.tsx  |  52 ++
 .../overviewCard/overviewCardWrapper.tsx           |  79 +++
 .../components/overviewCard/overviewSimpleCard.tsx | 147 ++++++
 .../overviewCard/overviewStorageCard.tsx           | 241 +++++++++
 .../overviewCard/overviewSummaryCard.tsx           | 108 ++++
 .../src/v2/components/storageBar/storageBar.tsx    |  93 ++++
 .../src/v2/pages/overview/overview.less            |  26 +
 .../src/v2/pages/overview/overview.tsx             | 542 +++++++++++++++++++++
 .../recon/ozone-recon-web/src/v2/routes-v2.tsx     |  26 +
 .../ozone-recon-web/src/v2/types/overview.types.ts |  66 +++
 14 files changed, 1487 insertions(+), 6 deletions(-)

diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
index 5416ca7f00..8cfb23ad68 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/api/db.json
@@ -5,8 +5,9 @@
     "healthyDatanodes": 24,
     "storageReport": {
       "capacity": 202114732032,
-      "used": 16384,
-      "remaining": 182447632384
+      "used": 4667099648,
+      "remaining": 182447632384,
+      "committed": 12000222315
     },
     "containers": 3230,
     "missingContainers": 1002,
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.less
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.less
index 3d2cbc814e..1895cabc18 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.less
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.less
@@ -44,6 +44,13 @@
   font-weight: 500;
 }
 
+.page-header-v2 {
+  padding: 10px 20px;
+  font-size: 20px;
+  font-weight: 500;
+  background-color: #FFFFFF;
+}
+
 .content-div {
   padding: 24px;
   background-color: #FFF;
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.tsx
index 3fec211e7e..c52fe9efa9 100644
--- 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.tsx
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/app.tsx
@@ -23,6 +23,7 @@ import NavBar from './components/navBar/navBar';
 import Breadcrumbs from './components/breadcrumbs/breadcrumbs';
 import { HashRouter as Router, Switch, Route, Redirect } from 
'react-router-dom';
 import { routes } from '@/routes';
+import { routesV2 } from '@/v2/routes-v2';
 import { MakeRouteWithSubRoutes } from '@/makeRouteWithSubRoutes';
 import classNames from 'classnames';
 
@@ -51,7 +52,7 @@ class App extends React.Component<Record<string, object>, 
IAppState> {
   };
 
   render() {
-    const { collapsed } = this.state;
+    const { collapsed, enableNewUI } = this.state;
     const layoutClass = classNames('content-layout', { 'sidebar-collapsed': 
collapsed });
 
 
@@ -73,13 +74,16 @@ class App extends React.Component<Record<string, object>, 
IAppState> {
                   }} />
               </div>
             </Header>
-            <Content style={{ margin: '0 16px 0', overflow: 'initial' }}>
+            <Content style={(enableNewUI) ? {} : { margin: '0 16px 0', 
overflow: 'initial' }}>
               <Switch>
                 <Route exact path='/'>
                   <Redirect to='/Overview' />
                 </Route>
-                {
-                  routes.map(
+                {(enableNewUI)
+                  ? routesV2.map(
+                    (route, index) => <MakeRouteWithSubRoutes key={index} 
{...route} />
+                  )
+                  : routes.map(
                     (route, index) => <MakeRouteWithSubRoutes key={index} 
{...route} />
                   )
                 }
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/eChart/eChart.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/eChart/eChart.tsx
new file mode 100644
index 0000000000..8be22fcc9f
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/eChart/eChart.tsx
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useRef, useEffect } from "react";
+import { init, getInstanceByDom } from 'echarts';
+import type { CSSProperties } from "react";
+import type { EChartsOption, ECharts, SetOptionOpts } from 'echarts';
+
+export interface EChartProps {
+  option: EChartsOption;
+  style?: CSSProperties;
+  settings?: SetOptionOpts;
+  loading?: boolean;
+  theme?: 'light';
+  onClick?: () => any | void;
+}
+
+const EChart = ({
+  option,
+  style,
+  settings,
+  loading,
+  theme,
+  onClick
+}: EChartProps): JSX.Element => {
+  const chartRef = useRef<HTMLDivElement>(null);
+  useEffect(() => {
+    // Initialize chart
+    let chart: ECharts | undefined;
+    if (chartRef.current !== null) {
+      chart = init(chartRef.current, theme);
+      if (onClick) {
+        chart.on('click', onClick);
+      }
+    }
+
+    // Add chart resize listener
+    // ResizeObserver is leading to a bit janky UX
+    function resizeChart() {
+      chart?.resize();
+    }
+    window.addEventListener("resize", resizeChart);
+
+    // Return cleanup function
+    return () => {
+      chart?.dispose();
+      window.removeEventListener("resize", resizeChart);
+    };
+  }, [theme]);
+
+  useEffect(() => {
+    // Update chart
+    if (chartRef.current !== null) {
+      const chart = getInstanceByDom(chartRef.current);
+      chart!.setOption(option, settings);
+      if (onClick) {
+        chart!.on('click', onClick);
+      }
+    }
+  }, [option, settings, theme]); // Whenever theme changes we need to add 
option and setting due to it being deleted in cleanup function
+
+  useEffect(() => {
+    // Update chart
+    if (chartRef.current !== null) {
+      const chart = getInstanceByDom(chartRef.current);
+      // eslint-disable-next-line @typescript-eslint/no-unused-expressions
+      loading === true ? chart!.showLoading() : chart!.hideLoading();
+    }
+  }, [loading, theme]); // If we switch theme we should put chart in loading 
mode, and also if loading changes i.e completes then hide loader
+
+  return <div ref={chartRef} style={{ width: "100em", height: "50em", margin: 
'auto', ...style }} />;
+}
+
+export default EChart;
\ No newline at end of file
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/errorBoundary/errorBoundary.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/errorBoundary/errorBoundary.tsx
new file mode 100644
index 0000000000..a7f7c9f45a
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/errorBoundary/errorBoundary.tsx
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from "react";
+
+type ErrorProps = {
+  fallback: string | React.ReactNode;
+  children: React.ReactNode;
+}
+
+type ErrorState = {
+  hasError: boolean;
+}
+
+class ErrorBoundary extends React.Component<ErrorProps, ErrorState>{
+  constructor(props: ErrorProps) {
+    super(props);
+    this.state = { hasError: false }
+  }
+
+  static getDerivedStateFromError(error: Error) {
+    return { hasError: true }
+  }
+
+  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
+    console.error(error, errorInfo)
+  }
+
+  render(): React.ReactNode {
+    if (this.state.hasError) {
+      return this.props.fallback;
+    }
+    return this.props.children;
+  }
+}
+
+export default ErrorBoundary;
\ No newline at end of file
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewCardWrapper.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewCardWrapper.tsx
new file mode 100644
index 0000000000..5e0998e6f6
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewCardWrapper.tsx
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+// ------------- Types -------------- //
+type OverviewCardWrapperProps = {
+  linkToUrl: string;
+  title: string;
+  children: React.ReactElement;
+}
+
+// ------------- Component -------------- //
+const OverviewCardWrapper: React.FC<OverviewCardWrapperProps> = ({
+  linkToUrl = '/',
+  title = '',
+  children = <></>
+}) => {
+
+  const setCurrentActiveTab = (title: string): { active: string } => {
+    if (title === 'Open Keys Summary') {
+      return {
+        active: '2'
+      }
+    }
+    else if (title === 'Pending Deleted Keys Summary') {
+      return {
+        active: '3'
+      }
+    }
+    else if (title === 'OM Service') {
+      return {
+        active: '4'
+      }
+    }
+    return {
+      active: '1'
+    }
+  };
+
+  if (linkToUrl === '/Om') {
+    return (
+      <Link to={{
+        pathname: linkToUrl,
+        state: { activeTab: setCurrentActiveTab(title).active }
+      }} >
+        {children}
+      </Link>
+    );
+  }
+  else if (linkToUrl) {
+    return (
+      <Link to={linkToUrl}>
+        {children}
+      </Link>
+    );
+  }
+  else {
+    return children;
+  }
+}
+
+export default OverviewCardWrapper;
\ No newline at end of file
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSimpleCard.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSimpleCard.tsx
new file mode 100644
index 0000000000..183ae73bc4
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSimpleCard.tsx
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Card, Col, Row } from 'antd';
+import { Link } from 'react-router-dom';
+import {
+  ClusterOutlined,
+  ContainerOutlined,
+  DatabaseOutlined,
+  DeleteOutlined,
+  DeploymentUnitOutlined,
+  FileTextOutlined,
+  FolderOpenOutlined,
+  InboxOutlined,
+  QuestionCircleOutlined
+} from '@ant-design/icons';
+
+
+// ------------- Types -------------- //
+type IconOptions = {
+  [key: string]: React.ReactElement
+}
+
+type OverviewCardProps = {
+  icon: string;
+  data: number | React.ReactElement;
+  title: string;
+  hoverable?: boolean;
+  loading?: boolean;
+  linkToUrl?: string;
+}
+
+// ------------- Styles -------------- //
+const defaultIconStyle: React.CSSProperties = {
+  fontSize: '50px',
+  float: 'right'
+};
+const iconStyle: React.CSSProperties = {
+  fontSize: '20px',
+  paddingRight: '4px',
+  float: 'inline-start'
+};
+const cardHeadStyle: React.CSSProperties = { fontSize: '14px' };
+const cardBodyStyle: React.CSSProperties = {
+  padding: '16px',
+  justifyTracks: 'space-between'
+};
+const dataColStyle: React.CSSProperties = { fontSize: '24px' };
+const titleLinkStyle: React.CSSProperties = { fontWeight: 400 }
+
+// Since AntD no longer supports string icon component
+// we are using a utility function to map the strings to
+// the appropriate Icon to render
+const IconSelector = ({
+  iconType, style
+}: {
+  iconType: string;
+  style: React.CSSProperties
+}) => {
+  const Icons: IconOptions = {
+    'cluster': <ClusterOutlined style={style} />,
+    'deployment-unit': <DeploymentUnitOutlined style={style} />,
+    'database': <DatabaseOutlined style={style} />,
+    'container': <ContainerOutlined style={style} />,
+    'inbox': <InboxOutlined style={style} />,
+    'folder-open': <FolderOpenOutlined style={style} />,
+    'file-text': <FileTextOutlined style={style} />,
+    'delete': <DeleteOutlined style={style} />
+  };
+
+  const selectIcon = (iconType: string): React.ReactElement => {
+    // Setting the default Icon as a question mark in case no match found
+    let ico = <QuestionCircleOutlined style={defaultIconStyle} />
+
+    const found = Object.entries(Icons).find(
+      ([k]) => k.toLowerCase() === iconType.toLowerCase()
+    );
+
+    if (found) {
+      [, ico] = found;
+    }
+    return ico;
+  }
+  return selectIcon(iconType);
+}
+
+
+// ------------- Component -------------- //
+const OverviewSimpleCard: React.FC<OverviewCardProps> = ({
+  icon = '',
+  data = 0,
+  title = '',
+  hoverable = false,
+  loading = false,
+  linkToUrl = ''
+}) => {
+
+  const titleElement = (linkToUrl)
+    ? (
+      <div className='card-title-div'>
+        {title}
+        <Link
+          to={linkToUrl}
+          style={titleLinkStyle}>
+          View More
+        </Link>
+      </div>)
+    : title
+
+  return (
+    <Card
+      size='small'
+      loading={loading}
+      hoverable={hoverable}
+      title={(linkToUrl) ? titleElement : title}
+      headStyle={cardHeadStyle}
+      bodyStyle={cardBodyStyle}>
+      <Row
+        align='middle'>
+        <Col>
+          <IconSelector iconType={icon} style={iconStyle} />
+        </Col>
+        <Col style={dataColStyle}>
+          {data}
+        </Col>
+      </Row>
+    </Card>
+  );
+}
+
+export default OverviewSimpleCard;
\ No newline at end of file
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewStorageCard.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewStorageCard.tsx
new file mode 100644
index 0000000000..d41f5dbcfb
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewStorageCard.tsx
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useMemo } from 'react';
+import filesize from 'filesize';
+import { Card, Row, Col, Table, Tag } from 'antd';
+
+import EChart from '@/v2/components/eChart/eChart';
+import OverviewCardWrapper from 
'@/v2/components/overviewCard/overviewCardWrapper';
+
+import { StorageReport } from '@/v2/types/overview.types';
+
+// ------------- Types -------------- //
+type OverviewStorageCardProps = {
+  loading?: boolean;
+  storageReport: StorageReport;
+}
+
+const size = filesize.partial({ round: 1 });
+
+function getUsagePercentages(
+  { used, remaining, capacity, committed }: StorageReport): ({
+    ozoneUsedPercentage: number,
+    nonOzoneUsedPercentage: number,
+    committedPercentage: number,
+    usagePercentage: number
+  }) {
+  return {
+    ozoneUsedPercentage: Math.floor(used / capacity * 100),
+    nonOzoneUsedPercentage: Math.floor((capacity - remaining - used) / 
capacity * 100),
+    committedPercentage: Math.floor(committed / capacity * 100),
+    usagePercentage: Math.floor((capacity - remaining) / capacity * 100)
+  }
+}
+
+// ------------- Styles -------------- //
+const cardHeadStyle: React.CSSProperties = { fontSize: '14px' };
+const cardBodyStyle: React.CSSProperties = { padding: '16px' };
+const cardStyle: React.CSSProperties = {
+  boxSizing: 'border-box',
+  height: '100%'
+}
+const eChartStyle: React.CSSProperties = {
+  width: '280px',
+  height: '200px'
+}
+
+
+// ------------- Component -------------- //
+const OverviewStorageCard: React.FC<OverviewStorageCardProps> = ({
+  loading = false,
+  storageReport = {
+    capacity: 0,
+    used: 0,
+    remaining: 0,
+    committed: 0
+  }
+}) => {
+
+  const {
+    ozoneUsedPercentage,
+    nonOzoneUsedPercentage,
+    committedPercentage,
+    usagePercentage
+  } = useMemo(() =>
+    getUsagePercentages(storageReport),
+    [
+      storageReport.capacity,
+      storageReport.committed,
+      storageReport.remaining,
+      storageReport.used,
+    ]
+  )
+
+  let capacityData = [{
+    value: ozoneUsedPercentage,
+    itemStyle: {
+      color: '#52C41A'
+    }
+  }, {
+    value: nonOzoneUsedPercentage,
+    itemStyle: {
+      color: '#1890FF'
+    }
+  }, {
+    value: committedPercentage,
+    itemStyle: {
+      color: '#FF595E'
+    }
+  }]
+  // Remove all zero values
+  // because guage chart shows a dot if value is zero
+  capacityData = capacityData.filter((val) => val.value > 0)
+
+  const eChartOptions = {
+    title: {
+      left: 'center',
+      bottom: 'bottom',
+      text: `${size(storageReport.capacity - storageReport.remaining)} / 
${size(storageReport.capacity)}`,
+      textStyle: {
+        fontWeight: 'normal',
+        fontFamily: 'Roboto'
+      }
+    },
+    series: [
+      {
+        type: 'gauge',
+        startAngle: 90,
+        endAngle: -270,
+        radius: '70%',
+        center: ['50%', '45%'],
+        bottom: '50%',
+        pointer: {
+          show: false
+        },
+        progress: {
+          show: true,
+          overlap: true,
+          roundCap: true,
+          clip: true
+        },
+        splitLine: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        axisLabel: {
+          show: false,
+          distance: 50
+        },
+        detail: {
+          rich: {
+            value: {
+              fontSize: 24,
+              fontWeight: 400,
+              fontFamily: 'Roboto',
+              color: '#1B232A'
+            },
+            percent: {
+              fontSize: 20,
+              fontWeight: 400,
+              color: '#1B232A'
+            }
+          },
+          formatter: `{value|${usagePercentage}}{percent|%}`,
+          offsetCenter: [0, 0]
+        },
+        data: capacityData
+      }
+    ]
+  }
+
+  const cardChildren = (
+    <Card
+      size='small'
+      className={'overview-card'}
+      loading={loading}
+      hoverable={false}
+      title='Cluster Capacity'
+      headStyle={cardHeadStyle}
+      bodyStyle={cardBodyStyle}
+      style={cardStyle}>
+      <Row justify='space-between'>
+        <Col
+          className='echart-col'
+          xs={24} sm={24} md={12} lg={12} xl={12}>
+          <EChart
+            option={eChartOptions}
+            style={eChartStyle} />
+        </Col>
+        <Col xs={24} sm={24} md={12} lg={12} xl={12}>
+          <Table
+            size='small'
+            pagination={false}
+            columns={[
+              {
+                title: 'Usage',
+                dataIndex: 'usage',
+                key: 'usage'
+              },
+              {
+                title: 'Size',
+                dataIndex: 'size',
+                key: 'size',
+                align: 'right'
+              },
+            ]}
+            dataSource={[
+              {
+                key: 'ozone-used',
+                usage: <Tag key='ozone-used' color='green'>Ozone Used</Tag>,
+                size: size(storageReport.used)
+              },
+              {
+                key: 'non-ozone-used',
+                usage: <Tag key='non-ozone-used' color='blue'>Non Ozone 
Used</Tag>,
+                size: size(storageReport.capacity - storageReport.remaining - 
storageReport.used)
+              },
+              {
+                key: 'remaining',
+                usage: <Tag key='remaining' color='#E6EBF8'>
+                  <span style={{ color: '#4c7cf5' }}>Remaining</span>
+                </Tag>,
+                size: size(storageReport.remaining)
+              },
+              {
+                key: 'pre-allocated',
+                usage: <Tag key='pre-allocated' color='red'>Container 
Pre-allocated</Tag>,
+                size: size(storageReport.committed)
+              }
+            ]} />
+        </Col>
+      </Row>
+    </Card>
+  )
+
+  return (
+    <OverviewCardWrapper
+      linkToUrl={'/DiskUsage'}
+      title='Report'
+      children={cardChildren} />
+  )
+}
+
+export default OverviewStorageCard;
\ No newline at end of file
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSummaryCard.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSummaryCard.tsx
new file mode 100644
index 0000000000..42c28676dd
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/overviewCard/overviewSummaryCard.tsx
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Card, Row, Table } from 'antd';
+
+import { ColumnType } from 'antd/es/table';
+import { Link } from 'react-router-dom';
+
+// ------------- Types -------------- //
+type TableData = {
+  key: React.Key;
+  name: string;
+  value: string;
+  action?: React.ReactElement | string;
+}
+
+type OverviewTableCardProps = {
+  title: string;
+  columns: ColumnType<TableData>[];
+  tableData: TableData[];
+  hoverable?: boolean;
+  loading?: boolean;
+  data?: string | React.ReactElement;
+  linkToUrl?: string;
+  showHeader?: boolean;
+}
+
+// ------------- Styles -------------- //
+const cardStyle: React.CSSProperties = {
+  height: '100%'
+}
+const cardHeadStyle: React.CSSProperties = {
+  fontSize: '14px'
+}
+const cardBodyStyle: React.CSSProperties = {
+  padding: '16px',
+  justifyTracks: 'space-between'
+}
+
+
+// ------------- Component -------------- //
+const OverviewSummaryCard: React.FC<OverviewTableCardProps> = ({
+  data = '',
+  title = '',
+  hoverable = false,
+  loading = false,
+  columns = [],
+  tableData = [],
+  linkToUrl = '',
+  showHeader = false
+}) => {
+
+  const titleElement = (linkToUrl)
+    ? (
+      <div className='card-title-div'>
+        {title}
+        <Link
+          to={linkToUrl}
+          style={{
+            fontWeight: 400
+          }}>View Insights</Link>
+      </div>)
+    : title
+
+  return (
+    <Card
+      size='small'
+      className={'overview-card'}
+      loading={loading}
+      hoverable={hoverable}
+      title={titleElement}
+      headStyle={cardHeadStyle}
+      bodyStyle={cardBodyStyle}
+      style={cardStyle}>
+      {
+        (data) &&
+        <Row gutter={[0, 50]}>
+          {data}
+        </Row>
+      }
+      <Table
+        showHeader={showHeader || false}
+        tableLayout='fixed'
+        size="small"
+        pagination={false}
+        dataSource={tableData}
+        columns={columns} />
+    </Card>
+  )
+}
+
+export default OverviewSummaryCard;
\ No newline at end of file
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/storageBar/storageBar.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/storageBar/storageBar.tsx
new file mode 100644
index 0000000000..591b0088b0
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/storageBar/storageBar.tsx
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Progress } from 'antd';
+import filesize from 'filesize';
+import Icon from '@ant-design/icons';
+import { withRouter } from 'react-router-dom';
+import Tooltip from 'antd/lib/tooltip';
+
+import { FilledIcon } from '@/utils/themeIcons';
+import { getCapacityPercent } from '@/utils/common';
+import type { StorageReport } from '@/v2/types/overview.types';
+
+const size = filesize.partial({
+  standard: 'iec',
+  round: 1
+});
+
+type StorageReportProps = {
+  showMeta: boolean;
+} & StorageReport
+
+
+const StorageBar = (props: StorageReportProps = {
+  capacity: 0,
+  used: 0,
+  remaining: 0,
+  committed: 0,
+  showMeta: true,
+}) => {
+  const { capacity, used, remaining, committed, showMeta } = props;
+
+  const nonOzoneUsed = capacity - remaining - used;
+  const totalUsed = capacity - remaining;
+  const tooltip = (
+    <>
+      <div>
+        <Icon component={FilledIcon} className='ozone-used-bg' />
+        Ozone Used ({size(used)})
+      </div>
+      <div>
+        <Icon component={FilledIcon} className='non-ozone-used-bg' />
+        Non Ozone Used ({size(nonOzoneUsed)})
+      </div>
+      <div>
+        <Icon component={FilledIcon} className='remaining-bg' />
+        Remaining ({size(remaining)})
+      </div>
+      <div>
+        <Icon component={FilledIcon} className='committed-bg' />
+        Container Pre-allocated ({size(committed)})
+      </div>
+    </>
+  );
+  const metaElement = (showMeta) ? (
+    <div>
+      {size(used + nonOzoneUsed)} / {size(capacity)}
+    </div>
+  ) : <></>;
+
+
+  return (
+    <div className='storage-cell-container'>
+      <Tooltip title={tooltip} placement='bottomLeft'>
+        {metaElement}
+        <Progress
+          strokeLinecap='round'
+          percent={getCapacityPercent(totalUsed, capacity)}
+          success={{ percent: getCapacityPercent(used, capacity) }}
+          className='capacity-bar' strokeWidth={3} />
+      </Tooltip>
+    </div>
+  );
+}
+
+
+export default StorageBar;
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.less
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.less
new file mode 100644
index 0000000000..24fc453f4b
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.less
@@ -0,0 +1,26 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "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
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+.card-title-div {
+  display: flex;
+  justify-content: space-between;
+}
+
+.echart-col {
+  justify-items: center;
+}
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx
new file mode 100644
index 0000000000..f511cb3a16
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/overview/overview.tsx
@@ -0,0 +1,542 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import moment from 'moment';
+import filesize from 'filesize';
+import axios, { CanceledError } from 'axios';
+import { Row, Col, Button } from 'antd';
+import {
+  CheckCircleFilled,
+  WarningFilled
+} from '@ant-design/icons';
+import { Link } from 'react-router-dom';
+
+import AutoReloadPanel from '@/components/autoReloadPanel/autoReloadPanel';
+import OverviewSummaryCard from 
'@/v2/components/overviewCard/overviewSummaryCard';
+import OverviewStorageCard from 
'@/v2/components/overviewCard/overviewStorageCard';
+import OverviewSimpleCard from 
'@/v2/components/overviewCard/overviewSimpleCard';
+
+import { AutoReloadHelper } from '@/utils/autoReloadHelper';
+import { showDataFetchError } from '@/utils/common';
+import { AxiosGetHelper, cancelRequests, PromiseAllSettledGetHelper } from 
'@/utils/axiosRequestHelper';
+
+import { ClusterStateResponse, OverviewState, StorageReport } from 
'@/v2/types/overview.types';
+
+import './overview.less';
+
+
+const size = filesize.partial({ round: 1 });
+
+const getHealthIcon = (value: string): React.ReactElement => {
+  const values = value.split('/');
+  if (values.length == 2 && values[0] < values[1]) {
+    return (
+      <>
+        <div className='icon-warning' style={{
+          fontSize: '20px',
+          alignItems: 'center'
+        }}>
+          <WarningFilled style={{
+            marginRight: '5px'
+          }} />
+          Unhealthy
+        </div>
+      </>
+    )
+  }
+  return (
+    <div className='icon-success' style={{
+      fontSize: '20px',
+      alignItems: 'center'
+    }}>
+      <CheckCircleFilled style={{
+        marginRight: '5px'
+      }} />
+      Healthy
+    </div>
+  )
+}
+
+const checkResponseError = (responses: Awaited<Promise<any>>[]) => {
+  const responseError = responses.filter(
+    (resp) => resp.status === 'rejected'
+  );
+
+  if (responseError.length !== 0) {
+    responseError.forEach((err) => {
+      if (err.reason.toString().includes("CanceledError")) {
+        throw new CanceledError('canceled', "ERR_CANCELED");
+      }
+      else {
+        const reqMethod = err.reason.config.method;
+        const reqURL = err.reason.config.url
+        showDataFetchError(
+          `Failed to ${reqMethod} URL ${reqURL}\n${err.reason.toString()}`
+        );
+      }
+    })
+  }
+}
+
+const getSummaryTableValue = (
+  value: number | string | undefined,
+  colType: 'value' | undefined = undefined
+): string => {
+  if (!value) return 'N/A';
+  if (colType === 'value') String(value as string)
+  return size(value as number)
+}
+
+const Overview: React.FC<{}> = () => {
+
+  let cancelOverviewSignal: AbortController;
+  let cancelOMDBSyncSignal: AbortController;
+
+  const [state, setState] = useState<OverviewState>({
+    loading: false,
+    datanodes: '',
+    pipelines: 0,
+    containers: 0,
+    volumes: 0,
+    buckets: 0,
+    keys: 0,
+    missingContainersCount: 0,
+    lastRefreshed: 0,
+    lastUpdatedOMDBDelta: 0,
+    lastUpdatedOMDBFull: 0,
+    omStatus: '',
+    openContainers: 0,
+    deletedContainers: 0,
+    openSummarytotalUnrepSize: 0,
+    openSummarytotalRepSize: 0,
+    openSummarytotalOpenKeys: 0,
+    deletePendingSummarytotalUnrepSize: 0,
+    deletePendingSummarytotalRepSize: 0,
+    deletePendingSummarytotalDeletedKeys: 0,
+    scmServiceId: '',
+    omServiceId: ''
+  })
+  const [storageReport, setStorageReport] = useState<StorageReport>({
+    capacity: 0,
+    used: 0,
+    remaining: 0,
+    committed: 0
+  })
+
+  // Component mounted, fetch initial data
+  useEffect(() => {
+    loadOverviewPageData();
+    autoReloadHelper.startPolling();
+    return (() => {
+      // Component will Un-mount
+      autoReloadHelper.stopPolling();
+      cancelRequests([
+        cancelOMDBSyncSignal,
+        cancelOverviewSignal
+      ]);
+    })
+  }, [])
+
+  const loadOverviewPageData = () => {
+    setState({
+      ...state,
+      loading: true
+    });
+
+    // Cancel any previous pending requests
+    cancelRequests([
+      cancelOMDBSyncSignal,
+      cancelOverviewSignal
+    ]);
+
+    const { requests, controller } = PromiseAllSettledGetHelper([
+      '/api/v1/clusterState',
+      '/api/v1/task/status',
+      '/api/v1/keys/open/summary',
+      '/api/v1/keys/deletePending/summary'
+    ], cancelOverviewSignal);
+    cancelOverviewSignal = controller;
+
+    requests.then(axios.spread((
+      clusterStateResponse: Awaited<Promise<any>>,
+      taskstatusResponse: Awaited<Promise<any>>,
+      openResponse: Awaited<Promise<any>>,
+      deletePendingResponse: Awaited<Promise<any>>
+    ) => {
+
+      checkResponseError([
+        clusterStateResponse,
+        taskstatusResponse,
+        openResponse,
+        deletePendingResponse
+      ]);
+
+      const clusterState: ClusterStateResponse = 
clusterStateResponse.value?.data ?? {
+        missingContainers: 'N/A',
+        totalDatanodes: 'N/A',
+        healthyDatanodes: 'N/A',
+        pipelines: 'N/A',
+        storageReport: {
+          capacity: 0,
+          used: 0,
+          remaining: 0,
+          committed: 0
+        },
+        containers: 'N/A',
+        volumes: 'N/A',
+        buckets: 'N/A',
+        keys: 'N/A',
+        openContainers: 'N/A',
+        deletedContainers: 'N/A',
+        keysPendingDeletion: 'N/A',
+        scmServiceId: 'N/A',
+        omServiceId: 'N/A',
+      };
+      const taskStatus = taskstatusResponse.value?.data ?? [{
+        taskName: 'N/A',
+        lastUpdatedTimestamp: 0,
+        lastUpdatedSeqNumber: 0
+      }];
+      const missingContainersCount = clusterState.missingContainers;
+      const omDBDeltaObject = taskStatus && taskStatus.find((item: any) => 
item.taskName === 'OmDeltaRequest');
+      const omDBFullObject = taskStatus && taskStatus.find((item: any) => 
item.taskName === 'OmSnapshotRequest');
+
+      setState({
+        ...state,
+        loading: false,
+        datanodes: 
`${clusterState.healthyDatanodes}/${clusterState.totalDatanodes}`,
+        pipelines: clusterState.pipelines,
+        containers: clusterState.containers,
+        volumes: clusterState.volumes,
+        buckets: clusterState.buckets,
+        keys: clusterState.keys,
+        missingContainersCount: missingContainersCount,
+        openContainers: clusterState.openContainers,
+        deletedContainers: clusterState.deletedContainers,
+        lastRefreshed: Number(moment()),
+        lastUpdatedOMDBDelta: omDBDeltaObject?.lastUpdatedTimestamp,
+        lastUpdatedOMDBFull: omDBFullObject?.lastUpdatedTimestamp,
+        openSummarytotalUnrepSize: 
openResponse?.value?.data?.totalUnreplicatedDataSize,
+        openSummarytotalRepSize: 
openResponse?.value?.data?.totalReplicatedDataSize,
+        openSummarytotalOpenKeys: openResponse?.value?.data?.totalOpenKeys,
+        deletePendingSummarytotalUnrepSize: 
deletePendingResponse?.value?.data?.totalUnreplicatedDataSize,
+        deletePendingSummarytotalRepSize: 
deletePendingResponse?.value?.data?.totalReplicatedDataSize,
+        deletePendingSummarytotalDeletedKeys: 
deletePendingResponse?.value?.data?.totalDeletedKeys,
+        scmServiceId: clusterState?.scmServiceId,
+        omServiceId: clusterState?.omServiceId
+      });
+      setStorageReport({
+        ...storageReport,
+        ...clusterState.storageReport
+      });
+    })).catch((error: Error) => {
+      setState({
+        ...state,
+        loading: false
+      });
+      showDataFetchError(error.toString());
+    });
+  }
+
+  let autoReloadHelper: AutoReloadHelper = new 
AutoReloadHelper(loadOverviewPageData);
+
+  const syncOmData = () => {
+    setState({
+      ...state,
+      loading: true
+    });
+
+    const { request, controller } = AxiosGetHelper(
+      '/api/v1/triggerdbsync/om',
+      cancelOMDBSyncSignal,
+      'OM-DB Sync request cancelled because data was updated'
+    );
+    cancelOMDBSyncSignal = controller;
+
+    request.then(omStatusResponse => {
+      const omStatus = omStatusResponse.data;
+      setState({
+        ...state,
+        loading: false,
+        omStatus: omStatus
+      });
+    }).catch((error: Error) => {
+      setState({
+        ...state,
+        loading: false
+      });
+      showDataFetchError(error.toString());
+    });
+  };
+
+  const {
+    loading, datanodes, pipelines,
+    containers, volumes, buckets,
+    openSummarytotalUnrepSize,
+    openSummarytotalRepSize,
+    openSummarytotalOpenKeys,
+    deletePendingSummarytotalUnrepSize,
+    deletePendingSummarytotalRepSize,
+    deletePendingSummarytotalDeletedKeys,
+    keys, missingContainersCount,
+    lastRefreshed, lastUpdatedOMDBDelta,
+    lastUpdatedOMDBFull,
+    omStatus, openContainers,
+    deletedContainers, scmServiceId, omServiceId
+  } = state;
+
+  const healthCardIndicators = (
+    <>
+      <Col span={14}>
+        Datanodes
+        {getHealthIcon(datanodes)}
+      </Col>
+      <Col span={10}>
+        Containers
+        {getHealthIcon(`${(containers - 
missingContainersCount)}/${containers}`)}
+      </Col>
+    </>
+  )
+
+  const datanodesLink = (
+    <Button
+      type='link'
+      size='small'>
+      <Link to='/Datanodes'> View More </Link>
+    </Button>
+  )
+
+  const containersLink = (missingContainersCount > 0)
+    ? (
+      <Button
+        type='link'
+        size='small'>
+        <Link to='/MissingContainers'> View More</Link>
+      </Button>
+    ) : (
+      <Button
+        type='link'
+        size='small'>
+        <Link to='/Containers'> View More</Link>
+      </Button>
+    )
+
+  return (
+    <>
+      <div className='page-header-v2'>
+        Overview
+        <AutoReloadPanel isLoading={loading} lastRefreshed={lastRefreshed}
+          lastUpdatedOMDBDelta={lastUpdatedOMDBDelta} 
lastUpdatedOMDBFull={lastUpdatedOMDBFull}
+          togglePolling={autoReloadHelper.handleAutoReloadToggle} 
onReload={loadOverviewPageData} omSyncLoad={syncOmData} omStatus={omStatus} />
+      </div>
+      <div style={{ padding: '24px' }}>
+        <Row
+          align='stretch'
+          gutter={[
+            {
+              xs: 24,
+              sm: 24,
+              md: 16,
+              lg: 16,
+              xl: 16
+            }, 20]}>
+          <Col xs={24} sm={24} md={24} lg={10} xl={10}>
+            <OverviewSummaryCard
+              title='Health'
+              data={healthCardIndicators}
+              showHeader={true}
+              columns={[
+                {
+                  title: '',
+                  dataIndex: 'name',
+                  key: 'name'
+                },
+                {
+                  title: 'Available',
+                  dataIndex: 'value',
+                  key: 'value',
+                  align: 'right'
+                },
+                {
+                  title: 'Actions',
+                  dataIndex: 'action',
+                  key: 'action',
+                  align: 'right'
+                }
+              ]}
+              tableData={[
+                {
+                  key: 'datanodes',
+                  name: 'Datanodes',
+                  value: datanodes,
+                  action: datanodesLink
+                },
+                {
+                  key: 'containers',
+                  name: 'Containers',
+                  value: `${(containers - 
missingContainersCount)}/${containers}`,
+                  action: containersLink
+                }
+              ]}
+            />
+          </Col>
+          <Col xs={24} sm={24} md={24} lg={14} xl={14}>
+            <OverviewStorageCard storageReport={storageReport} 
loading={loading} />
+          </Col>
+        </Row>
+        <Row gutter={[
+          {
+            xs: 24,
+            sm: 24,
+            md: 16,
+            lg: 16,
+            xl: 16
+          }, 20]}>
+          <Col flex="1 0 20%">
+            <OverviewSimpleCard
+              title='Volumes'
+              icon='inbox'
+              loading={loading}
+              data={volumes}
+              linkToUrl='/Volumes' />
+          </Col>
+          <Col flex="1 0 20%">
+            <OverviewSimpleCard
+              title='Buckets'
+              icon='folder-open'
+              loading={loading}
+              data={buckets}
+              linkToUrl='/Buckets' />
+          </Col>
+          <Col flex="1 0 20%">
+            <OverviewSimpleCard
+              title='Keys'
+              icon='file-text'
+              loading={loading}
+              data={keys} />
+          </Col>
+          <Col flex="1 0 20%">
+            <OverviewSimpleCard
+              title='Pipelines'
+              icon='deployment-unit'
+              loading={loading}
+              data={pipelines}
+              linkToUrl='/Pipelines' />
+          </Col>
+          <Col flex="1 0 20%">
+            <OverviewSimpleCard
+              title='Deleted Containers'
+              icon='delete'
+              loading={loading}
+              data={deletedContainers} />
+          </Col>
+        </Row>
+        <Row gutter={[
+          {
+            xs: 24,
+            sm: 24,
+            md: 16,
+            lg: 16,
+            xl: 16
+          }, 20]}>
+          <Col xs={24} sm={24} md={24} lg={12} xl={12}>
+            <OverviewSummaryCard
+              title='Open Keys Summary'
+              loading={loading}
+              columns={[
+                {
+                  title: 'Name',
+                  dataIndex: 'name',
+                  key: 'name'
+                },
+                {
+                  title: 'Size',
+                  dataIndex: 'value',
+                  key: 'size',
+                  align: 'right'
+                }
+              ]}
+              tableData={[
+                {
+                  key: 'total-replicated-data',
+                  name: 'Total Replicated Data',
+                  value: getSummaryTableValue(openSummarytotalRepSize)
+                },
+                {
+                  key: 'total-unreplicated-data',
+                  name: 'Total Unreplicated Data',
+                  value: getSummaryTableValue(openSummarytotalUnrepSize)
+                },
+                {
+                  key: 'open-keys',
+                  name: 'Open Keys',
+                  value: getSummaryTableValue(
+                    openSummarytotalOpenKeys,
+                    'value'
+                  )
+                }
+              ]}
+              linkToUrl='/Om' />
+          </Col>
+          <Col xs={24} sm={24} md={24} lg={12} xl={12}>
+            <OverviewSummaryCard
+              title='Delete Pending Keys Summary'
+              loading={loading}
+              columns={[
+                {
+                  title: 'Name',
+                  dataIndex: 'name',
+                  key: 'name'
+                },
+                {
+                  title: 'Size',
+                  dataIndex: 'value',
+                  key: 'size',
+                  align: 'right'
+                }
+              ]}
+              tableData={[
+                {
+                  key: 'total-replicated-data',
+                  name: 'Total Replicated Data',
+                  value: getSummaryTableValue(deletePendingSummarytotalRepSize)
+                },
+                {
+                  key: 'total-unreplicated-data',
+                  name: 'Total Unreplicated Data',
+                  value: 
getSummaryTableValue(deletePendingSummarytotalUnrepSize)
+                },
+                {
+                  key: 'delete-pending-keys',
+                  name: 'Delete Pending Keys',
+                  value: getSummaryTableValue(
+                    deletePendingSummarytotalDeletedKeys,
+                    'value'
+                  )
+                }
+              ]}
+              linkToUrl='/Om' />
+          </Col>
+        </Row>
+      </div>
+    </>
+  );
+}
+
+export default Overview;
\ No newline at end of file
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
new file mode 100644
index 0000000000..4cdd700d50
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Overview  from '@/v2/pages/overview/overview';
+
+export const routesV2: IRoute[] = [
+  {
+    path: '/Overview',
+    component: Overview
+  }
+];
diff --git 
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/overview.types.ts
 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/overview.types.ts
new file mode 100644
index 0000000000..f8390fd434
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/overview.types.ts
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export type ClusterStateResponse = {
+  missingContainers: number;
+  totalDatanodes: number;
+  healthyDatanodes: number;
+  pipelines: number;
+  storageReport: StorageReport;
+  containers: number;
+  volumes: number;
+  buckets: number;
+  keys: number;
+  openContainers: number;
+  deletedContainers: number;
+  keysPendingDeletion: number;
+  scmServiceId: string;
+  omServiceId: string;
+}
+
+export type OverviewState = {
+  loading: boolean;
+  datanodes: string;
+  pipelines: number;
+  containers: number;
+  volumes: number;
+  buckets: number;
+  keys: number;
+  missingContainersCount: number;
+  lastRefreshed: number;
+  lastUpdatedOMDBDelta: number;
+  lastUpdatedOMDBFull: number;
+  omStatus: string;
+  openContainers: number;
+  deletedContainers: number;
+  openSummarytotalUnrepSize: number;
+  openSummarytotalRepSize: number;
+  openSummarytotalOpenKeys: number;
+  deletePendingSummarytotalUnrepSize: number;
+  deletePendingSummarytotalRepSize: number;
+  deletePendingSummarytotalDeletedKeys: number;
+  scmServiceId: string;
+  omServiceId: string;
+}
+
+export type StorageReport = {
+  capacity: number;
+  used: number;
+  remaining: number;
+  committed: number;
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to