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

sandeepk318 pushed a commit to branch frontend-refactor
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/frontend-refactor by this push:
     new bca23ad2ad AMBARI-26635 : Ambari Web React: Reduce redundant API calls 
and polling in dashboard, alerts, and services pages (#4177)
bca23ad2ad is described below

commit bca23ad2ad10e1243acb57d79558af1e79907363
Author: Himanshu Maurya <[email protected]>
AuthorDate: Tue Aug 25 13:11:21 2026 +0530

    AMBARI-26635 : Ambari Web React: Reduce redundant API calls and polling in 
dashboard, alerts, and services pages (#4177)
    
    * AMBARI-26635 : Ambari Web React: Reduce redundant API calls and polling 
in dashboard, alerts, and services pages
---
 ambari-web/latest/src/AppLoader.tsx                |   9 +-
 .../latest/src/api/centralizedServiceStateApi.ts   | 104 ++++++----
 ambari-web/latest/src/api/configsApi.ts            |   2 +-
 ambari-web/latest/src/components/Navbar.tsx        |  96 +++------
 .../src/components/Sidebar/RunAllServiceCheck.tsx  |  33 +--
 .../latest/src/hooks/useHDFSConfigUpdater.ts       |  22 +-
 .../latest/src/hooks/useHbaseConfigUpdater.ts      |  35 +---
 ambari-web/latest/src/hooks/useStackVersion.ts     |  66 +-----
 .../latest/src/hooks/useYarnConfigUpdater.ts       |  40 +---
 .../src/screens/Alerts/AlertDefinitionDetails.tsx  |  25 +--
 ambari-web/latest/src/screens/Alerts/Alerts.tsx    |  18 +-
 ambari-web/latest/src/screens/Hosts/details.tsx    |   3 -
 ambari-web/latest/src/screens/Services/Actions.tsx |  48 ++---
 .../latest/src/screens/Services/ServiceSummary.tsx | 113 +++++------
 ambari-web/latest/src/store/AlertsContext.tsx      | 221 +++++++++++++++++++++
 ambari-web/latest/src/store/ServiceContext.tsx     |  10 +-
 ambari-web/latest/src/store/context.tsx            |  85 +++++++-
 pom.xml                                            |   1 +
 18 files changed, 525 insertions(+), 406 deletions(-)

diff --git a/ambari-web/latest/src/AppLoader.tsx 
b/ambari-web/latest/src/AppLoader.tsx
index 8007a21bca..f08df1d10e 100644
--- a/ambari-web/latest/src/AppLoader.tsx
+++ b/ambari-web/latest/src/AppLoader.tsx
@@ -20,6 +20,7 @@ import { useContext, useEffect, useState } from "react";
 import { Navigate, Outlet, useLocation, useNavigate } from "react-router-dom";
 import { Alert, Button, ProgressBar } from "react-bootstrap";
 import { AppContext, AppProvider } from "./store/context";
+import { AlertsProvider } from "./store/AlertsContext";
 import { ModalProvider } from "./store/ModalContext";
 import { useAuth } from "./hooks/useAuth";
 import useAuthorizationPolicy from "./hooks/useAuthorizationPolicy";
@@ -107,9 +108,11 @@ export function AuthenticatedApplication() {
 
   return (
     <AppProvider>
-      <ModalProvider>
-        <ApplicationLoader />
-      </ModalProvider>
+      <AlertsProvider>
+        <ModalProvider>
+          <ApplicationLoader />
+        </ModalProvider>
+      </AlertsProvider>
     </AppProvider>
   );
 }
diff --git a/ambari-web/latest/src/api/centralizedServiceStateApi.ts 
b/ambari-web/latest/src/api/centralizedServiceStateApi.ts
index 3c3d03ed32..29b1da8c62 100644
--- a/ambari-web/latest/src/api/centralizedServiceStateApi.ts
+++ b/ambari-web/latest/src/api/centralizedServiceStateApi.ts
@@ -33,11 +33,68 @@ class CentralizedServiceStateApi {
   private pendingRequest: Promise<Map<string, ServiceStateData>> | null = null;
 
   /**
-   * Fetches all service states and alerts in a single API call (Ember.js 
pattern)
-   * This replaces individual ServiceApi.getServiceState() calls
+   * Calculate alert counts per service from alert summary and definitions 
(EmberJS pattern)
+   * Matches alert_definition_summary_mapper.js logic
+   *
+   * @param alertSummary - Alert summary with grouped alerts (by definition_id)
+   * @param alertDefinitions - Alert definitions with service_name mapping
+   */
+  private calculateServiceAlertCounts(
+    alertSummary?: { alerts_summary_grouped: any[] },
+    alertDefinitions?: any[]
+  ): Map<string, { alertsCount: number, hasCriticalAlerts: boolean }> {
+    const serviceAlerts = new Map<string, { alertsCount: number, 
hasCriticalAlerts: boolean }>();
+
+    if (!alertSummary?.alerts_summary_grouped || !alertDefinitions) {
+      return serviceAlerts;
+    }
+
+    // Create map of definition_id -> service_name
+    const definitionIdToService = new Map<number, string>();
+    alertDefinitions.forEach((def: any) => {
+      if (def.id && def.service_name) {
+        definitionIdToService.set(def.id, def.service_name);
+      }
+    });
+
+    // Group alerts by service_name and count CRITICAL + WARNING
+    alertSummary.alerts_summary_grouped.forEach((alert: any) => {
+      const definitionId = alert.definition_id;
+      if (!definitionId) return;
+
+      const serviceName = definitionIdToService.get(definitionId);
+      if (!serviceName) return;
+
+      const criticalCount = alert.summary?.CRITICAL?.count || 0;
+      const warningCount = alert.summary?.WARNING?.count || 0;
+      const totalCount = criticalCount + warningCount;
+      const hasCritical = criticalCount > 0;
+
+      if (!serviceAlerts.has(serviceName)) {
+        serviceAlerts.set(serviceName, { alertsCount: 0, hasCriticalAlerts: 
false });
+      }
+
+      const current = serviceAlerts.get(serviceName)!;
+      current.alertsCount += totalCount;
+      current.hasCriticalAlerts = current.hasCriticalAlerts || hasCritical;
+    });
+
+    return serviceAlerts;
+  }
+
+  /**
+   * Fetches service states and calculates alert counts (EmberJS pattern)
+   *
+   * Alert counts come from /alerts?format=groupedSummary via AlertsContext 
(useAlerts hook),
+   * not from a separate /alerts API call here - alertSummary/alertDefinitions 
carry that data.
+   *
    * REQUEST DEDUPLICATION: If a request is already in progress, return the 
pending promise
    */
-  async fetchAllServiceStatesAndAlerts(clusterName: string): 
Promise<Map<string, ServiceStateData>> {
+  async fetchAllServiceStatesAndAlerts(
+    clusterName: string,
+    alertSummary?: { alerts_summary_grouped: any[] },
+    alertDefinitions?: any[]
+  ): Promise<Map<string, ServiceStateData>> {
     const now = Date.now();
 
     // Return cached data if still fresh
@@ -57,52 +114,21 @@ class CentralizedServiceStateApi {
           method: "GET",
         });
 
-        // Get alerts with proper maintenance state filtering (following Ember 
pattern)
-        // This API call will return NO items for services/components in 
maintenance mode
-        const alertsResponse = await ambariApi.request({
-          url: 
`/clusters/${clusterName}/alerts?fields=Alert/service_name,Alert/state&Alert/state.in(CRITICAL,WARNING)&Alert/maintenance_state.in(OFF)&minimal_response=true`,
-          method: "GET",
-        });
-
         const newCache = new Map<string, ServiceStateData>();
 
-        // Count alerts per service - API already filters out maintenance mode 
alerts
-        const serviceAlertsCount: { [key: string]: { critical: number; 
warning: number } } = {};
-
-        alertsResponse.data.items?.forEach((alert: any) => {
-          const serviceName = alert.Alert?.service_name;
-          const alertState = alert.Alert?.state;
-
-          if (serviceName && alertState) {
-            if (!serviceAlertsCount[serviceName]) {
-              serviceAlertsCount[serviceName] = { critical: 0, warning: 0 };
-            }
-
-            if (alertState === 'CRITICAL') {
-              serviceAlertsCount[serviceName].critical++;
-            } else if (alertState === 'WARNING') {
-              serviceAlertsCount[serviceName].warning++;
-            }
-          }
-        });
+        const serviceAlertCounts = 
this.calculateServiceAlertCounts(alertSummary, alertDefinitions);
 
         response.data.items?.forEach((service: any) => {
           const serviceName = service.ServiceInfo.service_name;
           const state = service.ServiceInfo.state;
 
-          // Use API-filtered alert counts (already excludes maintenance mode 
alerts)
-          const serviceAlerts = serviceAlertsCount[serviceName] || { critical: 
0, warning: 0 };
-          const criticalAlerts = serviceAlerts.critical;
-          const warningAlerts = serviceAlerts.warning;
-
-          const alertsCount = criticalAlerts + warningAlerts;
-          const hasCriticalAlerts = criticalAlerts > 0;
+          const alertData = serviceAlertCounts.get(serviceName) || { 
alertsCount: 0, hasCriticalAlerts: false };
 
           newCache.set(serviceName, {
             serviceName,
             state,
-            alertsCount,
-            hasCriticalAlerts,
+            alertsCount: alertData.alertsCount,
+            hasCriticalAlerts: alertData.hasCriticalAlerts,
           });
         });
 
@@ -114,7 +140,7 @@ class CentralizedServiceStateApi {
 
         return this.cache;
       } catch (error) {
-        console.error('Error fetching service states and alerts:', error);
+        console.error('Error fetching service states:', error);
         // Return existing cache on error
         return this.cache;
       } finally {
diff --git a/ambari-web/latest/src/api/configsApi.ts 
b/ambari-web/latest/src/api/configsApi.ts
index 2838e3674f..1fd3d5fe33 100644
--- a/ambari-web/latest/src/api/configsApi.ts
+++ b/ambari-web/latest/src/api/configsApi.ts
@@ -26,7 +26,7 @@ const ConfigsApi = {
     verison: string,
     services: string
   ) {
-    const url = 
`stacks/${stack}/versions/${verison}/services?StackServices/service_name.in(${services})&fields=configurations/*,configurations/dependencies/*,StackServices/config_types/*`;
+    const url = 
`stacks/${stack}/versions/${verison}/services?StackServices/service_name.in(${services})&fields=configurations/*,configurations/dependencies/*,StackServices/config_types/*,StackServices/service_check_supported`;
     const response = await ambariApi.request({
       url: url,
       method: "GET",
diff --git a/ambari-web/latest/src/components/Navbar.tsx 
b/ambari-web/latest/src/components/Navbar.tsx
index d23d87b090..02b762484f 100644
--- a/ambari-web/latest/src/components/Navbar.tsx
+++ b/ambari-web/latest/src/components/Navbar.tsx
@@ -36,12 +36,9 @@ import {
   faMedkit,
 } from "@fortawesome/free-solid-svg-icons";
 import { AppContext } from "../store/context.tsx";
-import { Notifications } from "../screens/Alerts/types";
 import { useLocation, useNavigate } from "react-router-dom";
 import AmbariAboutModal from "../AmbariAboutModal.tsx";
 import "../styles/app.scss";
-import usePolling from "../hooks/usePolling.ts";
-import { AlertsApi } from "../api/alertsApi.ts";
 import {
   classicExperienceUrl,
   redirectToAdminView,
@@ -50,6 +47,7 @@ import modalManager from "../store/ModalManager.ts";
 import BackgroundOperations from "../screens/BackgroundOperations/index.tsx";
 import { useCallback } from "react";
 import { useAuth } from "../hooks/useAuth";
+import { useAlerts } from "../store/AlertsContext";
 import useAuthorizationPolicy from "../hooks/useAuthorizationPolicy";
 import { clusterNavigationEnabled } from "../Utils/authPolicy";
 import { openViewInstance, ViewInstance } from "../Utils/viewUtils";
@@ -85,27 +83,30 @@ export default function NavBar({
   hostname,
   enableDigitalClock,
 }: NavBarProps) {
-  const [notifications, setNotifications] = useState<Notifications[]>([]);
   const [showUserSettingsModal, setShowUserSettingsModal] = useState(false);
-  const [filteredNotifications, setFilteredNotifications] = useState<
-    Notifications[]
-  >([]);
+  const [filteredNotifications, setFilteredNotifications] = 
useState<any[]>([]);
   const [alertCounts, setAlertCounts] = useState({
     all: 0,
     critical: 0,
     warning: 0,
   });
+
+  // FOLLOWING EMBERJS PATTERN: Get alert data from useAlerts hook (WebSocket 
updates)
+  // EmberJS: mainAlertDefinitionsController.unhealthyAlertInstancesCount
+  // - Computed from [email protected] (App.AlertDefinition.find())
+  // - Updated via WebSocket /events/alerts
+  // - NO POLLING for navbar alerts
+  const { unhealthyAlertInstances } = useAlerts();
+
   const location = useLocation();
   const [selectedFilter, setSelectedFilter] = useState("all");
   const {
     clusterName,
-    cluster,
     serverClock,
     userTimezone,
   } = useContext(AppContext);
   const [showAmbariAboutModal, setShowAmbariAboutModal] = useState(false);
-  
-  const isClusterInstalled = cluster?.provisioning_state === "INSTALLED";
+
   const navigate = useNavigate();
 
   const clusterNavigation = clusterNavigationEnabled(
@@ -113,74 +114,43 @@ export default function NavBar({
     location.pathname,
   );
 
-  const fetchAlerts = async () => {
-    // TLHASD-745: Only fetch alerts if cluster is installed
-    if (!clusterName) {
-      pausePolling(); // Pause polling if clusterName is not available
-      return;
-    } else {
-      resumePolling(); // Resume polling if clusterName is available
-    }
-    
-    if (!isClusterInstalled) {
-      pausePolling(); // Pause polling if cluster is not installed
-      return;
-    }
-    
-    resumePolling(); // Resume polling if clusterName is available and cluster 
is installed
-    
-    try {
-      const fields =
-        
"Alert/component_name,Alert/definition_id,Alert/definition_name,Alert/host_name,Alert/id,Alert/instance,Alert/label,Alert/latest_timestamp,Alert/maintenance_state,Alert/original_timestamp,Alert/scope,Alert/service_name,Alert/state,Alert/text,Alert/repeat_tolerance,Alert/repeat_tolerance_remaining&Alert/state.in(CRITICAL,WARNING)&Alert/maintenance_state.in(OFF)&from=0&page_size=100";
-      const time = Date.now();
-      const data = await AlertsApi.getAlertsNotifications(
-        clusterName,
-        fields,
-        time
-      );
-      setNotifications(data.items);
-      calculateAlertCounts(data.items);
-    } catch (error) {
-      console.error("Error fetching alerts:", error);
-    }
-  };
-
-  const { stopPolling, pausePolling, resumePolling } = usePolling(
-    fetchAlerts,
-    30000
-  );
+  // REMOVED fetchAlerts and polling - using WebSocket data from useAlerts hook
+  // EmberJS pattern: navbar alerts come from mainAlertDefinitionsController 
computed properties
+  // which are based on alert definitions summary (updated via WebSocket 
/events/alerts)
 
+  // Update local state when unhealthyAlertInstances changes (from WebSocket)
   useEffect(() => {
-    if (clusterName && isClusterInstalled) {
-      fetchAlerts();
+    if (unhealthyAlertInstances && unhealthyAlertInstances.length > 0) {
+      calculateAlertCounts(unhealthyAlertInstances);
+    } else {
+      setAlertCounts({ all: 0, critical: 0, warning: 0 });
+      setFilteredNotifications([]);
     }
-  }, [clusterName, isClusterInstalled]);
+  }, [unhealthyAlertInstances]);
 
   useEffect(() => {
     filterNotifications();
-  }, [selectedFilter, notifications]);
+  }, [selectedFilter, unhealthyAlertInstances]);
 
-  const calculateAlertCounts = (alerts: Notifications[]) => {
+  const calculateAlertCounts = (alerts: any[]) => {
     const counts = { all: alerts.length, critical: 0, warning: 0 };
     alerts.forEach((alert) => {
-      if (alert.Alert.state === "CRITICAL") counts.critical++;
-      if (alert.Alert.state === "WARNING") counts.warning++;
+      const state = alert.Alert?.state;
+      if (state === "CRITICAL") counts.critical++;
+      if (state === "WARNING") counts.warning++;
     });
     setAlertCounts(counts);
   };
 
-  useEffect(() => {
-    filterNotifications();
-  }, [selectedFilter, notifications]);
-
   const filterNotifications = () => {
+    const alerts = unhealthyAlertInstances || [];
     if (selectedFilter === "all") {
-      setFilteredNotifications(notifications);
+      setFilteredNotifications(alerts);
     } else {
       setFilteredNotifications(
-        notifications.filter(
-          (notification) =>
-            notification.Alert.state === selectedFilter.toUpperCase()
+        alerts.filter(
+          (notification: any) =>
+            notification.Alert?.state === selectedFilter.toUpperCase()
         )
       );
     }
@@ -195,10 +165,10 @@ export default function NavBar({
   const { isAuthorized } = useAuthorizationPolicy();
 
   const handleSignOut = useCallback(async () => {
-    stopPolling();
+    // REMOVED stopPolling() - no longer polling for navbar alerts (using 
WebSocket)
     await logout();
     navigate("/login", { replace: true });
-  }, [logout, navigate, stopPolling]);
+  }, [logout, navigate]);
 
   const canManageAmbari = isAuthorized(
     "AMBARI.ADD_DELETE_CLUSTERS, AMBARI.ASSIGN_ROLES, AMBARI.EDIT_STACK_REPOS, 
AMBARI.MANAGE_GROUPS, AMBARI.MANAGE_STACK_VERSIONS, AMBARI.MANAGE_USERS, 
AMBARI.MANAGE_VIEWS, AMBARI.RENAME_CLUSTER"
diff --git a/ambari-web/latest/src/components/Sidebar/RunAllServiceCheck.tsx 
b/ambari-web/latest/src/components/Sidebar/RunAllServiceCheck.tsx
index 2d074cb553..e0885e8d1c 100644
--- a/ambari-web/latest/src/components/Sidebar/RunAllServiceCheck.tsx
+++ b/ambari-web/latest/src/components/Sidebar/RunAllServiceCheck.tsx
@@ -28,10 +28,9 @@ import { ActionsApi } from "../../api/actionsApi.ts";
 import BackgroundOperations from "../../screens/BackgroundOperations";
 import modalManager from "../../store/ModalManager.ts";
 import { get, map } from "lodash";
-import { ServiceApi } from "../../api/serviceApi.ts";
 
 const RunAllServiceCheck = () => {
-  const { clusterName, services, cluster } = useContext(AppContext);
+  const { clusterName, services, upgradeIsRunning, upgradeSuspended, 
serviceCheckSupportedMap } = useContext(AppContext);
   const { allServiceModels } = useContext(ServiceContext);
   const { isAuthorized } = useAuthorizationPolicy();
   const [showConfirmation, setShowConfirmation] = useState(false);
@@ -39,14 +38,13 @@ const RunAllServiceCheck = () => {
 
   const canRunServiceCheck = isAuthorized("SERVICE.RUN_SERVICE_CHECK");
 
-  if (!canRunServiceCheck) {
+  // Block during active upgrade (not suspended)
+  const isUpgradeBlocking = upgradeIsRunning && !upgradeSuspended;
+
+  if (!canRunServiceCheck || isUpgradeBlocking) {
     return null;
   }
 
-  const stackInfo = get(cluster, "version", "").split("-");
-  const stackName = stackInfo[0];
-  const stackVersion = stackInfo[1];
-
   const runAllServiceChecks = async () => {
     setIsRunning(true);
     setShowConfirmation(false);
@@ -57,28 +55,11 @@ const RunAllServiceCheck = () => {
       // Build array of service check promises to execute in parallel
       const serviceCheckPromises = installedServiceNames.map(async 
(serviceName) => {
         try {
-          // Check if service supports service check
-          let isServiceCheckSupported = false;
-          try {
-            const response = await ServiceApi.isServiceCheckSupported(
-              clusterName,
-              serviceName,
-              stackName,
-              stackVersion
-            );
-            isServiceCheckSupported = get(
-              response.data,
-              "StackServices.service_check_supported",
-              false
-            );
-          } catch (error) {
-            console.error(`Error checking service check support for 
${serviceName}`, error);
-            return null; // Skip this service if we can't determine support
-          }
+          // Use cached service_check_supported from initial stack configs 
fetch (no per-service API call)
+          const isServiceCheckSupported = 
serviceCheckSupportedMap[serviceName] || false;
 
           // Skip if service doesn't support service check
           if (!isServiceCheckSupported) {
-            console.log(`Skipping ${serviceName} - service check not 
supported`);
             return null;
           }
 
diff --git a/ambari-web/latest/src/hooks/useHDFSConfigUpdater.ts 
b/ambari-web/latest/src/hooks/useHDFSConfigUpdater.ts
index 88937ed2c6..2a37d84c2a 100644
--- a/ambari-web/latest/src/hooks/useHDFSConfigUpdater.ts
+++ b/ambari-web/latest/src/hooks/useHDFSConfigUpdater.ts
@@ -52,13 +52,12 @@ export const useHDFSConfigUpdater = () => {
   const { configsData } = useHDFSConfigsTags();
 
   // @ts-ignore
-  const { clusterName } = useContext(AppContext);
+  const { clusterName, clockDistance } = useContext(AppContext);
   // @ts-ignore
   const { parsedSocketMessages } = useContext(AppContext);
 
   // @ts-ignore
   const { allServiceModels, updateRegistry } = useContext(ServiceContext);
-  const [serverClockTime, setServerClockTime] = useState<any>();
   //const vdpStackVersion = get(cluster, "version", "").split("-")[1];
   const hasNameNodeHAEnabledUseEffectRunOnce = useRef(false);
   const [isHAEnabledForNamenode, setIsHAEnabledForNamenode] = useState(false);
@@ -143,17 +142,6 @@ export const useHDFSConfigUpdater = () => {
       updateRegistry(allServiceModels);
     }
   };
-  const fetchServerCLockTime = async () => {
-    const fields = "?fields=RootServiceComponents/server_clock";
-    const responseData = await ServiceApi.ambariService(fields);
-    const serverClock = get(
-      responseData,
-      "RootServiceComponents.server_clock",
-      null
-    );
-    setServerClockTime(serverClock);
-  };
-
   function inferNamespace() {
     const hdfsModel = cloneDeep(allServiceModels["hdfs"]);
     const isHAEnabled = hdfsModel?.isNameNodeHaEnabled;
@@ -523,12 +511,6 @@ export const useHDFSConfigUpdater = () => {
   const calclateNamenodeUptime = (startTime: number) => {
     const currentConfig = cloneDeep(allServiceModels["hdfs"]);
     const hdfsServiceObj = currentConfig.getServiceObject();
-    let clientClock = Date.now();
-
-    let serverClock = serverClockTime;
-    serverClock = serverClock.toString();
-    serverClock = serverClock.length < 13 ? serverClock + "000" : serverClock;
-    const clockDistance = serverClock - clientClock;
     const uptime = startTime;
     if (uptime && uptime > 0) {
       const appDateTime = Date.now() + clockDistance;
@@ -1171,7 +1153,6 @@ export const useHDFSConfigUpdater = () => {
       updateHDFSMasterComponents();
       findMasterSlaveClientComponents();
       calcDiskUsagePartandPercent();
-      fetchServerCLockTime();
     }
   }, [polledHostComponentsData]);
 
@@ -1187,7 +1168,6 @@ export const useHDFSConfigUpdater = () => {
   }, [masterSlaveClientsData, clusterName]);
 
   useEffect(() => {
-    fetchServerCLockTime();
     //findMasterSlaveClientComponents();
     updateWorkStatusValues();
   }, []);
diff --git a/ambari-web/latest/src/hooks/useHbaseConfigUpdater.ts 
b/ambari-web/latest/src/hooks/useHbaseConfigUpdater.ts
index e1389a2964..2f5e3de754 100644
--- a/ambari-web/latest/src/hooks/useHbaseConfigUpdater.ts
+++ b/ambari-web/latest/src/hooks/useHbaseConfigUpdater.ts
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { useContext, useEffect, useState } from "react";
+import { useContext, useEffect } from "react";
 import { cloneDeep, find, get, isEmpty, isEqual } from "lodash";
 import { ServiceApi } from "../api/serviceApi";
 import { cachedServiceApi } from "../api/cachedServiceApi";
@@ -38,30 +38,18 @@ export const useHbaseConfigUpdater = () => {
   } = useContext(ServiceContext);
   
   // @ts-ignore
-  const { services, clusterName, parsedSocketMessages } = 
useContext(AppContext);
-  
+  const { services, clusterName, parsedSocketMessages, clockDistance } = 
useContext(AppContext);
+
   // Early return if HBASE service is not installed
-  const isHbaseInstalled = services && Array.isArray(services) && 
+  const isHbaseInstalled = services && Array.isArray(services) &&
     services.some((service: any) => service.ServiceInfo.service_name === 
"HBASE");
-  
+
   if (!isHbaseInstalled) {
     return;
   }
-  
+
   //@ts-ignore
   const { allServiceModels, updateRegistry } = useContext(ServiceContext);
-  const [serverClockTime, setServerClockTime] = useState<any>();
-
-  const fetchServerCLockTime = async () => {
-    const fields = "?fields=RootServiceComponents/server_clock";
-    const responseData = await ServiceApi.ambariService(fields);
-    const serverClock = get(
-      responseData,
-      "RootServiceComponents.server_clock",
-      null
-    );
-    setServerClockTime(serverClock);
-  };
 
   const fetchHbaseMasterSlaveClientsData = async () => {
     let hbaseComponentsData = 
cachedServiceApi.getServiceComponentData("HBASE");
@@ -246,16 +234,6 @@ export const useHbaseConfigUpdater = () => {
   const calculateHbaseMasterUptime = (startOrActiveTime: number) => {
     const currentConfig = cloneDeep(allServiceModels["hbase"]);
     const hbaseServiceObj = currentConfig?.getServiceObject();
-    let clientClock = Date.now();
-
-    let serverClock = serverClockTime;
-    if (!serverClock) {
-      return null; // Return null if serverClockTime is not available
-    }
-    
-    serverClock = serverClock.toString();
-    serverClock = serverClock.length < 13 ? serverClock + "000" : serverClock;
-    const clockDistance = serverClock - clientClock;
     const uptime = startOrActiveTime;
     if (uptime && uptime > 0) {
       const appDateTime = Date.now() + clockDistance;
@@ -532,7 +510,6 @@ export const useHbaseConfigUpdater = () => {
 
   useEffect(() => {
     updateHbaseHostComponentsData();
-    fetchServerCLockTime();
     findMasterSlaveClientComponents();
     parseAlertsWebSocketMessages();
   }, []);
diff --git a/ambari-web/latest/src/hooks/useStackVersion.ts 
b/ambari-web/latest/src/hooks/useStackVersion.ts
index 89a0b7a63e..128fd81c37 100644
--- a/ambari-web/latest/src/hooks/useStackVersion.ts
+++ b/ambari-web/latest/src/hooks/useStackVersion.ts
@@ -16,73 +16,11 @@
  * limitations under the License.
  */
 
-import { useContext, useEffect, useState } from "react";
-import VersionsApi from "../api/versionsApi";
-import { forEach, get } from "lodash";
+import { useContext } from "react";
 import { AppContext } from "../store/context";
 
 const useStackVersion = () => {
-  const { clusterName } = useContext(AppContext);
-  const [stackVersion, setStackVersion] = useState();
-  const [stackVersionList, setStackVersionList] = useState([]);
-
-  const getStackVersion = async () => {
-    const response = await VersionsApi.getServices(clusterName);
-    setStackVersion(response);
-  };
-
-  useEffect(() => {
-    getStackVersion();
-  }, []);
-
-  useEffect(() => {
-    if (stackVersion) {
-      let stackVersionListCopy: any = [];
-      forEach(get(stackVersion, "items", []), (item: any) => {
-        const repoVersionId = get(
-          item,
-          "ClusterStackVersions.repository_version"
-        );
-        const repoVersion = get(item, "repository_versions", []).find(
-          (repo: any) => get(repo, "RepositoryVersions.id") === repoVersionId
-        )?.RepositoryVersions?.repository_version;
-        stackVersionListCopy.push({
-          id: get(item, "ClusterStackVersions.id"),
-          cluster_name: get(item, "ClusterStackVersions.cluster_name"),
-          stack: get(item, "ClusterStackVersions.stack"),
-          version: get(item, "ClusterStackVersions.version"),
-          state: get(item, "ClusterStackVersions.state"),
-          displayName: get(item, 
"repository_versions.[0].RepositoryVersions.display_name"),
-          not_installed_hosts: get(
-            item,
-            "ClusterStackVersions.host_states.NOT_REQUIRED"
-          ),
-          installing_hosts: get(
-            item,
-            "ClusterStackVersions.host_states.INSTALLING"
-          ),
-          installed_hosts: get(
-            item,
-            "ClusterStackVersions.host_states.INSTALLED"
-          ),
-          install_failed_hosts: get(
-            item,
-            "ClusterStackVersions.host_states.INSTALL_FAILED"
-          ),
-          out_of_sync_hosts: get(
-            item,
-            "ClusterStackVersions.host_states.OUT_OF_SYNC"
-          ),
-          current_hosts: get(item, "ClusterStackVersions.host_states.CURRENT"),
-          supports_revert: get(item, "ClusterStackVersions.supports_revert"),
-          repository_version_id: repoVersionId,
-          repository_version: repoVersion,
-        });
-      });
-      setStackVersionList(stackVersionListCopy);
-    }
-  }, [stackVersion]);
-
+  const { stackVersion, stackVersionList } = useContext(AppContext);
   return { stackVersion, stackVersionList };
 };
 
diff --git a/ambari-web/latest/src/hooks/useYarnConfigUpdater.ts 
b/ambari-web/latest/src/hooks/useYarnConfigUpdater.ts
index 55e98738cd..816ae3e71b 100644
--- a/ambari-web/latest/src/hooks/useYarnConfigUpdater.ts
+++ b/ambari-web/latest/src/hooks/useYarnConfigUpdater.ts
@@ -16,9 +16,8 @@
  * limitations under the License.
  */
 
-import { useContext, useEffect, useRef, useState } from "react";
+import { useContext, useEffect, useRef } from "react";
 import { cloneDeep, find, get, isEmpty, isEqual } from "lodash";
-import { ServiceApi } from "../api/serviceApi";
 import { cachedServiceApi } from "../api/cachedServiceApi";
 import { centralizedServiceStateApi } from "../api/centralizedServiceStateApi";
 import { ServiceComponentMetricsEnums } from 
"../enums/ServiceComponentMetricsEnums";
@@ -37,32 +36,20 @@ export const useYarnConfigUpdater = () => {
   } = useContext(ServiceContext);
   
   // @ts-ignore
-  const { services, clusterName, parsedSocketMessages } = 
useContext(AppContext);
-  
+  const { services, clusterName, parsedSocketMessages, clockDistance } = 
useContext(AppContext);
+
   // Early return if YARN service is not installed
-  const isYarnInstalled = services && Array.isArray(services) && 
+  const isYarnInstalled = services && Array.isArray(services) &&
     services.some((service: any) => service.ServiceInfo.service_name === 
"YARN");
-  
+
   if (!isYarnInstalled) {
     return;
   }
 
   // @ts-ignore
   const { allServiceModels, updateRegistry } = useContext(ServiceContext);
-  const [serverClockTime, setServerClockTime] = useState<any>();
   const hasResourceManagerHAEnabledUseEffectRunOnce = useRef(false);
 
-  const fetchServerCLockTime = async () => {
-    const fields = "?fields=RootServiceComponents/server_clock";
-    const responseData = await ServiceApi.ambariService(fields);
-    const serverClock = get(
-      responseData,
-      "RootServiceComponents.server_clock",
-      null
-    );
-    setServerClockTime(serverClock);
-  };
-
   const fetchYARNMasterSlaveClientsData = async () => {
     let yarnComponentsData = cachedServiceApi.getServiceComponentData("YARN");
     
@@ -280,16 +267,6 @@ export const useYarnConfigUpdater = () => {
   const calclateResourceManagereUptime = (startTime: number) => {
     const currentConfig = cloneDeep(allServiceModels["yarn"]);
     const yarnServiceObj = currentConfig.getServiceObject();
-    let clientClock = Date.now();
-
-    let serverClock = serverClockTime;
-    if (!serverClock) {
-      return null; // Return null if serverClockTime is not available
-    }
-    
-    serverClock = serverClock.toString();
-    serverClock = serverClock.length < 13 ? serverClock + "000" : serverClock;
-    const clockDistance = serverClock - clientClock;
     const uptime = startTime;
     if (uptime && uptime > 0) {
       const appDateTime = Date.now() + clockDistance;
@@ -806,7 +783,6 @@ export const useYarnConfigUpdater = () => {
       updateYARNData();
       updateYARNMasterComponents();
       findMasterSlaveClientComponents();
-      fetchServerCLockTime();
       calcDiskUsagePartandPercent();
     }
   }, [polledHostComponentsData]);
@@ -822,12 +798,6 @@ export const useYarnConfigUpdater = () => {
     hasResourceManagerHAEnabledUseEffectRunOnce.current = true;
   }, [allServiceModels]);
 
-  useEffect(() => {
-    fetchServerCLockTime();
-    //isRMAEnabled();
-    //findMasterSlaveClientComponents();
-  }, []);
-
   useEffect(() => {
     updateAlertsAndServiceStateData();
   }, [allServiceModels, serviceStatesData]);
diff --git a/ambari-web/latest/src/screens/Alerts/AlertDefinitionDetails.tsx 
b/ambari-web/latest/src/screens/Alerts/AlertDefinitionDetails.tsx
index 8f6d6719ad..b709749448 100644
--- a/ambari-web/latest/src/screens/Alerts/AlertDefinitionDetails.tsx
+++ b/ambari-web/latest/src/screens/Alerts/AlertDefinitionDetails.tsx
@@ -30,9 +30,12 @@ import { AppContext } from "../../store/context";
 import { AlertEditorHandle, AlertStatusObject, MergedAlert } from './types';
 import { buildAlertDefinitionDetails } from '../../Utils/alertDefinitions';
 import Modal from '../../components/Modal';
+import { useAlerts } from '../../store/AlertsContext';
 
 const AlertDefinitionDetails = () => {
     const { clusterName } = useContext(AppContext);
+    // Alert groups + summary already loaded/kept in sync by AlertsContext - 
no need to refetch them here.
+    const { alertGroups, alertSummary } = useAlerts();
     const params = useParams<{ alertId?: string }>();
     const [isLoaded, setIsLoaded] = useState(false);
     const [alertDefinition, setAlertDefinition] = useState<MergedAlert | 
null>(null);
@@ -106,22 +109,20 @@ const AlertDefinitionDetails = () => {
             try {
                 const alertDefinitionId = params.alertId;
                 if (alertDefinitionId) {
-                    const [groupsResponse, summariesResponse, 
alertDefinitionResponse] = await Promise.all([
-                        AlertsApi.getAlerts(
-                            clusterName,
-                            
'AlertGroup/default,AlertGroup/definitions,AlertGroup/id,AlertGroup/name,AlertGroup/targets',
-                            Date.now()
-                        ),
-                        
AlertsApi.getGroupFormattedAlertsNotifications(clusterName, Date.now()),
-                        AlertsApi.getAlertDefinitionById(clusterName, 
alertDefinitionId, Date.now())
-                    ]);
+                    // Alert groups + summary come from AlertsContext (shared, 
WebSocket-updated) -
+                    // only the full per-definition detail 
(source/config/help_url) needs its own fetch.
+                    const alertDefinitionResponse = await 
AlertsApi.getAlertDefinitionById(
+                        clusterName,
+                        alertDefinitionId,
+                        Date.now(),
+                    );
                     const definition = 
alertDefinitionResponse?.AlertDefinition ||
                         alertDefinitionResponse?.items?.[0]?.AlertDefinition;
                     if (!definition) throw new Error('Alert definition not 
found');
                     const details = buildAlertDefinitionDetails(
                         definition,
-                        groupsResponse?.items || [],
-                        summariesResponse?.alerts_summary_grouped || [],
+                        alertGroups || [],
+                        alertSummary?.alerts_summary_grouped || [],
                     );
                     setAlertDefinition(details);
                     setStatuses(details.statuses);
@@ -137,7 +138,7 @@ const AlertDefinitionDetails = () => {
         };
 
         fetchAlertDetails();
-    }, [params.alertId, clusterName, retryTrigger]);
+    }, [params.alertId, clusterName, retryTrigger, alertGroups, alertSummary]);
 
     if (!isLoaded) {
         return <Spinner />;
diff --git a/ambari-web/latest/src/screens/Alerts/Alerts.tsx 
b/ambari-web/latest/src/screens/Alerts/Alerts.tsx
index c38ded03ad..e8c85c65a3 100644
--- a/ambari-web/latest/src/screens/Alerts/Alerts.tsx
+++ b/ambari-web/latest/src/screens/Alerts/Alerts.tsx
@@ -37,7 +37,6 @@ import {SortingState} from "@tanstack/react-table";
 import MenuBar from './MenuBar'
 import {formatAlertStatusDisplay} from "./alertStatus";
 import useAuthorizationPolicy from '../../hooks/useAuthorizationPolicy';
-import usePolling from '../../hooks/usePolling';
 
 const DEFAULT_ALERT_SORTING: SortingState = [{ id: 'statuses', desc: true }];
 
@@ -69,7 +68,7 @@ const Alerts = () => {
     const [alertDefinitions, setAlertDefinitions] = 
useState<AlertDefinition[]>([]);
     const [searchFilters, setSearchFilters] = 
useState<SearchFilter[]>(initialViewState.searchFilters);
     const [filteredAlerts, setFilteredAlerts] = useState<MergedAlert[]>([]);
-    const [isModalOpen, setIsModalOpen] = useState(false);
+    const [, setIsModalOpen] = useState(false);
     const [listLoadError, setListLoadError] = useState('');
     const [definitionLoadError, setDefinitionLoadError] = useState('');
     const listRequestId = useRef(0);
@@ -177,11 +176,11 @@ const Alerts = () => {
         }
     }, [clusterName]);
 
-    // Use usePolling hook with pause/resume based on modal state
-    const { pausePolling, resumePolling } = usePolling(fetchData, 30000);
-
+    // Load once on mount - alert data updates are pushed via WebSocket 
(/events/alerts),
+    // not polled, matching the EmberJS pattern (no independent 30s poll per 
page).
     useEffect(() => {
         if (clusterName) {
+            fetchData();
             fetchAlertDefinitions();
         }
     }, [clusterName, fetchAlertDefinitions]);
@@ -199,15 +198,6 @@ const Alerts = () => {
         }));
     }, [clusterName, searchFilters, sorting]);
 
-    // Control polling based on modal state
-    useEffect(() => {
-        if (isModalOpen) {
-            pausePolling();
-        } else {
-            resumePolling();
-        }
-    }, [isModalOpen, pausePolling, resumePolling]);
-
     // Fetch alert groups when needed
 
     const handleSearch = (filters: SearchFilter[]) => {
diff --git a/ambari-web/latest/src/screens/Hosts/details.tsx 
b/ambari-web/latest/src/screens/Hosts/details.tsx
index 81b40d9a8a..07bc3bdcdd 100644
--- a/ambari-web/latest/src/screens/Hosts/details.tsx
+++ b/ambari-web/latest/src/screens/Hosts/details.tsx
@@ -196,9 +196,6 @@ const hostPassiveModeRequest = async (
 
 const updateHost = (state: string) => {
   infoPassiveState(state);
-  // setTimeout(() => {
-  //   window.location.reload();
-  // }, 2000);
 };
 
 const doStartAllComponents = (context: any) => {
diff --git a/ambari-web/latest/src/screens/Services/Actions.tsx 
b/ambari-web/latest/src/screens/Services/Actions.tsx
index e61b6c17e3..20e0db7da6 100644
--- a/ambari-web/latest/src/screens/Services/Actions.tsx
+++ b/ambari-web/latest/src/screens/Services/Actions.tsx
@@ -167,10 +167,11 @@ const ActionsContent = ({ serviceName, className }: 
ActionsProps) => {
     isClusterInstalled,
     supports,
     wizardIsNotFinished,
+    serviceCheckSupportedMap,
     backgroundOperations,
     fetchBackgroundOperationsSnapshot,
   } = useContext(AppContext);
-  const { allServiceModels } = useContext(ServiceContext);
+  const { allServiceModels, serviceStatesData } = useContext(ServiceContext);
 
   // Authorization hooks - implementing Ember.js App.isAuthorized patterns
   const { havePermissions, isAuthorized } = useAuthorizationPolicy();
@@ -389,46 +390,31 @@ const ActionsContent = ({ serviceName, className }: 
ActionsProps) => {
       return false;
     }
 
-    async function fetchServiceState() {
+    // OPTIMIZED: Use cached service state data instead of making individual 
API call
+    // EmberJS uses App.Service.find() which loads from Ember Data store
+    // Modern UI now uses serviceStatesData from ServiceContext (centralized 
polling)
+    // This eliminates unnecessary /services/{serviceName} API calls
+    function loadServiceStateFromCache() {
       if (
         clusterName &&
         serviceName &&
         installedServiceNames.includes(serviceName)
       ) {
-        const response = await ServiceApi.getServiceState(
-          clusterName,
-          serviceName.toUpperCase()
-        );
-        setServiceState(response.data.ServiceInfo);
+        // Get service state from centralized cache (already being polled)
+        const cachedServiceState = 
serviceStatesData.get(serviceName.toUpperCase());
+        if (cachedServiceState) {
+          setServiceState(cachedServiceState);
+        }
       }
     }
 
-    async function fetchServiceCheckSupported() {
+    function fetchServiceCheckSupported() {
       if (!allServiceModels || 
!allServiceModels[serviceNameModelMapping[serviceName]]) {
         return;
       }
 
-      // Match Ember.js logic exactly: check if service supports service check 
from stack definition
-      let isServiceCheckSupportedFromStack = false;
-      
-      try {
-        const response = await ServiceApi.isServiceCheckSupported(
-          clusterName,
-          //@ts-ignore
-          serviceName,
-          stackName,
-          stackVersion
-        );
-        isServiceCheckSupportedFromStack = get(
-          response.data,
-          "StackServices.service_check_supported",
-          false
-        );
-      } catch (error) {
-        console.error("Error fetching service check support", error);
-        // Default to false if we can't determine support
-        isServiceCheckSupportedFromStack = false;
-      }
+      // Use cached service_check_supported from initial stack configs fetch 
(like Ember's App.services.supportsServiceCheck)
+      const isServiceCheckSupportedFromStack = 
serviceCheckSupportedMap[serviceName] || false;
 
       // Apply Ember.js isSmokeTestDisabled logic
       let isSmokeTestDisabled = false;
@@ -452,9 +438,9 @@ const ActionsContent = ({ serviceName, className }: 
ActionsProps) => {
     }
 
     //fetchClusterName();
-    fetchServiceState();
+    loadServiceStateFromCache(); // OPTIMIZED: Load from cache instead of API 
call
     fetchServiceCheckSupported();
-  }, [serviceName, clusterName, allServiceModels, serviceModels]);
+  }, [serviceName, clusterName, allServiceModels, serviceModels, 
serviceStatesData]);
 
   useEffect(() => {
     const message = socketMessages[0];
diff --git a/ambari-web/latest/src/screens/Services/ServiceSummary.tsx 
b/ambari-web/latest/src/screens/Services/ServiceSummary.tsx
index 47b4c57efc..90064205ac 100644
--- a/ambari-web/latest/src/screens/Services/ServiceSummary.tsx
+++ b/ambari-web/latest/src/screens/Services/ServiceSummary.tsx
@@ -22,13 +22,10 @@ import ServiceComponents from "./ServiceComponents";
 import ServiceMetrics from "./ServiceMetrics";
 import OptimizedServiceQuicklinks from "./OptimizedServiceQuicklinks";
 import HDFSFederationSummary from "./HDFSFederationSummary";
-import { filter, find, get, isNumber, isObject, map } from "lodash";
-import { AlertsApi } from "../../api/alertsApi";
+import { find, isNumber, isObject } from "lodash";
 import { useContext, useEffect, useState } from "react";
-import { AppContext } from "../../store/context";
 import { ServiceContext } from "../../store/ServiceContext";
-import usePolling from "../../hooks/usePolling";
-import { centralizedServiceStateApi } from 
"../../api/centralizedServiceStateApi";
+import { useAlerts } from "../../store/AlertsContext";
 
 type SummaryProps = {
   serviceName: string;
@@ -37,9 +34,15 @@ type SummaryProps = {
 function ServiceSummary({ serviceName, selectedTab }: SummaryProps) {
   const [alerts, setAlerts] = useState<any>([]);
   const [alertsCount, setAlertsCount] = useState<number>(0);
-  const { clusterName, isClusterInstalled } = useContext(AppContext);
   const { allServiceModels } = useContext(ServiceContext);
 
+  // FOLLOWING EMBERJS PATTERN: Get alert data from useAlerts hook (WebSocket 
updates)
+  // EmberJS: App.AlertDefinition.find() from Ember Data store
+  // - Loaded once via updateAlertDefinitions() and 
updateAlertDefinitionSummary()
+  // - Updated via WebSocket /events/alerts
+  // - NO POLLING on service summary page
+  const { alertDefinitions, alertSummary } = useAlerts();
+
   const STATUS_PRIORITY_ORDER = [
     "CRITICAL",
     "OK",
@@ -48,38 +51,39 @@ function ServiceSummary({ serviceName, selectedTab }: 
SummaryProps) {
     "NONE",
   ];
 
-  async function getAlerts() {
-    const alertDefinitionsFields = 
`AlertDefinition/component_name,AlertDefinition/description,AlertDefinition/enabled,AlertDefinition/repeat_tolerance,AlertDefinition/repeat_tolerance_enabled,AlertDefinition/id,AlertDefinition/ignore_host,AlertDefinition/interval,AlertDefinition/label,AlertDefinition/name,AlertDefinition/scope,AlertDefinition/service_name,AlertDefinition/source,AlertDefinition/help_url`;
-    const alertDefinitions = await AlertsApi.getAlertDefinition(
-      clusterName,
-      alertDefinitionsFields,
-      Date.now()
+  // REMOVED getAlerts() and polling - using WebSocket data from useAlerts hook
+  // EmberJS pattern: service summary uses App.AlertDefinition.find() from 
store
+  // which is populated once and updated via WebSocket /events/alerts
+
+  // Process alerts when alertDefinitions or alertSummary changes (from 
WebSocket)
+  useEffect(() => {
+    if (!alertDefinitions || !alertSummary || !serviceName) {
+      setAlerts([]);
+      return;
+    }
+
+    const allGroupedAlerts = alertSummary?.alerts_summary_grouped || [];
+
+    // Filter alert definitions for this service
+    const alertsForSelectedService = alertDefinitions.filter(
+      (def: any) => def.service_name === serviceName
     );
-    const { items } = alertDefinitions;
-
-    // FIXED: Get ALL alerts (including maintenance mode) to properly display 
them with maintenance styling
-    const { alerts_summary_grouped: allGroupedAlerts } =
-      await AlertsApi.getGroupFormattedAlertsNotifications(clusterName);
-    
-    const alertsForSelectedService = filter(items, [
-      "AlertDefinition.service_name",
-      serviceName,
-    ]);
-
-    const inferredAlerts = map(alertsForSelectedService, (alert: any) => {
+
+    // Map definitions to alerts with summary data
+    const inferredAlerts = alertsForSelectedService.map((alert: any) => {
       const matchingAlert = find(allGroupedAlerts, [
         "definition_id",
-        get(alert, "AlertDefinition.id", ""),
+        alert.id,
       ]);
 
       return {
-        label: get(alert, "AlertDefinition.label", ""),
-        name: get(alert, "AlertDefinition.name", ""),
-        description: get(alert, "AlertDefinition.description", ""),
-        id: get(alert, "AlertDefinition.id", ""),
-        component_name: get(alert, "AlertDefinition.component_name", ""),
+        label: alert.label || "",
+        name: alert.name || "",
+        description: alert.description || "",
+        id: alert.id || "",
+        component_name: alert.component_name || "",
         summary: matchingAlert
-          ? matchingAlert?.summary
+          ? matchingAlert.summary
           : {
               NONE: { count: 1, maintenance_count: 0 },
               CRITICAL: { count: 0, maintenance_count: 0 },
@@ -90,59 +94,46 @@ function ServiceSummary({ serviceName, selectedTab }: 
SummaryProps) {
         highestStatus: "",
       };
     });
+
+    // Determine highest status for each alert
     for (const alert of inferredAlerts) {
       const statuses =
         alert && isObject(alert) && alert.summary
-          ? Object.keys(alert?.summary).map((status: any) => ({
+          ? Object.keys(alert.summary).map((status: any) => ({
               status,
               count: alert.summary[status].count,
               maintenance_count: alert.summary[status].maintenance_count,
             }))
           : [];
-      
-      // FIXED: Determine highest status based on both regular and maintenance 
alerts
-      // This preserves the actual alert status even when in maintenance mode
+
       let highestStatus = "none";
       const statusOrder = ["critical", "warning", "ok", "unknown"];
-      
+
       for (const priorityStatus of statusOrder) {
-        const statusItem = statuses.find(s => s.status.toLowerCase() === 
priorityStatus);
-        if (statusItem && (statusItem.count > 0 || 
statusItem.maintenance_count > 0)) {
+        const statusItem = statuses.find(
+          (s) => s.status.toLowerCase() === priorityStatus
+        );
+        if (
+          statusItem &&
+          (statusItem.count > 0 || statusItem.maintenance_count > 0)
+        ) {
           highestStatus = priorityStatus;
           break;
         }
       }
-      
+
       alert.highestStatus = highestStatus.toUpperCase();
     }
+
+    // Sort by status priority
     const sortedInferredAlerts = STATUS_PRIORITY_ORDER.map((status) => {
       return inferredAlerts.filter(
         (alert: any) => alert.highestStatus === status
       );
     }).flat();
-    setAlerts(sortedInferredAlerts);
-  }
-  usePolling(getAlerts, 30000);
-  
-  useEffect(() => {
-    if (isClusterInstalled) {
-      setAlertsCount(0);
-      getAlerts();
-    }
-  }, [serviceName, isClusterInstalled]);
 
-  useEffect(() => {
-    if (!clusterName || !serviceName) return;
-
-    const unsubscribe = 
centralizedServiceStateApi.subscribe((serviceStatesData) => {
-      const serviceStateData = serviceStatesData.get(serviceName);
-      if (serviceStateData) {
-        getAlerts();
-      }
-    });
-
-    return unsubscribe;
-  }, [clusterName, serviceName]);
+    setAlerts(sortedInferredAlerts);
+  }, [alertDefinitions, alertSummary, serviceName]);
   useEffect(() => {
     if (alerts.length) {
       let inferredAlertsCount = 0;
diff --git a/ambari-web/latest/src/store/AlertsContext.tsx 
b/ambari-web/latest/src/store/AlertsContext.tsx
new file mode 100644
index 0000000000..77b5e5d420
--- /dev/null
+++ b/ambari-web/latest/src/store/AlertsContext.tsx
@@ -0,0 +1,221 @@
+/**
+ * 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.
+ */
+
+/**
+ * AlertsContext - centralized alert data management with WebSocket updates.
+ *
+ * Single source of truth for alert groups/definitions/summary so consumers
+ * (Navbar, ServiceSummary, Alerts, AlertDefinitionDetails, ServiceContext)
+ * don't each independently poll the alerts APIs.
+ *
+ * Pattern, matching EmberJS:
+ * - Initial load once: alert groups -> alert definitions -> alert summary -> 
unhealthy alerts
+ * - WebSocket updates on /events/alerts refresh the summary and unhealthy 
alerts
+ * - Polling only for unhealthy alerts, and only while on the /main/alerts 
route
+ */
+
+import React, { createContext, useState, useEffect, useCallback, useContext, 
useRef } from 'react';
+import { AlertsApi } from '../api/alertsApi';
+import { getCurrTimeInSec } from '../Utils/Utility';
+import { AppContext } from './context';
+import { useLocation } from 'react-router-dom';
+
+interface AlertsContextType {
+  alertGroups: any[];
+  alertDefinitions: any[];
+  alertSummary: any;
+  unhealthyAlertInstances: any[];
+  isLoading: boolean;
+  refreshUnhealthyAlerts: () => Promise<void>;
+}
+
+const AlertsContext = createContext<AlertsContextType | undefined>(undefined);
+
+const UNHEALTHY_ALERTS_POLL_INTERVAL = 10000; // 10 seconds - matching EmberJS
+
+export const AlertsProvider: React.FC<{ children: React.ReactNode }> = ({ 
children }) => {
+  const { clusterName, parsedSocketMessages } = useContext(AppContext);
+  const location = useLocation();
+
+  const [alertGroups, setAlertGroups] = useState<any[]>([]);
+  const [alertDefinitions, setAlertDefinitions] = useState<any[]>([]);
+  const [alertSummary, setAlertSummary] = useState<any>(null);
+  const [unhealthyAlertInstances, setUnhealthyAlertInstances] = 
useState<any[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+
+  const initialLoadComplete = useRef(false);
+  const pollIntervalRef = useRef<NodeJS.Timeout | null>(null);
+
+  const isAlertsRoute = location.pathname.includes('/main/alerts');
+
+  const loadAlertGroups = useCallback(async () => {
+    if (!clusterName) return;
+
+    try {
+      const currTime = getCurrTimeInSec();
+      const response = await AlertsApi.getAlerts(
+        clusterName,
+        
'AlertGroup/default,AlertGroup/definitions,AlertGroup/id,AlertGroup/name,AlertGroup/targets',
+        currTime
+      );
+
+      if (response?.items) {
+        setAlertGroups(response.items);
+      }
+    } catch (error) {
+      console.error('[AlertsContext] Error loading alert groups:', error);
+    }
+  }, [clusterName]);
+
+  const loadAlertDefinitions = useCallback(async () => {
+    if (!clusterName) return;
+
+    try {
+      const currTime = getCurrTimeInSec();
+      const response = await AlertsApi.getAlertDefinition(
+        clusterName,
+        
'AlertDefinition/component_name,AlertDefinition/description,AlertDefinition/enabled,AlertDefinition/id,AlertDefinition/label,AlertDefinition/name,AlertDefinition/service_name',
+        currTime
+      );
+
+      if (response?.items) {
+        const definitions = response.items.map((item: any) => ({
+          ...item.AlertDefinition,
+          label: item.AlertDefinition.label || item.AlertDefinition.name,
+          component_name: item.AlertDefinition.component_name || 'N/A'
+        }));
+        setAlertDefinitions(definitions);
+      }
+    } catch (error) {
+      console.error('[AlertsContext] Error loading alert definitions:', error);
+    }
+  }, [clusterName]);
+
+  const loadAlertDefinitionSummary = useCallback(async () => {
+    if (!clusterName) return;
+
+    try {
+      const currTime = getCurrTimeInSec();
+      const response = await 
AlertsApi.getGroupFormattedAlertsNotifications(clusterName, currTime);
+
+      setAlertSummary(response);
+    } catch (error) {
+      console.error('[AlertsContext] Error loading alert summary:', error);
+    }
+  }, [clusterName]);
+
+  const loadUnhealthyAlertInstances = useCallback(async () => {
+    if (!clusterName) return;
+
+    try {
+      const response = await AlertsApi.getAlertsListDetailed(clusterName);
+
+      if (response?.items) {
+        setUnhealthyAlertInstances(response.items);
+      }
+    } catch (error) {
+      console.error('[AlertsContext] Error loading unhealthy alerts:', error);
+    }
+  }, [clusterName]);
+
+  // Initial load - once per application session (like EmberJS's 
cluster_controller loadAlerts())
+  const performInitialLoad = useCallback(async () => {
+    if (!clusterName || initialLoadComplete.current) return;
+
+    setIsLoading(true);
+
+    try {
+      await loadAlertGroups();
+      await loadAlertDefinitions();
+      await loadAlertDefinitionSummary();
+      await loadUnhealthyAlertInstances();
+
+      initialLoadComplete.current = true;
+    } catch (error) {
+      console.error('[AlertsContext] Error during initial load:', error);
+    } finally {
+      setIsLoading(false);
+    }
+  }, [clusterName, loadAlertGroups, loadAlertDefinitions, 
loadAlertDefinitionSummary, loadUnhealthyAlertInstances]);
+
+  useEffect(() => {
+    performInitialLoad();
+  }, [performInitialLoad]);
+
+  // Poll unhealthy alerts only while on the alerts route
+  useEffect(() => {
+    if (pollIntervalRef.current) {
+      clearInterval(pollIntervalRef.current);
+      pollIntervalRef.current = null;
+    }
+
+    if (isAlertsRoute && initialLoadComplete.current) {
+      pollIntervalRef.current = setInterval(() => {
+        loadUnhealthyAlertInstances();
+      }, UNHEALTHY_ALERTS_POLL_INTERVAL);
+    }
+
+    return () => {
+      if (pollIntervalRef.current) {
+        clearInterval(pollIntervalRef.current);
+        pollIntervalRef.current = null;
+      }
+    };
+  }, [isAlertsRoute, loadUnhealthyAlertInstances]);
+
+  // WebSocket updates for alert summary; refresh unhealthy alerts to stay in 
sync
+  useEffect(() => {
+    if (!initialLoadComplete.current || parsedSocketMessages.length === 0) {
+      return;
+    }
+
+    const latestMessage = parsedSocketMessages[0];
+
+    if (latestMessage?.destination === '/events/alerts' && 
latestMessage.summaries) {
+      const clusterId = latestMessage.clusterId || 
Object.keys(latestMessage.summaries)[0];
+      const clusterSummaries = latestMessage.summaries[clusterId];
+
+      if (clusterSummaries) {
+        const updatedSummary = { alerts_summary_grouped: 
Object.values(clusterSummaries) };
+        setAlertSummary(updatedSummary);
+
+        // Keep the detailed unhealthy alert list in sync with the new summary
+        loadUnhealthyAlertInstances();
+      }
+    }
+  }, [parsedSocketMessages, loadUnhealthyAlertInstances]);
+
+  const value: AlertsContextType = {
+    alertGroups,
+    alertDefinitions,
+    alertSummary,
+    unhealthyAlertInstances,
+    isLoading,
+    refreshUnhealthyAlerts: loadUnhealthyAlertInstances,
+  };
+
+  return <AlertsContext.Provider 
value={value}>{children}</AlertsContext.Provider>;
+};
+
+export const useAlerts = (): AlertsContextType => {
+  const context = useContext(AlertsContext);
+  if (!context) {
+    throw new Error('useAlerts must be used within an AlertsProvider');
+  }
+  return context;
+};
diff --git a/ambari-web/latest/src/store/ServiceContext.tsx 
b/ambari-web/latest/src/store/ServiceContext.tsx
index 9ed8bae806..71f8a1fd5e 100644
--- a/ambari-web/latest/src/store/ServiceContext.tsx
+++ b/ambari-web/latest/src/store/ServiceContext.tsx
@@ -36,6 +36,7 @@ import YARNService from "../models/yarn.ts";
 import HiveService from "../models/hive.ts";
 import usePolling from "../hooks/usePolling.ts";
 import { AppContext } from "./context.tsx";
+import { useAlerts } from "./AlertsContext.tsx";
 import usePrevious from "../hooks/usePrevious.ts";
 import { ServiceApi } from "../api/serviceApi.ts";
 import { cachedServiceApi } from "../api/cachedServiceApi.ts";
@@ -92,6 +93,9 @@ const ServiceProvider: React.FC<ServiceProviderProps> = ({ 
children }) => {
   const [serviceStatesData, setServiceStatesData] = useState<Map<string, 
any>>(new Map());
 
   const { clusterName } = useContext(AppContext);
+
+  // Alert data from AlertsContext, used to calculate service alert counts 
without a separate /alerts call
+  const { alertSummary, alertDefinitions } = useAlerts();
   
   const isOnClusterAdminPage = location.pathname.includes('/main/admin/');
 
@@ -299,7 +303,7 @@ const ServiceProvider: React.FC<ServiceProviderProps> = ({ 
children }) => {
           console.log('Component maintenance mode changed for services:', 
componentMaintenanceChanges);
           // Force refresh of centralized service state data to get updated 
alerts
           centralizedServiceStateApi.clearCache();
-          await 
centralizedServiceStateApi.fetchAllServiceStatesAndAlerts(clusterName);
+          await 
centralizedServiceStateApi.fetchAllServiceStatesAndAlerts(clusterName, 
alertSummary, alertDefinitions);
         }
       }
     } catch (error) {
@@ -349,7 +353,7 @@ const ServiceProvider: React.FC<ServiceProviderProps> = ({ 
children }) => {
             console.log('Maintenance mode changed for services:', 
changedServices);
             // Force refresh of centralized service state data to get updated 
alerts
             centralizedServiceStateApi.clearCache();
-            await 
centralizedServiceStateApi.fetchAllServiceStatesAndAlerts(clusterName);
+            await 
centralizedServiceStateApi.fetchAllServiceStatesAndAlerts(clusterName, 
alertSummary, alertDefinitions);
           }
         }
       } catch (error) {
@@ -401,7 +405,7 @@ const ServiceProvider: React.FC<ServiceProviderProps> = ({ 
children }) => {
         if (!isPollingActive) return;
 
         try {
-          const statesData = await 
centralizedServiceStateApi.fetchAllServiceStatesAndAlerts(clusterName);
+          const statesData = await 
centralizedServiceStateApi.fetchAllServiceStatesAndAlerts(clusterName, 
alertSummary, alertDefinitions);
           setServiceStatesData(statesData);
         } catch (error) {
           console.error('Error polling service states:', error);
diff --git a/ambari-web/latest/src/store/context.tsx 
b/ambari-web/latest/src/store/context.tsx
index 6ea9741ad6..d85c11d021 100644
--- a/ambari-web/latest/src/store/context.tsx
+++ b/ambari-web/latest/src/store/context.tsx
@@ -31,8 +31,9 @@ import { Client } from "@stomp/stompjs";
 import ClusterApi from "../api/clusterApi";
 import { ChooseServicesApi } from "../api/chooseServicesApi";
 import { ServicesApi } from "../api/servicesApi";
-import { get, isEmpty, isUndefined, map, set } from "lodash";
+import { forEach, get, isEmpty, isUndefined, map, set } from "lodash";
 import ConfigsApi from "../api/configsApi";
+import VersionsApi from "../api/versionsApi";
 import { mapStackConfigProperties } from "../Utils/Utility";
 import useAuth from "../hooks/useAuth";
 import { parsePersistedValue, persistedPayload } from 
"../Utils/persistedSettings";
@@ -117,6 +118,10 @@ interface AppContextProps {
   wizardUser: string;
   isClusterInstalled?: boolean;
   loginName: string;
+  clockDistance: number;
+  serviceCheckSupportedMap: Record<string, boolean>;
+  stackVersion: any;
+  stackVersionList: any[];
 }
 
 type BackgroundRequestPage = {
@@ -189,6 +194,10 @@ export const AppContext = createContext<AppContextProps>({
   wizardUser: "",
   isClusterInstalled: false,
   loginName: "",
+  clockDistance: 0,
+  serviceCheckSupportedMap: {},
+  stackVersion: undefined,
+  stackVersionList: [],
 });
 
 export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
@@ -243,10 +252,65 @@ export const AppProvider: React.FC<{ children: 
React.ReactNode }> = ({
   }));
   const [services, setServices] = useState([]);
   const [stackConfigurations, setStackConfigurations] = useState([]);
+  // Service check supported map - static stack property, loaded once from 
initial stack configs fetch
+  const [serviceCheckSupportedMap, setServiceCheckSupportedMap] = 
useState<Record<string, boolean>>({});
+
+  // Stack versions - fetched once at startup, refreshed on /events/upgrade 
(like Ember's DS.Model store)
+  const [stackVersion, setStackVersion] = useState<any>(undefined);
+  const [stackVersionList, setStackVersionList] = useState<any[]>([]);
+
+  const fetchStackVersionList = async () => {
+    try {
+      const response = await VersionsApi.getServices(clusterName);
+      setStackVersion(response);
+      const items = get(response, "items", []);
+      const list: any[] = [];
+      forEach(items, (item: any) => {
+        const repoVersionId = get(item, 
"ClusterStackVersions.repository_version");
+        const repoVersion = get(item, "repository_versions", []).find(
+          (repo: any) => get(repo, "RepositoryVersions.id") === repoVersionId
+        )?.RepositoryVersions?.repository_version;
+        list.push({
+          id: get(item, "ClusterStackVersions.id"),
+          cluster_name: get(item, "ClusterStackVersions.cluster_name"),
+          stack: get(item, "ClusterStackVersions.stack"),
+          version: get(item, "ClusterStackVersions.version"),
+          state: get(item, "ClusterStackVersions.state"),
+          displayName: get(item, 
"repository_versions.[0].RepositoryVersions.display_name"),
+          not_installed_hosts: get(item, 
"ClusterStackVersions.host_states.NOT_REQUIRED"),
+          installing_hosts: get(item, 
"ClusterStackVersions.host_states.INSTALLING"),
+          installed_hosts: get(item, 
"ClusterStackVersions.host_states.INSTALLED"),
+          install_failed_hosts: get(item, 
"ClusterStackVersions.host_states.INSTALL_FAILED"),
+          out_of_sync_hosts: get(item, 
"ClusterStackVersions.host_states.OUT_OF_SYNC"),
+          current_hosts: get(item, "ClusterStackVersions.host_states.CURRENT"),
+          supports_revert: get(item, "ClusterStackVersions.supports_revert"),
+          repository_version_id: repoVersionId,
+          repository_version: repoVersion,
+        });
+      });
+      setStackVersionList(list);
+    } catch (error) {
+      console.error("Failed to fetch stack versions:", error);
+    }
+  };
   const [userBgPreferences, setUserBgPreferences] = useState(true);
   const [userTimezone, setUserTimezone] = useState(detectUserTimezone());
   const [allHostNames, setAllHostNames] = useState([]);
 
+  // Clock distance (server - client offset), derived from serverClock 
(fetched once by
+  // getAmbariProperties) instead of a separate 
RootServiceComponents/server_clock request.
+  const [clockDistance, setClockDistance] = useState<number>(0);
+
+  useEffect(() => {
+    if (serverClock === null) {
+      return;
+    }
+    const clientClock = Date.now();
+    let serverClockMs = serverClock.toString();
+    serverClockMs = serverClockMs.length < 13 ? serverClockMs + "000" : 
serverClockMs;
+    setClockDistance(parseInt(serverClockMs, 10) - clientClock);
+  }, [serverClock]);
+
   // Background Operations - persistent cache like Ember.js singleton
   const [backgroundOperations, setBackgroundOperations] = 
useState<BackgroundRequest[]>([]);
   const [backgroundOperationsPageSize, setBackgroundOperationsPageSizeState] = 
useState(20);
@@ -349,6 +413,7 @@ export const AppProvider: React.FC<{ children: 
React.ReactNode }> = ({
       fetchClusterServices();
       fetchAllHostNames();
       fetchUpgradeStates();
+      fetchStackVersionList();
     } else if (!isUndefined(isClusterInstalled) && !isClusterInstalled) {
       setAppLoaded(true);
     }
@@ -399,6 +464,19 @@ export const AppProvider: React.FC<{ children: 
React.ReactNode }> = ({
         );
         const stackConfigs = mapStackConfigProperties(response);
         setStackConfigurations(stackConfigs);
+
+        // Extract service_check_supported map (static stack property, like 
Ember's App.services.supportsServiceCheck)
+        const checkSupportedMap: Record<string, boolean> = {};
+        if (response?.items) {
+          response.items.forEach((item: any) => {
+            const svcName = get(item, "StackServices.service_name", "");
+            const supported = get(item, 
"StackServices.service_check_supported", false);
+            if (svcName) {
+              checkSupportedMap[svcName] = supported;
+            }
+          });
+        }
+        setServiceCheckSupportedMap(checkSupportedMap);
       }
     }
     fetchStackConfigs();
@@ -614,6 +692,7 @@ export const AppProvider: React.FC<{ children: 
React.ReactNode }> = ({
         }
       }
       fetchUpgradeStates();
+      fetchStackVersionList();
     }
 
   }, [parsedSocketMessages]);
@@ -780,6 +859,10 @@ export const AppProvider: React.FC<{ children: 
React.ReactNode }> = ({
         wizardUser,
         isClusterInstalled,
         loginName: loginName || "",
+        clockDistance,
+        serviceCheckSupportedMap,
+        stackVersion,
+        stackVersionList,
       }}
     >
       {children}
diff --git a/pom.xml b/pom.xml
index 17c19becc8..4796b3e953 100644
--- a/pom.xml
+++ b/pom.xml
@@ -284,6 +284,7 @@
             <exclude>**/hdp_mon_nagios_addons.conf</exclude>
             <exclude>**/*.json</exclude>
             <exclude>**/*.svg</exclude>
+            <exclude>**/*.csv</exclude>
             <exclude>derby.log</exclude>
             <exclude>CHANGES.txt</exclude>
             <exclude>pass.txt</exclude>


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

Reply via email to