You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ranger.apache.org by dh...@apache.org on 2026/09/08 12:16:37 UTC

(ranger) branch master updated: RANGER-3917 : Ranger UI should have the provision to display the audit metrics for each active plugin (#1209)

This is an automated email from the ASF dual-hosted git repository.

dhavaljrajpara pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new 2382a4817 RANGER-3917 : Ranger UI should have the provision to display the audit metrics for each active plugin (#1209)
2382a4817 is described below

commit 2382a48178a186ebb13810fd0d3fa96e0a9807cc
Author: Dhaval Rajpara <dh...@gmail.com>
AuthorDate: Tue Sep 8 17:46:32 2026 +0530

    RANGER-3917 : Ranger UI should have the provision to display the audit metrics for each active plugin (#1209)
---
 .../src/main/webapp/react-webapp/package-lock.json |  55 +++
 .../src/main/webapp/react-webapp/package.json      |   2 +
 .../src/main/webapp/react-webapp/src/App.jsx       |   2 +
 .../main/webapp/react-webapp/src/styles/style.css  |   7 +
 .../main/webapp/react-webapp/src/utils/XAEnums.js  |   3 +-
 .../src/views/AuditEvent/AuditLayout.jsx           |   5 +-
 .../src/views/AuditEvent/Metrics/MetricGraphs.jsx  | 210 +++++++++++
 .../src/views/AuditEvent/Metrics/MetricsLogs.jsx   | 407 +++++++++++++++++++++
 .../react-webapp/src/views/AuditEvent/action.js    |   2 +
 .../react-webapp/src/views/AuditEvent/reducer.js   |  20 +
 .../react-webapp/src/views/SideBar/SideBarBody.jsx |  11 +
 11 files changed, 722 insertions(+), 2 deletions(-)

diff --git a/security-admin/src/main/webapp/react-webapp/package-lock.json b/security-admin/src/main/webapp/react-webapp/package-lock.json
index e372ac8f2..a08f908d1 100644
--- a/security-admin/src/main/webapp/react-webapp/package-lock.json
+++ b/security-admin/src/main/webapp/react-webapp/package-lock.json
@@ -16,6 +16,7 @@
         "babel-loader": "^8.2.2",
         "bootstrap": "^5.3.2",
         "bootstrap-switch-button-react": "^1.2.0",
+        "chart.js": "^4.1.2",
         "classnames": "^2.3.1",
         "clipboard-polyfill": "^4.0.1",
         "create-react-class": "^15.7.0",
@@ -35,6 +36,7 @@
         "qs": "^6.15.2",
         "react": "^18.2.0",
         "react-bootstrap": "^2.10.0",
+        "react-chartjs-2": "^5.2.0",
         "react-datetime": "^3.2.0",
         "react-dom": "^18.2.0",
         "react-final-form": "^6.5.9",
@@ -2037,6 +2039,13 @@
         "tslib": "2"
       }
     },
+    "node_modules/@kurkle/color": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+      "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@leichtgewicht/ip-codec": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
@@ -3414,6 +3423,19 @@
       ],
       "license": "CC-BY-4.0"
     },
+    "node_modules/chart.js": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+      "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@kurkle/color": "^0.3.0"
+      },
+      "engines": {
+        "pnpm": ">=8"
+      }
+    },
     "node_modules/chokidar": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -6457,6 +6479,17 @@
         }
       }
     },
+    "node_modules/react-chartjs-2": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz",
+      "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "chart.js": "^4.1.1",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
     "node_modules/react-datetime": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/react-datetime/-/react-datetime-3.2.0.tgz",
@@ -9742,6 +9775,12 @@
         "@jsonjoy.com/codegen": "^1.0.0"
       }
     },
+    "@kurkle/color": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+      "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+      "dev": true
+    },
     "@leichtgewicht/ip-codec": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
@@ -10814,6 +10853,15 @@
       "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==",
       "dev": true
     },
+    "chart.js": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+      "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+      "dev": true,
+      "requires": {
+        "@kurkle/color": "^0.3.0"
+      }
+    },
     "chokidar": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -12991,6 +13039,13 @@
         "warning": "^4.0.3"
       }
     },
+    "react-chartjs-2": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz",
+      "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==",
+      "dev": true,
+      "requires": {}
+    },
     "react-datetime": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/react-datetime/-/react-datetime-3.2.0.tgz",
diff --git a/security-admin/src/main/webapp/react-webapp/package.json b/security-admin/src/main/webapp/react-webapp/package.json
index 1b3092df0..e6b9bd935 100644
--- a/security-admin/src/main/webapp/react-webapp/package.json
+++ b/security-admin/src/main/webapp/react-webapp/package.json
@@ -24,6 +24,7 @@
     "babel-loader": "^8.2.2",
     "bootstrap": "^5.3.2",
     "bootstrap-switch-button-react": "^1.2.0",
+    "chart.js": "^4.1.2",
     "classnames": "^2.3.1",
     "clipboard-polyfill": "^4.0.1",
     "create-react-class": "^15.7.0",
@@ -43,6 +44,7 @@
     "qs": "^6.15.2",
     "react": "^18.2.0",
     "react-bootstrap": "^2.10.0",
+    "react-chartjs-2": "^5.2.0",
     "react-datetime": "^3.2.0",
     "react-dom": "^18.2.0",
     "react-final-form": "^6.5.9",
diff --git a/security-admin/src/main/webapp/react-webapp/src/App.jsx b/security-admin/src/main/webapp/react-webapp/src/App.jsx
index 520dba6df..265e5ec93 100755
--- a/security-admin/src/main/webapp/react-webapp/src/App.jsx
+++ b/security-admin/src/main/webapp/react-webapp/src/App.jsx
@@ -84,6 +84,7 @@ const PluginStatusLogs = lazy(
 const UserSyncLogs = lazy(
   () => import("Views/AuditEvent/UserSync/UserSyncLogs")
 );
+const MetricsLogs = lazy(() => import("Views/AuditEvent/Metrics/MetricsLogs"));
 const PolicyListingTabView = lazy(
   () => import("Views/PolicyListing/PolicyListingTabView")
 );
@@ -343,6 +344,7 @@ export default class App extends Component {
                     <Route path="agent" element={<PluginsLogs />} />
                     <Route path="pluginStatus" element={<PluginStatusLogs />} />
                     <Route path="userSync" element={<UserSyncLogs />} />
+                    <Route path="metric" element={<MetricsLogs />} />
                   </Route>
                   {/* AUDIT LOGS DETAILS VIEW */}
                   <Route
diff --git a/security-admin/src/main/webapp/react-webapp/src/styles/style.css b/security-admin/src/main/webapp/react-webapp/src/styles/style.css
index dfe685078..e38bf68b9 100644
--- a/security-admin/src/main/webapp/react-webapp/src/styles/style.css
+++ b/security-admin/src/main/webapp/react-webapp/src/styles/style.css
@@ -1996,6 +1996,13 @@ tbody.drag-drop-wrap > tr > td:hover:first-child:after {
   width: 6px;
 }
 
+.btn-default-selected {
+  color: #0b7fad;
+  border-color: #0b7fad;
+  background-color: #fff;
+  outline: none;
+}
+
 /* SideBar */
 .wrapper {
   display: flex;
diff --git a/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js b/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
index b0f73fa90..5b07e14e8 100755
--- a/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
+++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
@@ -657,7 +657,8 @@ export const PathAssociateWithModule = {
     "/reports/audit/agent",
     "/reports/audit/pluginStatus",
     "/reports/audit/userSync",
-    "/reports/audit/eventlog/:eventId"
+    "/reports/audit/eventlog/:eventId",
+    "/reports/audit/metric"
   ],
   "Security Zone": [
     "/zones/zone/list",
diff --git a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/AuditLayout.jsx b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/AuditLayout.jsx
index 7d6d8b501..7d9c5d41e 100644
--- a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/AuditLayout.jsx
+++ b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/AuditLayout.jsx
@@ -88,8 +88,10 @@ class AuditLayout extends Component {
         this.props.location.pathname == "/reports/audit/pluginStatus"
       ) {
         activeTabVal = "pluginStatus";
-      } else {
+      } else if (this.props.location.pathname == "/reports/audit/userSync") {
         activeTabVal = "userSync";
+      } else {
+        activeTabVal = "metric";
       }
     }
     return activeTabVal;
@@ -112,6 +114,7 @@ class AuditLayout extends Component {
           <Tab eventKey="agent" title="Plugins" />
           <Tab eventKey="pluginStatus" title="Plugin Status" />
           <Tab eventKey="userSync" title="User Sync" />
+          <Tab eventKey="metric" title="Metrics" />
         </Tabs>
         <Outlet
           context={{
diff --git a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/Metrics/MetricGraphs.jsx b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/Metrics/MetricGraphs.jsx
new file mode 100644
index 000000000..d9afd86e6
--- /dev/null
+++ b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/Metrics/MetricGraphs.jsx
@@ -0,0 +1,210 @@
+/*
+ * 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, { useState, useEffect, useRef } from "react";
+import { fetchApi } from "Utils/fetchAPI";
+import { serverError } from "Utils/XAUtils";
+import {
+  Chart as ChartJS,
+  CategoryScale,
+  LinearScale,
+  BarElement,
+  Title,
+  Tooltip,
+  Legend
+} from "chart.js";
+import { Bar } from "react-chartjs-2";
+import { ModalLoader } from "Components/CommonComponents";
+import { isEmpty } from "lodash";
+import dateFormat from "dateformat";
+
+const MetricGraphs = (props) => {
+  const { metricData } = props;
+  const [loader, setLoader] = useState(false);
+  const [hrsMetricsInfo, setHrsMetricsInfo] = useState([]);
+  const [daysMetricsInfo, setDaysMetricsInfo] = useState([]);
+  const [showHideGraph, setShowHideGraph] = useState(true);
+  const hrsRef = useRef();
+
+  useEffect(() => {
+    hrsRef.current.click();
+  }, []);
+
+  ChartJS.register(
+    CategoryScale,
+    LinearScale,
+    BarElement,
+    Title,
+    Tooltip,
+    Legend
+  );
+
+  const params = {
+    serviceName: metricData.serviceName,
+    serviceType: metricData.serviceType,
+    clusterName: metricData.clusterName,
+    clientIP: metricData.clientIP,
+    appId: metricData.appId
+  };
+
+  const hrsMetrics = async () => {
+    setLoader(true);
+    let hrsMetricData = [];
+    try {
+      hrsMetricData = await fetchApi({
+        url: "audit/dailymetrics",
+        params: params
+      });
+      setHrsMetricsInfo(hrsMetricData.data.auditMetricsByHours);
+    } catch (error) {
+      serverError(error);
+    }
+    setLoader(false);
+  };
+
+  const daysMetrics = async () => {
+    setLoader(true);
+    let daysMetricInfo = [];
+    try {
+      daysMetricInfo = await fetchApi({
+        url: "audit/daysmetrics",
+        params: params
+      });
+
+      setDaysMetricsInfo(daysMetricInfo.data.auditMetricsByDays);
+    } catch (error) {
+      serverError(error);
+    }
+    setLoader(false);
+  };
+
+  const hrsOptions = {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: "top"
+      }
+    }
+  };
+  const hrsLabels = hrsMetricsInfo?.map((metrics) => {
+    return `${metrics.hours} hrs`;
+  });
+
+  const hrsData = {
+    labels: hrsLabels,
+    datasets: [
+      {
+        label: "Audit Metrics By Hours",
+        data: hrsMetricsInfo?.map((metrics) => {
+          return metrics.numberOfAudits;
+        }),
+        backgroundColor: "#0f3554",
+        borderColor: "#0f3554"
+      }
+    ]
+  };
+
+  const dayOptions = {
+    responsive: true,
+    plugins: {
+      legend: {
+        position: "top"
+      }
+    }
+  };
+  const dayLabels = daysMetricsInfo?.map((metrics) => {
+    return dateFormat(parseInt(metrics.auditDate), "mm/dd/yyyy");
+  });
+
+  const dayData = {
+    labels: dayLabels,
+    datasets: [
+      {
+        label: "Audit Metrics By Day",
+        data: daysMetricsInfo?.map((metrics) => {
+          return metrics.numberOfAudits;
+        }),
+        backgroundColor: "#0f3554",
+        borderColor: "#0f3554"
+      }
+    ]
+  };
+
+  const NoDataFound = () => {
+    return (
+      <div data-id="emptyGraphSet" data-cy="emptyGraphSet">
+        <center>
+          <strong>No Metrics Data found</strong>
+        </center>
+      </div>
+    );
+  };
+
+  return loader ? (
+    <ModalLoader />
+  ) : (
+    <>
+      <div className="text-end mb-2">
+        <button
+          type="button"
+          onClick={() => {
+            daysMetrics();
+            setShowHideGraph(false);
+          }}
+          className={`btn btn-outline-dark btn-sm me-2 ${
+            showHideGraph ? "" : "btn-default-selected"
+          }`}
+        >
+          Day
+        </button>
+        <button
+          type="button"
+          ref={hrsRef}
+          className={`btn btn-outline-dark btn-sm ${
+            showHideGraph ? "btn-default-selected" : ""
+          }`}
+          onClick={() => {
+            hrsMetrics();
+            setShowHideGraph(true);
+          }}
+        >
+          Hour
+        </button>
+      </div>
+
+      {showHideGraph ? (
+        !isEmpty(hrsMetricsInfo) ? (
+          <div className="wrap" data-id="graphSet" data-cy="graphSet">
+            <Bar options={hrsOptions} data={hrsData} />
+          </div>
+        ) : (
+          <NoDataFound />
+        )
+      ) : !isEmpty(daysMetricsInfo) ? (
+        <div className="wrap" data-id="graphSet" data-cy="graphSet">
+          <Bar options={dayOptions} data={dayData} />
+        </div>
+      ) : (
+        <NoDataFound />
+      )}
+    </>
+  );
+};
+
+export default MetricGraphs;
diff --git a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/Metrics/MetricsLogs.jsx b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/Metrics/MetricsLogs.jsx
new file mode 100644
index 000000000..a901b005f
--- /dev/null
+++ b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/Metrics/MetricsLogs.jsx
@@ -0,0 +1,407 @@
+/*
+ * 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, { useCallback, useEffect, useReducer } from "react";
+import {
+  useSearchParams,
+  useOutletContext,
+  useLocation
+} from "react-router-dom";
+import { Table, Modal, Button, Row, Col } from "react-bootstrap";
+import XATableLayout from "Components/XATableLayout";
+import { fetchApi } from "Utils/fetchAPI";
+import moment from "moment-timezone";
+import StructuredFilter from "Components/structured-filter/react-typeahead/tokenizer";
+import {
+  fetchSearchFilterParams,
+  parseSearchFilter,
+  serverError,
+  isKeyAdmin,
+  isKMSAuditor
+} from "Utils/XAUtils";
+import { AuditFilterEntries, Loader } from "Components/CommonComponents";
+import { toUpper, sortBy, filter, map, cloneDeep, pick } from "lodash";
+import MetricGraphs from "./MetricGraphs";
+import { getServiceDef } from "Utils/appState";
+import { ACTIONS } from "Views/AuditEvent/action";
+import { reducer, METRICS_INITIAL_STATE } from "Views/AuditEvent/reducer";
+
+function MetricsLogs() {
+  const [state, dispatch] = useReducer(reducer, METRICS_INITIAL_STATE);
+
+  const location = useLocation();
+
+  const { services, servicesAvailable } = useOutletContext();
+
+  const { allServiceDefs } = cloneDeep(getServiceDef());
+  const isKMSRole = isKeyAdmin() || isKMSAuditor();
+
+  const [searchParams, setSearchParams] = useSearchParams();
+
+  useEffect(() => {
+    if (servicesAvailable !== null) {
+      const { searchFilterParam, defaultSearchFilterParam, searchParam } =
+        fetchSearchFilterParams("metrics", searchParams, searchFilterOptions);
+
+      // Updating the states for search params, search filter, default search filter and localStorage
+      setSearchParams(searchParam, { replace: true });
+      if (
+        JSON.stringify(state.searchFilterParams) !==
+        JSON.stringify(searchFilterParam)
+      ) {
+        dispatch({
+          type: ACTIONS.SET_SEARCH_FILTER_PARAMS,
+          searchFilterParams: searchFilterParam,
+          refreshTableData: moment.now()
+        });
+      }
+      dispatch({
+        type: ACTIONS.SET_DEFAULT_SEARCH_FILTER_PARAMS,
+        defaultSearchFilterParams: defaultSearchFilterParam
+      });
+      localStorage.setItem("metrics", JSON.stringify(searchParam));
+      dispatch({ type: ACTIONS.SET_CONTENT_LOADER, contentLoader: false });
+    }
+  }, [location.search, servicesAvailable]);
+
+  const fetchMetricInfo = useCallback(
+    async ({ pageSize, pageIndex, gotoPage }) => {
+      dispatch({ type: ACTIONS.SET_TABLE_LOADER, loader: true });
+      if (servicesAvailable !== null) {
+        const params = {
+          ...state.searchFilterParams,
+          pageSize,
+          startIndex: pageIndex * pageSize
+        };
+
+        try {
+          const response = await fetchApi({
+            url: "audit/metrics",
+            params: params
+          });
+
+          const logsEntries = pick(response.data, [
+            "startIndex",
+            "pageSize",
+            "totalCount",
+            "resultSize"
+          ]);
+          const logsResp = response.data?.rangerAuditMetricsList || [];
+          const totalCount = response.data?.totalCount || 0;
+
+          dispatch({
+            type: ACTIONS.SET_TABLE_DATA,
+            tableListingData: logsResp,
+            entries: logsEntries,
+            pageCount: Math.ceil(totalCount / pageSize),
+            resetPage: { page: gotoPage }
+          });
+        } catch (error) {
+          serverError(error);
+          console.error(`Error occurred while fetching Metric logs! ${error}`);
+        }
+
+        dispatch({ type: ACTIONS.SET_TABLE_LOADER, loader: false });
+      }
+    },
+    [state.refreshTableData, servicesAvailable]
+  );
+
+  const refreshTable = () => {
+    dispatch({
+      type: ACTIONS.SET_SEARCH_FILTER_PARAMS,
+      searchFilterParams: state.searchFilterParams,
+      refreshTableData: moment.now()
+    });
+  };
+
+  const closeMetricModal = () => {
+    dispatch({
+      type: ACTIONS.SHOW_METRIC_MODAL,
+      showMetricModal: false,
+      metricData: null
+    });
+  };
+
+  const openMetricGraphModal = (metricData) => {
+    dispatch({
+      type: ACTIONS.SHOW_METRIC_GRAPH_MODAL,
+      showMetricGraphModal: true,
+      metricGraphData: metricData
+    });
+  };
+
+  const closeMetricGraphModal = () => {
+    dispatch({
+      type: ACTIONS.SHOW_METRIC_GRAPH_MODAL,
+      showMetricGraphModal: false,
+      metricGraphData: state.metricGraphData
+    });
+  };
+
+  const columns = React.useMemo(
+    () => [
+      {
+        Header: "Service Name",
+        accessor: "serviceName",
+        Cell: (rawValue) => {
+          return rawValue?.value ? rawValue.value : <center>--</center>;
+        },
+        disableSortBy: true
+      },
+      {
+        Header: "Service Type",
+        accessor: "serviceType",
+        Cell: (rawValue) => {
+          return rawValue?.value ? rawValue.value : <center>--</center>;
+        },
+        disableSortBy: true
+      },
+      {
+        Header: "Application Type",
+        accessor: "appId",
+        Cell: (rawValue) => {
+          return rawValue?.value ? (
+            <span>{rawValue.value}</span>
+          ) : (
+            <center>&quot;--&quot;</center>
+          );
+        },
+        disableSortBy: true
+      },
+      {
+        Header: "Cluster Name",
+        accessor: "clusterName",
+        Cell: (rawValue) => {
+          return rawValue?.value ? (
+            <span>{rawValue.value}</span>
+          ) : (
+            <center>--</center>
+          );
+        },
+        disableSortBy: true
+      },
+      {
+        Header: "Client IP",
+        accessor: "clientIP",
+        Cell: (rawValue) => {
+          return rawValue?.value ? (
+            <span>{rawValue.value}</span>
+          ) : (
+            <center>&quot;--&quot;</center>
+          );
+        },
+        disableSortBy: true
+      },
+      {
+        Header: "Metrics Graph",
+        accessor: "metricsGraph",
+        Cell: (rawValue) => {
+          return (
+            <div>
+              <button
+                className="btn btn-outline-dark btn-sm"
+                data-id="metricGraph"
+                data-cy="metricGraph"
+                onClick={() => {
+                  openMetricGraphModal(rawValue.row.original);
+                }}
+              >
+                Metrics Graph
+              </button>
+            </div>
+          );
+        },
+        disableSortBy: true
+      }
+    ],
+    []
+  );
+
+  const updateSearchFilter = (filter) => {
+    const { searchFilterParam, searchParam } = parseSearchFilter(
+      filter,
+      searchFilterOptions
+    );
+
+    dispatch({
+      type: ACTIONS.SET_SEARCH_FILTER_PARAMS,
+      searchFilterParams: searchFilterParam,
+      refreshTableData: moment.now()
+    });
+
+    setSearchParams(searchParam, { replace: true });
+    localStorage.setItem("metrics", JSON.stringify(searchParam));
+
+    if (typeof state.resetPage?.page === "function") {
+      state.resetPage.page(0);
+    }
+  };
+
+  const getServiceDefType = () => {
+    let serviceDefType = [];
+
+    serviceDefType = map(allServiceDefs, function (serviceDef) {
+      return {
+        label: toUpper(serviceDef.displayName),
+        value: serviceDef.name
+      };
+    });
+
+    return serviceDefType;
+  };
+
+  const getServices = () => {
+    let servicesName = [];
+    servicesName = filter(services, function (service) {
+      return !isKMSRole
+        ? service.type !== "tag" && service.type !== "kms"
+        : service.type !== "tag";
+    });
+
+    return sortBy(servicesName, "name")?.map((service) => ({
+      label: service.displayName,
+      value: service.name
+    }));
+  };
+
+  const searchFilterOptions = [
+    {
+      category: "serviceName",
+      label: "Service Name",
+      urlLabel: "serviceName",
+      type: "textoptions",
+      options: getServices
+    },
+    {
+      category: "serviceType",
+      label: "Service Type",
+      urlLabel: "serviceType",
+      type: "textoptions",
+      options: getServiceDefType
+    }
+  ];
+
+  const showMetricDetails = (metrics) => {
+    let metricText = metrics?.metricsText;
+    if (metricText !== undefined) {
+      for (let val in metricText) {
+        return (
+          <>
+            <td>{val}</td>
+            <td>{JSON.stringify(metricText[val])}</td>
+          </>
+        );
+      }
+    } else {
+      return (
+        <td>
+          <div>
+            <center>No Data Founds</center>
+          </div>
+        </td>
+      );
+    }
+  };
+
+  return state.contentLoader ? (
+    <Loader />
+  ) : (
+    <div className="wrap">
+      <React.Fragment>
+        <Row className="mb-2">
+          <Col sm={12}>
+            <div className="searchbox-border">
+              <StructuredFilter
+                key="metrics-audit-search-filter"
+                placeholder="Search for your user metric audits..."
+                options={sortBy(searchFilterOptions, ["label"])}
+                onChange={updateSearchFilter}
+                defaultSelected={state.defaultSearchFilterParams}
+              />
+            </div>
+          </Col>
+        </Row>
+
+        <AuditFilterEntries
+          entries={state.entries}
+          refreshTable={refreshTable}
+        />
+
+        <XATableLayout
+          data={state.tableListingData}
+          columns={columns}
+          loading={state.loader}
+          totalCount={state.entries && state.entries.totalCount}
+          fetchData={fetchMetricInfo}
+          pageCount={state.pageCount}
+          columnSort={true}
+        />
+
+        <Modal show={state.showMetricModal} onHide={closeMetricModal} size="lg">
+          <Modal.Header closeButton>
+            <Modal.Title>
+              <h4>Metrics Details</h4>
+            </Modal.Title>
+          </Modal.Header>
+          <Modal.Body>
+            <Table bordered hover>
+              <thead className="thead-light">
+                <tr>
+                  <th>Name</th>
+                  <th>Value</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr>{showMetricDetails(state.metricData)}</tr>
+              </tbody>
+            </Table>
+          </Modal.Body>
+          <Modal.Footer>
+            <Button variant="primary" size="sm" onClick={closeMetricModal}>
+              OK
+            </Button>
+          </Modal.Footer>
+        </Modal>
+
+        <Modal
+          show={state.showMetricGraphModal}
+          size="lg"
+          onHide={closeMetricGraphModal}
+        >
+          <Modal.Header closeButton>
+            <Modal.Title>
+              <h4>Metric Graph</h4>
+            </Modal.Title>
+          </Modal.Header>
+          <Modal.Body className="overflow-auto p-3 mb-3 mb-md-0 me-md-3">
+            <MetricGraphs metricData={state.metricGraphData} />
+          </Modal.Body>
+          <Modal.Footer>
+            <Button variant="primary" size="sm" onClick={closeMetricGraphModal}>
+              OK
+            </Button>
+          </Modal.Footer>
+        </Modal>
+      </React.Fragment>
+    </div>
+  );
+}
+
+export default MetricsLogs;
diff --git a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/action.js b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/action.js
index 9379f102a..002dae416 100644
--- a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/action.js
+++ b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/action.js
@@ -26,6 +26,8 @@ export const ACTIONS = {
   SHOW_POLICY_MODAL: "show-policy-modal",
   SHOW_ROW_MODAL: "show-row-modal",
   SHOW_SESSION_MODAL: "show-session-modal",
+  SHOW_METRIC_MODAL: "show-metric-modal",
+  SHOW_METRIC_GRAPH_MODAL: "show-metric-graph-modal",
   SHOW_SYNC_TABLE_MODAL: "show-sync-table-modal",
   SET_SECURITY_ZONES: "set-security-zones"
 };
diff --git a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/reducer.js b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/reducer.js
index b1223d4d2..96d3cedf0 100644
--- a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/reducer.js
+++ b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/reducer.js
@@ -65,6 +65,14 @@ export const PLUGIN_STATUS_INITIAL_STATE = {
   ...INITIAL_STATE
 };
 
+export const METRICS_INITIAL_STATE = {
+  ...INITIAL_STATE,
+  showMetricModal: false,
+  metricData: null,
+  showMetricGraphModal: false,
+  metricGraphData: null
+};
+
 export const USERSYNC_INITIAL_STATE = {
   ...INITIAL_STATE,
   showSyncTableModal: false,
@@ -121,6 +129,18 @@ export const reducer = (state, action) => {
         showSessionModal: action.showSessionModal,
         sessionId: action.sessionId
       };
+    case ACTIONS.SHOW_METRIC_MODAL:
+      return {
+        ...state,
+        showMetricModal: action.showMetricModal,
+        metricData: action.metricData
+      };
+    case ACTIONS.SHOW_METRIC_GRAPH_MODAL:
+      return {
+        ...state,
+        showMetricGraphModal: action.showMetricGraphModal,
+        metricGraphData: action.metricGraphData
+      };
     case ACTIONS.SHOW_SYNC_TABLE_MODAL:
       return {
         ...state,
diff --git a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx
index a8e87f8ab..1a1daacb1 100644
--- a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx
+++ b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx
@@ -538,6 +538,17 @@ export const SideBarBody = (props) => {
                       User Sync
                     </NavLink>
                   </li>
+                  <li className="list-group-item">
+                    <NavLink
+                      to="/reports/audit/metric"
+                      onClick={() => {
+                        props.closeCollapse();
+                      }}
+                      className="list-group-item"
+                    >
+                      Metrics
+                    </NavLink>
+                  </li>
                 </React.Fragment>
               )}
             </ul>