{
+ 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 (
+
+
+ No Metrics Data found
+
+
+ );
+ };
+
+ return loader ? (
+
+ ) : (
+ <>
+
+
+
+
+
+ {showHideGraph ? (
+ !isEmpty(hrsMetricsInfo) ? (
+
+
+
+ ) : (
+
+ )
+ ) : !isEmpty(daysMetricsInfo) ? (
+
+
+
+ ) : (
+
+ )}
+ >
+ );
+};
+
+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 0000000000..a901b005f9
--- /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 : --;
+ },
+ disableSortBy: true
+ },
+ {
+ Header: "Service Type",
+ accessor: "serviceType",
+ Cell: (rawValue) => {
+ return rawValue?.value ? rawValue.value : --;
+ },
+ disableSortBy: true
+ },
+ {
+ Header: "Application Type",
+ accessor: "appId",
+ Cell: (rawValue) => {
+ return rawValue?.value ? (
+ {rawValue.value}
+ ) : (
+ "--"
+ );
+ },
+ disableSortBy: true
+ },
+ {
+ Header: "Cluster Name",
+ accessor: "clusterName",
+ Cell: (rawValue) => {
+ return rawValue?.value ? (
+ {rawValue.value}
+ ) : (
+ --
+ );
+ },
+ disableSortBy: true
+ },
+ {
+ Header: "Client IP",
+ accessor: "clientIP",
+ Cell: (rawValue) => {
+ return rawValue?.value ? (
+ {rawValue.value}
+ ) : (
+ "--"
+ );
+ },
+ disableSortBy: true
+ },
+ {
+ Header: "Metrics Graph",
+ accessor: "metricsGraph",
+ Cell: (rawValue) => {
+ return (
+
+
+
+ );
+ },
+ 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 (
+ <>
+ {val} |
+ {JSON.stringify(metricText[val])} |
+ >
+ );
+ }
+ } else {
+ return (
+
+
+ No Data Founds
+
+ |
+ );
+ }
+ };
+
+ return state.contentLoader ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Metrics Details
+
+
+
+
+
+
+ | Name |
+ Value |
+
+
+
+ {showMetricDetails(state.metricData)}
+
+
+
+
+
+
+
+
+
+
+
+ Metric Graph
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+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 9379f102ae..002dae416d 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 b1223d4d2c..96d3cedf07 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 a8e87f8ab3..1a1daacb1b 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
+
+ {
+ props.closeCollapse();
+ }}
+ className="list-group-item"
+ >
+ Metrics
+
+
)}