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>"--"</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>"--"</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>