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

jialiang 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 72c4298a9e AMBARI-26395:Step Wizard and Operation Progress components 
(#4049)
72c4298a9e is described below

commit 72c4298a9ee96a69695ec45b57474e4c740ed40e
Author: vanshuhassija <[email protected]>
AuthorDate: Tue Aug 26 15:08:22 2025 +0530

    AMBARI-26395:Step Wizard and Operation Progress components (#4049)
---
 ambari-web/latest/package-lock.json                | 356 +++++++++++++--
 ambari-web/latest/package.json                     |   6 +
 ambari-web/latest/src/Utils/Utility.ts             |   6 +
 ambari-web/latest/src/Utils/db.ts                  | 178 ++++++++
 ambari-web/latest/src/api/chooseServicesApi.ts     |  48 ++
 ambari-web/latest/src/api/clusterApi.ts            | 203 +++++++++
 ambari-web/latest/src/api/config/axiosConfig.ts    | 117 +++++
 ambari-web/latest/src/api/configsApi.ts            | 270 ++++++++++++
 ambari-web/latest/src/api/loginApi.ts              | 129 ++++++
 ambari-web/latest/src/api/requestApi.ts            | 188 ++++++++
 ambari-web/latest/src/api/servicesApi.ts           |  45 ++
 .../latest/src/components/OperationProgress.tsx    | 335 ++++++++++++++
 .../latest/src/components/StepWizard/index.tsx     |  32 +-
 ambari-web/latest/src/constants.ts                 |  28 ++
 ambari-web/latest/src/hooks/useDebounce.ts         |  44 ++
 ambari-web/latest/src/hooks/usePagination.ts       |  63 +++
 ambari-web/latest/src/hooks/usePolling.ts          |  64 +++
 ambari-web/latest/src/hooks/usePrevious.ts         |  27 ++
 ambari-web/latest/src/hooks/useStepWizard.ts       | 141 ++++++
 ambari-web/latest/src/store/context.tsx            | 489 +++++++++++++++++++++
 ambari-web/latest/src/store/reducer.ts             |  29 ++
 ambari-web/latest/src/store/types.ts               |  27 ++
 ambari-web/latest/src/types/StepWizard.ts          |  28 ++
 23 files changed, 2813 insertions(+), 40 deletions(-)

diff --git a/ambari-web/latest/package-lock.json 
b/ambari-web/latest/package-lock.json
index 9de0dad382..fb55447ecb 100644
--- a/ambari-web/latest/package-lock.json
+++ b/ambari-web/latest/package-lock.json
@@ -10,16 +10,21 @@
       "dependencies": {
         "@fortawesome/free-solid-svg-icons": "^6.7.2",
         "@fortawesome/react-fontawesome": "^0.2.2",
+        "@stomp/stompjs": "^7.1.1",
         "@types/lodash": "^4.17.16",
+        "axios": "^1.11.0",
         "bootstrap": "^5.3.6",
+        "classnames": "^2.5.1",
+        "dayjs": "^1.11.13",
         "i18next": "^25.1.2",
         "i18next-browser-languagedetector": "^8.1.0",
+        "js-cookie": "^3.0.5",
         "lodash": "^4.17.21",
-        "moment-timezone": "^0.5.48",
         "react": "^19.0.0",
         "react-bootstrap": "^2.10.10",
         "react-bootstrap-icons": "^1.11.6",
         "react-dom": "^19.0.0",
+        "react-hot-toast": "^2.5.2",
         "react-i18next": "^15.5.1",
         "react-router-dom": "^7.6.0",
         "sass": "^1.88.0",
@@ -27,6 +32,8 @@
       },
       "devDependencies": {
         "@eslint/js": "^9.21.0",
+        "@types/js-cookie": "^3.0.6",
+        "@types/node": "^24.2.1",
         "@types/react": "^19.0.10",
         "@types/react-dom": "^19.0.4",
         "@vitejs/plugin-react": "^4.3.4",
@@ -1782,6 +1789,11 @@
         "win32"
       ]
     },
+    "node_modules/@stomp/stompjs": {
+      "version": "7.1.1",
+      "resolved": 
"https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.1.1.tgz";,
+      "integrity": 
"sha512-chcDs6YkAnKp1FqzwhGvh3i7v0+/ytzqWdKYw6XzINEKAzke/iD00dNgFPWSZEqktHOK+C1gSzXhLkLbARIaZw=="
+    },
     "node_modules/@swc/helpers": {
       "version": "0.5.17",
       "resolved": 
"https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz";,
@@ -1843,6 +1855,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/js-cookie": {
+      "version": "3.0.6",
+      "resolved": 
"https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz";,
+      "integrity": 
"sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+      "dev": true
+    },
     "node_modules/@types/json-schema": {
       "version": "7.0.15",
       "resolved": 
"https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz";,
@@ -1856,6 +1874,15 @@
       "integrity": 
"sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==",
       "license": "MIT"
     },
+    "node_modules/@types/node": {
+      "version": "24.2.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz";,
+      "integrity": 
"sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
+      "dev": true,
+      "dependencies": {
+        "undici-types": "~7.10.0"
+      }
+    },
     "node_modules/@types/prop-types": {
       "version": "15.7.14",
       "resolved": 
"https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz";,
@@ -2208,6 +2235,21 @@
       "dev": true,
       "license": "Python-2.0"
     },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz";,
+      "integrity": 
"sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz";,
+      "integrity": 
"sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.4",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": 
"https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz";,
@@ -2291,6 +2333,18 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": 
"https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz";,
+      "integrity": 
"sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/callsites": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz";,
@@ -2357,8 +2411,7 @@
     "node_modules/classnames": {
       "version": "2.5.1",
       "resolved": 
"https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz";,
-      "integrity": 
"sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
-      "license": "MIT"
+      "integrity": 
"sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
     },
     "node_modules/color-convert": {
       "version": "2.0.1",
@@ -2380,6 +2433,17 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": 
"https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz";,
+      "integrity": 
"sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": 
"https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz";,
@@ -2424,6 +2488,11 @@
       "integrity": 
"sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
       "license": "MIT"
     },
+    "node_modules/dayjs": {
+      "version": "1.11.13",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz";,
+      "integrity": 
"sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+    },
     "node_modules/debug": {
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz";,
@@ -2449,6 +2518,14 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": 
"https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz";,
+      "integrity": 
"sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/dequal": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz";,
@@ -2481,6 +2558,19 @@
         "csstype": "^3.0.2"
       }
     },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": 
"https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz";,
+      "integrity": 
"sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.5.155",
       "resolved": 
"https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz";,
@@ -2488,6 +2578,47 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": 
"https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz";,
+      "integrity": 
"sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz";,
+      "integrity": 
"sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": 
"https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz";,
+      "integrity": 
"sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": 
"https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz";,
+      "integrity": 
"sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/esbuild": {
       "version": "0.25.4",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz";,
@@ -2855,6 +2986,40 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/follow-redirects": {
+      "version": "1.15.11",
+      "resolved": 
"https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz";,
+      "integrity": 
"sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh";
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz";,
+      "integrity": 
"sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/fsevents": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz";,
@@ -2870,6 +3035,14 @@
         "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
       }
     },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": 
"https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz";,
+      "integrity": 
"sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
     "node_modules/gensync": {
       "version": "1.0.0-beta.2",
       "resolved": 
"https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz";,
@@ -2880,6 +3053,41 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": 
"https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz";,
+      "integrity": 
"sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz";,
+      "integrity": 
"sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/glob-parent": {
       "version": "6.0.2",
       "resolved": 
"https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz";,
@@ -2906,6 +3114,25 @@
         "url": "https://github.com/sponsors/sindresorhus";
       }
     },
+    "node_modules/goober": {
+      "version": "2.1.16",
+      "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz";,
+      "integrity": 
"sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
+      "peerDependencies": {
+        "csstype": "^3.0.10"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz";,
+      "integrity": 
"sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
     "node_modules/graphemer": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz";,
@@ -2923,6 +3150,42 @@
         "node": ">=8"
       }
     },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": 
"https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz";,
+      "integrity": 
"sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": 
"https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz";,
+      "integrity": 
"sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb";
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz";,
+      "integrity": 
"sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/html-parse-stringify": {
       "version": "3.0.1",
       "resolved": 
"https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz";,
@@ -3064,6 +3327,14 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/js-cookie": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz";,
+      "integrity": 
"sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz";,
@@ -3173,8 +3444,7 @@
     "node_modules/lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz";,
-      "integrity": 
"sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "license": "MIT"
+      "integrity": 
"sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
     "node_modules/lodash.merge": {
       "version": "4.6.2",
@@ -3205,6 +3475,14 @@
         "yallist": "^3.0.2"
       }
     },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": 
"https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz";,
+      "integrity": 
"sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz";,
@@ -3229,6 +3507,25 @@
         "node": ">=8.6"
       }
     },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz";,
+      "integrity": 
"sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": 
"https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz";,
+      "integrity": 
"sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz";,
@@ -3242,27 +3539,6 @@
         "node": "*"
       }
     },
-    "node_modules/moment": {
-      "version": "2.30.1",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz";,
-      "integrity": 
"sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
-      "license": "MIT",
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/moment-timezone": {
-      "version": "0.5.48",
-      "resolved": 
"https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz";,
-      "integrity": 
"sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
-      "license": "MIT",
-      "dependencies": {
-        "moment": "^2.29.4"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz";,
@@ -3485,6 +3761,11 @@
         "react": ">=0.14.0"
       }
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": 
"https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz";,
+      "integrity": 
"sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
     "node_modules/punycode": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz";,
@@ -3560,7 +3841,6 @@
       "version": "1.11.6",
       "resolved": 
"https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.11.6.tgz";,
       "integrity": 
"sha512-ycXiyeSyzbS1C4+MlPTYe0riB+UlZ7LV7YZQYqlERV2cxDiKtntI0huHmP/3VVvzPt4tGxqK0K+Y6g7We3U6tQ==",
-      "license": "MIT",
       "dependencies": {
         "prop-types": "^15.7.2"
       },
@@ -3580,6 +3860,22 @@
         "react": "^19.1.0"
       }
     },
+    "node_modules/react-hot-toast": {
+      "version": "2.5.2",
+      "resolved": 
"https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz";,
+      "integrity": 
"sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==",
+      "dependencies": {
+        "csstype": "^3.1.3",
+        "goober": "^2.1.16"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "react": ">=16",
+        "react-dom": ">=16"
+      }
+    },
     "node_modules/react-i18next": {
       "version": "15.5.1",
       "resolved": 
"https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.1.tgz";,
@@ -4028,6 +4324,12 @@
         "react": ">=15.0.0"
       }
     },
+    "node_modules/undici-types": {
+      "version": "7.10.0",
+      "resolved": 
"https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz";,
+      "integrity": 
"sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
+      "dev": true
+    },
     "node_modules/update-browserslist-db": {
       "version": "1.1.3",
       "resolved": 
"https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz";,
diff --git a/ambari-web/latest/package.json b/ambari-web/latest/package.json
index 77b6d90cf3..5d8c569c5b 100755
--- a/ambari-web/latest/package.json
+++ b/ambari-web/latest/package.json
@@ -12,19 +12,23 @@
   "dependencies": {
     "@fortawesome/free-solid-svg-icons": "^6.7.2",
     "@fortawesome/react-fontawesome": "^0.2.2",
+    "@stomp/stompjs": "^7.1.1",
     "@types/lodash": "^4.17.16",
+    "axios": "^1.11.0",
     "bootstrap": "^5.3.6",
     "classnames": "^2.5.1",
     "dayjs": "^1.11.13",
     "html-react-parser": "^5.2.6",
     "i18next": "^25.1.2",
     "i18next-browser-languagedetector": "^8.1.0",
+    "js-cookie": "^3.0.5",
     "isomorphic-dompurify": "^2.26.0",
     "lodash": "^4.17.21",
     "react": "^19.0.0",
     "react-bootstrap": "^2.10.10",
     "react-bootstrap-icons": "^1.11.6",
     "react-dom": "^19.0.0",
+    "react-hot-toast": "^2.5.2",
     "react-i18next": "^15.5.1",
     "react-router-dom": "^7.6.0",
     "sass": "^1.88.0",
@@ -32,6 +36,8 @@
   },
   "devDependencies": {
     "@eslint/js": "^9.21.0",
+    "@types/js-cookie": "^3.0.6",
+    "@types/node": "^24.2.1",
     "@types/react": "^19.0.10",
     "@types/react-dom": "^19.0.4",
     "@vitejs/plugin-react": "^4.3.4",
diff --git a/ambari-web/latest/src/Utils/Utility.ts 
b/ambari-web/latest/src/Utils/Utility.ts
index 588779a422..1fba1250ee 100644
--- a/ambari-web/latest/src/Utils/Utility.ts
+++ b/ambari-web/latest/src/Utils/Utility.ts
@@ -459,6 +459,12 @@ export function 
isShownOnAddServiceAssignMasterPage(component:string,isMaster:bo
   return isVisible;
 }
 
+
+export const redirectToLogin = () => {
+  window.location.href = "#/login";
+  window.location.reload();
+}
+
 export const translate = (messageKey: string) => {
   return parse(DOMPurify.sanitize(t(messageKey), { USE_PROFILES: { html: true 
} } ));
 }
diff --git a/ambari-web/latest/src/Utils/db.ts 
b/ambari-web/latest/src/Utils/db.ts
new file mode 100644
index 0000000000..e2352c6189
--- /dev/null
+++ b/ambari-web/latest/src/Utils/db.ts
@@ -0,0 +1,178 @@
+/**
+ * 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 { Utility } from './Utility';
+
+interface DbData {
+    app: {
+      loginName: string;
+      authenticated: boolean;
+      user?: any;
+      auth?: any;
+      configs: any[];
+      tags: any[];
+      tables: {
+        filterConditions: Record<string, any>;
+        displayLength: Record<string, any>;
+        startIndex: Record<string, any>;
+        sortingConditions: Record<string, any>;
+        selectedItems: Record<string, any>;
+      };
+    };
+    Installer: Record<string, any>;
+    AddHost: Record<string, any>;
+    AddService: Record<string, any>;
+    WidgetWizard: Record<string, any>;
+    KerberosWizard: Record<string, any>;
+    ReassignMaster: Record<string, any>;
+    AddSecurity: Record<string, any>;
+    HighAvailabilityWizard: Record<string, any>;
+    RollbackHighAvailabilityWizard: Record<string, any>;
+    tmp: Record<string, any>;
+    [key: string]: any;
+  }
+
+  const InitialData: DbData = {
+    app: {
+      loginName: '',
+      authenticated: false,
+      configs: [],
+      tags: [],
+      tables: {
+        filterConditions: {},
+        displayLength: {},
+        startIndex: {},
+        sortingConditions: {},
+        selectedItems: {}
+      }
+    },
+    Installer: {},
+    AddHost: {},
+    AddService: {},
+    WidgetWizard: {},
+    KerberosWizard: {},
+    ReassignMaster: {},
+    AddSecurity: {},
+    HighAvailabilityWizard: {},
+    RollbackHighAvailabilityWizard: {},
+    tmp: {}
+  };
+
+  class Database {
+    private data: DbData;
+
+    constructor() {
+      this.data = this.getDb() || InitialData;
+    }
+
+    private checkNamespace(namespace: string): boolean {
+      if (!namespace) return false;
+      if (!this.data[namespace]) {
+        this.data[namespace] = {};
+      }
+      return true;
+    }
+
+    getDb(): DbData | null {
+      try {
+        const stored = localStorage.getItem('ambari');
+        return stored ? JSON.parse(stored) : null;
+      } catch (e) {
+        console.error('Error reading from localStorage:', e);
+        return null;
+      }
+    }
+
+    private setDb(data: DbData): void {
+      try {
+        localStorage.setItem('ambari', JSON.stringify(data));
+      } catch (e) {
+        console.error('Error writing to localStorage:', e);
+      }
+    }
+
+    getItem(key: string): string | null {
+      const value = localStorage.getItem(key);
+      if (value === null) return null;
+      return Utility.decryptData(value);
+}
+    setItem(key: string, value: string): void {
+      try {
+        // Always encrypt before storing to ensure consistency
+        const encrypted = Utility.encryptData(value);
+        localStorage.setItem(key, encrypted);
+      } catch (e) {
+        console.error(`Error encrypting data for key ${key}:`, e);
+        // Fallback to storing unencrypted data
+        localStorage.setItem(key, value);
+        console.warn(`Stored unencrypted data for key ${key} due to encryption 
failure`);
+      }
+    }
+
+    cleanUp(): void {
+      this.data = InitialData;
+      this.setDb(this.data);
+    }
+
+    createNameSpace(namespace: string): void {
+      if (!this.data[namespace]) {
+        this.data[namespace] = {};
+        this.setDb(this.data);
+      }
+    }
+
+    // Core get/set methods
+    get(namespace: string, key: string): any {
+      const data = this.getDb();
+      if (!data || !this.checkNamespace(namespace)) return null;
+      return key.includes('user-pref') ? 
+        data[namespace][key] : 
+        this.getNestedValue(data[namespace], key);
+    }
+
+    set(namespace: string, key: string, value: any): void {
+      const data = this.getDb() || InitialData;
+      if (!this.checkNamespace(namespace)) return;
+      if (key.includes('user-pref')) {
+        data[namespace][key] = value;
+      } else {
+        this.setNestedValue(data[namespace], key, value);
+      }
+      this.setDb(data);
+    }
+
+    private getNestedValue(obj: any, path: string): any {
+      return path.split('.').reduce((acc, part) => acc && acc[part], obj);
+    }
+
+    private setNestedValue(obj: any, path: string, value: any): void {
+      const parts = path.split('.');
+      const last = parts.pop()!;
+      const target = parts.reduce((acc, part) => {
+        if (!acc[part]) acc[part] = {};
+        return acc[part];
+      }, obj);
+      target[last] = value;
+    }
+
+    getInitialData(): string {
+      return JSON.parse(JSON.stringify(InitialData));
+    }
+
+  }
+
+  export const db = new Database()
\ No newline at end of file
diff --git a/ambari-web/latest/src/api/chooseServicesApi.ts 
b/ambari-web/latest/src/api/chooseServicesApi.ts
new file mode 100644
index 0000000000..ce7b9c9a55
--- /dev/null
+++ b/ambari-web/latest/src/api/chooseServicesApi.ts
@@ -0,0 +1,48 @@
+/**
+ * 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 { ambariApi } from "./config/axiosConfig";
+
+export const ChooseServicesApi = {
+  serviceDetails: async function (serviceName: string, clusterName: string) {
+    const url = `/clusters/${clusterName}/services/${serviceName}`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  servicesList: async function (clusterName: string) {
+    const url = `/clusters/${clusterName}/services`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+
+  getServices: async (stack: string, version: string, services?: string[]) => {
+    const url = 
`stacks/${stack}/versions/${version}/services?fields=StackServices/*,components/*,components/dependencies/Dependencies/scope,components/dependencies/Dependencies/service_name,artifacts/Artifacts/artifact_name${
+      services ? `&StackServices/service_name.in(${services.join(",")})` : ""
+    }`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+};
diff --git a/ambari-web/latest/src/api/clusterApi.ts 
b/ambari-web/latest/src/api/clusterApi.ts
new file mode 100644
index 0000000000..2d7c1cb549
--- /dev/null
+++ b/ambari-web/latest/src/api/clusterApi.ts
@@ -0,0 +1,203 @@
+/**
+ * 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 { ambariApi, supressErrorAmbariApi } from "./config/axiosConfig";
+
+const ClusterApi = {
+  loadAmbariProperties: async (fields = "") => {
+    const url = `/services/AMBARI/components/AMBARI_SERVER${fields}`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  fetchClusterDetails: async function (updateClusterPayloadData: object, 
clusterName: string) {
+    const url = `/clusters/${clusterName}`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "PUT",
+      data: updateClusterPayloadData
+    });
+    return response;
+  },
+  getDesiredClusterConfigs: async function (clusterName: 
string,fields=`Clusters/desired_configs`) {
+    const url = `/clusters/${clusterName}?fields=${fields}`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  updateCluster:async function(clusterName:string,data:any){
+    const url=`/clusters/${clusterName}`
+    const response=await ambariApi.request({
+      url:url,
+      method:"PUT",
+      data
+    })
+    return response.data
+  },
+  getCluster: async function (clusterName:string) {
+    const url = 
`/clusters/${clusterName}?fields=Clusters/desired_configs/cluster-env`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getAllClusters: async function () {
+    const url = `/clusters`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getRequests: async function (clusterName: string,pageSize:number) {
+    const url = 
`/clusters/${clusterName}/requests?to=end&page_size=${pageSize}&fields=Requests/end_time,Requests/id,Requests/progress_percent,Requests/request_context,Requests/request_status,Requests/start_time,Requests/cluster_name,Requests/user_name&minimal_response=true`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  deleteCluster: async function (clusterName:string) {
+    const url = `/clusters/${clusterName}`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "DELETE",
+    })
+    return response.data
+  },
+  getRequestById: async function (clusterName: string,requestId:number|string) 
{
+    const url = 
`/clusters/${clusterName}/requests/${requestId}?fields=*,tasks/Tasks/request_id,tasks/Tasks/command,tasks/Tasks/command_detail,tasks/Tasks/ops_display_name,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,tasks/Tasks/status&minimal_response=true`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  updateRequest:async function 
(clusterName:string,requestId:number|string,payload:any){
+    const url=`/clusters/${clusterName}/requests/${requestId}`
+    const response = await ambariApi.request({
+      url: url,
+      method: "PUT",
+      data:payload
+    });
+    return response.data;
+  },
+  getClusterRequestTaskLogs:async 
function(clusterName:string,requestId:number|string,taskId:number|string){
+    const url=`/clusters/${clusterName}/requests/${requestId}/tasks/${taskId}`
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getClusterName : async function () {
+      const url = `/clusters?fields=Clusters`;
+      const response = await ambariApi.request({
+        url: url,
+        method: "GET"
+    });
+    const clusterName = response?.data?.items[0]?.Clusters?.cluster_name;
+    console.log("CLUSTER NAME", response.data.items[0].Clusters.cluster_name)
+    return clusterName;
+  },
+  getClusterData: async function () {
+    const url= 
`/clusters?fields=Clusters/provisioning_state,Clusters/security_type,Clusters/version,Clusters/cluster_id`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getPersistData: async function (key:any) {
+    const url = `/persist/${key}`;
+    const response = await supressErrorAmbariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  postPersistData: async function (data:any) {
+    const url = `/persist`;
+    const response = await supressErrorAmbariApi.request({
+      url: url,
+      method: "POST",
+      data
+    });
+    return response.data;
+  },
+  getHosts: async function (clusterName:string) {
+    const url = `/clusters/${clusterName}/hosts?minimal_response=true`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getUpgradeState: async function (clusterName: string) {
+    const url = `/clusters/${clusterName}/upgrades?fields=Upgrade`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET"
+    })
+    return response.data;
+  },
+  noopPolling: async function () {
+    // const timestamp = new Date().getTime();
+    const url = `/clusters`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response;
+  },
+  getUserTimeout: async () => {
+    return ambariApi.request({
+      url: '/services/AMBARI/components/AMBARI_SERVER',
+      method: 'GET',
+      params: {
+        fields: 
'RootServiceComponents/properties/user.inactivity.timeout.default',
+        _: Date.now() // Cache buster
+      }
+    });
+  },
+  
+  createClusterCustomAction: async function (clusterName: string, payload: 
any) {
+    const url = `/clusters/${clusterName}/requests`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "POST",
+      data: payload
+    });
+    return response.data;
+  },
+  createCustomAction: async function ( payload: any) {
+    const url = `/requests`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "POST",
+      data: payload,
+    });
+    return response.data;
+  }
+  }
+
+export default ClusterApi;
\ No newline at end of file
diff --git a/ambari-web/latest/src/api/config/axiosConfig.ts 
b/ambari-web/latest/src/api/config/axiosConfig.ts
new file mode 100644
index 0000000000..af5cbffc43
--- /dev/null
+++ b/ambari-web/latest/src/api/config/axiosConfig.ts
@@ -0,0 +1,117 @@
+/**
+ * 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 axios from "axios";
+import { toast } from "react-hot-toast";
+import { get } from "lodash";
+
+const config = {
+  development: {
+    VITE_API_PROXY_TARGET: "<PROXY URL HERE>",
+    //VITE_TOKEN: "<PROXY TOKEN HERE>",
+  },
+  production: {
+    VITE_API_PROXY_TARGET: "",
+  },
+};
+
+let currentEnv = "development"; // however you determine the current 
environment
+
+if (process.env.NODE_ENV) {
+  currentEnv = process.env.NODE_ENV;
+}
+
+const createAxiosInstance = (baseURL: string, headers = {}) => {
+  if (currentEnv != undefined) {
+    if (currentEnv == "development") {
+      headers = {
+        "Content-Type": "application/json",
+        // Authorization: `Basic 
${btoa(localStorage.getItem("proxy_token")||"")}`,
+        ...headers,
+      };
+    } else {
+      headers = {
+        "Content-Type": "application/json",
+        // Authorization: `Basic 
${btoa(localStorage.getItem("proxy_token")||"")}`,
+        ...headers,
+      };
+    }
+  } else {
+    console.error(`No configuration found for target: ${currentEnv}`);
+  }
+
+  const instance = axios.create({
+    baseURL,
+    withCredentials: true,
+    headers: headers,
+  });
+
+  instance.interceptors.response.use(undefined, (error) => {
+    const responseMessage = get(error, "response.data.message", undefined);
+    // Check for 403 Forbidden status
+    if (error.response && error.response.status === 403) {
+      // Redirect to login page
+      window.location.href = "/#/login";
+      return Promise.reject(error);
+    }
+    if (responseMessage && error.response.status !== 400) {
+      toast.error(responseMessage);
+    }
+    return Promise.reject(error);
+  });
+
+  return instance;
+};
+
+const createSupressErrorAxiosInstance = (baseURL: string, headers = {}) => {
+  if (currentEnv != undefined) {
+    headers = {
+      "Content-Type": "application/json",
+      ...headers,
+    };
+  }
+
+  const instance = axios.create({
+    baseURL,
+    withCredentials: true,
+    headers: headers,
+  });
+
+  instance.interceptors.response.use(undefined, (error) => {
+    // Check for 403 Forbidden status
+    if (error.response && error.response.status === 403) {
+      // Redirect to login page
+      window.location.href = "/#/login";
+      return Promise.reject(error);
+    }
+    return Promise.reject(error);
+  });
+
+  return instance;
+};
+
+let endpoint = "";
+if (config.development.VITE_API_PROXY_TARGET != undefined) {
+  if (config.development.VITE_API_PROXY_TARGET != "") {
+    endpoint = "/api/v1";
+  } else {
+    endpoint = `${config.production.VITE_API_PROXY_TARGET}/api/v1`;
+  }
+}
+
+export const ambariApi = createAxiosInstance(endpoint);
+export const supressErrorAmbariApi = createSupressErrorAxiosInstance(endpoint);
diff --git a/ambari-web/latest/src/api/configsApi.ts 
b/ambari-web/latest/src/api/configsApi.ts
new file mode 100644
index 0000000000..a320da67bb
--- /dev/null
+++ b/ambari-web/latest/src/api/configsApi.ts
@@ -0,0 +1,270 @@
+/**
+ * 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 { AxiosResponse } from "axios";
+import { ambariApi } from "./config/axiosConfig";
+
+const ConfigsApi = {
+  getServiceConfigurations: async function (
+    stack: string,
+    verison: string,
+    services: string
+  ) {
+    const url = 
`stacks/${stack}/versions/${verison}/services?StackServices/service_name.in(${services})&fields=configurations/*,configurations/dependencies/*,StackServices/config_types/*`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getConfigProperties: async function (
+    stack: string,
+    verison: string,
+    services: string
+  ) {
+    const url = 
`stacks/${stack}/versions/${verison}/services?StackServices/service_name.in(${services})&fields=configurations/*,configurations/dependencies/*,StackServices/display_name,StackServices/config_types/*&_=1728974996201`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+
+  validateConfigProperties: async function (
+    stack: string,
+    verison: string,
+    payload: any
+  ) {
+    const url = `stacks/${stack}/versions/${verison}/validations`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "POST",
+      data: payload,
+    });
+    return response.data;
+  },
+
+  getConfigValues: async function (clusterName: string, services: string) {
+    const url = 
`clusters/${clusterName}/configurations/service_config_versions?service_name.in(${services})&is_current=true&fields=*`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+
+  getVersionConfigValues: async function (
+    clusterName: string,
+    services: string,
+    version: string
+  ) {
+    const url = 
`clusters/${clusterName}/configurations/service_config_versions?(service_name=${services}&service_config_version.in(${version}))`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+
+  getTheme: async (
+    stackName: string,
+    stackVersion: string,
+    services: string
+  ) => {
+    const url = 
`/stacks/${stackName}/versions/${stackVersion}/services?StackServices/service_name.in(${services})&themes/ThemeInfo/default=true&fields=themes/*`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  loadConfigTags: async (clusterName: string) => {
+    const url = `/clusters/${clusterName}?fields=Clusters/desired_configs`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  reassignLoadConfigs: async (clusterName: string, urlParams: string) => {
+    const url = `/clusters/${clusterName}/configurations?${urlParams}`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  updateConfigTags: async function (clusterName: string) {
+    const url = `/clusters/${clusterName}?fields=Clusters/desired_configs`;
+    const { data } = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    const tags = [];
+    for (let site in data.Clusters.desired_configs) {
+      tags.push({
+        siteName: site,
+        tagName: data.Clusters.desired_configs[site].tag,
+      });
+    }
+    return tags;
+  },
+  getConfigsByTags: async function (clusterName: string, params: string) {
+    const url = `/clusters/${clusterName}/configurations?${params}`;
+    const { data } = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return data;
+  },
+  updateServiceConfigurations: async function (clusterName: string, data: any) 
{
+    const url = `/clusters/${clusterName}`;
+    const response = await ambariApi.request({
+      url,
+      method: "PUT",
+      data: {
+        Clusters: {
+          desired_config: data.desired_config,
+        },
+      },
+    });
+    return response.data;
+  },
+  updateServiceMultiConfigurations: async function (
+    clusterName: string,
+    data: any
+  ) {
+    const url = `/clusters/${clusterName}`;
+    const response = await ambariApi.request({
+      url,
+      method: "PUT",
+      data: data.configs,
+    });
+    return response.data;
+  },
+
+  loadConfigsFromStack: async function (
+    stack: string,
+    version: string,
+    serviceNames: string[]
+  ) {
+    const url = serviceNames.length
+      ? 
`/stacks/${stack}/versions/${version}/services?StackServices/service_name.in(${serviceNames})&fields=configurations/*,configurations/dependencies/*,StackServices/config_types/*`
+      : 
`/stacks/${stack}/versions/${version}/services?fields=configurations/*,StackServices/config_types/*`;
+    const { data } = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return data;
+  },
+  getConfigGroups: async function (clusterName: string, serviceName: string) {
+    const url = 
`clusters/${clusterName}/config_groups?ConfigGroup/tag.in(${serviceName})&fields=*`;
+    const { data } = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return data;
+  },
+  createNewConfigGroup: async function (clusterName: string, payload: any) {
+    const url = `clusters/${clusterName}/config_groups`;
+    const { data } = await ambariApi.request({
+      url,
+      method: "POST",
+      data: payload,
+    });
+    return data;
+  },
+  saveConfigs: async function (clusterName: string, payload: any) {
+    const url = `clusters/${clusterName}`;
+    const { data } = await ambariApi.request({
+      url,
+      method: "PUT",
+      data: payload,
+    });
+    return data;
+  },
+  updateConfigGroupProperties: async function (
+    clusterName: string,
+    groupId: string,
+    payload: any
+  ) {
+    const url = `clusters/${clusterName}/config_groups/${groupId}`;
+    const { data } = await ambariApi.request({
+      url,
+      method: "PUT",
+      data: payload,
+    });
+    return data;
+  },
+  getDesiredConfigsInfo: async (
+    clusterName: string
+  ): Promise<AxiosResponse> => {
+    const url = 
`/clusters/${clusterName}?fields=Clusters/desired_configs&_=${Date.now()}\``;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response;
+  },
+  getEnabledConfigsForRangerPlugins: async (
+    hdfsTagVersion: string,
+    hbaseTagVersion: string,
+    hiveTagVersion: string,
+    yarnTagVersion: string,
+    clusterName: string
+  ): Promise<AxiosResponse> => {
+    const url =
+      
`/clusters/${clusterName}/configurations?(type=ranger-hdfs-plugin-properties&tag=${hdfsTagVersion})|`
 +
+      `(type=ranger-yarn-plugin-properties&tag=${yarnTagVersion})|` +
+      `(type=hive-env&tag=${hiveTagVersion})|` +
+      
`(type=ranger-hbase-plugin-properties&tag=${hbaseTagVersion})&_=${Date.now()}`;
+
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response;
+  },
+  getRecommendations: async function (
+    stack: string,
+    version: string,
+    payload: any
+  ) {
+    const url = `stacks/${stack}/versions/${version}/recommendations`;
+    const { data } = await ambariApi.request({
+      url,
+      method: "POST",
+      data: payload,
+    });
+    return data;
+  },
+  getServiceConfigVersions: async function (
+    clusterName: string,
+    serviceName: string
+  ): Promise<AxiosResponse> {
+    const serviceConfigFields =
+      
"service_config_version,user,hosts,group_id,group_name,is_current,createtime,service_name,service_config_version_note,stack_id,is_cluster_compatible";
+    const url = 
`clusters/${clusterName}/configurations/service_config_versions?service_name=${serviceName}&fields=${serviceConfigFields}&sortBy=service_config_version.desc&minimal_response=true&_=${Date.now()}`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data.items;
+  },
+};
+
+export default ConfigsApi;
diff --git a/ambari-web/latest/src/api/loginApi.ts 
b/ambari-web/latest/src/api/loginApi.ts
new file mode 100644
index 0000000000..97cbc5e66c
--- /dev/null
+++ b/ambari-web/latest/src/api/loginApi.ts
@@ -0,0 +1,129 @@
+/**
+ * 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 {ambariApi} from "./config/axiosConfig.ts";
+import {misc} from "../Utils/misc.ts";
+import { db } from "../Utils/db";
+import Cookies from 'js-cookie';
+
+interface LoginParams {
+  usr: string;
+  loginName: string;
+}
+interface dataLoginType {
+  Users: any;
+  loginData: any;
+}
+interface LoginDataParamsType {
+  loginName: string;
+  loginData: any;
+}
+const LoginApi = {
+
+  authenticate: async function (username: string, password: string) {
+    const hashForUserNamePassword = misc.utf8ToB64(username + ":" + password);
+    const response = await ambariApi.request({
+      url: "/auth",
+      method: "POST",
+      headers: {
+        'Content-Type': 'text/plain',
+        Authorization: "Basic " + hashForUserNamePassword,
+      },
+    });
+    return response;
+  },
+  handleSuccessfulLogin: async function (params: LoginParams) {
+    const url = 
`/users/${encodeURIComponent(params.loginName)}?fields=*,privileges/PrivilegeInfo/cluster_name,privileges/PrivilegeInfo/permission_name`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+      data: {
+        usr: params.usr,
+        loginName: params.loginName
+      }
+    });
+    return response;
+  },
+  loadAuthorizationsCallback: async function(params: LoginParams) {
+    const url = 
`/users/${encodeURIComponent(params.loginName)}/authorizations?fields=*`
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+      data: {userName: params.loginName},
+    });
+    return response;
+  },
+  afterLoginSuccessCallback: async function(data: dataLoginType) {
+    const response = await ambariApi.request({
+      url: "/settings/motd",
+      method: "GET",
+      data: {
+        loginName: data.Users.user_name,
+        loginData: data
+      }
+    });
+    return response.data;
+  },
+  setClusterDataCallback: async function(params: LoginDataParamsType) {
+    const requestData = {
+      loginName: params.loginName,
+      loginData: params.loginData
+    };
+    const response = await ambariApi.request({
+      url: 
"/clusters?fields=Clusters/provisioning_state,Clusters/security_type,Clusters/version,Clusters/cluster_id",
+      data: requestData,
+    })
+    return response;
+  },
+  logout: async () => {
+    Cookies.remove('AMBARISESSIONID', { path: '/' ,domain: 'localhost', 
secure: true })    
+    console.log('After logout cookies:', document.cookie);
+    const timestamp = Date.now();
+    try {
+      const response = await ambariApi.request({
+        url: `/logout?_=${timestamp}`,
+        method: "GET",
+        headers: {
+          'X-Requested-By': 'X-Requested-By'
+        },
+        withCredentials: true,
+        auth: {
+          username: timestamp.toString(),
+          password: timestamp.toString()
+        }
+      });
+
+      // Only clean up and redirect on successful logout
+      if (response.status === 200) {
+        db.cleanUp();
+            // Remove AMBARI-SESSION-ID cookie
+        // // Clear auth header
+        // delete ambariApi.defaults.headers.common['Authorization'];
+        // // Clear JWT cookie
+        // document.cookie = "jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; 
path=/;";
+        window.location.replace('/#/login');
+      }
+      return response;
+    } catch (error) {
+      console.error('Logout failed:', error);
+      throw error; // Let the UI handle the error
+    }
+  },
+  
+}
+export default LoginApi;
+
diff --git a/ambari-web/latest/src/api/requestApi.ts 
b/ambari-web/latest/src/api/requestApi.ts
new file mode 100644
index 0000000000..536d4cc948
--- /dev/null
+++ b/ambari-web/latest/src/api/requestApi.ts
@@ -0,0 +1,188 @@
+/**
+ * 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 { set } from "lodash";
+import { ambariApi } from "./config/axiosConfig";
+export const RequestApi = {
+    getRequestStatus: async function (clusterName: string,requestId:string) {
+        const url = 
`/clusters/${clusterName}/requests/${requestId}?fields=*,tasks/Tasks/request_id,tasks/Tasks/command,tasks/Tasks/command_detail,tasks/Tasks/ops_display_name,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,tasks/Tasks/status&minimal_response=true`;
+        const response = await ambariApi.request({
+            url: url,
+            method: "GET",
+        });
+        return response.data;
+    },
+     getRunningRequests: async function (clusterName: string) {
+        const url = 
`/clusters/${clusterName}/requests/?page_size=20&fields=Requests/request_status&Requests/request_status.in(IN_PROGRESS)`;
+        const response = await ambariApi.request({
+            url: url,
+            method: "GET",
+        });
+        return response.data;
+    },
+    stopServices: async function (clusterName:string, stopServicesdata: 
object) {
+        const url = `clusters/${clusterName}/services`;
+        const response = await ambariApi.request({
+          url: url,
+          method: "PUT",
+          data: stopServicesdata,
+          headers:{
+            "Content-Type":"text/plain"
+          }
+        });
+        return response.data;
+    },
+    startServices: async function (clusterName: string, payload: any, params: 
string, method="PUT"){
+      const url = `/clusters/${clusterName}/services?params/${params}`;
+      const response = await ambariApi.request({
+        url: url, 
+        method: method, 
+        data: payload,
+        headers:{
+          "Content-Type":"text/plain"
+        }
+      })
+      return response.data;
+    },
+    performRequests: async function (clusterName: string, payload: any, 
method="PUT") {
+      const url = 
`/clusters/${clusterName}/services?ServiceInfo/state=INSTALLED&ServiceInfo/service_name=KERBEROS`
+      const response = await ambariApi.request({
+        url: url, 
+        method: method,
+        data: payload,
+        headers:{
+          "Content-Type":"text/plain"
+        }
+      })
+      return response.data;
+    },
+    postRequest: async function (clusterName: string, payload: any, 
method="POST") {
+      const url = `/clusters/${clusterName}/requests`
+      const response = await ambariApi.request({
+        url: url,
+        method: method,
+        data: payload,
+      })
+      return response.data;
+    },
+    getServices: async function (clusterName: string, payload: any, params: 
string, method="PUT"){
+      const url =   `/clusters/${clusterName}/services?${params}`
+      const response = await ambariApi.request({
+        url: url,
+        method: method,
+        data: payload,
+        headers: {
+          "Content-Type": "text/plain"
+        }
+      })
+      return response.data;
+    },
+    getServicesWithStatus: async function (clusterName: string, payload: any, 
params: string, method="PUT"){
+      const url =   `/clusters/${clusterName}/services?${params}`
+      const response = await ambariApi.request({
+        url: url,
+        method: method,
+        data: payload,
+        headers: {
+          "Content-Type": "text/plain"
+        }
+      })
+      if (!response.data) 
+        response.data = {};
+      
+      set(response.data, "status", response.status);
+      return response.data;
+    },
+    preparingOperations: async function (clusterName: string, payload: any, 
params="") {
+      let url = `/clusters/${clusterName}`;
+      if(params !== "") 
+        url = `${url}?${params}`
+      const response = await ambariApi.request({
+        url: url,
+        method: "PUT",
+        data: payload,
+        headers:{
+          "Content-Type":"text/plain"
+        }
+      })
+      return response.data;
+    },
+    regenerateKeytabs: async function (clusterName: string, payload: any, 
params: string) {
+      const url = `/clusters/${clusterName}?${params}`;
+      const response = await ambariApi.request({
+        url: url,
+        method: "PUT",
+        data: payload,
+        headers: {
+          "Content-Type": "text/plain"
+        }
+      })
+      return response.data;
+    },
+    kerberosDescriptor: async function (clusterName: string, payload: any) {
+      const url = `/clusters/${clusterName}/artifacts/kerberos_descriptor`;
+      const response = await ambariApi.request({
+        url: url,
+        method: "POST",
+        data: payload,
+        headers: {
+          "Content-Type": "text/plain"
+        }
+      })
+      return response.data;
+    },
+    getTask: async function (
+      clusterName: string,
+      requestId: string,
+      taskId: string
+    ) {
+      const url = 
`/clusters/${clusterName}/requests/${requestId}/tasks/${taskId}`;
+      const response = await ambariApi.request({
+        url: url,
+        method: "GET",
+      });
+      return response.data;
+    },
+    installPackages: async function (clusterName: string, payload: any) {
+      const url = `/clusters/${clusterName}/stack_versions`
+      const response = await ambariApi.request({
+        url: url,
+        method: "POST",
+        data: payload,
+        headers: {
+          "Content-Type": "text/plain"
+        }
+      })
+      return response.data
+    }, 
+    getTaskId : async function (requestId: string) {
+      const url = `/requests/${requestId}/tasks/?`;
+      const response = await ambariApi.request({
+        url: url,
+        method: "GET",
+      });
+      return response.data;
+    },
+    getTaskStatus: async function (requestID:string,taskId: string) {
+      const url = `requests/${requestID}/tasks/${taskId}?`;
+      const response = await ambariApi.request({
+        url: url,
+        method: "GET",
+      });
+      return response.data;
+    }
+};
\ No newline at end of file
diff --git a/ambari-web/latest/src/api/servicesApi.ts 
b/ambari-web/latest/src/api/servicesApi.ts
new file mode 100644
index 0000000000..1d176867bb
--- /dev/null
+++ b/ambari-web/latest/src/api/servicesApi.ts
@@ -0,0 +1,45 @@
+/**
+ * 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 { ambariApi } from "./config/axiosConfig";
+
+export const ServicesApi = {
+  getServices: async (stack: string, version: string) => {
+    const url = 
`stacks/${stack}/versions/${version}/services?fields=StackServices/*,components/*,components/dependencies/Dependencies/scope,components/dependencies/Dependencies/service_name,artifacts/Artifacts/artifact_name`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  deleteServiceWithUpdatedConfigs: async function (
+    clusterName: string,
+    data: any
+  ) {
+    const url = `/clusters/${clusterName}`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "PUT",
+      headers: {
+        "Content-Type": "text/plain",
+        "X-Requested-By": "ambari-web",
+      },
+      data: JSON.stringify(data),
+    });
+    return response.data;
+  },
+};
diff --git a/ambari-web/latest/src/components/OperationProgress.tsx 
b/ambari-web/latest/src/components/OperationProgress.tsx
new file mode 100644
index 0000000000..9467a390e0
--- /dev/null
+++ b/ambari-web/latest/src/components/OperationProgress.tsx
@@ -0,0 +1,335 @@
+/**
+ * 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 { cloneDeep, filter, findIndex, get, has, set } from "lodash";
+import { useContext, useEffect, useRef, useState } from "react";
+import usePolling from "../hooks/usePolling";
+import { RequestApi } from "../api/requestApi";
+import { AppContext } from "../store/context";
+import { isFailed, isFinished } from "../Utils/Utility";
+import { ProgressStatus} from "../constants";
+import { Button, ProgressBar, Stack } from "react-bootstrap";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+  faCircleCheck,
+  faTimes,
+  faUndo,
+} from "@fortawesome/free-solid-svg-icons";
+// import modalManager from "../store/ModalManager";
+// import BackgroundOperations from "../screens/BackgroundOperations";
+
+type PropTypes = {
+  title: string;
+  description: string;
+  setCompletionStatus: (completed: boolean) => void;
+  operations: [
+    {
+      id: string | number;
+      label: "string";
+      callback: any;
+      skippable: boolean;
+    }
+  ];
+  dispatch?: (operationsState: any) => void;
+};
+
+type OperationRequestResponse = {
+  Requests: {
+    id: number | string;
+    status: string;
+  };
+  href: string;
+  status?:number;
+};
+
+function OperationsProgress({
+  // title,
+  // description,
+  setCompletionStatus,
+  operations,
+  dispatch,
+}: PropTypes) {
+  const [operationsState, setOperationsState] = useState(operations);
+  const operationsRef = useRef(operations);
+  const activeOperationId = useRef<any>(null);
+  const { clusterName } = useContext(AppContext);
+  const { stopPolling, pausePolling, resumePolling } = usePolling(
+    trackCurrentRequestStatus
+  );
+  const activeRequestId = useRef<string|number>(0);
+  const startedTasks: any = useRef([]);
+  async function trackCurrentRequestStatus() {
+    const operationsStateCopy = cloneDeep(operationsRef.current);
+    const trackingStatusForOperation: any = operationsStateCopy.find(
+      (operation) => operation.id == activeOperationId.current
+    );
+    if (activeRequestId.current) {
+      const activeRequestStatus = await RequestApi.getRequestStatus(
+        clusterName,
+        activeRequestId.current as any
+      );
+      const { Requests } = activeRequestStatus;
+      if (activeRequestStatus?.Requests?.request_status) {
+        set(
+          trackingStatusForOperation,
+          "requestId",
+          activeRequestStatus?.Requests?.id
+        );
+        set(
+          trackingStatusForOperation,
+          "status",
+          activeRequestStatus?.Requests?.request_status||"FAILED"
+        );
+        set(
+          trackingStatusForOperation,
+          "progress",
+          activeRequestStatus?.Requests?.progress_percent
+        );
+        set(
+          trackingStatusForOperation,
+          "requestInfo",
+          activeRequestStatus?.Requests
+        );
+        const requestStages = filter(
+          activeRequestStatus.stages,
+          function (stage) {
+            return has(stage, "Stage.context");
+          }
+        );
+        set(trackCurrentRequestStatus, "stages", requestStages);
+        setOperationsState(operationsStateCopy);
+        operationsRef.current = operationsStateCopy;
+        console.log("Current request status is", Requests.request_status);
+        if (isFinished(Requests.request_status)) {
+          console.log("Operation Progress operation finished");
+          if (Requests.request_status === ProgressStatus.FAILED) {
+            pausePolling();
+          } else {
+            if (activeOperationId.current == (operations as any)?.at(-1)?.id) {
+              stopPolling();
+              setCompletionStatus(true);
+            } else {
+              const currentActiveIndex = findIndex(operations, [
+                "id",
+                Number(activeOperationId?.current),
+              ]);
+              executeTask(operationsRef.current[Number(currentActiveIndex) + 
1]?.id);
+            }
+          }
+        }
+      }
+    }
+  }
+  async function executeTask(id: string | number) {
+    id = Number(id);
+    if (!startedTasks.current.includes(id)) {
+      activeOperationId.current = id;
+      startedTasks.current.push(id);
+      const operationsStateCopy = cloneDeep(operationsRef.current);
+      const matchingOperation: any = operationsStateCopy.find(
+        (operation) => operation.id == id
+      );
+      if (matchingOperation) {
+        try {
+        const operationCallbackResponse:OperationRequestResponse = await 
matchingOperation?.callback();
+        if (operationCallbackResponse?.Requests) {
+          matchingOperation.requestId = 
operationCallbackResponse?.Requests?.id;
+          activeRequestId.current = operationCallbackResponse?.Requests?.id;
+        }
+        //TODO: @vhassija Please verify for all statusCode
+        else if(operationCallbackResponse?.status === 200|| 
!operationCallbackResponse){
+          if (activeOperationId.current == (operationsRef.current as 
any)?.at(-1)?.id) {
+            stopPolling();
+            setCompletionStatus(true);
+          } else {
+            const currentActiveIndex = 
operationsRef.current.findIndex((operation) => operation.id == 
activeOperationId.current);
+            executeTask(operationsRef.current[Number(currentActiveIndex) + 
1]?.id);
+          }
+        }
+        else{
+          console.error("Operation failed with response", 
operationCallbackResponse);
+          matchingOperation.status = "FAILED";
+        }
+        }
+      catch(err){
+        console.error("Got request", err)
+        matchingOperation.status = "FAILED";
+
+      }
+      }
+      setOperationsState(operationsStateCopy);
+      operationsRef.current = operationsStateCopy;
+    }
+  }
+  const retryOperation = () => {
+    startedTasks.current = startedTasks.current.filter((task:any) => {
+      task != activeOperationId.current;
+    });
+    executeTask(activeOperationId.current as any);
+    resumePolling();
+  };
+  const renderStagesForOperation = (operation: any) => {
+    return (
+      <Stack direction="vertical">
+        {operation.stages.map((stage: any) => {
+          return (
+            <Stack
+              direction="horizontal"
+              className="justify-content-between mt-3"
+              key={stage.context}
+            >
+              <div className="d-flex align-items-center">
+                {isFinished(stage.status) && (
+                  <FontAwesomeIcon icon={faCircleCheck} color="success" />
+                )}
+                {isFailed(stage.status) && (
+                  <FontAwesomeIcon icon={faTimes} color="danger" />
+                )}
+                <div>{stage.context} </div>
+                {isFailed(stage.status) ? (
+                  <Button
+                    size="sm"
+                    onClick={retryOperation}
+                    variant="success"
+                    className="ms-2"
+                  >
+                    <FontAwesomeIcon className="me-2" icon={faUndo} />
+                    Retry Operation
+                  </Button>
+                ) : null}
+              </div>
+              {get(stage, "progress_percent", 0) &&
+              !isFinished(stage.status) ? (
+                <ProgressBar
+                  striped
+                  className={`w-25`}
+                  variant="info"
+                  now={stage.progress_percent}
+                  label={`${Math.floor(stage.progress)}%`}
+                />
+              ) : null}
+            </Stack>
+          );
+        })}
+      </Stack>
+    );
+  };
+
+  useEffect(() => {
+    if(dispatch){
+      dispatch(operationsState);
+    }
+  }, [JSON.stringify(operationsState)]);
+
+  useEffect(() => {
+    let idx = -1;
+    for (let i = operationsRef.current.length - 1; i >= 0; i--) {
+      if (
+        get(operationsRef.current[i], "requestId", "") ||
+        get(operationsRef.current[i], "status", "")
+      ) {
+        idx = i;
+        activeOperationId.current = operationsRef.current?.[i]?.id;
+        break;
+      }
+    }
+    if (idx === -1) {
+      executeTask(operationsRef.current?.[0]?.id);
+    } else {
+      for (let i = 0; i <= idx; i++) {
+        if (
+          get(operationsRef.current[i], "requestId", "") ||
+          get(operationsRef.current[i], "status", "")
+        ) {
+          startedTasks.current.push(operationsRef.current[i].id);
+        }
+      }
+    }
+  }, []);
+
+
+  return (
+    <div className="p-3">
+      <Stack direction="vertical">
+        {operationsState.map((operation: any) => {
+          const operationStages = operation.stages || [];
+          if (operationStages.length) {
+            return renderStagesForOperation(operation);
+          } else {
+            return (
+              <Stack
+                direction="horizontal"
+                className="justify-content-between mt-3"
+                key={operation.label}
+              >
+                <div className="d-flex align-items-center">
+                  <div
+                    onClick={() => {
+                    //   modalManager.show(
+                    //     <BackgroundOperations
+                    //       isOpen
+                    //       onClose={() => {
+                    //         modalManager.hide();
+                    //       }}
+                    //       rootLevel={ViewLevel.HOSTS}
+                    //       requestId={
+                    //         operation.requestId || 
operation?.requestInfo?.id
+                    //       }
+                    //     />
+                    //   );
+                    }}
+                    className={`${
+                      isFinished(operation.status) ||
+                      has(operation, "progress") ||
+                      has(operation, "requestId")
+                        ? "custom-link"
+                        : ""
+                    }`}
+                  >
+                    {operation.label}{" "}
+                  </div>
+                  {isFailed(operation.status) ? (
+                    <Button
+                      size="sm"
+                      onClick={retryOperation}
+                      variant="success"
+                      className="ms-2"
+                    >
+                      <FontAwesomeIcon className="me-2" icon={faUndo} />
+                      Retry Operation
+                    </Button>
+                  ) : null}
+                </div>
+                {has(operation, "progress") && !isFinished(operation.status) ? 
(
+                  <ProgressBar
+                    striped
+                    className={`w-25`}
+                    variant="info"
+                    now={operation.progress}
+                    label={`${Math.floor(operation.progress)}%`}
+                  />
+                ) : null}
+              </Stack>
+            );
+          }
+        })}
+      </Stack>
+    </div>
+  );
+}
+
+export default OperationsProgress;
diff --git a/ambari-web/latest/src/components/StepWizard/index.tsx 
b/ambari-web/latest/src/components/StepWizard/index.tsx
index 88d15f2ea3..6cafd67fb8 100644
--- a/ambari-web/latest/src/components/StepWizard/index.tsx
+++ b/ambari-web/latest/src/components/StepWizard/index.tsx
@@ -15,29 +15,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { FunctionComponent, memo, useState } from "react";
+import { FunctionComponent, memo, useContext, useState } from "react";
 import "./styles.scss";
 import classNames from "classnames";
-import { CheckLg} from "react-bootstrap-icons";
+import { CheckLg } from "react-bootstrap-icons";
 import ConfirmationModal from "../ConfirmationModal";
+import { get } from "lodash";
+
 interface StepWizardProps {
   wizardUtilities: any;
+  Context?: React.Context<any>;
 }
 
 const StepWizard: FunctionComponent<StepWizardProps> = ({
   wizardUtilities,
+  Context,
 }: any) => {
-  const {
-    activeStep,
-    wizardSteps,
-    jumpToStep,
-    canJumpFromCurrentStep,
-  } = wizardUtilities;
+  const { activeStep, wizardSteps, jumpToStep, canJumpFromCurrentStep } =
+    wizardUtilities;
+  const contextValue = useContext<any>(Context || {});
+  const flushStateToDb = get(contextValue, "flushStateToDb", "");
   const [jumpStep, setjumpStep] = useState(0);
   const [showNavigationModal, setShowNavigationModal] = useState(false);
-  console.log("active step in stepwizard ", activeStep)
   return (
-    <div className="step-wizard h-100" style={{ position: "relative" }}>
+    <div className="step-wizard h-95" style={{ position: "relative" }}>
       <ConfirmationModal
         isOpen={showNavigationModal}
         onClose={() => {
@@ -47,6 +48,9 @@ const StepWizard: FunctionComponent<StepWizardProps> = ({
         modalBody={`If you proceed to go back to Step ${jumpStep}, you will 
lose any changes you made.`}
         successCallback={() => {
           jumpToStep(jumpStep);
+          if (flushStateToDb) {
+            flushStateToDb("jump", jumpStep);
+          }
           setShowNavigationModal(false);
         }}
       />
@@ -58,8 +62,10 @@ const StepWizard: FunctionComponent<StepWizardProps> = ({
               <div
                 key={currentStep}
                 onClick={() => {
-                  setShowNavigationModal(true);
-                  setjumpStep(Number(currentStep));
+                  if (wizardSteps[activeStep].canGoBack) {
+                    setShowNavigationModal(true);
+                    setjumpStep(Number(currentStep));
+                  }
                 }}
                 className={classNames(
                   "d-flex align-items-center step-wizard-step cursor-pointer",
@@ -95,7 +101,7 @@ const StepWizard: FunctionComponent<StepWizardProps> = ({
           })}
         </div>
         <div className="px-5 py-4 w-100 mh-100 y-scroll wizard-content">
-          {wizardSteps[activeStep].Component}
+          {wizardSteps[activeStep]?.Component}
         </div>
       </div>
       {/* <div className="step-wizard-footer d-flex justify-content-between 
bg-white p-2">
diff --git a/ambari-web/latest/src/constants.ts 
b/ambari-web/latest/src/constants.ts
new file mode 100644
index 0000000000..09d775ee8c
--- /dev/null
+++ b/ambari-web/latest/src/constants.ts
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export enum ClusterProgressStatus {
+  PROVISIONING = "PROVISIONING",
+  ENABLING_NAMENODE_HA = "ENABLING_NAMENODE_HA",
+  ADDING_HOST = "ADDING_HOST",
+  ADDING_SERVICE = "ADDING_SERVICE",
+}
+export enum ProgressStatus {
+  IN_PROGRESS = "IN_PROGRESS",
+  COMPLETED = "COMPLETED",
+  FAILED = "FAILED",
+}
\ No newline at end of file
diff --git a/ambari-web/latest/src/hooks/useDebounce.ts 
b/ambari-web/latest/src/hooks/useDebounce.ts
new file mode 100644
index 0000000000..67f047840f
--- /dev/null
+++ b/ambari-web/latest/src/hooks/useDebounce.ts
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from "react";
+
+export const useDebounce = (  callback: (...args: any[]) => void,
+  delay: number,
+) => {
+  const callbackRef = React.useRef(callback);
+  React.useLayoutEffect(() => {
+    callbackRef.current = callback;
+  });
+  let timer: ReturnType<typeof setTimeout>;
+  const naiveDebounce = (    func: (...args: any[]) => void,
+    delayMs: number,
+    ...args: any[]
+  ) => {
+    clearTimeout(timer);
+    timer = setTimeout(() => { 
+      func(...args);
+    }, delayMs);  };
+  
+  return React.useMemo(() => (...args: any) => naiveDebounce(
+    callbackRef.current,
+    delay, 
+    ...args,
+  ), [delay]);
+};
+ 
+ 
\ No newline at end of file
diff --git a/ambari-web/latest/src/hooks/usePagination.ts 
b/ambari-web/latest/src/hooks/usePagination.ts
new file mode 100644
index 0000000000..ffd79ffdb3
--- /dev/null
+++ b/ambari-web/latest/src/hooks/usePagination.ts
@@ -0,0 +1,63 @@
+/**
+ * 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 { useState, useEffect, useMemo } from 'react';
+
+const usePagination = (items: any[], initialItemsPerPage=10) => {
+  const [currentPage, setCurrentPage] = useState(1);
+  const [itemsPerPage, setItemsPerPage] = useState(initialItemsPerPage);
+
+// Calculate max page based on current items and itemsPerPage
+  const maxPage = Math.max(1, Math.ceil(items.length / itemsPerPage));
+
+  // Reset to page 1 if the data changes significantly or if current page is 
out of bounds
+  useEffect(() => {
+    if (currentPage > maxPage) {
+      setCurrentPage(1);
+    }
+  }, [items.length, maxPage, currentPage]);
+
+  // Use useMemo to ensure we don't unnecessarily recalculate the current items
+  // This helps ensure that sorting is preserved when paginating
+  const currentItems = useMemo(() => {
+    // Get the current slice of items based on pagination settings
+    return items.slice(
+        (currentPage - 1) * itemsPerPage,
+        currentPage * itemsPerPage
+    );
+  }, [items, currentPage, itemsPerPage]);
+
+  const changePage = (newPage: number) => {
+    const safePage = Math.max(1, Math.min(newPage, maxPage));
+    setCurrentPage(safePage);
+  };
+
+  const updateItemsPerPage = (newItemsPerPage: number) => {
+    setItemsPerPage(newItemsPerPage);
+    setCurrentPage(1); // Reset to the first page when changing items per page
+  };
+
+  return {
+    currentItems,
+    changePage,
+    currentPage,
+    maxPage,
+    itemsPerPage,
+    setItemsPerPage: updateItemsPerPage
+  };
+};
+export default usePagination;
\ No newline at end of file
diff --git a/ambari-web/latest/src/hooks/usePolling.ts 
b/ambari-web/latest/src/hooks/usePolling.ts
new file mode 100644
index 0000000000..5edc08406a
--- /dev/null
+++ b/ambari-web/latest/src/hooks/usePolling.ts
@@ -0,0 +1,64 @@
+/**
+ * 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.
+ */
+/* eslint-disable @typescript-eslint/ban-types */
+import { useEffect, useRef, useCallback, useState } from 'react';
+
+function usePolling(apiFunction: Function, interval = 2000) {
+  const savedCallback = useRef<Function>(null);
+  const intervalId = useRef<NodeJS.Timeout | null>(null);
+  const [isPaused, setIsPaused] = useState(false);
+
+  const stopPolling = useCallback(() => {
+    if (intervalId.current) {
+      clearInterval(intervalId.current);
+      intervalId.current = null;
+    }
+  }, []);
+
+  const pausePolling = useCallback(() => {
+    setIsPaused(true);
+    stopPolling();
+  }, [stopPolling]);
+
+  const resumePolling = useCallback(() => {
+    setIsPaused(false);
+  }, []);
+
+  // Remember the latest callback.
+  useEffect(() => {
+    savedCallback.current = apiFunction;
+  }, [apiFunction]);
+
+  // Set up the interval.
+  useEffect(() => {
+    function tick() {
+      if (savedCallback.current) {
+        savedCallback.current();
+      }
+    }
+    
+    if (!isPaused && interval !== null) {
+      intervalId.current = setInterval(tick, interval);
+      return () => stopPolling();
+    }
+  }, [interval, isPaused, stopPolling]);
+
+  return { stopPolling, pausePolling, resumePolling };
+}
+
+export default usePolling;
\ No newline at end of file
diff --git a/ambari-web/latest/src/hooks/usePrevious.ts 
b/ambari-web/latest/src/hooks/usePrevious.ts
new file mode 100644
index 0000000000..45cfc239d6
--- /dev/null
+++ b/ambari-web/latest/src/hooks/usePrevious.ts
@@ -0,0 +1,27 @@
+/**
+ * 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 { useEffect, useRef } from "react";
+
+const usePrevious = (value:any) => {
+    const ref = useRef<any>(null);
+    useEffect(() => {
+      ref.current = value;
+    });
+    return ref.current;
+  };
+export default usePrevious;
diff --git a/ambari-web/latest/src/hooks/useStepWizard.ts 
b/ambari-web/latest/src/hooks/useStepWizard.ts
new file mode 100644
index 0000000000..425c1bcefb
--- /dev/null
+++ b/ambari-web/latest/src/hooks/useStepWizard.ts
@@ -0,0 +1,141 @@
+/**
+ * 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 { useState, useEffect } from "react";
+// import { useNavigate } from "react-router-dom";
+import { cloneDeep } from "lodash";
+import { Step } from "../types/StepWizard";
+import { useLocation, useNavigate, useParams } from "react-router-dom";
+
+const useStepWizard = (steps: any, initialActiveStep = 0, onCancel?: any) => {
+  const [activeStep, setActiveStep] = useState(initialActiveStep || 0);
+  const navigate = useNavigate();
+  const [wizardSteps, setWizardSteps] = useState<{ [key: number]: Step }>(
+    steps
+  );
+  const { stepNumber } = useParams();
+  const location = useLocation();
+  useEffect(() => {
+    if (stepNumber) {
+      navigate(location.pathname.replace(/step\d+/g, `step${activeStep}`));
+    }
+  }, [activeStep]);
+
+  const initialiseNextCallback = async (next: any) => {
+    const wizardStepsCopy = { ...wizardSteps };
+    wizardStepsCopy[activeStep].onNext = next;
+    setWizardSteps(wizardStepsCopy);
+  };
+
+  const handleNext = async () => {
+    const wizardStepsCopy = { ...wizardSteps };
+
+    if (wizardSteps[activeStep].onNext) {
+      try {
+        const canProceed = await wizardSteps[activeStep].onNext();
+        if (canProceed) {
+          wizardStepsCopy[activeStep].completed = true;
+          setWizardSteps(wizardStepsCopy);
+          if (activeStep !== Object.keys(steps).length - 1) {
+            setActiveStep(activeStep + 1);
+          }
+        }
+      } catch (error) {
+        console.log("Cannot move to next step:", error);
+      }
+    }
+  };
+  const handleNextImperitive = async () => {
+    const wizardStepsCopy = { ...wizardSteps };
+    wizardStepsCopy[activeStep].completed = true;
+    setActiveStep(Number(activeStep) + 1);
+    setWizardSteps(wizardStepsCopy);
+  };
+  const handleBackImperitive = async () => {
+    const wizardStepsCopy = { ...wizardSteps };
+    wizardStepsCopy[activeStep].completed = false;
+    wizardStepsCopy[activeStep - 1].completed = false;
+    setActiveStep(Number(activeStep) - 1);
+    setWizardSteps(wizardStepsCopy);
+  };
+
+  const enableNext = async (nextCallback: any) => {
+    const wizardStepsCopy = { ...wizardSteps };
+    wizardStepsCopy[activeStep].isNextEnabled = true;
+    wizardStepsCopy[activeStep].onNext = nextCallback;
+    setWizardSteps(wizardStepsCopy);
+  };
+  const disableNext = () => {
+    const wizardStepsCopy = { ...wizardSteps };
+    wizardStepsCopy[activeStep].isNextEnabled = false;
+    setWizardSteps(wizardStepsCopy);
+  };
+
+  const handleBack = () => {
+    let wizardStepsCopy = { ...wizardSteps };
+    wizardStepsCopy[activeStep].completed = false;
+    wizardStepsCopy[activeStep - 1].completed = false;
+    setWizardSteps(wizardStepsCopy);
+    if (activeStep !== 0) setActiveStep(activeStep - 1);
+  };
+
+  const canJumpFromCurrentStep = (currentStep: number) => {
+    return !(
+      currentStep > activeStep ||
+      (currentStep !== activeStep && !wizardSteps[activeStep]?.canGoBack)
+    );
+  };
+
+  useEffect(() => {
+    // navigate(`/installer/step${activeStep}`);
+  }, [activeStep]);
+
+  const jumpToStep = (stepNumber: number, isImperitiveJump = false) => {
+    if (canJumpFromCurrentStep(stepNumber) || isImperitiveJump) {
+      const wizardStepsCopy = cloneDeep(wizardSteps);
+      for (const step in wizardStepsCopy) {
+        if (Number(step) >= stepNumber) {
+          wizardStepsCopy[step].completed = false;
+        } else {
+          wizardStepsCopy[step].completed = true;
+        }
+      }
+      setWizardSteps(wizardStepsCopy);
+      setActiveStep(stepNumber);
+    }
+  };
+  console.log("Wizard Steps in useStepWizard", wizardSteps);
+
+  return {
+    activeStep,
+    wizardSteps,
+    handleNext,
+    handleBack,
+    jumpToStep,
+    canJumpFromCurrentStep,
+    enableNext,
+    disableNext,
+    initialiseNextCallback,
+    handleNextImperitive,
+    handleBackImperitive,
+    currentStep: wizardSteps[activeStep],
+    prevStepNumber: activeStep - 1,
+    onCancel
+  };
+};
+
+export default useStepWizard;
diff --git a/ambari-web/latest/src/store/context.tsx 
b/ambari-web/latest/src/store/context.tsx
new file mode 100644
index 0000000000..f3886871a5
--- /dev/null
+++ b/ambari-web/latest/src/store/context.tsx
@@ -0,0 +1,489 @@
+/**
+ * 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, {
+  createContext,
+  Dispatch,
+  useEffect,
+  useReducer,
+  useState,
+} from "react";
+import { State, Action } from "./types";
+import { reducer, initialState } from "./reducer";
+import { Client } from "@stomp/stompjs";
+import ClusterApi from "../api/clusterApi";
+import { ChooseServicesApi } from "../api/chooseServicesApi";
+import { ServicesApi } from "../api/servicesApi";
+import { get, isEmpty, isString, map, set } from "lodash";
+import ConfigsApi from "../api/configsApi";
+import {
+//   mapStackConfigProperties,
+  redirectToLogin,
+} from "../Utils/Utility";
+import LoginApi from "../api/loginApi";
+import { db } from "../Utils/db";
+// import {LocalStorageOps} from "../Utils/LocalStorageOps";
+
+interface AppContextProps {
+  state: State;
+  dispatch: Dispatch<Action>;
+  client: any;
+  isSocketConnected: boolean;
+  parsedSocketMessages: any[];
+  clusterName: string;
+  services: any[];
+  cluster: any;
+  isAppLoaded: boolean;
+  serviceComponentInfo: any;
+  isKerberosEnabled: boolean;
+  stackConfigurations: any;
+  allHostNames: string[];
+  ambariProperties: any;
+  upgradeState: string;
+  setUpgradeState: (state: string) => void;
+  upgradeDirection: string;
+  setUpgradeDirection: (direction: string) => void;
+  upgradeSuspend: boolean;
+  currentStackVersion: string;
+  setCurrentStackVersion: (version: string) => void;
+  upgradeId: number;
+  setUpgradeId: (id: number) => void;
+  userUrl?: string;
+  sessionsValidated: boolean;
+  sessionExists: boolean;
+  clusterState: any;
+}
+
+export const AppContext = createContext<AppContextProps>({
+  state: initialState,
+  dispatch: () => undefined,
+  client: null,
+  isSocketConnected: false,
+  parsedSocketMessages: [],
+  clusterName: "",
+  services: [],
+  cluster: {},
+  isAppLoaded: false,
+  serviceComponentInfo: {},
+  isKerberosEnabled: false,
+  stackConfigurations: [],
+  allHostNames: [],
+  ambariProperties: {},
+  upgradeState: "",
+  setUpgradeState: () => {},
+  upgradeDirection: "",
+  setUpgradeDirection: () => {},
+  upgradeSuspend: false,
+  currentStackVersion: "",
+  setCurrentStackVersion: () => {},
+  upgradeId: 0,
+  setUpgradeId: () => {},
+  sessionExists: false,
+  sessionsValidated: false,
+  clusterState: {}
+});
+
+export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
+  children,
+}) => {
+  const [state, dispatch] = useReducer(reducer, initialState);
+  const [isSocketConnected, setIsSocketConnected] = useState(false);
+  const [socketClient, setSocketClient] = useState(null);
+  const [isAppLoaded, setAppLoaded] = useState(false);
+  const [parsedSocketMessages, setParsedSocketMessages] = useState<any[]>([]);
+  const [clusterName, setClusterName] = useState<string>("");
+  const [isKerberosEnabled, setIsKerberosEnabled] = useState(false);
+  const [cluster, setCluster] = useState<any>({});
+  const [serviceComponentInfo, setServiceComponentInfo] = useState<any>({});
+  const [upgradeState, setUpgradeState] = useState<string>("");
+  const [upgradeDirection, setUpgradeDirection] = useState<string>("");
+  const [upgradeSuspend, setUpgradeSuspend] = useState<boolean>(false);
+  const [upgradeId, setUpgradeId] = useState<number>(0);
+  const [currentStackVersion, setCurrentStackVersion] = useState<string>("");
+  const [ambariProperties, setAmbariProperties] = useState({});
+  const [sessionsValidated, setSessionsValidated] = useState(false);
+  const [sessionExists, setSessionExists] = useState(false);
+  const [clusterState, setClusterState] = useState({});
+  const [userUrl, setUserUrl] = useState("");
+  const client = new Client({
+    brokerURL: "/api/stomp/v1/websocket", // 'ws://localhost:15674/ws'
+    debug: function (str) {
+      console.log(str);
+    },
+    reconnectDelay: 1000,
+    heartbeatIncoming: 1000,
+    heartbeatOutgoing: 1000,
+  });
+  const [services, setServices] = useState([]);
+  const [stackConfigurations, setStackConfigurations] = useState([]);
+
+  const [allHostNames, setAllHostNames] = useState([]);
+  const fetchClusterServices = async () => {
+    try {
+      const clusterServices = await 
ChooseServicesApi.servicesList(clusterName);
+      setServices(clusterServices.items);
+    } catch (err) {
+      setServices([]);
+    }
+  };
+
+  const fetchClusterState = async () => {
+    try {
+      const state = await ClusterApi.getPersistData("CLUSTER_STATE");
+      setClusterState(state);
+    } catch (error) {
+      console.error("Failed to fetch cluster state:", error);
+    }
+  };
+
+  useEffect(() => {
+    fetchClusterState();
+  }, []);
+
+  useEffect(() => {
+    if (clusterName) {
+      fetchClusterServices();
+      fetchAllHostNames();
+      fetchUpgradeStates();
+    }
+  }, [clusterName]);
+
+  useEffect(() => {
+    async function fetchStackConfigs() {
+      const stack = get(cluster, "version", "").split("-")[0];
+      const version = get(cluster, "version", "").split("-")[1];
+      const serviceNames = map(services, "ServiceInfo.service_name").join(",");
+      if (stack && version && serviceNames) {
+        //@ts-ignore
+        const response = await ConfigsApi.getServiceConfigurations(
+          stack,
+          version,
+          serviceNames
+        );
+        //TODO: Uncomment this once mapStackConfigProperties is defined
+        // const stackConfigs = mapStackConfigProperties(response);
+        const stackConfigs:never[]=[];
+
+        setStackConfigurations(stackConfigs);
+      }
+    }
+    fetchStackConfigs();
+  }, [services, cluster]);
+
+  const fetchClusterName = async () => {
+    try {
+      const name = await ClusterApi.getClusterName();
+      setClusterName(name);
+    } catch (error) {
+      console.error("Failed to fetch cluster name:", error);
+    }
+  };
+  const fetchClusterData = async () => {
+    try {
+      const clusterData = await ClusterApi.getClusterData();
+      set(
+        clusterData,
+        "items.[0].Clusters.stack",
+        get(clusterData, "items.[0].Clusters.version", "")?.split("-")[0]
+      );
+      set(
+        clusterData,
+        "items[0].Clusters.versionNum",
+        get(clusterData, "items.[0].Clusters.version", "")?.split("-")[1]
+      );
+      setCluster(clusterData?.items[0]?.Clusters);
+      setIsKerberosEnabled(
+        clusterData?.items?.[0]?.Clusters?.security_type === "KERBEROS"
+      );
+    } catch (error) {
+      console.error("Failed to fetch cluster data:", error);
+    }
+  };
+
+  const fetchServiceComponentInfo = async () => {
+    const stack = get(cluster, "version", "").split("-")[0];
+    const version = get(cluster, "version", "").split("-")[1];
+    try {
+      const data = await ServicesApi.getServices(stack, version);
+      setServiceComponentInfo(data);
+    } catch (error) {
+      console.error("Failed to fetch service component info:", error);
+    }
+  };
+
+  const fetchAllHostNames = async () => {
+    try {
+      const data = await ClusterApi.getHosts(clusterName);
+      const hostNames = data.items.map((item: any) => item.Hosts.host_name);
+      setAllHostNames(hostNames);
+    } catch (error) {
+      console.log("Error getting hosts");
+    }
+  };
+  const getAmbariProperties = async () => {
+    const response = await ClusterApi.loadAmbariProperties();
+    setAmbariProperties(response);
+  };
+  const fetchUserInfo = async () => {
+    try {
+      try {
+        let username = "";
+        const ambariLocalData = db.getItem("ambari");
+        if (ambariLocalData) {
+          let parsedData = {};
+          try {
+            parsedData = JSON.parse(db.getItem("ambari") || "{}");
+            if (isString(parsedData)) {
+              parsedData = JSON.parse(parsedData);
+            }
+          } catch (err) {
+            console.log("Error parsing ambari data", err);
+            parsedData = {};
+          }
+
+          let ambari: any = parsedData;
+          if (ambari?.app?.loginName) {
+            username = ambari.app.loginName;
+            // If we already have a username, we can consider the user 
authenticated
+            if (!username) {
+              window.location.href = "/#/login";
+            }
+            setSessionsValidated(true);
+            setSessionExists(true);
+          }
+        } else {
+          redirectToLogin();
+        }
+        // If we don't have user info in localStorage
+
+        //fetch initial LS value for key ambari with empty fields
+        let initialAmbariLsData: any = { app: {} };
+
+        //set login name in the app object within the ambari object
+        initialAmbariLsData.app.loginName = encodeURIComponent(username);
+        initialAmbariLsData.app.authenticated = true;
+         const params = { usr: "", loginName: encodeURIComponent(username) };
+        const response = await LoginApi.handleSuccessfulLogin(params);
+        initialAmbariLsData.app.user = response.data.Users;
+        //convert JS object to JSON String and then encrypt the JSON String
+        initialAmbariLsData = JSON.stringify(initialAmbariLsData);
+        //encrypt the data and store it in Local Storage
+        db.setItem("ambari", initialAmbariLsData);
+        return true;
+      } catch (error) {
+        setSessionsValidated(true);
+        setSessionExists(false);
+        return false;
+      }
+    } catch (err) {
+      console.log("Error in fetching user info", err);
+      setSessionsValidated(true);
+      setSessionExists(false);
+      return false;
+    }
+  };
+
+  const fetchUpgradeStates = async () => {
+    const response = await ClusterApi.getUpgradeState(clusterName);
+
+    // response would have items get the last item.
+    const lastItemIndex = response?.items?.length - 1;
+    const upgradeState = get(
+      response,
+      `items[${lastItemIndex}].Upgrade.request_status`,
+      "NOT_REQUIRED"
+    );
+    const upgradeSuspend = get(
+      response,
+      `items[${lastItemIndex}].Upgrade.suspended`,
+      false
+    );
+    const upgradeId = get(
+      response,
+      `items[${lastItemIndex}].Upgrade.request_id`,
+      0
+    );
+    const upgradeDirection = get(response, 
`items[${lastItemIndex}].Upgrade.direction`, "UPGRADE");
+    setUpgradeDirection(upgradeDirection);
+    setUpgradeId(upgradeId);
+    setUpgradeState(upgradeState);
+    setUpgradeSuspend(upgradeSuspend);
+  };
+
+  async function getUserUrl() {
+    const persistedData = await ClusterApi.getPersistData(
+      "USER_REDIRECTION_URL"
+    );
+    setUserUrl(persistedData);
+  }
+  useEffect(() => {
+    async function moveAppToReadyState() {
+      const isAuthenticated = await fetchUserInfo();
+      setSessionsValidated(true);
+      setSessionExists(true);
+      if (isAuthenticated) {
+        await fetchClusterName();
+        await fetchClusterData();
+        await getAmbariProperties();
+        try {
+          await getUserUrl();
+        } catch (err) {
+        } finally {
+          setAppLoaded(true);
+        }
+      } else {
+      }
+    }
+    moveAppToReadyState();
+  }, []);
+
+  useEffect(() => {
+    if (!isEmpty(cluster)&&cluster?.versionNum&&cluster?.stack) {
+      fetchServiceComponentInfo();
+    }
+  }, [cluster]);
+
+  client.onConnect = function () {
+    setSocketClient(client as any);
+    setIsSocketConnected(true);
+    client.subscribe("/events/requests", (message: any) => {
+      // called when the client receives a STOMP message from the server
+      if (message.body) {
+        try {
+          const parsedMessage = JSON.parse(message.body);
+          //TODO: parsedSocketMessages can exceed to very long list, need to 
limit it to some constant
+          setParsedSocketMessages((prevMessages) => [
+            parsedMessage,
+            ...prevMessages,
+          ]);
+        } catch {
+          console.log("Error in parsing socket message");
+        }
+      } else {
+      }
+    });
+    client.subscribe("/events/services", (message: any) => {
+      // called when the client receives a STOMP message from the server
+      if (message.body) {
+        try {
+          const parsedMessage = JSON.parse(message.body);
+          //TODO: parsedSocketMessages can exceed to very long list, need to 
limit it to some constant
+          setParsedSocketMessages((prevMessages) => [
+            parsedMessage,
+            ...prevMessages,
+          ]);
+        } catch {
+          console.log("Error in parsing socket message");
+        }
+      } else {
+      }
+    });
+    client.subscribe("/events/hosts", (message: any) => {
+      if (message.body) {
+        try {
+          const parsedMessage = JSON.parse(message.body);
+          set(parsedMessage, "destination", message.headers.destination);
+          setParsedSocketMessages((prevMessages) => [
+            parsedMessage,
+            ...prevMessages,
+          ]);
+        } catch {
+          console.log("Error in parsing socket message");
+        }
+      } else {
+      }
+    });
+    client.subscribe("/events/requests", (message: any) => {
+      if (message.body) {
+        try {
+          const parsedMessage = JSON.parse(message.body);
+          set(parsedMessage, "destination", message.headers.destination);
+          setParsedSocketMessages((prevMessages) => [
+            parsedMessage,
+            ...prevMessages,
+          ]);
+        } catch {
+          console.log("Error in parsing socket message");
+        }
+      } else {
+      }
+    });
+    client.subscribe("/events/hostcomponents", (message: any) => {
+      if (message.body) {
+        try {
+          const parsedMessage = JSON.parse(message.body);
+          set(parsedMessage, "destination", message.headers.destination);
+          setParsedSocketMessages((prevMessages) => [
+            parsedMessage,
+            ...prevMessages,
+          ]);
+        } catch {
+          console.log("Error in parsing socket message");
+        }
+      } else {
+      }
+    });
+  };
+
+  client.onStompError = function (frame: any) {
+    // Will be invoked in case of error encountered at Broker
+    console.error("Broker reported error: " + frame.headers["message"]);
+    console.error("Additional details: " + frame.body);
+  };
+
+  useEffect(() => {
+    client.activate();
+  }, []);
+
+  // add a  method to check if clusterExists if yes return clusterName from 
API otherwise return empty string
+
+  return (
+    <AppContext.Provider
+      value={{
+        state,
+        dispatch,
+        client: socketClient,
+        isSocketConnected,
+        parsedSocketMessages,
+        clusterName,
+        cluster,
+        isAppLoaded,
+        services,
+        serviceComponentInfo,
+        ambariProperties,
+        isKerberosEnabled,
+        stackConfigurations,
+        allHostNames,
+        upgradeState,
+        setUpgradeState,
+        upgradeDirection,
+        setUpgradeDirection,
+        upgradeSuspend,
+        currentStackVersion,
+        setCurrentStackVersion,
+        upgradeId,
+        setUpgradeId,
+        userUrl,
+        sessionExists,
+        sessionsValidated,
+        clusterState,
+      }}
+    >
+      {children}
+    </AppContext.Provider>
+  );
+};
diff --git a/ambari-web/latest/src/store/reducer.ts 
b/ambari-web/latest/src/store/reducer.ts
new file mode 100644
index 0000000000..138456e5b9
--- /dev/null
+++ b/ambari-web/latest/src/store/reducer.ts
@@ -0,0 +1,29 @@
+/**
+ * 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 { State, Action, ActionTypes } from "./types";
+
+export const initialState: State = { selectedOption: "dashboard" };
+
+export const reducer = (state: State, action: Action): State => {
+  switch (action.type) {
+    case ActionTypes.SELECTOPTION:
+      return { ...state, selectedOption: action.payload };
+    default:
+      return state;
+  }
+};
diff --git a/ambari-web/latest/src/store/types.ts 
b/ambari-web/latest/src/store/types.ts
new file mode 100644
index 0000000000..eb6b7b7470
--- /dev/null
+++ b/ambari-web/latest/src/store/types.ts
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export interface State {
+  selectedOption: string;
+}
+
+export enum ActionTypes {
+    SELECTOPTION = "SELECTOPTION",
+}
+
+export type Action =
+  | { type: ActionTypes.SELECTOPTION; payload: string; }
diff --git a/ambari-web/latest/src/types/StepWizard.ts 
b/ambari-web/latest/src/types/StepWizard.ts
new file mode 100644
index 0000000000..7bdcf74f21
--- /dev/null
+++ b/ambari-web/latest/src/types/StepWizard.ts
@@ -0,0 +1,28 @@
+/**
+ * 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 { ReactNode } from "react";
+
+export interface Step {
+  label: String;
+  completed: Boolean;
+  Component: ReactNode;
+  canGoBack: Boolean;
+  isNextEnabled: Boolean;
+  onNext?:any;
+  nextLabel?:String;
+}


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

Reply via email to