You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ji...@apache.org on 2026/08/13 08:21:46 UTC
(ambari) branch frontend-refactor updated: AMBARI-26623: Complete React authentication and application shell parity (#4165)
This is an automated email from the ASF dual-hosted git repository.
JiaLiangC 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 2c1f3e6ce6 AMBARI-26623: Complete React authentication and application shell parity (#4165)
2c1f3e6ce6 is described below
commit 2c1f3e6ce621b92d89dd7e24a6ac7ca1cee4f144
Author: jialiang <ji...@gmail.com>
AuthorDate: Thu Aug 13 16:21:40 2026 +0800
AMBARI-26623: Complete React authentication and application shell parity (#4165)
* AMBARI-26623: Complete React authentication and application shell parity
* AMBARI-26623: Remove trailing blank line
---
ambari-web/latest/package-lock.json | 1579 +++++++++++++++++---
ambari-web/latest/package.json | 6 +-
ambari-web/latest/src/AmbariAboutModal.tsx | 88 +-
ambari-web/latest/src/App.tsx | 67 +-
ambari-web/latest/src/AppLoader.tsx | 244 +--
ambari-web/latest/src/InactivityTimeout.test.ts | 48 +
ambari-web/latest/src/InactivityTimeout.tsx | 183 ++-
ambari-web/latest/src/Utils/adminViewRedirect.ts | 5 +-
ambari-web/latest/src/Utils/authNavigation.test.ts | 77 +
ambari-web/latest/src/Utils/authNavigation.ts | 152 ++
ambari-web/latest/src/Utils/authPolicy.test.ts | 46 +
ambari-web/latest/src/Utils/authPolicy.ts | 52 +
ambari-web/latest/src/Utils/db.ts | 72 +-
.../{vite.config.ts => src/Utils/httpError.ts} | 13 +-
.../latest/src/Utils/persistedSettings.test.ts | 39 +
.../persistedSettings.ts} | 30 +-
ambari-web/latest/src/api/clusterApi.ts | 6 +-
ambari-web/latest/src/api/config/axiosConfig.ts | 23 +-
ambari-web/latest/src/api/loginApi.test.ts | 52 +
ambari-web/latest/src/api/loginApi.ts | 77 +-
.../AdminViewRouteGuard.test.ts} | 38 +-
.../AdminViewRouteGuard.tsx} | 41 +-
ambari-web/latest/src/components/AuthGuard.tsx | 21 +-
.../latest/src/components/DocumentTitleUpdater.tsx | 28 +-
.../components/FeatureRouteGuard.tsx} | 21 +-
ambari-web/latest/src/components/Modal.tsx | 48 +-
ambari-web/latest/src/components/Navbar.tsx | 136 +-
.../latest/src/components/Sidebar/SideItemList.tsx | 14 +-
.../latest/src/components/Sidebar/Sidebar.tsx | 15 +-
.../latest/src/components/UserSettingsModal.tsx | 168 ++-
ambari-web/latest/src/constants.ts | 29 +
ambari-web/latest/src/hooks/useAuth.ts | 6 +-
.../latest/src/layout/InstallerVersionGuard.tsx | 40 +
.../src/{InactivityTimeout.tsx => layout/Main.tsx} | 32 +-
ambari-web/latest/src/layout/Views.tsx | 80 +
ambari-web/latest/src/router/RoutesList.tsx | 400 +++--
.../screens/Authentication/AdminViewRedirect.tsx | 64 +
.../latest/src/screens/Authentication/Login.tsx | 107 +-
.../Authentication/LoginMessageModal.tsx} | 32 +-
.../latest/src/screens/Experimental/index.tsx | 139 ++
ambari-web/latest/src/store/UserContext.test.tsx | 125 ++
ambari-web/latest/src/store/UserContext.tsx | 480 +++---
ambari-web/latest/src/store/context.tsx | 241 ++-
.../UserSettingsModal.tsx => test/setup.ts} | 58 +-
ambari-web/latest/src/types/auth.ts | 7 +
ambari-web/latest/vite.config.ts | 8 +-
ambari-web/pom.xml | 3 +
.../ember-baseline/01-auth-shell.md | 50 +
.../ember-baseline/13-permissions-flags.md | 158 ++
.../ember-baseline/14-react-gap-matrix.md | 101 ++
.../ember-baseline/generated/ajax-calls.md | 440 ++++++
.../ember-baseline/generated/ajax-endpoints.md | 632 ++++++++
.../generated/api-by-module/auth-shell.md | 21 +
.../generated/browser-network-entrypoints.md | 66 +
.../ember-baseline/generated/direct-http-calls.md | 29 +
.../ember-baseline/generated/realtime-channels.md | 360 +++++
.../react-current/01-auth-shell-gap.md | 113 ++
.../react-current/issues/01-auth-shell.md | 85 ++
58 files changed, 5777 insertions(+), 1518 deletions(-)
diff --git a/ambari-web/latest/package-lock.json b/ambari-web/latest/package-lock.json
index 81a3077f6b..7e9dd3f7a3 100644
--- a/ambari-web/latest/package-lock.json
+++ b/ambari-web/latest/package-lock.json
@@ -11,6 +11,8 @@
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@stomp/stompjs": "^7.1.1",
+ "@tanstack/react-table": "^8.21.3",
+ "@types/file-saver": "^2.0.7",
"@types/lodash": "^4.17.16",
"@types/react-select": "^5.0.0",
"axios": "^1.11.0",
@@ -18,27 +20,34 @@
"chart.js": "^4.5.1",
"classnames": "^2.5.1",
"dayjs": "^1.11.13",
+ "deep-object-diff": "^1.1.9",
+ "file-saver": "^2.0.5",
"html-react-parser": "^5.2.6",
"i18next": "^25.1.2",
"i18next-browser-languagedetector": "^8.1.0",
"isomorphic-dompurify": "^2.26.0",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
+ "rc-slider": "^11.1.9",
"react": "^19.0.0",
"react-bootstrap": "^2.10.10",
"react-bootstrap-icons": "^1.11.6",
"react-chartjs-2": "^5.3.1",
"react-dom": "^19.0.0",
+ "react-helmet-async": "^3.0.0",
"react-hook-form": "^7.62.0",
"react-hot-toast": "^2.5.2",
"react-i18next": "^15.5.1",
"react-router-dom": "^7.6.0",
"react-select": "^5.10.2",
+ "react-use-websocket": "^4.13.0",
"sass": "^1.88.0",
- "timeago.js": "^4.0.2"
+ "timeago.js": "^4.0.2",
+ "universal-cookie": "^8.1.2"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
+ "@testing-library/react": "^16.3.0",
"@types/js-cookie": "^3.0.6",
"@types/node": "^24.2.1",
"@types/react": "^19.0.10",
@@ -48,9 +57,11 @@
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
+ "jsdom": "^26.1.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
- "vite": "^6.2.0"
+ "vite": "^6.2.0",
+ "vitest": "^3.2.4"
}
},
"node_modules/@acemir/cssom": {
@@ -74,49 +85,57 @@
}
},
"node_modules/@asamuzakjp/css-color": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz",
- "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@csstools/css-calc": "^3.0.0",
- "@csstools/css-color-parser": "^4.0.1",
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0",
- "lru-cache": "^11.2.5"
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
- "version": "11.2.5",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz",
- "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/@asamuzakjp/dom-selector": {
- "version": "6.7.8",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.8.tgz",
- "integrity": "sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ==",
+ "version": "6.8.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz",
+ "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==",
"license": "MIT",
"dependencies": {
"@asamuzakjp/nwsapi": "^2.3.9",
"bidi-js": "^1.0.3",
"css-tree": "^3.1.0",
"is-potential-custom-element-name": "^1.0.1",
- "lru-cache": "^11.2.5"
+ "lru-cache": "^11.2.6"
}
},
"node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
- "version": "11.2.5",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz",
- "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==",
+ "version": "11.5.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
+ "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
+ "node_modules/@asamuzakjp/generational-cache": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
+ "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
"node_modules/@asamuzakjp/nwsapi": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
@@ -153,7 +172,6 @@
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -405,10 +423,23 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@bramus/specificity": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
+ "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
+ "license": "MIT",
+ "dependencies": {
+ "css-tree": "^3.0.0"
+ },
+ "bin": {
+ "specificity": "bin/cli.js"
+ }
+ },
"node_modules/@csstools/color-helpers": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz",
- "integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -421,13 +452,14 @@
],
"license": "MIT-0",
"engines": {
- "node": ">=20.19.0"
+ "node": ">=18"
}
},
"node_modules/@csstools/css-calc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.0.0.tgz",
- "integrity": "sha512-q4d82GTl8BIlh/dTnVsWmxnbWJeb3kiU8eUH71UxlxnS+WIaALmtzTL8gR15PkYOexMQYVk0CO4qIG93C1IvPA==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -440,17 +472,18 @@
],
"license": "MIT",
"engines": {
- "node": ">=20.19.0"
+ "node": ">=18"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-color-parser": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz",
- "integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -463,21 +496,22 @@
],
"license": "MIT",
"dependencies": {
- "@csstools/color-helpers": "^6.0.1",
- "@csstools/css-calc": "^3.0.0"
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
},
"engines": {
- "node": ">=20.19.0"
+ "node": ">=18"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-parser-algorithms": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
- "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -489,18 +523,17 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
- "node": ">=20.19.0"
+ "node": ">=18"
},
"peerDependencies": {
- "@csstools/css-tokenizer": "^4.0.0"
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.0.26",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz",
- "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==",
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz",
+ "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==",
"funding": [
{
"type": "github",
@@ -511,12 +544,21 @@
"url": "https://opencollective.com/csstools"
}
],
- "license": "MIT-0"
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
+ }
},
"node_modules/@csstools/css-tokenizer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
- "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -528,9 +570,8 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
- "node": ">=20.19.0"
+ "node": ">=18"
}
},
"node_modules/@emotion/babel-plugin": {
@@ -1233,9 +1274,9 @@
}
},
"node_modules/@exodus/bytes": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.12.0.tgz",
- "integrity": "sha512-BuCOHA/EJdPN0qQ5MdgAiJSt9fYDHbghlgrj33gRdy/Yp1/FMCDhU6vJfcKrLC0TPWGSrfH3vYXBQWmFHxlddw==",
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
+ "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
@@ -1420,9 +1461,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
@@ -1780,7 +1821,6 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
@@ -2149,6 +2189,96 @@
"tslib": "^2.8.0"
}
},
+ "node_modules/@tanstack/react-table": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz",
+ "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/table-core": "8.21.3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/@tanstack/table-core": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
+ "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
+ "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2194,6 +2324,24 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
@@ -2201,6 +2349,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/file-saver": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
+ "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
+ "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",
@@ -2225,7 +2379,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
"integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
"dev": true,
- "peer": true,
"dependencies": {
"undici-types": "~7.10.0"
}
@@ -2247,7 +2400,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz",
"integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -2339,7 +2491,6 @@
"integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.32.1",
"@typescript-eslint/types": "8.32.1",
@@ -2543,13 +2694,127 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
}
},
+ "node_modules/@vitest/expect": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
+ "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz",
+ "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.7",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",
+ "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",
+ "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.7",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",
+ "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.7",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",
+ "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",
+ "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.7",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2593,6 +2858,17 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -2616,6 +2892,27 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2725,7 +3022,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001716",
"electron-to-chromium": "^1.5.149",
@@ -2739,6 +3035,16 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"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",
@@ -2781,6 +3087,23 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2803,7 +3126,6 @@
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -2811,6 +3133,16 @@
"pnpm": ">=8"
}
},
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -2877,12 +3209,16 @@
"license": "MIT"
},
"node_modules/cookie": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
- "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/cosmiconfig": {
@@ -2926,60 +3262,50 @@
}
},
"node_modules/css-tree": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
- "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
"license": "MIT",
"dependencies": {
- "mdn-data": "2.12.2",
- "source-map-js": "^1.0.1"
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/cssstyle": {
- "version": "5.3.7",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz",
- "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@asamuzakjp/css-color": "^4.1.1",
- "@csstools/css-syntax-patches-for-csstree": "^1.0.21",
- "css-tree": "^3.1.0",
- "lru-cache": "^11.2.4"
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
},
"engines": {
- "node": ">=20"
- }
- },
- "node_modules/cssstyle/node_modules/lru-cache": {
- "version": "11.2.5",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz",
- "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
+ "node": ">=18"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/data-urls": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
- "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^16.0.0"
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
},
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": ">=18"
}
},
"node_modules/dayjs": {
@@ -3010,6 +3336,16 @@
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"license": "MIT"
},
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -3017,6 +3353,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deep-object-diff": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz",
+ "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==",
+ "license": "MIT"
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -3047,6 +3389,14 @@
"node": ">=0.10"
}
},
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -3190,6 +3540,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
@@ -3284,7 +3641,6 @@
"integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3447,6 +3803,16 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -3457,6 +3823,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3531,6 +3907,12 @@
"node": ">=16.0.0"
}
},
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+ "license": "MIT"
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3814,15 +4196,16 @@
}
},
"node_modules/html-encoding-sniffer": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
- "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@exodus/bytes": "^1.6.0"
+ "whatwg-encoding": "^3.1.1"
},
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": ">=18"
}
},
"node_modules/html-parse-stringify": {
@@ -3929,7 +4312,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.27.1"
},
@@ -3951,6 +4333,19 @@
"@babel/runtime": "^7.23.2"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4088,43 +4483,195 @@
"node": ">=20.19.5"
}
},
- "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==",
+ "node_modules/isomorphic-dompurify/node_modules/@asamuzakjp/css-color": {
+ "version": "5.1.11",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
+ "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/generational-cache": "^1.0.1",
+ "@csstools/css-calc": "^3.2.0",
+ "@csstools/css-color-parser": "^4.1.0",
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ },
"engines": {
- "node": ">=14"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
- "node_modules/js-tokens": {
+ "node_modules/isomorphic-dompurify/node_modules/@csstools/color-helpers": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz",
+ "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/@csstools/css-calc": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz",
+ "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/@csstools/css-color-parser": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz",
+ "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^6.1.0",
+ "@csstools/css-calc": "^3.3.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/@csstools/css-parser-algorithms": {
"version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
},
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
+ "node_modules/isomorphic-dompurify/node_modules/@csstools/css-tokenizer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/cssstyle": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz",
+ "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==",
"license": "MIT",
"dependencies": {
- "argparse": "^2.0.1"
+ "@asamuzakjp/css-color": "^5.0.1",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.28",
+ "css-tree": "^3.1.0",
+ "lru-cache": "^11.2.6"
},
- "bin": {
- "js-yaml": "bin/js-yaml.js"
+ "engines": {
+ "node": ">=20"
}
},
- "node_modules/jsdom": {
- "version": "28.0.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.0.0.tgz",
- "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==",
+ "node_modules/isomorphic-dompurify/node_modules/data-urls": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
+ "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/entities": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
+ "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/html-encoding-sniffer": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
+ "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.6.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/jsdom": {
+ "version": "28.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz",
+ "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==",
"license": "MIT",
"dependencies": {
"@acemir/cssom": "^0.9.31",
- "@asamuzakjp/dom-selector": "^6.7.6",
+ "@asamuzakjp/dom-selector": "^6.8.1",
+ "@bramus/specificity": "^2.4.2",
"@exodus/bytes": "^1.11.0",
- "cssstyle": "^5.3.7",
+ "cssstyle": "^6.0.1",
"data-urls": "^7.0.0",
"decimal.js": "^10.6.0",
"html-encoding-sniffer": "^6.0.0",
@@ -4135,7 +4682,7 @@
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^6.0.0",
- "undici": "^7.20.0",
+ "undici": "^7.21.0",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
@@ -4154,40 +4701,202 @@
}
}
},
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "node_modules/isomorphic-dompurify/node_modules/lru-cache": {
+ "version": "11.5.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
+ "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/parse5": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
+ "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
"license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
+ "dependencies": {
+ "entities": "^8.0.0"
},
- "engines": {
- "node": ">=6"
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
+ "node_modules/isomorphic-dompurify/node_modules/tldts": {
+ "version": "7.4.10",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz",
+ "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==",
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.4.10"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
},
- "node_modules/json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "node_modules/isomorphic-dompurify/node_modules/tldts-core": {
+ "version": "7.4.10",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz",
+ "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==",
"license": "MIT"
},
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
+ "node_modules/isomorphic-dompurify/node_modules/tough-cookie": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz",
+ "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
},
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
+ "node_modules/isomorphic-dompurify/node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/webidl-conversions": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
+ "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/whatwg-mimetype": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
+ "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/isomorphic-dompurify/node_modules/whatwg-url": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.11.0",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "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",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "26.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.1.1",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.1",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true,
@@ -4276,6 +4985,13 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4286,6 +5002,27 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -4295,9 +5032,9 @@
}
},
"node_modules/mdn-data": {
- "version": "2.12.2",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
- "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"license": "CC0-1.0"
},
"node_modules/memoize-one": {
@@ -4408,6 +5145,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/nwsapi": {
+ "version": "2.2.24",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz",
+ "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -4498,9 +5242,10 @@
}
},
"node_modules/parse5": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
- "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
@@ -4513,6 +5258,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -4556,6 +5302,23 @@
"node": ">=8"
}
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4614,6 +5377,44 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4673,12 +5474,49 @@
],
"license": "MIT"
},
+ "node_modules/rc-slider": {
+ "version": "11.1.9",
+ "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz",
+ "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.36.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-util": {
+ "version": "5.44.4",
+ "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz",
+ "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-util/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4740,7 +5578,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -4748,6 +5585,26 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-helmet-async": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz",
+ "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "invariant": "^2.2.4",
+ "react-fast-compare": "^3.2.2",
+ "shallowequal": "^1.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/react-hook-form": {
"version": "7.71.1",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz",
@@ -4909,6 +5766,12 @@
"react-dom": ">=16.6.0"
}
},
+ "node_modules/react-use-websocket": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.13.0.tgz",
+ "integrity": "sha512-anMuVoV//g2N76Wxqvqjjo1X48r9Np3y1/gMl7arX84tAPXdy5R7sB5lO5hvCzQRYjqXwV8XMAiEBOUbyrZFrw==",
+ "license": "MIT"
+ },
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
@@ -5011,6 +5874,13 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -5035,12 +5905,18 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/sass": {
"version": "1.89.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.0.tgz",
"integrity": "sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@@ -5090,6 +5966,12 @@
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -5113,6 +5995,13 @@
"node": ">=8"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -5131,6 +6020,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -5144,6 +6047,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strip-literal": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
+ "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/style-to-js": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
@@ -5205,15 +6128,29 @@
"integrity": "sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==",
"license": "MIT"
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
- "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fdir": "^6.4.4",
- "picomatch": "^4.0.2"
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -5223,11 +6160,14 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.4",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
- "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -5238,12 +6178,11 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -5251,22 +6190,54 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
+ "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/tldts": {
- "version": "7.0.23",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz",
- "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==",
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "tldts-core": "^7.0.23"
+ "tldts-core": "^6.1.86"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
- "version": "7.0.23",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz",
- "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==",
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/to-regex-range": {
@@ -5283,27 +6254,29 @@
}
},
"node_modules/tough-cookie": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
- "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "dev": true,
"license": "BSD-3-Clause",
"dependencies": {
- "tldts": "^7.0.5"
+ "tldts": "^6.1.32"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tr46": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
- "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
- "node": ">=20"
+ "node": ">=18"
}
},
"node_modules/ts-api-utils": {
@@ -5344,7 +6317,6 @@
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5392,9 +6364,9 @@
}
},
"node_modules/undici": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.21.0.tgz",
- "integrity": "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
@@ -5406,6 +6378,15 @@
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"dev": true
},
+ "node_modules/universal-cookie": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-8.1.2.tgz",
+ "integrity": "sha512-kcKzTGNsxVytujrYOvQbvh//QyFrA53HrzCGyzh6i9ujCww5gfPrLK0tG+jJD40SIIldiEjBNPPSR8fBMS21GA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.1.1"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
@@ -5467,7 +6448,6 @@
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -5537,6 +6517,29 @@
}
}
},
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/vite/node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
@@ -5558,7 +6561,92 @@
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
- "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
+ "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.7",
+ "@vitest/mocker": "3.2.7",
+ "@vitest/pretty-format": "^3.2.7",
+ "@vitest/runner": "3.2.7",
+ "@vitest/snapshot": "3.2.7",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.7",
+ "@vitest/ui": "3.2.7",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -5597,35 +6685,51 @@
}
},
"node_modules/webidl-conversions": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
- "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
"license": "BSD-2-Clause",
"engines": {
- "node": ">=20"
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
- "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">=20"
+ "node": ">=18"
}
},
"node_modules/whatwg-url": {
- "version": "16.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz",
- "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==",
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@exodus/bytes": "^1.11.0",
- "tr46": "^6.0.0",
- "webidl-conversions": "^8.0.1"
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
},
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": ">=18"
}
},
"node_modules/which": {
@@ -5644,6 +6748,23 @@
"node": ">= 8"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -5654,6 +6775,28 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ws": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
+ "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
diff --git a/ambari-web/latest/package.json b/ambari-web/latest/package.json
index 07f0521e11..fa40876d12 100755
--- a/ambari-web/latest/package.json
+++ b/ambari-web/latest/package.json
@@ -7,6 +7,7 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
+ "test": "vitest run",
"preview": "vite preview"
},
"dependencies": {
@@ -54,12 +55,15 @@
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
+ "@testing-library/react": "^16.3.0",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
+ "jsdom": "^26.1.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
- "vite": "^6.2.0"
+ "vite": "^6.2.0",
+ "vitest": "^3.2.4"
}
}
diff --git a/ambari-web/latest/src/AmbariAboutModal.tsx b/ambari-web/latest/src/AmbariAboutModal.tsx
index d68895bd85..5db8d0c78b 100644
--- a/ambari-web/latest/src/AmbariAboutModal.tsx
+++ b/ambari-web/latest/src/AmbariAboutModal.tsx
@@ -15,84 +15,48 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { useEffect, useState } from "react";
+
+import { useContext } from "react";
import { Image } from "react-bootstrap";
-import { ServiceApi } from "./api/serviceApi";
-import { get } from "lodash";
-import Spinner from "./components/Spinner";
import Modal from "./components/Modal";
-import AmbariLogo from "./assets/img/ambari-logo.png"
-
-type AmbariAboutModalProps = {
- isOpen: boolean;
- onClose: () => void;
-};
+import AmbariLogo from "./assets/img/ambari-logo.png";
+import { AppContext } from "./store/context";
export default function AmbariAboutModal({
isOpen,
onClose,
-}: AmbariAboutModalProps) {
-
- const [loading, setLoading] = useState(false);
- const [ ambariVersion, setAmbariVersion ] = useState(false);
- useEffect(() => {
- async function getAmbariAboutInfo() {
- setLoading(true);
- const data: any = await ServiceApi.adminAboutInfo(
- "RootServiceComponents/component_version,RootServiceComponents/properties/server.os_family&minimal_response=true"
- );
- console.log("version", get(data, "RootServiceComponents.component_version"))
- setAmbariVersion(get(data, "RootServiceComponents.component_version"));
- setLoading(false);
- }
- if (!ambariVersion) {
- getAmbariAboutInfo();
- }
- }, []);
-
- const modalBody = loading ? (
- <Spinner />
- ) : (
- <div className="d-flex">
- <Image
- src={AmbariLogo}
- height={75}
- width={75}
- className="me-5"
- />
- <div>
- <h2>Apache Ambari</h2>
- <div className="mb-3">Version {ambariVersion}</div>
- <div>
- <a href="http://ambari.apache.org/" className="custom-link">
- Get involved!
- </a>
- </div>
- <div>
- <a
- href="http://www.apache.org/licenses/LICENSE-2.0"
- className="custom-link"
- >
- Licensed under the Apache License, Version 2.0
- </a>
- </div>
- </div>
- </div>
- );
-
+}: {
+ isOpen: boolean;
+ onClose: () => void;
+}) {
+ const { ambariServerVersion } = useContext(AppContext);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
modalTitle="About"
- modalBody={modalBody}
+ modalBody={(
+ <div className="d-flex">
+ <Image src={AmbariLogo} height={75} width={75} className="me-5" />
+ <div>
+ <h2>Apache Ambari</h2>
+ <div className="mb-3">Version {ambariServerVersion || "N/A"}</div>
+ <div><a href="https://ambari.apache.org/" className="custom-link">Get involved!</a></div>
+ <div>
+ <a href="https://www.apache.org/licenses/LICENSE-2.0" className="custom-link">
+ Licensed under the Apache License, Version 2.0
+ </a>
+ </div>
+ </div>
+ </div>
+ )}
successCallback={onClose}
options={{
modalSize: "modal-lg",
okButtonText: "OK",
cancelableViaBtn: false,
- okButtonVariant: "success"
+ okButtonVariant: "success",
}}
/>
);
-}
\ No newline at end of file
+}
diff --git a/ambari-web/latest/src/App.tsx b/ambari-web/latest/src/App.tsx
index a1f150e3de..d3a7d19fc9 100644
--- a/ambari-web/latest/src/App.tsx
+++ b/ambari-web/latest/src/App.tsx
@@ -16,73 +16,24 @@
* limitations under the License.
*/
-import { Suspense} from "react";
+import { Suspense } from "react";
+import { createHashRouter, RouterProvider } from "react-router-dom";
+import { HelmetProvider } from "react-helmet-async";
import { Toaster } from "react-hot-toast";
-import "./styles/app.scss";
-import {AppProvider} from "./store/context";
+import RoutesList from "./router/RoutesList";
import { UserProvider } from "./store/UserContext";
-import useWebSocket from "react-use-websocket";
import Spinner from "./components/Spinner";
-import AppLoader from "./AppLoader";
-import { ModalProvider } from "./store/ModalContext";
-import CustomModal from "./store/CustomModal";
-import clusterApi from "./api/clusterApi";
-import { Login } from "./screens/Authentication/Login";
-import { useEffect, useState } from "react";
-import InactivityTimeout from "./InactivityTimeout";
-import { HelmetProvider } from "react-helmet-async";
-import DocumentTitleUpdater from "./components/DocumentTitleUpdater";
+import "./styles/app.scss";
-function App() {
- const [userSessionTimeout, setUserSessionTimeout] = useState<number>(0);
- const socketUrl = "/api/stomp/v1/websocket";
- const { readyState: _readyState } = useWebSocket(socketUrl, {
- onOpen: () => console.log("opened"),
- //Will attempt to reconnect on all close events, such as server shutting down
- shouldReconnect: () => false,
- });
+const router = createHashRouter(RoutesList);
- // Fetch timeout value once when app loads
- useEffect(() => {
- const fetchTimeout = async () => {
- try {
- const response = await clusterApi.getUserTimeout();
- if (response.status === 200) {
- const timeoutInSeconds = response.data.RootServiceComponents.properties["server.http.session.inactive_timeout"];
- setUserSessionTimeout(timeoutInSeconds * 1000);
- }
- } catch (error) {
- console.error("Error fetching timeout:", error);
- setUserSessionTimeout(90000); // Default 90 seconds
- }
- };
- fetchTimeout();
- }, []);
-
- const isLoginPage = window.location.hash === '#/login';
-
- console.log("app")
+function App() {
return (
<HelmetProvider>
<Suspense fallback={<Spinner />}>
<UserProvider>
- {isLoginPage ? (
- <>
- <Login />
- </>
- ) : (
- <AppProvider>
- <ModalProvider>
- <DocumentTitleUpdater />
- <Toaster />
- {userSessionTimeout > 0 && (
- <InactivityTimeout timeout={userSessionTimeout} />
- )}
- <CustomModal />
- <AppLoader />
- </ModalProvider>
- </AppProvider>
- )}
+ <Toaster />
+ <RouterProvider router={router} />
</UserProvider>
</Suspense>
</HelmetProvider>
diff --git a/ambari-web/latest/src/AppLoader.tsx b/ambari-web/latest/src/AppLoader.tsx
index b5c0105827..bc4c904fde 100644
--- a/ambari-web/latest/src/AppLoader.tsx
+++ b/ambari-web/latest/src/AppLoader.tsx
@@ -17,151 +17,187 @@
*/
import { useContext, useEffect, useState } from "react";
-import { AppContext } from "./store/context";
-import { createHashRouter, RouterProvider } from "react-router-dom";
-import RoutesList from "./router/RoutesList";
-import { LocalStorageOps } from "./Utils/LocalStorageOps";
-import clusterApi from "./api/clusterApi";
-import { useLocation } from "react-router-dom";
-import { useNavigate } from "react-router-dom";
-import { toBePreservedPaths } from "./constants";
-import ClusterApi from "./api/clusterApi";
-import { ProgressBar } from "react-bootstrap";
+import { Navigate, Outlet, useLocation, useNavigate } from "react-router-dom";
+import { Alert, Button, ProgressBar } from "react-bootstrap";
+import { AppContext, AppProvider } from "./store/context";
+import { ModalProvider } from "./store/ModalContext";
+import { useAuth } from "./hooks/useAuth";
import usePolling from "./hooks/usePolling";
-
-// Separate component for route tracking that will be used inside router
-export function RouteTracker() {
- const [lastVisitedURL] = useState(
- () => LocalStorageOps.getItem("lastVisitedURL") || "/#/installer/step0"
- );
- const [hasCluster, setHasCluster] = useState<boolean | null>(null);
- const [isClusterChecked, setIsClusterChecked] = useState(false);
- const unpreservedPaths = ["/", "/login"];
-
+import ClusterApi from "./api/clusterApi";
+import { toBePreservedPaths } from "./constants";
+import {
+ consumePreferredPath,
+ normalizeInternalPath,
+ savePreferredPath,
+} from "./Utils/authNavigation";
+import CustomModal from "./store/CustomModal";
+import DocumentTitleUpdater from "./components/DocumentTitleUpdater";
+import InactivityTimeout from "./InactivityTimeout";
+import LoginMessageModal from "./screens/Authentication/LoginMessageModal";
+import { isViewOnlyUser, selectLandingPath } from "./Utils/authPolicy";
+
+export function AuthenticatedApplication() {
+ const {
+ isAuthenticated,
+ isLoading,
+ loginMessage,
+ retrySession,
+ sessionError,
+ } = useAuth();
const location = useLocation();
- const navigate = useNavigate();
-
- async function saveUserUrl() {
- const currentURL = location.pathname;
- await ClusterApi.postPersistData(
- JSON.stringify({
- USER_REDIRECTION_URL: currentURL,
- })
- );
- }
- // URL tracking effect
useEffect(() => {
- const currentURL = location.pathname;
- console.log("currentURL", currentURL);
- if (!unpreservedPaths.includes(currentURL)) {
- LocalStorageOps.setItem("lastVisitedURL", "/#" + currentURL);
- }
- //Check if toBePreservedPaths has any key with location.pathname substring
- if (
- Object.keys(toBePreservedPaths).some((path) => currentURL.includes(path))
- ) {
- saveUserUrl();
+ if (!isLoading && !isAuthenticated && location.pathname !== "/") {
+ savePreferredPath(`${location.pathname}${location.search}`);
}
- }, [location]);
+ }, [isAuthenticated, isLoading, location.pathname, location.search]);
- // Check if cluster exists when component mounts
useEffect(() => {
- async function checkCluster() {
+ if (!isAuthenticated) {
+ return;
+ }
+
+ let stopped = false;
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
+ const keepAlive = async () => {
try {
- const response = await clusterApi.getClusterData();
- const firstCluster = response?.items?.[0]?.Clusters;
- if (firstCluster && firstCluster?.provisioning_state === "INSTALLED")
- setHasCluster(true);
- } catch (error) {
- console.error("Error checking cluster existence:", error);
- setHasCluster(false);
+ await ClusterApi.noopPolling();
+ } catch {
+ // The global response handler owns authentication failures.
} finally {
- setIsClusterChecked(true);
+ if (!stopped) {
+ timeoutId = setTimeout(keepAlive, 60_000);
+ }
}
- }
- checkCluster();
- }, []); // Run only once on mount
+ };
+ timeoutId = setTimeout(keepAlive, 60_000);
+ return () => {
+ stopped = true;
+ if (timeoutId) clearTimeout(timeoutId);
+ };
+ }, [isAuthenticated]);
+
+ if (isLoading) {
+ return <div className="p-5"><h2>Loading...</h2></div>;
+ }
+ if (sessionError) {
+ return (
+ <div className="container py-5">
+ <Alert variant="danger">
+ <Alert.Heading>Unable to validate the Ambari session</Alert.Heading>
+ <p>{sessionError}</p>
+ <Button variant="outline-danger" onClick={() => void retrySession()}>
+ Retry
+ </Button>
+ </Alert>
+ </div>
+ );
+ }
+ if (!isAuthenticated) {
+ return <Navigate to="/login" replace />;
+ }
+ if (loginMessage) {
+ return <LoginMessageModal />;
+ }
- // Handle routing after cluster check
- useEffect(() => {
- if (!isClusterChecked) return;
+ return (
+ <AppProvider>
+ <ModalProvider>
+ <ApplicationLoader />
+ </ModalProvider>
+ </AppProvider>
+ );
+}
- const currentURL = location.pathname;
- const hashExclusiveUrl = LocalStorageOps.getItem("lastVisitedURL")?.replace(
- "/#",
- ""
- );
- let relocationPath = currentURL;
- if (hasCluster && currentURL.includes("/installer")) {
- relocationPath = "/main/dashboard/metrics";
- } else if (!hasCluster && currentURL.includes("/main")) {
- relocationPath = "/installer/step0";
- } else {
- if (currentURL === "/") {
- if (hashExclusiveUrl&&hasCluster) {
- relocationPath = hashExclusiveUrl;
- } else {
- if (hasCluster) {
- relocationPath = "/main/dashboard/metrics";
- } else {
- relocationPath = "/installer/step0";
- }
- }
- } else {
- relocationPath = currentURL;
- }
- }
+export function LandingRoute() {
+ const { cluster, isClusterInstalled } = useContext(AppContext);
+ const { authorizations } = useAuth();
+ const preferredPath = consumePreferredPath();
+ return <Navigate to={selectLandingPath({
+ clusterInstalled: Boolean(isClusterInstalled),
+ clusterName: cluster?.cluster_name,
+ preferredPath,
+ viewOnly: isViewOnlyUser(authorizations),
+ })} replace />;
+}
- navigate(relocationPath);
- }, [isClusterChecked, hasCluster, lastVisitedURL]);
+export function RouteTracker() {
+ const { cluster, isClusterInstalled } = useContext(AppContext);
+ const location = useLocation();
+ const navigate = useNavigate();
useEffect(() => {
- if (hasCluster === null) {
+ const currentPath = normalizeInternalPath(`${location.pathname}${location.search}`);
+ if (!currentPath || location.pathname === "/") {
return;
}
- const currentURL = location.pathname;
- if (hasCluster && currentURL.startsWith("/installer")) {
- navigate("/main/dashboard/metrics");
- } else if (!hasCluster && currentURL.startsWith("/main")) {
- navigate("/installer/step0");
+
+ savePreferredPath(currentPath);
+ if (Object.keys(toBePreservedPaths).some((path) => location.pathname.includes(path))) {
+ void ClusterApi.postPersistData({ USER_REDIRECTION_URL: currentPath });
}
- }, [location, hasCluster]);
+ }, [location.pathname, location.search]);
+
+ useEffect(() => {
+ if (isClusterInstalled && location.pathname.startsWith("/installer")) {
+ navigate("/main/dashboard/metrics", { replace: true });
+ } else if (
+ cluster?.cluster_name
+ && !isClusterInstalled
+ && location.pathname.startsWith("/main")
+ ) {
+ navigate("/installer/step0", { replace: true });
+ }
+ }, [cluster?.cluster_name, isClusterInstalled, location.pathname, navigate]);
return null;
}
-function AppLoader() {
- const { isAppLoaded } = useContext(AppContext);
+function ApplicationLoader() {
+ const { initializationError, isAppLoaded, retryInitialization } = useContext(AppContext);
const [progress, setProgress] = useState(0);
+ const { stopPolling } = usePolling(
+ () => setProgress((value) => Math.min(value + 10, 90)),
+ 500,
+ );
useEffect(() => {
if (isAppLoaded) {
stopPolling();
}
- }, [isAppLoaded]);
- function incrementProgress() {
- setProgress((p) => p + 20);
- }
+ }, [isAppLoaded, stopPolling]);
- const { stopPolling } = usePolling(incrementProgress,500);
-
- const router = createHashRouter([...(RoutesList as any)]);
+ if (initializationError) {
+ return (
+ <div className="container py-5">
+ <Alert variant="danger">
+ <Alert.Heading>Unable to initialize Ambari</Alert.Heading>
+ <p>{initializationError}</p>
+ <Button variant="outline-danger" onClick={retryInitialization}>Retry</Button>
+ </Alert>
+ </div>
+ );
+ }
if (!isAppLoaded) {
return (
<div className="vh-100 mt-0">
<div className="p-5">
<h2>Loading...</h2>
- <ProgressBar variant="blue" now={progress} className="mt-3" />
+ <ProgressBar variant="blue" now={progress} className="mt-3" />
</div>
</div>
);
}
+
return (
<>
- <RouterProvider router={router} />
+ <RouteTracker />
+ <DocumentTitleUpdater />
+ <InactivityTimeout />
+ <CustomModal />
+ <Outlet />
</>
);
}
-export default AppLoader;
+export default ApplicationLoader;
diff --git a/ambari-web/latest/src/InactivityTimeout.test.ts b/ambari-web/latest/src/InactivityTimeout.test.ts
new file mode 100644
index 0000000000..9c0984142c
--- /dev/null
+++ b/ambari-web/latest/src/InactivityTimeout.test.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 { describe, expect, it } from "vitest";
+import {
+ inactivityTimeoutSeconds,
+ isInactivityRouteExcluded,
+ remainingInactivitySeconds,
+} from "./InactivityTimeout";
+
+describe("inactivity policy", () => {
+ const properties = {
+ "user.inactivity.timeout.default": "900",
+ "user.inactivity.timeout.role.readonly.default": "300",
+ };
+
+ it("selects the administrator and readonly server properties", () => {
+ expect(inactivityTimeoutSeconds(properties, true)).toBe(900);
+ expect(inactivityTimeoutSeconds(properties, false)).toBe(300);
+ expect(inactivityTimeoutSeconds({}, true)).toBe(0);
+ });
+
+ it("excludes wizard steps and stack upgrade routes", () => {
+ expect(isInactivityRouteExcluded("/installer/step3")).toBe(true);
+ expect(isInactivityRouteExcluded("/main/admin/stack/upgrade")).toBe(true);
+ expect(isInactivityRouteExcluded("/main/hosts")).toBe(false);
+ });
+
+ it("computes the warning countdown from the last activity", () => {
+ expect(remainingInactivitySeconds(300, 1_000, 241_000)).toBe(60);
+ expect(remainingInactivitySeconds(300, 1_000, 301_000)).toBe(0);
+ });
+});
diff --git a/ambari-web/latest/src/InactivityTimeout.tsx b/ambari-web/latest/src/InactivityTimeout.tsx
index 90b943c229..3f86d9934a 100644
--- a/ambari-web/latest/src/InactivityTimeout.tsx
+++ b/ambari-web/latest/src/InactivityTimeout.tsx
@@ -16,20 +16,179 @@
* limitations under the License.
*/
-interface InactivityTimeoutProps {
- timeout?: number; // Make optional since we'll get it from server properties
+import { useCallback, useContext, useEffect, useRef, useState } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
+import Modal from "./components/Modal";
+import { AppContext } from "./store/context";
+import { useAuth } from "./hooks/useAuth";
+
+const WARNING_SECONDS = 60;
+const ACTIVITY_EVENTS: (keyof WindowEventMap)[] = ["mousemove", "keypress", "click"];
+
+export function inactivityTimeoutSeconds(
+ properties: Record<string, unknown> | undefined,
+ admin: boolean,
+): number {
+ const value = Number(properties?.[
+ admin
+ ? "user.inactivity.timeout.default"
+ : "user.inactivity.timeout.role.readonly.default"
+ ] || 0);
+ return Number.isFinite(value) && value > 0 ? value : 0;
}
-const InactivityTimeout: React.FC<InactivityTimeoutProps> = ({ timeout: propTimeout }) => {
+export function isInactivityRouteExcluded(pathname: string): boolean {
+ return pathname.includes("/step") || pathname.includes("/stack/upgrade");
+}
+
+export function remainingInactivitySeconds(
+ timeoutSeconds: number,
+ lastActivityTime: number,
+ currentTime: number,
+): number {
+ return timeoutSeconds - Math.floor((currentTime - lastActivityTime) / 1000);
+}
+
+export default function InactivityTimeout() {
+ const { ambariProperties } = useContext(AppContext);
+ const { isAdmin, logout } = useAuth();
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [remainingSeconds, setRemainingSeconds] = useState<number | null>(null);
+ const lastActivity = useRef(Date.now());
+ const warningVisible = useRef(false);
+ const signingOut = useRef(false);
+
+ const timeoutSeconds = inactivityTimeoutSeconds(ambariProperties, isAdmin());
+ const isExcludedRoute = isInactivityRouteExcluded(location.pathname);
+
+ const updateRemainingSeconds = useCallback((value: number | null) => {
+ warningVisible.current = value !== null;
+ setRemainingSeconds(value);
+ }, []);
+
+ const signOut = useCallback(async () => {
+ if (signingOut.current) {
+ return;
+ }
+ signingOut.current = true;
+ await logout();
+ navigate("/login", { replace: true });
+ }, [logout, navigate]);
+
+ useEffect(() => {
+ if (timeoutSeconds <= 0) {
+ return;
+ }
+
+ const markActive = () => {
+ if (!warningVisible.current) {
+ lastActivity.current = Date.now();
+ }
+ };
+ const bindFrame = (frame: HTMLIFrameElement, shouldBind: boolean) => {
+ try {
+ ACTIVITY_EVENTS.forEach((eventName) => {
+ if (shouldBind) {
+ frame.contentWindow?.addEventListener(eventName, markActive);
+ } else {
+ frame.contentWindow?.removeEventListener(eventName, markActive);
+ }
+ });
+ } catch {
+ // Cross-origin View iframes cannot be observed by the parent application.
+ }
+ };
+ const trackedFrames = new Map<HTMLIFrameElement, () => void>();
+ const trackFrame = (frame: HTMLIFrameElement) => {
+ if (trackedFrames.has(frame)) {
+ return;
+ }
+ const bindLoadedFrame = () => bindFrame(frame, true);
+ trackedFrames.set(frame, bindLoadedFrame);
+ frame.addEventListener("load", bindLoadedFrame);
+ bindLoadedFrame();
+ };
+ const untrackFrame = (frame: HTMLIFrameElement) => {
+ const bindLoadedFrame = trackedFrames.get(frame);
+ if (!bindLoadedFrame) {
+ return;
+ }
+ bindFrame(frame, false);
+ frame.removeEventListener("load", bindLoadedFrame);
+ trackedFrames.delete(frame);
+ };
+ const visitFrames = (node: Node, callback: (frame: HTMLIFrameElement) => void) => {
+ if (!(node instanceof Element)) {
+ return;
+ }
+ if (node instanceof HTMLIFrameElement) {
+ callback(node);
+ }
+ node.querySelectorAll("iframe").forEach(callback);
+ };
+
+ ACTIVITY_EVENTS.forEach((eventName) => window.addEventListener(eventName, markActive));
+ document.querySelectorAll("iframe").forEach(trackFrame);
+ const frameObserver = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ mutation.addedNodes.forEach((node) => visitFrames(node, trackFrame));
+ mutation.removedNodes.forEach((node) => visitFrames(node, untrackFrame));
+ });
+ });
+ frameObserver.observe(document.body, { childList: true, subtree: true });
+ const intervalId = window.setInterval(() => {
+ if (isExcludedRoute) {
+ lastActivity.current = Date.now();
+ updateRemainingSeconds(null);
+ return;
+ }
+
+ const remaining = remainingInactivitySeconds(
+ timeoutSeconds,
+ lastActivity.current,
+ Date.now(),
+ );
+ if (remaining <= 0) {
+ void signOut();
+ } else if (remaining <= WARNING_SECONDS) {
+ updateRemainingSeconds(remaining);
+ }
+ }, 1000);
+
+ return () => {
+ window.clearInterval(intervalId);
+ frameObserver.disconnect();
+ ACTIVITY_EVENTS.forEach((eventName) => window.removeEventListener(eventName, markActive));
+ Array.from(trackedFrames.keys()).forEach(untrackFrame);
+ };
+ }, [isExcludedRoute, signOut, timeoutSeconds, updateRemainingSeconds]);
+
+ if (remainingSeconds === null) {
+ return null;
+ }
+
return (
- <div className="inactivity-timeout">
- {propTimeout ? (
- <p>Your session will expire after {propTimeout} minutes of inactivity.</p>
- ) : (
- <p>Loading inactivity timeout...</p>
+ <Modal
+ isOpen
+ onClose={() => undefined}
+ modalTitle="Session Expiration"
+ modalBody={(
+ <p>
+ Your session will expire in <strong>{remainingSeconds}</strong> seconds due to inactivity.
+ </p>
)}
- </div>
+ successCallback={() => {
+ lastActivity.current = Date.now();
+ updateRemainingSeconds(null);
+ }}
+ options={{
+ cancelableViaIcon: false,
+ okButtonText: "Continue Session",
+ cancelButtonText: "Sign Out",
+ extraButtons: [{ text: "Sign Out", onClick: () => void signOut(), variant: "secondary" }],
+ cancelableViaBtn: false,
+ }}
+ />
);
-};
-
-export default InactivityTimeout;
+}
diff --git a/ambari-web/latest/src/Utils/adminViewRedirect.ts b/ambari-web/latest/src/Utils/adminViewRedirect.ts
index cde96b3bf5..78e9113e8f 100644
--- a/ambari-web/latest/src/Utils/adminViewRedirect.ts
+++ b/ambari-web/latest/src/Utils/adminViewRedirect.ts
@@ -17,5 +17,6 @@
*/
export async function redirectToAdminView(adminPage = "") {
- console.log("Redirecting to admin view:", adminPage);
-}
\ No newline at end of file
+ const suffix = adminPage ? `?page=${encodeURIComponent(adminPage)}` : "";
+ window.location.hash = `/adminView${suffix}`;
+}
diff --git a/ambari-web/latest/src/Utils/authNavigation.test.ts b/ambari-web/latest/src/Utils/authNavigation.test.ts
new file mode 100644
index 0000000000..2226ebaa4f
--- /dev/null
+++ b/ambari-web/latest/src/Utils/authNavigation.test.ts
@@ -0,0 +1,77 @@
+/**
+ * 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 { beforeEach, describe, expect, it, vi } from "vitest";
+import {
+ getExternalLoginUrl,
+ handleAuthenticationError,
+ normalizeInternalPath,
+ SESSION_EXPIRED_EVENT,
+} from "./authNavigation";
+
+describe("authentication navigation", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.hash = "#/login";
+ });
+
+ it.each([
+ ["/main/hosts", "/main/hosts"],
+ ["#/main/hosts?tab=summary", "/main/hosts?tab=summary"],
+ ["/#/installer/step0", "/installer/step0"],
+ ["//attacker.example/path", null],
+ ["https://attacker.example/path", null],
+ ["/login", null],
+ ["/login/local", null],
+ ["/", null],
+ ])("normalizes %s", (input, expected) => {
+ expect(normalizeInternalPath(input)).toBe(expected);
+ });
+
+ it("supports external login while preserving the return URL", () => {
+ expect(getExternalLoginUrl(
+ "https://knox.example/sso?originalUrl=",
+ "https://ambari.example/#/main/hosts",
+ false,
+ )).toBe(
+ "https://knox.example/sso?originalUrl="
+ + encodeURIComponent("https://ambari.example/#/main/hosts"),
+ );
+ });
+
+ it("does not redirect local login and caps external redirect loops", () => {
+ expect(getExternalLoginUrl("https://knox.example/", "return", true)).toBeNull();
+ expect(getExternalLoginUrl("https://knox.example/", "return", false)).not.toBeNull();
+ expect(getExternalLoginUrl("https://knox.example/", "return", false)).not.toBeNull();
+ expect(getExternalLoginUrl("https://knox.example/", "return", false)).not.toBeNull();
+ expect(getExternalLoginUrl("https://knox.example/", "return", false)).toBeNull();
+ });
+
+ it("expires the client session for 401 but not a plain authorization 403", () => {
+ const listener = vi.fn();
+ window.addEventListener(SESSION_EXPIRED_EVENT, listener);
+
+ expect(handleAuthenticationError({ status: 403, data: {} })).toBe(false);
+ expect(listener).not.toHaveBeenCalled();
+
+ expect(handleAuthenticationError({ status: 401, data: {} })).toBe(false);
+ expect(listener).toHaveBeenCalledOnce();
+ window.removeEventListener(SESSION_EXPIRED_EVENT, listener);
+ });
+});
diff --git a/ambari-web/latest/src/Utils/authNavigation.ts b/ambari-web/latest/src/Utils/authNavigation.ts
new file mode 100644
index 0000000000..dbbc1e95a7
--- /dev/null
+++ b/ambari-web/latest/src/Utils/authNavigation.ts
@@ -0,0 +1,152 @@
+/**
+ * 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 { LocalStorageOps } from "./LocalStorageOps";
+
+const PREFERRED_PATH_KEY = "lastVisitedURL";
+const REDIRECT_COUNT_KEY = "ambari.jwtRedirectCount";
+const MAX_EXTERNAL_REDIRECTS = 3;
+export const SESSION_EXPIRED_EVENT = "ambari:session-expired";
+
+export const LOGIN_PATH = "/login";
+export const LOCAL_LOGIN_PATH = "/login/local";
+
+export function currentHashPath(location: Pick<Location, "hash"> = window.location): string {
+ const value = location.hash.startsWith("#") ? location.hash.slice(1) : location.hash;
+ return value || "/";
+}
+
+export function normalizeInternalPath(value?: string | null): string | null {
+ if (!value) {
+ return null;
+ }
+
+ let candidate = value.trim();
+ if (candidate.startsWith("/#")) {
+ candidate = candidate.slice(2);
+ } else if (candidate.startsWith("#")) {
+ candidate = candidate.slice(1);
+ }
+
+ if (!candidate.startsWith("/") || candidate.startsWith("//")) {
+ return null;
+ }
+
+ const pathOnly = candidate.split("?")[0].split("#")[0];
+ if (pathOnly === "/" || pathOnly === LOGIN_PATH || pathOnly === LOCAL_LOGIN_PATH) {
+ return null;
+ }
+
+ return candidate;
+}
+
+export function savePreferredPath(path: string): void {
+ const normalized = normalizeInternalPath(path);
+ if (normalized) {
+ LocalStorageOps.setItem(PREFERRED_PATH_KEY, normalized);
+ }
+}
+
+export function consumePreferredPath(): string | null {
+ const normalized = normalizeInternalPath(LocalStorageOps.getItem(PREFERRED_PATH_KEY));
+ localStorage.removeItem(PREFERRED_PATH_KEY);
+ return normalized;
+}
+
+export function peekPreferredPath(): string | null {
+ return normalizeInternalPath(LocalStorageOps.getItem(PREFERRED_PATH_KEY));
+}
+
+export function resetExternalRedirectCount(): void {
+ sessionStorage.removeItem(REDIRECT_COUNT_KEY);
+}
+
+export function getExternalLoginUrl(
+ jwtProviderUrl: unknown,
+ returnUrl: string,
+ isLocalLogin: boolean,
+): string | null {
+ if (isLocalLogin || typeof jwtProviderUrl !== "string" || !jwtProviderUrl) {
+ return null;
+ }
+
+ const redirectCount = Number(sessionStorage.getItem(REDIRECT_COUNT_KEY) || "0");
+ if (!Number.isFinite(redirectCount) || redirectCount >= MAX_EXTERNAL_REDIRECTS) {
+ return null;
+ }
+
+ sessionStorage.setItem(REDIRECT_COUNT_KEY, String(redirectCount + 1));
+ return jwtProviderUrl + encodeURIComponent(returnUrl);
+}
+
+export function hasExceededExternalRedirectLimit(): boolean {
+ return Number(sessionStorage.getItem(REDIRECT_COUNT_KEY) || "0") >= MAX_EXTERNAL_REDIRECTS;
+}
+
+export function redirectToLocalLogin(): void {
+ const currentPath = currentHashPath();
+ if (currentPath !== LOGIN_PATH && currentPath !== LOCAL_LOGIN_PATH) {
+ savePreferredPath(currentPath);
+ }
+ window.location.hash = LOGIN_PATH;
+}
+
+function expireClientSession(): void {
+ window.dispatchEvent(new Event(SESSION_EXPIRED_EVENT));
+}
+
+export function handleAuthenticationError(response: {
+ status?: number;
+ data?: { jwtProviderUrl?: string };
+}): boolean {
+ if (response.status !== 401 && response.status !== 403) {
+ return false;
+ }
+
+ // A plain 403 is an authorization failure, not evidence that the session expired.
+ if (response.status === 403 && !response.data?.jwtProviderUrl) {
+ return false;
+ }
+
+ expireClientSession();
+
+ const currentPath = currentHashPath();
+ const isLocalLogin = currentPath.split("?")[0] === LOCAL_LOGIN_PATH;
+ const externalUrl = getExternalLoginUrl(
+ response.data?.jwtProviderUrl,
+ window.location.href,
+ isLocalLogin,
+ );
+
+ if (externalUrl) {
+ window.location.assign(externalUrl);
+ return true;
+ }
+
+ if (hasExceededExternalRedirectLimit() && !isLocalLogin) {
+ window.location.hash = `${LOCAL_LOGIN_PATH}?redirectError=true`;
+ return true;
+ }
+
+ if (currentPath !== LOGIN_PATH && !isLocalLogin) {
+ redirectToLocalLogin();
+ return true;
+ }
+
+ return false;
+}
diff --git a/ambari-web/latest/src/Utils/authPolicy.test.ts b/ambari-web/latest/src/Utils/authPolicy.test.ts
new file mode 100644
index 0000000000..cf63694150
--- /dev/null
+++ b/ambari-web/latest/src/Utils/authPolicy.test.ts
@@ -0,0 +1,46 @@
+/**
+ * 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 { describe, expect, it } from "vitest";
+import { hasVersionConflict, selectLandingPath } from "./authPolicy";
+
+describe("application landing policy", () => {
+ it.each([
+ [{ clusterInstalled: true, clusterName: "c1", viewOnly: false }, "/main/dashboard/metrics"],
+ [{
+ clusterInstalled: true,
+ clusterName: "c1",
+ preferredPath: "/main/hosts",
+ viewOnly: false,
+ }, "/main/hosts"],
+ [{ clusterInstalled: false, clusterName: "c1", viewOnly: false }, "/installer/step0"],
+ [{ clusterInstalled: false, clusterName: "", viewOnly: false }, "/adminView"],
+ [{ clusterInstalled: true, clusterName: "c1", viewOnly: true }, "/main/view"],
+ ])("selects the expected landing path", (input, expected) => {
+ expect(selectLandingPath(input)).toBe(expected);
+ });
+
+ it.each([
+ ["", "2.0.0", false],
+ ["2.0.0", "", false],
+ ["2.0.0", "2.0.0", false],
+ ["2.0.0", "2.0.1", true],
+ ])("compares packaged and server versions", (client, server, expected) => {
+ expect(hasVersionConflict(client, server)).toBe(expected);
+ });
+});
diff --git a/ambari-web/latest/src/Utils/authPolicy.ts b/ambari-web/latest/src/Utils/authPolicy.ts
new file mode 100644
index 0000000000..acd3da9551
--- /dev/null
+++ b/ambari-web/latest/src/Utils/authPolicy.ts
@@ -0,0 +1,52 @@
+/**
+ * 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 { Authorization } from "../types/auth";
+
+export function isViewOnlyUser(authorizations: Authorization[]): boolean {
+ return authorizations.length === 0
+ || (authorizations.length === 1
+ && authorizations[0].authorization_id === "VIEW.USE");
+}
+
+export function selectLandingPath({
+ clusterInstalled,
+ clusterName,
+ preferredPath,
+ viewOnly,
+}: {
+ clusterInstalled: boolean;
+ clusterName?: string;
+ preferredPath?: string | null;
+ viewOnly: boolean;
+}): string {
+ if (viewOnly) {
+ return "/main/view";
+ }
+ if (clusterInstalled) {
+ return preferredPath || "/main/dashboard/metrics";
+ }
+ if (clusterName) {
+ return "/installer/step0";
+ }
+ return "/adminView";
+}
+
+export function hasVersionConflict(clientVersion: string, serverVersion: string): boolean {
+ return Boolean(clientVersion && serverVersion && clientVersion !== serverVersion);
+}
diff --git a/ambari-web/latest/src/Utils/db.ts b/ambari-web/latest/src/Utils/db.ts
index e2352c6189..85934fc0d6 100644
--- a/ambari-web/latest/src/Utils/db.ts
+++ b/ambari-web/latest/src/Utils/db.ts
@@ -79,10 +79,10 @@ interface DbData {
this.data = this.getDb() || InitialData;
}
- private checkNamespace(namespace: string): boolean {
+ private checkNamespace(data: DbData, namespace: string): boolean {
if (!namespace) return false;
- if (!this.data[namespace]) {
- this.data[namespace] = {};
+ if (!data[namespace]) {
+ data[namespace] = {};
}
return true;
}
@@ -90,7 +90,12 @@ interface DbData {
getDb(): DbData | null {
try {
const stored = localStorage.getItem('ambari');
- return stored ? JSON.parse(stored) : null;
+ if (!stored) return null;
+ try {
+ return JSON.parse(Utility.decryptData(stored));
+ } catch {
+ return JSON.parse(stored);
+ }
} catch (e) {
console.error('Error reading from localStorage:', e);
return null;
@@ -99,7 +104,7 @@ interface DbData {
private setDb(data: DbData): void {
try {
- localStorage.setItem('ambari', JSON.stringify(data));
+ localStorage.setItem('ambari', Utility.encryptData(JSON.stringify(data)));
} catch (e) {
console.error('Error writing to localStorage:', e);
}
@@ -108,8 +113,14 @@ interface DbData {
getItem(key: string): string | null {
const value = localStorage.getItem(key);
if (value === null) return null;
- return Utility.decryptData(value);
-}
+ const decrypted = Utility.decryptData(value);
+ try {
+ JSON.parse(decrypted);
+ return decrypted;
+ } catch {
+ return value;
+ }
+ }
setItem(key: string, value: string): void {
try {
// Always encrypt before storing to ensure consistency
@@ -124,34 +135,63 @@ interface DbData {
}
cleanUp(): void {
- this.data = InitialData;
+ this.data = JSON.parse(JSON.stringify(InitialData));
this.setDb(this.data);
}
+ clearSession(): void {
+ const data = this.getDb() || this.getInitialData();
+ data.app = {
+ ...data.app,
+ loginName: '',
+ authenticated: false,
+ };
+ delete data.app.user;
+ delete data.app.auth;
+ this.data = data;
+ this.setDb(data);
+ }
+
createNameSpace(namespace: string): void {
- if (!this.data[namespace]) {
- this.data[namespace] = {};
- this.setDb(this.data);
+ const data = this.getDb() || this.getInitialData();
+ if (!data[namespace]) {
+ data[namespace] = {};
+ this.data = data;
+ this.setDb(data);
}
}
// Core get/set methods
get(namespace: string, key: string): any {
const data = this.getDb();
- if (!data || !this.checkNamespace(namespace)) return null;
+ if (!data || !this.checkNamespace(data, 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;
+ const data = this.getDb() || this.getInitialData();
+ if (!this.checkNamespace(data, namespace)) return;
if (key.includes('user-pref')) {
data[namespace][key] = value;
} else {
this.setNestedValue(data[namespace], key, value);
}
+ this.data = data;
+ this.setDb(data);
+ }
+
+ setSession(loginName: string, user: unknown, authorizations: string[]): void {
+ const data = this.getDb() || this.getInitialData();
+ data.app = {
+ ...data.app,
+ loginName: encodeURIComponent(loginName),
+ authenticated: true,
+ user,
+ auth: authorizations,
+ };
+ this.data = data;
this.setDb(data);
}
@@ -169,10 +209,10 @@ interface DbData {
target[last] = value;
}
- getInitialData(): string {
+ getInitialData(): DbData {
return JSON.parse(JSON.stringify(InitialData));
}
}
- export const db = new Database()
\ No newline at end of file
+ export const db = new Database()
diff --git a/ambari-web/latest/vite.config.ts b/ambari-web/latest/src/Utils/httpError.ts
old mode 100755
new mode 100644
similarity index 76%
copy from ambari-web/latest/vite.config.ts
copy to ambari-web/latest/src/Utils/httpError.ts
index 929c08ff59..5655220040
--- a/ambari-web/latest/vite.config.ts
+++ b/ambari-web/latest/src/Utils/httpError.ts
@@ -16,10 +16,11 @@
* limitations under the License.
*/
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import axios from "axios";
-export default defineConfig({
- plugins: [react()],
- base:"./"
-})
+export function responseErrorMessage(error: unknown, fallback: string): string {
+ if (axios.isAxiosError<{ message?: string }>(error)) {
+ return error.response?.data?.message || fallback;
+ }
+ return fallback;
+}
diff --git a/ambari-web/latest/src/Utils/persistedSettings.test.ts b/ambari-web/latest/src/Utils/persistedSettings.test.ts
new file mode 100644
index 0000000000..27cfd97445
--- /dev/null
+++ b/ambari-web/latest/src/Utils/persistedSettings.test.ts
@@ -0,0 +1,39 @@
+/**
+ * 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 { describe, expect, it } from "vitest";
+import { parsePersistedValue, persistedPayload } from "./persistedSettings";
+
+describe("persisted settings", () => {
+ it("round trips booleans, strings, and objects as JSON values", () => {
+ const payload = persistedPayload({ enabled: false, timezone: "UTC", wizard: {} });
+ expect(payload).toEqual({
+ enabled: "false",
+ timezone: '"UTC"',
+ wizard: "{}",
+ });
+ expect(parsePersistedValue(payload.enabled, true)).toBe(false);
+ expect(parsePersistedValue(payload.timezone, "Browser")).toBe("UTC");
+ expect(parsePersistedValue(payload.wizard, { stale: true })).toEqual({});
+ });
+
+ it("uses the fallback for empty or invalid persisted data", () => {
+ expect(parsePersistedValue(undefined, true)).toBe(true);
+ expect(parsePersistedValue("not-json", "Browser")).toBe("Browser");
+ });
+});
diff --git a/ambari-web/latest/src/InactivityTimeout.tsx b/ambari-web/latest/src/Utils/persistedSettings.ts
similarity index 61%
copy from ambari-web/latest/src/InactivityTimeout.tsx
copy to ambari-web/latest/src/Utils/persistedSettings.ts
index 90b943c229..88bfcc9925 100644
--- a/ambari-web/latest/src/InactivityTimeout.tsx
+++ b/ambari-web/latest/src/Utils/persistedSettings.ts
@@ -16,20 +16,22 @@
* limitations under the License.
*/
-interface InactivityTimeoutProps {
- timeout?: number; // Make optional since we'll get it from server properties
+export function parsePersistedValue<T>(value: unknown, fallback: T): T {
+ if (value === undefined || value === null || value === "") {
+ return fallback;
+ }
+ if (typeof value !== "string") {
+ return value as T;
+ }
+ try {
+ return JSON.parse(value) as T;
+ } catch {
+ return fallback;
+ }
}
-const InactivityTimeout: React.FC<InactivityTimeoutProps> = ({ timeout: propTimeout }) => {
- return (
- <div className="inactivity-timeout">
- {propTimeout ? (
- <p>Your session will expire after {propTimeout} minutes of inactivity.</p>
- ) : (
- <p>Loading inactivity timeout...</p>
- )}
- </div>
+export function persistedPayload(values: Record<string, unknown>): Record<string, string> {
+ return Object.fromEntries(
+ Object.entries(values).map(([key, value]) => [key, JSON.stringify(value)]),
);
-};
-
-export default InactivityTimeout;
+}
diff --git a/ambari-web/latest/src/api/clusterApi.ts b/ambari-web/latest/src/api/clusterApi.ts
index dca1291b8e..344e7a9a0f 100644
--- a/ambari-web/latest/src/api/clusterApi.ts
+++ b/ambari-web/latest/src/api/clusterApi.ts
@@ -122,7 +122,7 @@ const ClusterApi = {
return clusterName;
},
getClusterData: async function () {
- const url= `/clusters?fields=Clusters/provisioning_state,Clusters/security_type,Clusters/version,Clusters/cluster_id`;
+ const url= `/clusters?fields=Clusters/cluster_name,Clusters/provisioning_state,Clusters/security_type,Clusters/version,Clusters/cluster_id`;
const response = await ambariApi.request({
url: url,
method: "GET",
@@ -176,7 +176,7 @@ const ClusterApi = {
url: '/services/AMBARI/components/AMBARI_SERVER',
method: 'GET',
params: {
- fields: 'RootServiceComponents/properties/user.inactivity.timeout.default',
+ fields: 'RootServiceComponents/properties/user.inactivity.timeout.default,RootServiceComponents/properties/user.inactivity.timeout.role.readonly.default',
_: Date.now() // Cache buster
}
});
@@ -212,4 +212,4 @@ const ClusterApi = {
}
}
-export default ClusterApi;
\ No newline at end of file
+export default ClusterApi;
diff --git a/ambari-web/latest/src/api/config/axiosConfig.ts b/ambari-web/latest/src/api/config/axiosConfig.ts
index 8f98a7cab2..ecc6e42b0f 100644
--- a/ambari-web/latest/src/api/config/axiosConfig.ts
+++ b/ambari-web/latest/src/api/config/axiosConfig.ts
@@ -19,6 +19,13 @@
import axios from "axios";
import { toast } from "react-hot-toast";
import { get } from "lodash";
+import { handleAuthenticationError } from "../../Utils/authNavigation";
+
+declare module "axios" {
+ export interface AxiosRequestConfig {
+ skipAuthRedirect?: boolean;
+ }
+}
const config = {
development: {
@@ -63,13 +70,10 @@ const createAxiosInstance = (baseURL: string, 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 (error.response && !error.config?.skipAuthRedirect) {
+ handleAuthenticationError(error.response);
}
- if (responseMessage && error.response.status !== 400) {
+ if (responseMessage && error.response?.status !== 400 && !error.config?.skipAuthRedirect) {
toast.error(responseMessage);
}
return Promise.reject(error);
@@ -93,11 +97,8 @@ const createSupressErrorAxiosInstance = (baseURL: string, 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);
+ if (error.response && !error.config?.skipAuthRedirect) {
+ handleAuthenticationError(error.response);
}
return Promise.reject(error);
});
diff --git a/ambari-web/latest/src/api/loginApi.test.ts b/ambari-web/latest/src/api/loginApi.test.ts
new file mode 100644
index 0000000000..ce965a1c2f
--- /dev/null
+++ b/ambari-web/latest/src/api/loginApi.test.ts
@@ -0,0 +1,52 @@
+/**
+ * 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 { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({ request: vi.fn() }));
+vi.mock("./config/axiosConfig", () => ({
+ ambariApi: { request: mocks.request },
+}));
+
+import LoginApi from "./loginApi";
+
+describe("login API", () => {
+ beforeEach(() => mocks.request.mockReset());
+
+ it("sends UTF-8 Basic credentials without exposing them in the body", async () => {
+ mocks.request.mockResolvedValue({});
+ await LoginApi.authenticate("用户", "päss");
+
+ expect(mocks.request).toHaveBeenCalledWith(expect.objectContaining({
+ url: "/auth",
+ method: "POST",
+ headers: expect.objectContaining({
+ Authorization: `Basic ${Buffer.from("用户:päss", "utf8").toString("base64")}`,
+ }),
+ skipAuthRedirect: true,
+ }));
+ });
+
+ it("URL-encodes the username exactly once at the API boundary", async () => {
+ mocks.request.mockResolvedValue({});
+ await LoginApi.handleSuccessfulLogin({ usr: "", loginName: "user/name %" });
+ expect(mocks.request).toHaveBeenCalledWith(expect.objectContaining({
+ url: "/users/user%2Fname%20%25?fields=*,privileges/PrivilegeInfo/*",
+ }));
+ });
+});
diff --git a/ambari-web/latest/src/api/loginApi.ts b/ambari-web/latest/src/api/loginApi.ts
index 65dc9a93bc..9296827257 100644
--- a/ambari-web/latest/src/api/loginApi.ts
+++ b/ambari-web/latest/src/api/loginApi.ts
@@ -18,21 +18,17 @@
import {ambariApi} from "./config/axiosConfig";
import {misc} from "../Utils/misc";
-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;
+export interface LoginMessage {
+ text: string;
+ buttonText: string;
}
+
const LoginApi = {
authenticate: async function (username: string, password: string) {
@@ -44,11 +40,12 @@ const LoginApi = {
'Content-Type': 'text/plain',
Authorization: "Basic " + hashForUserNamePassword,
},
+ skipAuthRedirect: true,
});
return response;
},
handleSuccessfulLogin: async function (params: LoginParams) {
- const url = `/users/${encodeURIComponent(params.loginName)}?fields=*,privileges/PrivilegeInfo/cluster_name,privileges/PrivilegeInfo/permission_name`;
+ const url = `/users/${encodeURIComponent(params.loginName)}?fields=*,privileges/PrivilegeInfo/*`;
const response = await ambariApi.request({
url: url,
method: "GET",
@@ -68,31 +65,42 @@ const LoginApi = {
});
return response;
},
- afterLoginSuccessCallback: async function(data: dataLoginType) {
- const response = await ambariApi.request({
- url: "/settings/motd",
+ loadPrivileges: async function(loginName: string) {
+ return ambariApi.request({
+ url: `/users/${encodeURIComponent(loginName)}/privileges?fields=*`,
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({
+ probeSession: async function() {
+ return ambariApi.request({
url: "/clusters?fields=Clusters/provisioning_state,Clusters/security_type,Clusters/version,Clusters/cluster_id",
- data: requestData,
- })
- return response;
+ method: "GET",
+ });
+ },
+ loadLoginMessage: async function(): Promise<LoginMessage | null> {
+ try {
+ const response = await ambariApi.request({
+ url: "/settings/motd",
+ method: "GET",
+ });
+ const content = response.data?.Settings?.content;
+ if (typeof content !== "string") {
+ return null;
+ }
+ const parsed = JSON.parse(content.replace(/\n/g, "\\n"));
+ if (parsed?.status !== "true" || !parsed?.text) {
+ return null;
+ }
+ return {
+ text: String(parsed.text).replace(/(\r\n|\n|\r)/g, "\n"),
+ buttonText: parsed.button ? String(parsed.button) : "OK",
+ };
+ } catch {
+ return null;
+ }
},
logout: async () => {
- Cookies.remove('AMBARISESSIONID', { path: '/' ,domain: 'localhost', secure: true })
- console.log('After logout cookies:', document.cookie);
+ Cookies.remove('AMBARISESSIONID', { path: '/' });
const timestamp = Date.now();
try {
const response = await ambariApi.request({
@@ -108,23 +116,12 @@ const LoginApi = {
}
});
- // 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
+ throw error;
}
},
}
export default LoginApi;
-
diff --git a/ambari-web/latest/src/InactivityTimeout.tsx b/ambari-web/latest/src/components/AdminViewRouteGuard.test.ts
similarity index 53%
copy from ambari-web/latest/src/InactivityTimeout.tsx
copy to ambari-web/latest/src/components/AdminViewRouteGuard.test.ts
index 90b943c229..10bd036597 100644
--- a/ambari-web/latest/src/InactivityTimeout.tsx
+++ b/ambari-web/latest/src/components/AdminViewRouteGuard.test.ts
@@ -16,20 +16,28 @@
* limitations under the License.
*/
-interface InactivityTimeoutProps {
- timeout?: number; // Make optional since we'll get it from server properties
-}
+import { describe, expect, it } from "vitest";
+import { canEnterAdminView } from "./AdminViewRouteGuard";
-const InactivityTimeout: React.FC<InactivityTimeoutProps> = ({ timeout: propTimeout }) => {
- return (
- <div className="inactivity-timeout">
- {propTimeout ? (
- <p>Your session will expire after {propTimeout} minutes of inactivity.</p>
- ) : (
- <p>Loading inactivity timeout...</p>
- )}
- </div>
- );
-};
+describe("Admin View route policy", () => {
+ it("keeps the classic stack-upgrade guard for an existing cluster", () => {
+ expect(canEnterAdminView({
+ ambariAdmin: true,
+ canUpgradeStack: false,
+ clusterName: "c1",
+ })).toBe(false);
+ expect(canEnterAdminView({
+ ambariAdmin: false,
+ canUpgradeStack: true,
+ clusterName: "c1",
+ })).toBe(true);
+ });
-export default InactivityTimeout;
+ it("allows the no-cluster Ambari administrator landing", () => {
+ expect(canEnterAdminView({
+ ambariAdmin: true,
+ canUpgradeStack: false,
+ clusterName: "",
+ })).toBe(true);
+ });
+});
diff --git a/ambari-web/latest/src/InactivityTimeout.tsx b/ambari-web/latest/src/components/AdminViewRouteGuard.tsx
similarity index 50%
copy from ambari-web/latest/src/InactivityTimeout.tsx
copy to ambari-web/latest/src/components/AdminViewRouteGuard.tsx
index 90b943c229..2787fec0de 100644
--- a/ambari-web/latest/src/InactivityTimeout.tsx
+++ b/ambari-web/latest/src/components/AdminViewRouteGuard.tsx
@@ -16,20 +16,31 @@
* limitations under the License.
*/
-interface InactivityTimeoutProps {
- timeout?: number; // Make optional since we'll get it from server properties
-}
+import { ReactNode, useContext } from "react";
+import { Navigate } from "react-router-dom";
+import { useAuth } from "../hooks/useAuth";
+import { AppContext } from "../store/context";
-const InactivityTimeout: React.FC<InactivityTimeoutProps> = ({ timeout: propTimeout }) => {
- return (
- <div className="inactivity-timeout">
- {propTimeout ? (
- <p>Your session will expire after {propTimeout} minutes of inactivity.</p>
- ) : (
- <p>Loading inactivity timeout...</p>
- )}
- </div>
- );
-};
+export function canEnterAdminView({
+ ambariAdmin,
+ canUpgradeStack,
+ clusterName,
+}: {
+ ambariAdmin: boolean;
+ canUpgradeStack: boolean;
+ clusterName: string;
+}): boolean {
+ return canUpgradeStack || (!clusterName && ambariAdmin);
+}
-export default InactivityTimeout;
+export default function AdminViewRouteGuard({ children }: { children: ReactNode }) {
+ const { clusterName } = useContext(AppContext);
+ const { hasAuthorization, isAdmin } = useAuth();
+ return canEnterAdminView({
+ ambariAdmin: isAdmin(),
+ canUpgradeStack: hasAuthorization("CLUSTER.UPGRADE_DOWNGRADE_STACK"),
+ clusterName,
+ })
+ ? children
+ : <Navigate to="/" replace />;
+}
diff --git a/ambari-web/latest/src/components/AuthGuard.tsx b/ambari-web/latest/src/components/AuthGuard.tsx
index 9505ebd411..d85919b657 100644
--- a/ambari-web/latest/src/components/AuthGuard.tsx
+++ b/ambari-web/latest/src/components/AuthGuard.tsx
@@ -18,7 +18,7 @@
import React from "react";
import { useAuth } from "../hooks/useAuth";
-import { Alert } from "react-bootstrap";
+import { Navigate } from "react-router-dom";
interface AuthGuardProps {
children: React.ReactNode;
@@ -116,12 +116,16 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
requireAuthorization,
requirePrivilege,
clusterName,
- //@ts-ignore
redirectTo = "/login",
}) => {
- const handleUnauthorized = () => {
-
- };
+ const { isAuthenticated, isLoading } = useAuth();
+ if (isLoading) {
+ return <div>Loading...</div>;
+ }
+
+ if (requireAuth && !isAuthenticated) {
+ return <Navigate to="/login" replace />;
+ }
return (
<AuthGuard
@@ -131,12 +135,7 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
requireAuthorization={requireAuthorization}
requirePrivilege={requirePrivilege}
clusterName={clusterName}
- onUnauthorized={handleUnauthorized}
- fallback={
- <Alert variant="danger" className="m-3">
- You do not have permission to view this content.
- </Alert>
- }
+ fallback={<Navigate to={redirectTo} replace />}
>
{children}
</AuthGuard>
diff --git a/ambari-web/latest/src/components/DocumentTitleUpdater.tsx b/ambari-web/latest/src/components/DocumentTitleUpdater.tsx
index c9993d6d05..bb3bbc44de 100644
--- a/ambari-web/latest/src/components/DocumentTitleUpdater.tsx
+++ b/ambari-web/latest/src/components/DocumentTitleUpdater.tsx
@@ -16,10 +16,26 @@
* limitations under the License.
*/
-const DocumentTitleUpdater = () => {
- return (
- <div>In Progress</div>
- );
-};
+import { useContext, useEffect } from "react";
+import { useLocation } from "react-router-dom";
+import { AppContext } from "../store/context";
-export default DocumentTitleUpdater;
\ No newline at end of file
+export default function DocumentTitleUpdater() {
+ const { clusterName } = useContext(AppContext);
+ const location = useLocation();
+
+ useEffect(() => {
+ if (location.pathname.startsWith("/experimental")) {
+ document.title = "Ambari - Experimental";
+ } else if (clusterName) {
+ const displayName = clusterName.length > 13
+ ? `${clusterName.slice(0, 10)}...`
+ : clusterName;
+ document.title = `Ambari - ${displayName}`;
+ } else {
+ document.title = "Ambari";
+ }
+ }, [clusterName, location.pathname]);
+
+ return null;
+}
diff --git a/ambari-web/latest/vite.config.ts b/ambari-web/latest/src/components/FeatureRouteGuard.tsx
old mode 100755
new mode 100644
similarity index 66%
copy from ambari-web/latest/vite.config.ts
copy to ambari-web/latest/src/components/FeatureRouteGuard.tsx
index 929c08ff59..d9d1af5b3d
--- a/ambari-web/latest/vite.config.ts
+++ b/ambari-web/latest/src/components/FeatureRouteGuard.tsx
@@ -16,10 +16,19 @@
* limitations under the License.
*/
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import { ReactNode, useContext } from "react";
+import { Navigate } from "react-router-dom";
+import { AppContext } from "../store/context";
-export default defineConfig({
- plugins: [react()],
- base:"./"
-})
+export default function FeatureRouteGuard({
+ children,
+ feature,
+}: {
+ children: ReactNode;
+ feature: string;
+}) {
+ const { supports } = useContext(AppContext);
+ return supports[feature]
+ ? children
+ : <Navigate to="/main/dashboard/metrics" replace />;
+}
diff --git a/ambari-web/latest/src/components/Modal.tsx b/ambari-web/latest/src/components/Modal.tsx
index 4d238be994..9326513c8a 100644
--- a/ambari-web/latest/src/components/Modal.tsx
+++ b/ambari-web/latest/src/components/Modal.tsx
@@ -74,7 +74,7 @@ export default function Modal({
} = options;
const sortedExtraButtons = Array.isArray(extraButtons)
- ? extraButtons.sort((a: any, b: any) => (a.order || 0) - (b.order || 0))
+ ? [...extraButtons].sort((a: any, b: any) => (a.order || 0) - (b.order || 0))
: [];
return (
@@ -110,24 +110,34 @@ export default function Modal({
{cancelButtonText}
</DefaultButton>
) : null}
- {sortedExtraButtons.map((button: any) => (
- <Button
- key={button.text}
- className={classNames(
- "ps-3",
- "pe-3",
- "text-white",
- "custom-btn",
- button.className
- )}
- variant={button.variant}
- onClick={button.onClick}
- size={buttonSize}
- {...button}
- >
- {button.text}
- </Button>
- ))}
+ {sortedExtraButtons.map((button: any) => {
+ const {
+ text,
+ onClick,
+ order: _order,
+ className: buttonClassName,
+ variant,
+ ...buttonProps
+ } = button;
+ return (
+ <Button
+ {...buttonProps}
+ key={text}
+ className={classNames(
+ "ps-3",
+ "pe-3",
+ "text-white",
+ "custom-btn",
+ buttonClassName
+ )}
+ variant={variant}
+ onClick={onClick}
+ size={buttonSize}
+ >
+ {text}
+ </Button>
+ );
+ })}
{cancelableViaSuccessBtn ? (
<Button
className={classNames(
diff --git a/ambari-web/latest/src/components/Navbar.tsx b/ambari-web/latest/src/components/Navbar.tsx
index f37fb9c21d..afe2b417ae 100644
--- a/ambari-web/latest/src/components/Navbar.tsx
+++ b/ambari-web/latest/src/components/Navbar.tsx
@@ -45,10 +45,7 @@ import { AlertsApi } from "../api/alertsApi.ts";
import { redirectToAdminView } from "../Utils/adminViewRedirect";
import modalManager from "../store/ModalManager.ts";
import BackgroundOperations from "../screens/BackgroundOperations/index.tsx";
-import { db } from "../Utils/db";
-import Cookies from "universal-cookie";
import { useCallback } from "react";
-import { isString } from "lodash";
import { useAuth } from "../hooks/useAuth";
type NavbarOption = {
@@ -63,6 +60,8 @@ type NavbarOption = {
type NavBarProps = {
subPath: string;
viewsList: any[];
+ clusterControls?: boolean;
+ homePath?: string;
runningRequestsCount?: number;
hostMaintenanceState?: string;
hostname?: string;
@@ -71,6 +70,8 @@ type NavBarProps = {
export default function NavBar({
subPath,
viewsList,
+ clusterControls = true,
+ homePath = "/main/dashboard/metrics",
runningRequestsCount,
hostMaintenanceState,
hostname,
@@ -86,34 +87,12 @@ export default function NavBar({
warning: 0,
});
const location = useLocation();
- const localStorageData = db.getItem("ambari");
- let login = "";
- if (localStorageData) {
- let parsedData = {};
- try {
- parsedData = JSON.parse(localStorageData || "{}");
- 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) {
- login = ambari.app.loginName;
- // If we already have a username, we can consider the user authenticated
- }
- }
-
const [selectedFilter, setSelectedFilter] = useState("all");
const { clusterName, cluster } = useContext(AppContext);
const [showAmbariAboutModal, setShowAmbariAboutModal] = useState(false);
const isClusterInstalled = cluster?.provisioning_state === "INSTALLED";
const navigate = useNavigate();
- const cookies = new Cookies();
const isInstaller = () => {
const path = location.pathname;
@@ -200,73 +179,50 @@ export default function NavBar({
return viewsList?.length;
};
- const logOffBeforeSend = useCallback((headers: Record<string, string>) => {
- headers["Authorization"] = "";
- return headers;
- }, []);
+ // Authorization hooks - implementing Ember.js showSettingsPopup authorization pattern
+ const { user, hasAuthorization, logout } = useAuth();
+ const { isNonWizardUser, upgradeIsRunning, upgradeSuspended } = useContext(AppContext);
const handleSignOut = useCallback(async () => {
- try {
- stopPolling();
- db.cleanUp();
- db.setItem("ambari", JSON.stringify(db.getInitialData()));
-
- localStorage.removeItem("ambari");
-
- sessionStorage.clear();
-
- cookies.remove("AMBARISESSIONID", {
- path: "/", // Cookie is set with path '/'
- domain: "localhost", // Cookie is set for localhost
- secure: true, // Cookie has secure flag
- sameSite: "lax", // Default SameSite value
- });
-
- const response = await fetch("/logout", {
- method: "GET",
- headers: logOffBeforeSend({
- "Content-Type": "text/plain",
- }),
- });
-
- if (response.ok) {
- window.location.href = "/#/login";
- } else {
- window.location.href = "/#/login";
- }
- } catch (error) {
- window.location.href = "/#/login";
- }
- }, [navigate, stopPolling]);
-
- // Authorization hooks - implementing Ember.js showSettingsPopup authorization pattern
- const { isAdmin } = useAuth();
- const { upgradeIsRunning, upgradeSuspended } = useContext(AppContext);
+ stopPolling();
+ await logout();
+ navigate("/login", { replace: true });
+ }, [logout, navigate, stopPolling]);
// Check if upgrade is blocking operations (running but not suspended)
// FIXED: Add additional check for upgrade suspended state to prevent flaky behavior
// When upgrade is suspended/paused, admin options should be available
const isUpgradeBlocking = upgradeIsRunning && !upgradeSuspended;
+ const canUseRestrictedNavigation = !isUpgradeBlocking && !isNonWizardUser;
+ const canManageAmbari = canUseRestrictedNavigation && hasAuthorization(
+ "AMBARI.ADD_DELETE_CLUSTERS, AMBARI.ASSIGN_ROLES, AMBARI.EDIT_STACK_REPOS, AMBARI.MANAGE_GROUPS, AMBARI.MANAGE_STACK_VERSIONS, AMBARI.MANAGE_USERS, AMBARI.MANAGE_VIEWS, AMBARI.RENAME_CLUSTER"
+ );
+ const canSeeSettings = canUseRestrictedNavigation
+ && hasAuthorization("AMBARI.MANAGE_SETTINGS");
+ const canOpenSettings = !isNonWizardUser
+ && hasAuthorization("CLUSTER.UPGRADE_DOWNGRADE_STACK");
+
const navbarOptions: NavbarOption[] = [
{
label: "About",
callback: () => setShowAmbariAboutModal(true),
},
- // FIXED: Consolidate admin checks to eliminate code duplication (AI code review suggestion)
- // Admin options should be available when upgrade is suspended/paused
- ...(isAdmin()
+ ...(canManageAmbari
? [
{
label: "Manage Ambari",
- callback: () => {
- // First navigate to adminView route
- redirectToAdminView();
- },
+ callback: () => void redirectToAdminView(),
},
+ ]
+ : []),
+ ...(canSeeSettings
+ ? [
{
label: "Settings",
- callback: () => setShowUserSettingsModal(true),
+ callback: () => {
+ if (canOpenSettings) setShowUserSettingsModal(true);
+ },
},
]
: []),
@@ -276,26 +232,6 @@ export default function NavBar({
},
];
- // FIXED: Apply upgrade blocking logic only to the callback functions, not to the dropdown items visibility
- // This prevents flaky behavior where dropdown items disappear during upgrade pause
- const getNavbarOptionsWithUpgradeCheck = () => {
- return navbarOptions.map(option => {
- // Block admin actions during active upgrades (not suspended)
- if ((option.label === "Manage Ambari" || option.label === "Settings") && isUpgradeBlocking) {
- return {
- ...option,
- callback: () => {
- // Could show a message that admin functions are disabled during upgrade
- console.log("Admin functions are disabled during active upgrade");
- }
- };
- }
- return option;
- });
- };
-
- const finalNavbarOptions = getNavbarOptionsWithUpgradeCheck();
-
return (
<div>
{showAmbariAboutModal ? (
@@ -321,7 +257,7 @@ export default function NavBar({
className="me-1"
icon={faHome}
style={{ fontSize: 24, cursor: "pointer" }}
- onClick={() => navigate("/main/dashboard/metrics")}
+ onClick={() => navigate(homePath)}
/>
{hostname && hostMaintenanceState === "ON" ? (
<div className="d-flex align-items-center">
@@ -353,7 +289,7 @@ export default function NavBar({
{clusterName}
</Nav.Link>
<div style={{ width: "20px" }}></div>
- {isInstaller() ? null : (
+ {isInstaller() || !clusterControls ? null : (
<div
onClick={() => {
modalManager.show(
@@ -382,7 +318,7 @@ export default function NavBar({
</div>
)}
<div style={{ width: "20px" }}></div>
- {isInstaller() ? null : (
+ {isInstaller() || !clusterControls ? null : (
<NotificationDropdown
notifications={filteredNotifications}
onFilterChange={setSelectedFilter}
@@ -440,19 +376,19 @@ export default function NavBar({
data-bs-toggle="dropdown"
>
<FontAwesomeIcon icon={faUser} className="me-2" />
- <div className="navbar-text navbar-size">{login}</div>
+ <div className="navbar-text navbar-size">{user?.user_name || ""}</div>
</Dropdown.Toggle>
<Dropdown.Menu className="rounded-0">
- {finalNavbarOptions.slice(0, -1).map((option) => (
+ {navbarOptions.slice(0, -1).map((option) => (
<Dropdown.Item key={option.label} onClick={option.callback}>
{option.label}
</Dropdown.Item>
))}
<DropdownDivider />
<Dropdown.Item
- onClick={finalNavbarOptions[finalNavbarOptions.length - 1].callback}
+ onClick={navbarOptions[navbarOptions.length - 1].callback}
>
- {finalNavbarOptions[finalNavbarOptions.length - 1].label}
+ {navbarOptions[navbarOptions.length - 1].label}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
diff --git a/ambari-web/latest/src/components/Sidebar/SideItemList.tsx b/ambari-web/latest/src/components/Sidebar/SideItemList.tsx
index e98b5b3cb0..9041d24103 100644
--- a/ambari-web/latest/src/components/Sidebar/SideItemList.tsx
+++ b/ambari-web/latest/src/components/Sidebar/SideItemList.tsx
@@ -43,7 +43,12 @@ enum SideItemLabels {
* Generate sidebar items with authorization checks
* Based on Ember.js ui/app/views/main/admin.js authorization patterns
*/
-const getSideItemList = (hasAuthorization: (auth: string) => boolean, upgradeInProgress: boolean = false, upgradeHolding: boolean = false): SideItem[] => {
+const getSideItemList = (
+ hasAuthorization: (auth: string) => boolean,
+ supports: Record<string, boolean>,
+ upgradeInProgress: boolean = false,
+ upgradeHolding: boolean = false,
+): SideItem[] => {
const adminChildren: SideItem[] = [];
// Stack and Versions - Requires CLUSTER.VIEW_STACK_DETAILS OR CLUSTER.UPGRADE_DOWNGRADE_STACK (matches Ember.js)
@@ -71,7 +76,8 @@ const getSideItemList = (hasAuthorization: (auth: string) => boolean, upgradeInP
}
// Kerberos - Requires CLUSTER.TOGGLE_KERBEROS
- if (hasAuthorization('CLUSTER.TOGGLE_KERBEROS') || upgradeInProgress || upgradeHolding) {
+ if (supports.enableToggleKerberos
+ && (hasAuthorization('CLUSTER.TOGGLE_KERBEROS') || upgradeInProgress || upgradeHolding)) {
adminChildren.push({
id: SideItemLabels.KERBEROS,
icon: <></>,
@@ -83,7 +89,9 @@ const getSideItemList = (hasAuthorization: (auth: string) => boolean, upgradeInP
}
// Service Auto Start - Requires SERVICE.START_STOP authorization (matches ServiceAutoStart component)
- if (hasAuthorization('SERVICE.START_STOP') || upgradeInProgress || upgradeHolding) {
+ const canSeeAutoStart = hasAuthorization('SERVICE.START_STOP, CLUSTER.MODIFY_CONFIGS')
+ && hasAuthorization('SERVICE.MANAGE_AUTO_START, CLUSTER.MANAGE_AUTO_START');
+ if (supports.serviceAutoStart && (canSeeAutoStart || upgradeInProgress || upgradeHolding)) {
adminChildren.push({
id: SideItemLabels.SERVICE_AUTO_START,
icon: <></>,
diff --git a/ambari-web/latest/src/components/Sidebar/Sidebar.tsx b/ambari-web/latest/src/components/Sidebar/Sidebar.tsx
index 718fcdb701..5bf7c3f550 100644
--- a/ambari-web/latest/src/components/Sidebar/Sidebar.tsx
+++ b/ambari-web/latest/src/components/Sidebar/Sidebar.tsx
@@ -50,7 +50,13 @@ const SideBar = ({
isSidebarCollapsed,
setIsSidebarCollapsed,
}: SideBarProps) => {
- const { clusterName, services: contextServices, upgradeInProgress, upgradeHolding } = useContext(AppContext);
+ const {
+ clusterName,
+ services: contextServices,
+ supports,
+ upgradeHolding,
+ upgradeInProgress,
+ } = useContext(AppContext);
const [openOptions, setOpenOptions] = useState<string[]>([SideItemLabels.SERVICES]);
const [selectedOption, setSelectedOption] = useState<string>("");
const { allServiceModels } = useContext(ServiceContext);
@@ -60,7 +66,12 @@ const SideBar = ({
const { hasAuthorization } = useAuth();
// Get authorization-aware sidebar items using computed upgrade properties
- const authorizedSideItemList = getSideItemList(hasAuthorization, upgradeInProgress, upgradeHolding);
+ const authorizedSideItemList = getSideItemList(
+ hasAuthorization,
+ supports,
+ upgradeInProgress,
+ upgradeHolding,
+ );
const [services, setServices] = useState<
{
name: string;
diff --git a/ambari-web/latest/src/components/UserSettingsModal.tsx b/ambari-web/latest/src/components/UserSettingsModal.tsx
index 4d574303a2..cba308294c 100644
--- a/ambari-web/latest/src/components/UserSettingsModal.tsx
+++ b/ambari-web/latest/src/components/UserSettingsModal.tsx
@@ -16,29 +16,181 @@
* limitations under the License.
*/
+import { useContext, useEffect, useState } from "react";
+import { Alert, Form, Table } from "react-bootstrap";
+import ClusterApi from "../api/clusterApi";
+import LoginApi from "../api/loginApi";
+import { useAuth } from "../hooks/useAuth";
+import { detectUserTimezone, parseTimezones } from "../Utils/timezone";
+import { parsePersistedValue, persistedPayload } from "../Utils/persistedSettings";
import Modal from "./Modal";
+import { AppContext } from "../store/context";
type UserSettingsModalProps = {
isOpen: boolean;
onClose: () => void;
};
-const UserSettingsModal = ({ isOpen, onClose }: UserSettingsModalProps) => {
+type PrivilegeGroup = {
+ name: string;
+ privileges: string[];
+};
+
+export default function UserSettingsModal({ isOpen, onClose }: UserSettingsModalProps) {
+ const { user, isAdmin, hasAuthorization } = useAuth();
+ const { syncUserBgPreferences } = useContext(AppContext);
+ const [showBackgroundOperations, setShowBackgroundOperations] = useState(true);
+ const [timezone, setTimezone] = useState(detectUserTimezone());
+ const [initialTimezone, setInitialTimezone] = useState("");
+ const [clusterPrivileges, setClusterPrivileges] = useState<PrivilegeGroup[]>([]);
+ const [viewPrivileges, setViewPrivileges] = useState<PrivilegeGroup[]>([]);
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+ const [error, setError] = useState("");
+ const loginName = user?.user_name || "";
+ const showBackgroundKey = `admin-settings-show-bg-${loginName}`;
+ const timezoneKey = `admin-settings-timezone-${loginName}`;
+ const canPersist = hasAuthorization("CLUSTER.MANAGE_USER_PERSISTED_DATA");
+
+ useEffect(() => {
+ if (!isOpen || !loginName) {
+ return;
+ }
+ async function load() {
+ setLoading(true);
+ setError("");
+ try {
+ const [settings, privilegesResponse] = await Promise.all([
+ ClusterApi.getPersistData(),
+ LoginApi.loadPrivileges(loginName),
+ ]);
+ const detectedTimezone = detectUserTimezone();
+ const savedTimezone = parsePersistedValue(settings?.[timezoneKey], detectedTimezone);
+ setTimezone(savedTimezone);
+ setInitialTimezone(savedTimezone);
+ setShowBackgroundOperations(parsePersistedValue(settings?.[showBackgroundKey], true));
+ if (canPersist) {
+ const missingDefaults: Record<string, unknown> = {};
+ if (settings?.[showBackgroundKey] === undefined) {
+ missingDefaults[showBackgroundKey] = true;
+ }
+ if (settings?.[timezoneKey] === undefined) {
+ missingDefaults[timezoneKey] = detectedTimezone;
+ }
+ if (Object.keys(missingDefaults).length) {
+ void ClusterApi.postPersistData(persistedPayload(missingDefaults)).catch(() => undefined);
+ }
+ }
+
+ const clusters = new Map<string, string[]>();
+ const views = new Map<string, string[]>();
+ (privilegesResponse.data.items || []).forEach((item: any) => {
+ const privilege = item.PrivilegeInfo;
+ const target = privilege.type === "CLUSTER" ? clusters : privilege.type === "VIEW" ? views : null;
+ const name = privilege.type === "CLUSTER" ? privilege.cluster_name : privilege.instance_name;
+ if (target && name) {
+ target.set(name, [...(target.get(name) || []), privilege.permission_label]);
+ }
+ });
+ setClusterPrivileges(Array.from(clusters, ([name, privileges]) => ({ name, privileges })));
+ setViewPrivileges(Array.from(views, ([name, privileges]) => ({ name, privileges })));
+ } catch (loadError: any) {
+ setError(loadError?.response?.data?.message || "Unable to load user settings.");
+ } finally {
+ setLoading(false);
+ }
+ }
+ void load();
+ }, [canPersist, isOpen, loginName, showBackgroundKey, timezoneKey]);
+
+ const save = async () => {
+ if (!canPersist) return;
+ setSaving(true);
+ setError("");
+ try {
+ await ClusterApi.postPersistData(persistedPayload({
+ [showBackgroundKey]: showBackgroundOperations,
+ [timezoneKey]: timezone,
+ }));
+ syncUserBgPreferences(showBackgroundOperations);
+ onClose();
+ if (timezone !== initialTimezone) {
+ window.location.reload();
+ }
+ } catch (saveError: any) {
+ setError(saveError?.response?.data?.message || "Unable to save user settings.");
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const privilegeRows = (groups: PrivilegeGroup[]) => groups.map((group) => (
+ <tr key={group.name}>
+ <td>{group.name}</td>
+ <td>{group.privileges.join(", ")}</td>
+ </tr>
+ ));
+
return (
<Modal
isOpen={isOpen}
onClose={onClose}
modalTitle="User Settings"
- modalBody={<div/>}
- successCallback={() => {}}
+ modalBody={loading ? <p>Loading...</p> : (
+ <div>
+ {error ? <Alert variant="danger">{error}</Alert> : null}
+ <h4>General</h4>
+ <Form.Check
+ className="mb-4"
+ type="checkbox"
+ label="Do not show the Background Operations dialog automatically"
+ checked={!showBackgroundOperations}
+ onChange={(event) => setShowBackgroundOperations(!event.target.checked)}
+ />
+ <h4>Locale</h4>
+ <Form.Select
+ className="mb-4"
+ value={timezone}
+ onChange={(event) => setTimezone(event.target.value)}
+ aria-label="Timezone"
+ >
+ {parseTimezones().map((item) => (
+ <option key={item.value} value={item.value}>{item.label}</option>
+ ))}
+ </Form.Select>
+ {isAdmin() ? (
+ <Alert variant="info">Ambari administrators have all privileges.</Alert>
+ ) : (
+ <Table hover responsive>
+ <thead><tr><th>Cluster</th><th>Cluster Role</th></tr></thead>
+ <tbody>
+ {clusterPrivileges.length ? privilegeRows(clusterPrivileges) : (
+ <tr><td colSpan={2}>No cluster privileges</td></tr>
+ )}
+ </tbody>
+ <thead><tr><th>View</th><th>View Permissions</th></tr></thead>
+ <tbody>
+ {viewPrivileges.length ? privilegeRows(viewPrivileges) : (
+ <tr><td colSpan={2}>No View privileges</td></tr>
+ )}
+ </tbody>
+ </Table>
+ )}
+ {!canPersist ? (
+ <Alert variant="warning">
+ You do not have permission to persist user settings.
+ </Alert>
+ ) : null}
+ </div>
+ )}
+ successCallback={() => void save()}
options={{
modalSize: "modal-lg",
- okButtonText: "SAVE",
+ okButtonText: saving ? "SAVING..." : "SAVE",
cancelButtonText: "CANCEL",
- okButtonVariant: "success"
+ okButtonVariant: "success",
+ okButtonDisabled: loading || saving || !canPersist,
}}
/>
- )
+ );
}
-
-export default UserSettingsModal;
\ No newline at end of file
diff --git a/ambari-web/latest/src/constants.ts b/ambari-web/latest/src/constants.ts
index 7f41f05cbf..83d82a8593 100644
--- a/ambari-web/latest/src/constants.ts
+++ b/ambari-web/latest/src/constants.ts
@@ -147,6 +147,35 @@ export const toBePreservedPaths = {
kerberos: "KERBEROS_WIZARD_LAST_PATH",
};
+export const DEFAULT_SUPPORTS: Record<string, boolean> = {
+ preUpgradeCheck: true,
+ displayOlderVersions: false,
+ autoRollbackHA: false,
+ alwaysEnableManagedMySQLForHive: false,
+ preKerberizeCheck: false,
+ customizeAgentUserAccount: false,
+ installGanglia: false,
+ opsDuringRollingUpgrade: false,
+ customizedWidgetLayout: false,
+ showPageLoadTime: false,
+ skipComponentStartAfterInstall: false,
+ preInstallChecks: false,
+ serviceAutoStart: true,
+ logSearch: true,
+ redhatSatellite: false,
+ addingNewRepository: false,
+ kerberosStackAdvisor: true,
+ logCountVizualization: false,
+ createAlerts: false,
+ enabledWizardForHostOrderedUpgrade: true,
+ manageJournalNode: true,
+ enableToggleKerberos: true,
+ enableAddDeleteServices: true,
+ regenerateKeytabsOnSingleHost: false,
+ disableCredentialsAutocompleteForRepoUrls: true,
+ enableNewServiceRestartOptions: false,
+};
+
export const selectMasterComponentsForService = {
HIVE: ["HIVE_METASTORE", "HIVE_SERVER"],
ZOOKEEPER: ["ZOOKEEPER_SERVER"],
diff --git a/ambari-web/latest/src/hooks/useAuth.ts b/ambari-web/latest/src/hooks/useAuth.ts
index 1866b95163..b66e3fb872 100644
--- a/ambari-web/latest/src/hooks/useAuth.ts
+++ b/ambari-web/latest/src/hooks/useAuth.ts
@@ -29,6 +29,7 @@ export const useAuth = () => {
user: context.user,
isAuthenticated: context.isAuthenticated,
isLoading: context.isLoading,
+ sessionError: context.sessionError,
// Authorization data
authorizations: context.authorizations,
@@ -47,7 +48,10 @@ export const useAuth = () => {
// Actions
login: context.login,
logout: context.logout,
- refreshUserData: context.refreshUserData
+ refreshUserData: context.refreshUserData,
+ retrySession: context.retrySession,
+ loginMessage: context.loginMessage,
+ acknowledgeLoginMessage: context.acknowledgeLoginMessage,
};
};
diff --git a/ambari-web/latest/src/layout/InstallerVersionGuard.tsx b/ambari-web/latest/src/layout/InstallerVersionGuard.tsx
new file mode 100644
index 0000000000..a362c8eaaf
--- /dev/null
+++ b/ambari-web/latest/src/layout/InstallerVersionGuard.tsx
@@ -0,0 +1,40 @@
+/**
+ * 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, useContext } from "react";
+import { Alert } from "react-bootstrap";
+import { AppContext } from "../store/context";
+import { hasVersionConflict } from "../Utils/authPolicy";
+
+export default function InstallerVersionGuard({ children }: { children: ReactNode }) {
+ const { ambariServerVersion } = useContext(AppContext);
+ const clientVersion = import.meta.env.VITE_AMBARI_VERSION || "";
+ if (!hasVersionConflict(clientVersion, ambariServerVersion || "")) {
+ return children;
+ }
+
+ return (
+ <main className="container py-5">
+ <h2>Ambari Server / Web Client Version Mismatch</h2>
+ <Alert variant="danger">
+ Ambari Server version <strong>{ambariServerVersion}</strong> does not match
+ Web Client version <strong>{clientVersion}</strong>. Cluster installation is blocked.
+ </Alert>
+ </main>
+ );
+}
diff --git a/ambari-web/latest/src/InactivityTimeout.tsx b/ambari-web/latest/src/layout/Main.tsx
similarity index 59%
copy from ambari-web/latest/src/InactivityTimeout.tsx
copy to ambari-web/latest/src/layout/Main.tsx
index 90b943c229..264cb638b7 100644
--- a/ambari-web/latest/src/InactivityTimeout.tsx
+++ b/ambari-web/latest/src/layout/Main.tsx
@@ -16,20 +16,20 @@
* limitations under the License.
*/
-interface InactivityTimeoutProps {
- timeout?: number; // Make optional since we'll get it from server properties
-}
-
-const InactivityTimeout: React.FC<InactivityTimeoutProps> = ({ timeout: propTimeout }) => {
- return (
- <div className="inactivity-timeout">
- {propTimeout ? (
- <p>Your session will expire after {propTimeout} minutes of inactivity.</p>
- ) : (
- <p>Loading inactivity timeout...</p>
- )}
- </div>
- );
-};
+import { Navigate, useLocation } from "react-router-dom";
+import { isViewOnlyUser } from "../Utils/authPolicy";
+import { useAuth } from "../hooks/useAuth";
+import DashboardLayout from "./Dashboard";
+import ViewsLayout from "./Views";
-export default InactivityTimeout;
+export default function MainLayout() {
+ const { authorizations } = useAuth();
+ const location = useLocation();
+ if (!isViewOnlyUser(authorizations)) {
+ return <DashboardLayout />;
+ }
+ if (!location.pathname.startsWith("/main/view")) {
+ return <Navigate to="/main/view" replace />;
+ }
+ return <ViewsLayout />;
+}
diff --git a/ambari-web/latest/src/layout/Views.tsx b/ambari-web/latest/src/layout/Views.tsx
new file mode 100644
index 0000000000..36f44fba1d
--- /dev/null
+++ b/ambari-web/latest/src/layout/Views.tsx
@@ -0,0 +1,80 @@
+/**
+ * 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, useState } from "react";
+import { Outlet } from "react-router-dom";
+import ViewApi from "../api/viewApi";
+import LicenseFooter from "../components/LicenseFooter";
+import NavBar from "../components/Navbar";
+
+type ViewLink = {
+ instance_name: string;
+ label?: string;
+ version: string;
+ view_name: string;
+};
+
+type ViewInstanceResponse = { ViewInstanceInfo?: Omit<ViewLink, "version" | "view_name"> };
+type ViewVersionResponse = {
+ ViewVersionInfo?: { version?: string };
+ instances?: ViewInstanceResponse[];
+};
+type ViewResponse = {
+ ViewInfo?: { view_name?: string };
+ versions?: ViewVersionResponse[];
+};
+
+function viewLinks(data: { items?: ViewResponse[] }): ViewLink[] {
+ return (data?.items || []).flatMap((item) => {
+ const viewName = item?.ViewInfo?.view_name;
+ return (item?.versions || []).flatMap((version) => (
+ (version?.instances || []).flatMap((instance) => {
+ const instanceInfo = instance.ViewInstanceInfo;
+ const versionNumber = version.ViewVersionInfo?.version;
+ return instanceInfo?.instance_name && versionNumber && viewName
+ ? [{ ...instanceInfo, version: versionNumber, view_name: viewName }]
+ : [];
+ })
+ ));
+ });
+}
+
+export default function ViewsLayout() {
+ const [views, setViews] = useState<ViewLink[]>([]);
+
+ useEffect(() => {
+ ViewApi.getInstances()
+ .then((data) => setViews(viewLinks(data)))
+ .catch(() => setViews([]));
+ }, []);
+
+ return (
+ <div className="d-flex flex-column h-100">
+ <NavBar
+ clusterControls={false}
+ homePath="/main/view"
+ viewsList={views}
+ subPath="Views"
+ />
+ <div className="h-100" style={{ paddingBottom: "80px", overflowY: "auto" }}>
+ <Outlet />
+ </div>
+ <LicenseFooter hasSidebar={false} />
+ </div>
+ );
+}
diff --git a/ambari-web/latest/src/router/RoutesList.tsx b/ambari-web/latest/src/router/RoutesList.tsx
index 0f7ddfb923..652ea41294 100644
--- a/ambari-web/latest/src/router/RoutesList.tsx
+++ b/ambari-web/latest/src/router/RoutesList.tsx
@@ -16,284 +16,218 @@
* limitations under the License.
*/
-import { RouteObject, Navigate, Outlet } from "react-router-dom";
-import ClusterCreationWizard from "../screens/ClusterWizard";
-import { Login } from "../screens/Authentication/Login.tsx";
+import { Navigate, Outlet, RouteObject } from "react-router-dom";
+import { AuthenticatedApplication, LandingRoute } from "../AppLoader";
+import { ProtectedRoute } from "../components/AuthGuard";
+import AdminRouteGuard from "../components/AdminRouteGuard";
import InstallerLayout from "../layout/Installer";
-import HostsList from "../screens/Hosts/HostsList.tsx";
-import Alerts from "../screens/Alerts/Alerts.tsx";
-import DashboardLayout from "../layout/Dashboard";
-import ServiceAutoStart from "../screens/ServiceAutoStart/index.tsx";
-import ServiceAccounts from "../screens/ServiceAccounts/index.tsx";
-import EnableKerberos from "../screens/KerberosWizard/EnableKerberos.tsx";
-import { Actions } from "../screens/Services/Actions.tsx";
-import AlertDefinitionDetails from "../screens/Alerts/AlertDefinitionDetails.tsx";
-import ServiceDashboard from "../screens/Services/ServiceDashboard.tsx";
-import { redirectToAdminView } from "../Utils/adminViewRedirect";
-import { Hosts } from "../screens/Hosts/index.tsx";
-import Spinner from "../components/Spinner";
-import ViewDetails from "../screens/Views/ViewDetails.tsx";
-import ViewsListPage from "../screens/Views/ViewsListPage";
-import { RouteTracker } from "../AppLoader.tsx";
-import StackAndVersions from "../screens/ClusterAdmin/StackAndVersions/StackAndVersions.tsx";
+import MainLayout from "../layout/Main";
+import InstallerVersionGuard from "../layout/InstallerVersionGuard";
+import ClusterCreationWizard from "../screens/ClusterWizard";
import {
ClusterCreationContext,
ClusterCreationProvider,
-} from "../screens/ClusterWizard/clusterStore/context.tsx";
-import wizardSteps from "../screens/ClusterWizard/wizardSteps.tsx";
-import ServiceLoader from "../screens/Services/ServiceLoader.tsx";
-import Dashboard from "../screens/Dashboard/Index.tsx";
-import AddWizardUrlMapping from "../screens/Services/AddWizardUrlMapping.tsx";
-import { ProtectedRoute } from "../components/AuthGuard";
-import AdminRouteGuard from "../components/AdminRouteGuard";
+} from "../screens/ClusterWizard/clusterStore/context";
+import wizardSteps from "../screens/ClusterWizard/wizardSteps";
+import { Login } from "../screens/Authentication/Login";
+import HostsList from "../screens/Hosts/HostsList";
+import { Hosts } from "../screens/Hosts";
+import Alerts from "../screens/Alerts/Alerts";
+import AlertDefinitionDetails from "../screens/Alerts/AlertDefinitionDetails";
+import ServiceAutoStart from "../screens/ServiceAutoStart";
+import ServiceAccounts from "../screens/ServiceAccounts";
+import EnableKerberos from "../screens/KerberosWizard/EnableKerberos";
+import { Actions } from "../screens/Services/Actions";
+import ServiceDashboard from "../screens/Services/ServiceDashboard";
+import ServiceLoader from "../screens/Services/ServiceLoader";
+import AddWizardUrlMapping from "../screens/Services/AddWizardUrlMapping";
+import ViewDetails from "../screens/Views/ViewDetails";
+import ViewsListPage from "../screens/Views/ViewsListPage";
+import StackAndVersions from "../screens/ClusterAdmin/StackAndVersions/StackAndVersions";
+import Dashboard from "../screens/Dashboard/Index";
+import AdminViewRedirect from "../screens/Authentication/AdminViewRedirect";
+import Experimental from "../screens/Experimental";
+import FeatureRouteGuard from "../components/FeatureRouteGuard";
+import AdminViewRouteGuard from "../components/AdminViewRouteGuard";
const RoutesList: RouteObject[] = [
{
path: "/",
- element: (
- <>
- <RouteTracker />
- <Outlet />
- </>
- ),
+ element: <Outlet />,
children: [
+ { path: "login", element: <Login /> },
+ { path: "login/local", element: <Login isLocalLogin /> },
{
- path: "installer",
- element: <InstallerLayout />,
+ element: <AuthenticatedApplication />,
children: [
+ { index: true, element: <LandingRoute /> },
{
- path: ":stepNumber",
+ path: "adminView",
element: (
- <ClusterCreationWizard
- Context={ClusterCreationContext}
- Provider={ClusterCreationProvider}
- wizardSteps={wizardSteps as any}
- />
+ <AdminViewRouteGuard>
+ <AdminViewRedirect />
+ </AdminViewRouteGuard>
),
},
- ],
- },
- {
- path: "/login",
- element: <Login />,
- },
-
- {
- path: "/main",
- element: (
- <>
- <DashboardLayout />
- </>
- ),
- children: [
{
- path: "service/add/:stepNumber",
- element: <AddWizardUrlMapping />,
+ path: "experimental",
+ element: (
+ <ProtectedRoute
+ requireAuthorization="AMBARI.MANAGE_SETTINGS"
+ redirectTo="/"
+ >
+ <Experimental />
+ </ProtectedRoute>
+ ),
},
{
- path: "dashboard",
- element: <Outlet />,
+ path: "installer",
+ element: <InstallerVersionGuard><InstallerLayout /></InstallerVersionGuard>,
children: [
{
- index: true,
- element: <Navigate to="metrics" replace />,
- },
- {
- path: ":tabName",
- element: <Dashboard />,
+ path: ":stepNumber",
+ element: (
+ <ClusterCreationWizard
+ Context={ClusterCreationContext}
+ Provider={ClusterCreationProvider}
+ wizardSteps={wizardSteps as any}
+ />
+ ),
},
],
},
{
- path: "actions",
- element: <Actions serviceName="HDFS" />,
- },
-
- {
- path: "services",
- element: <Outlet />,
+ path: "main",
+ element: <MainLayout />,
children: [
+ { path: "service/add/:stepNumber", element: <AddWizardUrlMapping /> },
{
- path: ":serviceName",
+ path: "dashboard",
element: <Outlet />,
children: [
- {
- path: ":tabName",
- element: <ServiceDashboard />,
- },
+ { index: true, element: <Navigate to="metrics" replace /> },
+ { path: ":tabName", element: <Dashboard /> },
],
},
+ { path: "actions", element: <Actions serviceName="HDFS" /> },
{
- path: "highAvailability/:componentName/enable/:stepNumber",
- element: <ServiceLoader />,
- },
- {
- path:":componentName/federation/:stepNumber",
- element: <ServiceLoader />,
+ path: "services",
+ element: <Outlet />,
+ children: [
+ {
+ path: ":serviceName",
+ element: <Outlet />,
+ children: [
+ { path: ":tabName", element: <ServiceDashboard /> },
+ ],
+ },
+ {
+ path: "highAvailability/:componentName/enable/:stepNumber",
+ element: <ServiceLoader />,
+ },
+ {
+ path: ":componentName/federation/:stepNumber",
+ element: <ServiceLoader />,
+ },
+ {
+ path: "highAvailability/:componentName/manage/:stepNumber",
+ element: <ServiceLoader />,
+ },
+ ],
},
{
- path: "highAvailability/:componentName/manage/:stepNumber",
+ path: "service/reassign/:componentName/:stepNumber",
element: <ServiceLoader />,
},
-
- ],
- },
- {
- path:"service",
- element:<Outlet/>,
- children:[{
- path:"reassign/:componentName/:stepNumber",
- element:<ServiceLoader />
- }]
- },
- {
- path: "hosts",
- element: <HostsList />,
- },
- {
- path: "hosts/component/:componentName",
- element: <HostsList />,
- },
- {
- path: "hosts/version/:versionName/:versionStatus",
- element: <HostsList />,
- },
- {
- path: "hosts/:hostname/:tab",
- element: <Hosts />,
- },
- {
- path: "host/add/:stepNumber",
- element: (
- <AddWizardUrlMapping />
- ),
- },
- {
- path: "alerts",
- element: <Alerts />,
- },
- {
- path: "alerts/:alertId",
- element: <AlertDefinitionDetails />,
- },
- {
- path: "admin",
- element: (
- <AdminRouteGuard>
- <Outlet />
- </AdminRouteGuard>
- ),
- children: [
- {
- index: true,
- element: <Navigate to="stack/services" replace />,
- },
+ { path: "hosts", element: <HostsList /> },
+ { path: "hosts/component/:componentName", element: <HostsList /> },
+ { path: "hosts/version/:versionName/:versionStatus", element: <HostsList /> },
+ { path: "hosts/:hostname/:tab", element: <Hosts /> },
+ { path: "host/add/:stepNumber", element: <AddWizardUrlMapping /> },
+ { path: "alerts", element: <Alerts /> },
+ { path: "alerts/:alertId", element: <AlertDefinitionDetails /> },
{
- path: "stack/:tabName",
+ path: "admin",
element: (
- <ProtectedRoute
- requireAuthorization="CLUSTER.VIEW_STACK_DETAILS, CLUSTER.UPGRADE_DOWNGRADE_STACK"
- redirectTo="/main/dashboard/metrics"
- >
- <StackAndVersions />
- </ProtectedRoute>
+ <AdminRouteGuard>
+ <Outlet />
+ </AdminRouteGuard>
),
+ children: [
+ { index: true, element: <Navigate to="stack/services" replace /> },
+ {
+ path: "stack/:tabName",
+ element: (
+ <ProtectedRoute
+ requireAuthorization="CLUSTER.VIEW_STACK_DETAILS, CLUSTER.UPGRADE_DOWNGRADE_STACK"
+ redirectTo="/main/dashboard/metrics"
+ >
+ <StackAndVersions />
+ </ProtectedRoute>
+ ),
+ },
+ {
+ path: "serviceAutoStart",
+ element: (
+ <FeatureRouteGuard feature="serviceAutoStart">
+ <ProtectedRoute
+ requireAuthorization="SERVICE.MANAGE_AUTO_START, CLUSTER.MANAGE_AUTO_START"
+ redirectTo="/main/dashboard/metrics"
+ >
+ <ServiceAutoStart />
+ </ProtectedRoute>
+ </FeatureRouteGuard>
+ ),
+ },
+ {
+ path: "serviceAccounts",
+ element: (
+ <ProtectedRoute
+ requireAuthorization="SERVICE.SET_SERVICE_USERS_GROUPS"
+ redirectTo="/main/dashboard/metrics"
+ >
+ <ServiceAccounts />
+ </ProtectedRoute>
+ ),
+ },
+ {
+ path: "kerberos",
+ element: (
+ <FeatureRouteGuard feature="enableToggleKerberos">
+ <ProtectedRoute
+ requireAuthorization="CLUSTER.TOGGLE_KERBEROS"
+ redirectTo="/main/dashboard/metrics"
+ >
+ <EnableKerberos />
+ </ProtectedRoute>
+ </FeatureRouteGuard>
+ ),
+ },
+ {
+ path: "kerberos/enable/:stepNumber",
+ element: (
+ <FeatureRouteGuard feature="enableToggleKerberos">
+ <ProtectedRoute
+ requireAuthorization="CLUSTER.TOGGLE_KERBEROS"
+ redirectTo="/main/dashboard/metrics"
+ >
+ <EnableKerberos />
+ </ProtectedRoute>
+ </FeatureRouteGuard>
+ ),
+ },
+ ],
},
- {
- path: "serviceAutoStart",
- element: (
- <ProtectedRoute
- requireAuthorization="SERVICE.START_STOP"
- redirectTo="/main/dashboard/metrics"
- >
- <ServiceAutoStart />
- </ProtectedRoute>
- ),
- },
- {
- path: "serviceAccounts",
- element: (
- <ProtectedRoute
- requireAuthorization="SERVICE.SET_SERVICE_USERS_GROUPS"
- redirectTo="/main/dashboard/metrics"
- >
- <ServiceAccounts />
- </ProtectedRoute>
- ),
- },
- {
- path: "kerberos",
- element: (
- <ProtectedRoute
- requireAuthorization="CLUSTER.TOGGLE_KERBEROS"
- redirectTo="/main/dashboard/metrics"
- >
- <EnableKerberos />
- </ProtectedRoute>
- ),
- },
- {
- path: "kerberos/enable/:stepNumber",
- element: (
- <ProtectedRoute
- requireAuthorization="CLUSTER.TOGGLE_KERBEROS"
- redirectTo="/main/dashboard/metrics"
- >
- <EnableKerberos />
- </ProtectedRoute>
- ),
- }
- ],
- },
- {
- path: "views/:viewName/:viewVersion/:instanceName/*",
- element: <ViewDetails />,
- },
- {
- path: "view",
- element: <ViewsListPage />,
- },
- {
- path: "view/:viewName",
- element: <Navigate to="/main/view" replace />,
- },
- ],
- },
- {
- path: "main",
- element: (
- <>
- {/* <DashboardWrapper /> */}
- <Outlet />
- </>
- ),
- children: [
- {
- path: "admin",
- element: <Outlet />,
- children: [
- {
- path: "serviceAccounts",
- element: <ServiceAccounts />,
- },
+ { path: "views/:viewName/:viewVersion/:instanceName/*", element: <ViewDetails /> },
+ { path: "view", element: <ViewsListPage /> },
+ { path: "view/:viewName", element: <Navigate to="/main/view" replace /> },
],
},
],
},
+ { path: "*", element: <Navigate to="/" replace /> },
],
},
- {
- path: "adminView",
- loader: () => {
- redirectToAdminView();
- return null;
- },
- element: <Spinner />, // This won't actually render as we'll redirect immediately
- },
- {
- path: "/login",
- element: <Login />,
- },
];
export default RoutesList;
diff --git a/ambari-web/latest/src/screens/Authentication/AdminViewRedirect.tsx b/ambari-web/latest/src/screens/Authentication/AdminViewRedirect.tsx
new file mode 100644
index 0000000000..be912d4867
--- /dev/null
+++ b/ambari-web/latest/src/screens/Authentication/AdminViewRedirect.tsx
@@ -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.
+ */
+
+import { useEffect, useState } from "react";
+import { Navigate, useLocation } from "react-router-dom";
+import Spinner from "../../components/Spinner";
+import { ServiceApi } from "../../api/serviceApi";
+
+type ServerVersionResponse = {
+ components?: Array<{
+ RootServiceComponents?: { component_version?: string };
+ }>;
+};
+
+function latestServerVersion(data: ServerVersionResponse): string | null {
+ const versions = (data?.components || [])
+ .map((item) => item.RootServiceComponents?.component_version)
+ .filter((value): value is string => Boolean(value))
+ .map((value) => value.replace(/[^\d.-]/g, ""))
+ .sort();
+ return versions.at(-1) || null;
+}
+
+export default function AdminViewRedirect() {
+ const [failed, setFailed] = useState(false);
+ const location = useLocation();
+
+ useEffect(() => {
+ async function redirect() {
+ try {
+ const data = await ServiceApi.getAmbariServerVersion();
+ const version = latestServerVersion(data);
+ if (!version) {
+ throw new Error("Ambari Server version is unavailable");
+ }
+ const page = new URLSearchParams(location.search).get("page");
+ const adminHash = page ? `#/${encodeURIComponent(page)}` : "#/";
+ window.location.assign(
+ `/views/ADMIN_VIEW/${encodeURIComponent(version)}/INSTANCE/${adminHash}`,
+ );
+ } catch {
+ setFailed(true);
+ }
+ }
+ void redirect();
+ }, [location.search]);
+
+ return failed ? <Navigate to="/main/view" replace /> : <Spinner />;
+}
diff --git a/ambari-web/latest/src/screens/Authentication/Login.tsx b/ambari-web/latest/src/screens/Authentication/Login.tsx
index ef161f83b4..12ea549058 100644
--- a/ambari-web/latest/src/screens/Authentication/Login.tsx
+++ b/ambari-web/latest/src/screens/Authentication/Login.tsx
@@ -27,24 +27,44 @@ import {
Image,
} from "react-bootstrap";
import React, { useEffect, useState } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
import AmbariLogo from "../../assets/img/ambari-logo.png";
import "../../../custom.scss";
-import { LocalStorageOps } from "../../Utils/LocalStorageOps";
import { useUserContext } from "../../store/UserContext";
+import { LOCAL_LOGIN_PATH } from "../../Utils/authNavigation";
+import LoginMessageModal from "./LoginMessageModal";
-export const Login = () => {
+export const Login = ({ isLocalLogin = false }: { isLocalLogin?: boolean }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [errorMessage, setErrorMessage] = useState("");
// Use the UserContext for authentication
- const { login, isLoading, loginError } = useUserContext();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const {
+ login,
+ isAuthenticated,
+ isLoading,
+ loginError,
+ loginMessage,
+ } = useUserContext();
useEffect(()=>{
if (loginError) {
setErrorMessage(loginError);
}
- },[loginError])
+ },[loginError]);
+
+ useEffect(() => {
+ document.title = "Ambari";
+ }, []);
+
+ useEffect(() => {
+ if (isAuthenticated && !loginMessage) {
+ navigate("/", { replace: true });
+ }
+ }, [isAuthenticated, loginMessage, navigate]);
const handleSignIn = async (event: React.FormEvent) => {
event.preventDefault();
@@ -53,79 +73,16 @@ export const Login = () => {
try {
// Use the UserContext login method
const success = await login(username, password);
- console.log("Login success:", success);
- if (success) {
- // Redirect after successful login
- const hashExclusiveUrl = LocalStorageOps.getItem(
- "lastVisitedURL"
- )?.replace("/#", "");
-
- if (hashExclusiveUrl) {
- window?.location?.replace("/#" + hashExclusiveUrl);
- } else {
- window.location.replace("/#");
- }
- window.location.reload();
- } else {
+ if (!success) {
setErrorMessage(loginError || "Login failed. Please check your credentials.");
}
- } catch (error: any) {
+ } catch {
setErrorMessage(loginError || "An unexpected error occurred during login.");
}
};
- // const handleLoginSuccess = async (params: LoginParams) => {
- // try {
- // const response = await LoginApi.handleSuccessfulLogin(params);
- // await afterLoginSuccess(response.data, params as any);
- // setErrorMessage("");
- // } catch (error) {
- // handleLoginError(error);
- // }
- // };
- // const afterLoginSuccess = async (data: dataLoginType, params: LoginDataParamsType) => {
- // try {
- // await LoginApi.afterLoginSuccessCallback(data);
- // // if (afterLoginSuccessCallbackResponse.status !== 200) {
- // // throw new Error("after login success callback failed");
- // // }
- // await setClusterData(params);
- // } catch (error) {
- // await setClusterData(params);
- // handleLoginError(error);
- // }
- // }
- // const setClusterData = async (params: LoginDataParamsType) => {
- // try {
- // const response = await LoginApi.setClusterDataCallback(params)
- // if (!response.data.items) {
- // throw new Error("Failed to set cluster data");
- // }
- // await loadAuthorizations(params as any);
- // setErrorMessage("");
- // } catch (error) {
- // handleLoginError(error);
- // }
- // }
- // const loadAuthorizations = async (params: LoginParams) => {
- // try {
- // const response = await LoginApi.loadAuthorizationsCallback(params);
- // const authorizationIds = response.data.items.map(
- // (item: any) => item.AuthorizationInfo.authorization_id
- // );
-
- // let ambariData = JSON.parse(Utility.decryptData(db.getItem("ambari") || "") || "");
- // console.log("ambariData", ambariData)
- // ambariData.app.auth = authorizationIds;
- // db.setItem("ambari", Utility.encryptData(JSON.stringify(ambariData)));
-
- // // After successful login and authorization, redirect to metrics dashboard
- // window.location.href = '/#/main/dashboard/metrics';
- // } catch (error) {
- // handleLoginError(error);
- // }
- // }
return (
<>
+ <LoginMessageModal />
<div
className="w-100 d-flex align-items-center py-2 px-4"
style={{ background: "#313d54" }}
@@ -143,6 +100,11 @@ export const Login = () => {
<Card.Title className="text-start">
<h2>Sign in</h2>
</Card.Title>
+ {isLocalLogin && new URLSearchParams(location.search).has("redirectError") ? (
+ <Alert variant="warning" className="my-3">
+ External authentication could not be completed. Use local Ambari credentials.
+ </Alert>
+ ) : null}
{errorMessage ? (
<Alert variant="danger" className="my-3">
{errorMessage}
@@ -173,6 +135,11 @@ export const Login = () => {
>
{isLoading ? "Signing In..." : "SIGN IN"}
</Button>
+ {!isLocalLogin ? (
+ <a className="d-block mt-3" href={`#${LOCAL_LOGIN_PATH}`}>
+ Sign in with local credentials
+ </a>
+ ) : null}
</Form.Group>
</Form>
</Card>
diff --git a/ambari-web/latest/src/components/UserSettingsModal.tsx b/ambari-web/latest/src/screens/Authentication/LoginMessageModal.tsx
similarity index 62%
copy from ambari-web/latest/src/components/UserSettingsModal.tsx
copy to ambari-web/latest/src/screens/Authentication/LoginMessageModal.tsx
index 4d574303a2..97df32ed10 100644
--- a/ambari-web/latest/src/components/UserSettingsModal.tsx
+++ b/ambari-web/latest/src/screens/Authentication/LoginMessageModal.tsx
@@ -16,29 +16,27 @@
* limitations under the License.
*/
-import Modal from "./Modal";
+import Modal from "../../components/Modal";
+import { useAuth } from "../../hooks/useAuth";
-type UserSettingsModalProps = {
- isOpen: boolean;
- onClose: () => void;
-};
+export default function LoginMessageModal() {
+ const { loginMessage, acknowledgeLoginMessage } = useAuth();
+ if (!loginMessage) {
+ return null;
+ }
-const UserSettingsModal = ({ isOpen, onClose }: UserSettingsModalProps) => {
return (
<Modal
- isOpen={isOpen}
- onClose={onClose}
- modalTitle="User Settings"
- modalBody={<div/>}
- successCallback={() => {}}
+ isOpen
+ onClose={acknowledgeLoginMessage}
+ modalTitle="Login Message"
+ modalBody={<div style={{ whiteSpace: "pre-wrap" }}>{loginMessage.text}</div>}
+ successCallback={acknowledgeLoginMessage}
options={{
modalSize: "modal-lg",
- okButtonText: "SAVE",
- cancelButtonText: "CANCEL",
- okButtonVariant: "success"
+ cancelableViaBtn: false,
+ okButtonText: loginMessage.buttonText,
}}
/>
- )
+ );
}
-
-export default UserSettingsModal;
\ No newline at end of file
diff --git a/ambari-web/latest/src/screens/Experimental/index.tsx b/ambari-web/latest/src/screens/Experimental/index.tsx
new file mode 100644
index 0000000000..f554f65a9b
--- /dev/null
+++ b/ambari-web/latest/src/screens/Experimental/index.tsx
@@ -0,0 +1,139 @@
+/**
+ * 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 { useContext, useState } from "react";
+import { Alert, Button, Form, Table } from "react-bootstrap";
+import { useNavigate } from "react-router-dom";
+import ClusterApi from "../../api/clusterApi";
+import Modal from "../../components/Modal";
+import { useAuth } from "../../hooks/useAuth";
+import { db } from "../../Utils/db";
+import { persistedPayload } from "../../Utils/persistedSettings";
+import { AppContext } from "../../store/context";
+import { responseErrorMessage } from "../../Utils/httpError";
+
+export default function Experimental() {
+ const { user, hasAuthorization } = useAuth();
+ const {
+ isNonWizardUser,
+ setSupports: setSharedSupports,
+ supports: sharedSupports,
+ } = useContext(AppContext);
+ const navigate = useNavigate();
+ const [supports, setSupports] = useState(sharedSupports);
+ const [saving, setSaving] = useState(false);
+ const [error, setError] = useState("");
+ const [showReset, setShowReset] = useState(false);
+ const key = `user-pref-${user?.user_name || ""}-supports`;
+ const canReset = hasAuthorization("CLUSTER.MANAGE_USER_PERSISTED_DATA");
+
+ const save = async () => {
+ setSaving(true);
+ setError("");
+ try {
+ await ClusterApi.postPersistData(persistedPayload({ [key]: supports }));
+ setSharedSupports(supports);
+ navigate("/", { replace: true });
+ } catch (saveError) {
+ setError(responseErrorMessage(saveError, "Unable to save experimental settings."));
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const reset = async () => {
+ setSaving(true);
+ setError("");
+ try {
+ await ClusterApi.postPersistData(persistedPayload({ "wizard-data": {} }));
+ db.cleanUp();
+ localStorage.removeItem("lastVisitedURL");
+ window.location.hash = "/";
+ window.location.reload();
+ } catch (resetError) {
+ setError(responseErrorMessage(resetError, "Unable to reset UI state."));
+ setShowReset(false);
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ return (
+ <main className="container py-4">
+ <Alert variant="danger">
+ <h2>Ambari's Experimental Functionality</h2>
+ <strong>Changes made here are neither tested nor supported by Ambari.</strong>
+ <p className="mb-0 mt-2">
+ Experimental functionality can change or be removed without notice.
+ </p>
+ </Alert>
+ {error ? <Alert variant="danger">{error}</Alert> : null}
+ <Table hover responsive>
+ <thead>
+ <tr><th>Experimental Functionality</th><th>Enabled?</th></tr>
+ </thead>
+ <tbody>
+ {Object.entries(supports).map(([name, enabled]) => (
+ <tr key={name} className={enabled ? "table-active" : ""}>
+ <td>{name}</td>
+ <td>
+ <Form.Check
+ type="checkbox"
+ checked={enabled}
+ onChange={(event) => setSupports((current) => ({
+ ...current,
+ [name]: event.target.checked,
+ }))}
+ aria-label={`Enable ${name}`}
+ />
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </Table>
+ <div className="d-flex justify-content-between">
+ <div>
+ {canReset ? (
+ <Button
+ variant="danger"
+ onClick={() => setShowReset(true)}
+ disabled={saving || isNonWizardUser}
+ title={isNonWizardUser ? "Another user owns the active wizard." : undefined}
+ >
+ Reset UI States
+ </Button>
+ ) : null}
+ </div>
+ <div className="d-flex gap-2">
+ <Button variant="secondary" onClick={() => navigate("/", { replace: true })}>Cancel</Button>
+ <Button variant="primary" onClick={() => void save()} disabled={saving}>Save</Button>
+ </div>
+ </div>
+ {showReset ? (
+ <Modal
+ isOpen
+ onClose={() => setShowReset(false)}
+ modalTitle="Reset UI States"
+ modalBody="Reset UI state locally and on the server?"
+ successCallback={() => void reset()}
+ options={{ okButtonText: "Yes", okButtonVariant: "danger" }}
+ />
+ ) : null}
+ </main>
+ );
+}
diff --git a/ambari-web/latest/src/store/UserContext.test.tsx b/ambari-web/latest/src/store/UserContext.test.tsx
new file mode 100644
index 0000000000..419a0cbf74
--- /dev/null
+++ b/ambari-web/latest/src/store/UserContext.test.tsx
@@ -0,0 +1,125 @@
+/**
+ * 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 { act, render, waitFor } from "@testing-library/react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { UserContextType } from "../types/auth";
+
+const mocks = vi.hoisted(() => ({
+ authenticate: vi.fn(),
+ handleSuccessfulLogin: vi.fn(),
+ loadAuthorizationsCallback: vi.fn(),
+ loadLoginMessage: vi.fn(),
+ logout: vi.fn(),
+ probeSession: vi.fn(),
+}));
+vi.mock("../api/loginApi", () => ({ default: mocks }));
+
+import { UserProvider, useUserContext } from "./UserContext";
+import { db } from "../Utils/db";
+
+let currentContext: UserContextType;
+
+function ContextReader() {
+ currentContext = useUserContext();
+ return null;
+}
+
+const user = {
+ user_name: "operator/name",
+ user_id: 1,
+ user_type: "LOCAL",
+ admin: false,
+ operator: true,
+ cluster_user: false,
+ active: true,
+ ldap_user: false,
+ principal_type: "USER",
+};
+
+describe("user session lifecycle", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ localStorage.clear();
+ sessionStorage.clear();
+ mocks.probeSession.mockRejectedValue({ response: { status: 401 } });
+ mocks.loadLoginMessage.mockResolvedValue(null);
+ });
+
+ async function renderProvider() {
+ render(<UserProvider><ContextReader /></UserProvider>);
+ await waitFor(() => expect(currentContext.isLoading).toBe(false));
+ }
+
+ it("does not establish a session when authorization loading fails", async () => {
+ db.set("Installer", "currentStep", 4);
+ mocks.authenticate.mockResolvedValue({});
+ mocks.handleSuccessfulLogin.mockResolvedValue({
+ data: { Users: user, privileges: [] },
+ });
+ mocks.loadAuthorizationsCallback.mockRejectedValue(new Error("unavailable"));
+ await renderProvider();
+
+ let result = true;
+ await act(async () => {
+ result = await currentContext.login("operator/name", "secret");
+ });
+
+ expect(result).toBe(false);
+ expect(currentContext.isAuthenticated).toBe(false);
+ expect(db.get("Installer", "currentStep")).toBe(4);
+ });
+
+ it("recovers a server-authenticated session from the User header", async () => {
+ mocks.probeSession.mockResolvedValue({ headers: { user: "operator/name" } });
+ mocks.handleSuccessfulLogin.mockResolvedValue({
+ data: { Users: user, privileges: [] },
+ });
+ mocks.loadAuthorizationsCallback.mockResolvedValue({ data: { items: [] } });
+ await renderProvider();
+
+ expect(currentContext.isAuthenticated).toBe(true);
+ expect(currentContext.user?.user_name).toBe("operator/name");
+ expect(mocks.handleSuccessfulLogin).toHaveBeenCalledWith({
+ usr: "",
+ loginName: "operator/name",
+ });
+ });
+
+ it("completes client cleanup when server logout fails", async () => {
+ mocks.authenticate.mockResolvedValue({});
+ mocks.handleSuccessfulLogin.mockResolvedValue({
+ data: { Users: user, privileges: [] },
+ });
+ mocks.loadAuthorizationsCallback.mockResolvedValue({ data: { items: [] } });
+ mocks.logout.mockRejectedValue(new Error("server unavailable"));
+ await renderProvider();
+
+ await act(async () => {
+ expect(await currentContext.login("operator/name", "secret")).toBe(true);
+ });
+ db.set("Installer", "currentStep", 4);
+ await act(async () => {
+ await currentContext.logout();
+ });
+
+ expect(currentContext.isAuthenticated).toBe(false);
+ expect(currentContext.user).toBeNull();
+ expect(db.get("Installer", "currentStep")).toBeUndefined();
+ });
+});
diff --git a/ambari-web/latest/src/store/UserContext.tsx b/ambari-web/latest/src/store/UserContext.tsx
index dbff61a613..06ba15d5c1 100644
--- a/ambari-web/latest/src/store/UserContext.tsx
+++ b/ambari-web/latest/src/store/UserContext.tsx
@@ -16,58 +16,73 @@
* limitations under the License.
*/
-import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
-import { User, UserContextType, Authorization, Privilege} from '../types/auth';
-import LoginApi from '../api/loginApi';
-import { db } from '../Utils/db';
-import { isString } from 'lodash';
-import { AppContext } from './context';
+import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
+import { isString } from "lodash";
+import LoginApi, { LoginMessage } from "../api/loginApi";
+import { db } from "../Utils/db";
+import {
+ resetExternalRedirectCount,
+ SESSION_EXPIRED_EVENT,
+} from "../Utils/authNavigation";
+import { Authorization, Privilege, User, UserContextType } from "../types/auth";
const UserContext = createContext<UserContextType | undefined>(undefined);
export const useUserContext = () => {
const context = useContext(UserContext);
if (!context) {
- throw new Error('useUserContext must be used within a UserProvider');
+ throw new Error("useUserContext must be used within a UserProvider");
}
return context;
};
-interface UserProviderProps {
- children: React.ReactNode;
+function storedLoginName(): string | null {
+ const value = db.getItem("ambari");
+ if (!value) {
+ return null;
+ }
+
+ try {
+ let parsed: any = JSON.parse(value);
+ if (isString(parsed)) {
+ parsed = JSON.parse(parsed);
+ }
+ return parsed?.app?.loginName ? decodeURIComponent(parsed.app.loginName) : null;
+ } catch {
+ return null;
+ }
}
-export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
+export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [authorizations, setAuthorizations] = useState<Authorization[]>([]);
const [privileges, setPrivileges] = useState<Privilege[]>([]);
const [clusterPrivileges, setClusterPrivileges] = useState<Record<string, string[]>>({});
- const [viewPrivileges, setViewPrivileges] = useState<Record<string, { privileges: string[]; version: string; view_name: string }>>({});
+ const [viewPrivileges, setViewPrivileges] = useState<Record<string, {
+ privileges: string[];
+ version: string;
+ view_name: string;
+ }>>({});
const [loginError, setLoginError] = useState<string | null>(null);
-
- // Access AppContext for upgrade state - this will be available after AppProvider wraps UserProvider
- const appContext = useContext(AppContext);
- // Parse privileges into cluster and view privileges
- const parsePrivileges = useCallback((privilegesList: Privilege[]) => {
+ const [loginMessage, setLoginMessage] = useState<LoginMessage | null>(null);
+ const [sessionError, setSessionError] = useState<string | null>(null);
+
+ const parsePrivileges = useCallback((items: Privilege[]) => {
const clusters: Record<string, string[]> = {};
const views: Record<string, { privileges: string[]; version: string; view_name: string }> = {};
- privilegesList.forEach((privilege) => {
- if (privilege.type === 'CLUSTER' && privilege.cluster_name) {
- if (!clusters[privilege.cluster_name]) {
- clusters[privilege.cluster_name] = [];
- }
+ items.forEach((privilege) => {
+ if (privilege.type === "CLUSTER" && privilege.cluster_name) {
+ clusters[privilege.cluster_name] ??= [];
clusters[privilege.cluster_name].push(privilege.permission_label);
- } else if (privilege.type === 'VIEW' && privilege.instance_name) {
- if (!views[privilege.instance_name]) {
- views[privilege.instance_name] = {
- privileges: [],
- version: privilege.version || '',
- view_name: privilege.view_name || ''
- };
- }
+ } else if (privilege.type === "VIEW" && privilege.instance_name) {
+ views[privilege.instance_name] ??= {
+ privileges: [],
+ version: privilege.version || "",
+ view_name: privilege.view_name || "",
+ };
views[privilege.instance_name].privileges.push(privilege.permission_label);
}
});
@@ -76,299 +91,186 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
setViewPrivileges(views);
}, []);
- // Helper methods - implementing Ember.js App.havePermissions and App.isAuthorized logic
- const havePermissions = useCallback((authRoles: string): boolean => {
- if (!authorizations.length) {
- return false;
- }
-
- const authRolesList = authRoles.split(',').map(role => role.trim());
-
- // When Upgrade running(not suspended) only operations related to upgrade should be allowed
- // This matches the Ember.js logic in ui/app/app.js
- const upgradeState = appContext?.upgradeState || 'NOT_REQUIRED';
- const upgradeSuspended = appContext?.upgradeSuspend || false;
-
- // Check if upgrade is blocking operations
- if ((!upgradeSuspended &&
- !authRolesList.includes('CLUSTER.UPGRADE_DOWNGRADE_STACK') &&
- !authRolesList.includes('CLUSTER.MANAGE_USER_PERSISTED_DATA')) &&
- // TODO: Add supports.opsDuringRollingUpgrade check when available
- !['NOT_REQUIRED', 'COMPLETED'].includes(upgradeState)) {
- return false;
- }
-
- return authRolesList.some(auth =>
- authorizations.some(authorization => authorization.authorization_id === auth)
- );
- }, [authorizations, appContext?.upgradeState, appContext?.upgradeSuspend]);
+ const clearSession = useCallback(() => {
+ setUser(null);
+ setIsAuthenticated(false);
+ setAuthorizations([]);
+ setPrivileges([]);
+ setClusterPrivileges({});
+ setViewPrivileges({});
+ setLoginMessage(null);
+ db.clearSession();
+ }, []);
- const hasAuthorization = useCallback((authId: string): boolean => {
- // This implements App.isAuthorized logic: havePermissions + wizard check
- // For now, we'll use havePermissions. Wizard check can be added later when needed
-
- // Special case: Cluster operators should have HOST.ADD_DELETE_COMPONENTS permission
- // This matches Ember.js behavior where cluster operators can manage components
- if (authId === 'HOST.ADD_DELETE_COMPONENTS') {
- // Check if user has explicit authorization OR is a cluster operator
- return havePermissions(authId) || havePermissions('CLUSTER.ADMINISTRATOR');
+ const loadSession = useCallback(async (loginName: string): Promise<User> => {
+ const [userResponse, authorizationResponse, message] = await Promise.all([
+ LoginApi.handleSuccessfulLogin({ usr: "", loginName }),
+ LoginApi.loadAuthorizationsCallback({ usr: "", loginName }),
+ LoginApi.loadLoginMessage(),
+ ]);
+ const userData = userResponse.data.Users as User | undefined;
+ if (!userData) {
+ throw new Error("Ambari did not return the authenticated user");
}
-
- return havePermissions(authId);
- }, [havePermissions]);
- const hasPrivilege = useCallback((permissionName: string, clusterName?: string): boolean => {
- if (clusterName) {
- return clusterPrivileges[clusterName]?.includes(permissionName) || false;
- }
- // Check across all clusters if no specific cluster is provided
- return Object.values(clusterPrivileges).some(privs => privs.includes(permissionName));
- }, [clusterPrivileges]);
-
- const isAdmin = useCallback((): boolean => {
- const hasAmbariAdmin = privileges.some(privilege =>
- privilege.permission_name === 'AMBARI.ADMINISTRATOR'
+ const mappedPrivileges = (userResponse.data.privileges || []).map(
+ (item: any) => item.PrivilegeInfo,
);
-
- if (hasAmbariAdmin) {
- return true;
- }
-
- // Check if user has CLUSTER.ADMINISTRATOR permission for any cluster
- return hasAuthorization('CLUSTER.ADMINISTRATOR');
- }, [privileges, hasAuthorization]);
-
- const isOperator = useCallback((): boolean => {
- // Based on Ember.js router.js loginGetClustersSuccessCallback logic:
- // isOperator is set to true when user has CLUSTER.ADMINISTRATOR permission
- // This matches the exact logic in router.js lines 670-675
- return hasAuthorization('CLUSTER.ADMINISTRATOR');
- }, [hasAuthorization]);
-
- const isClusterUser = useCallback((): boolean => {
- // Based on Ember.js router.js loginGetClustersSuccessCallback logic:
- // isClusterUser is set to false when user has CLUSTER.ADMINISTRATOR permission
- // This matches the exact logic in router.js lines 670-675
- return !hasAuthorization('CLUSTER.ADMINISTRATOR');
- }, [hasAuthorization]);
-
- // Additional role helper - isClusterOperator (same as isOperator but more explicit naming)
- const isClusterOperator = useCallback((): boolean => {
- // This follows the users_mapper.js logic: user has CLUSTER.ADMINISTRATOR but not AMBARI.ADMINISTRATOR
- const hasAmbariAdmin = privileges.some(privilege =>
- privilege.permission_name === 'AMBARI.ADMINISTRATOR'
+ const mappedAuthorizations = (authorizationResponse.data.items || []).map(
+ (item: any) => item.AuthorizationInfo,
);
- return hasAuthorization('CLUSTER.ADMINISTRATOR') && !hasAmbariAdmin;
- }, [hasAuthorization, privileges]);
- // Load user authorizations
- const loadAuthorizations = useCallback(async (loginName: string): Promise<Authorization[]> => {
- try {
- const response = await LoginApi.loadAuthorizationsCallback({
- usr: '',
- loginName: encodeURIComponent(loginName)
- });
-
- const authList = response.data.items.map((item: any) => item.AuthorizationInfo);
- setAuthorizations(authList);
-
- // Store in database similar to Ember implementation
- const authIds = authList.map((auth: Authorization) => auth.authorization_id);
- db.set('app', 'auth', authIds);
-
- return authList;
- } catch (error) {
- console.error('Failed to load authorizations:', error);
- return [];
- }
- }, []);
-
- // Load user data and privileges
- const loadUserData = useCallback(async (loginName: string): Promise<User | null> => {
- try {
- const response = await LoginApi.handleSuccessfulLogin({
- usr: '',
- loginName: encodeURIComponent(loginName)
- });
-
- const userData = response.data.Users;
- setUser(userData);
-
- // Load privileges if they exist in the response
- if (response.data.privileges) {
- // Map the privileges from the API response structure
- const mappedPrivileges = response.data.privileges.map((item: any) => item.PrivilegeInfo);
- setPrivileges(mappedPrivileges);
- parsePrivileges(mappedPrivileges);
- }
-
- return userData;
- } catch (error) {
- console.error('Failed to load user data:', error);
- return null;
- }
+ setUser(userData);
+ setPrivileges(mappedPrivileges);
+ setAuthorizations(mappedAuthorizations);
+ parsePrivileges(mappedPrivileges);
+ const authorizationIds = mappedAuthorizations.map(
+ (authorization: Authorization) => authorization.authorization_id,
+ );
+ db.setSession(userData.user_name, userData, authorizationIds);
+ setLoginMessage(message);
+ setIsAuthenticated(true);
+ resetExternalRedirectCount();
+ return userData;
}, [parsePrivileges]);
- // Initialize user from stored data
- const initializeUser = useCallback(async (): Promise<boolean> => {
+ const initializeUser = useCallback(async () => {
+ setIsLoading(true);
+ setSessionError(null);
try {
- setIsLoading(true);
-
- const ambariLocalData = db.getItem('ambari');
- if (!ambariLocalData) {
- setIsLoading(false);
- return false;
- }
-
- let parsedData: any = {};
- try {
- parsedData = JSON.parse(ambariLocalData);
- if (isString(parsedData)) {
- parsedData = JSON.parse(parsedData);
- }
- } catch (err) {
- console.error('Error parsing ambari data:', err);
- setIsLoading(false);
- return false;
- }
-
- const loginName = parsedData?.app?.loginName;
+ const response = await LoginApi.probeSession();
+ const responseUser = response.headers?.user as string | undefined;
+ const loginName = responseUser || storedLoginName();
if (!loginName) {
- setIsLoading(false);
- return false;
+ clearSession();
+ return;
}
- // Load user data and authorizations
- const [userData] = await Promise.all([
- loadUserData(decodeURIComponent(loginName)),
- loadAuthorizations(decodeURIComponent(loginName))
- ]);
-
- if (userData) {
- setIsAuthenticated(true);
- setIsLoading(false);
- return true;
+ await loadSession(loginName);
+ } catch (error: any) {
+ clearSession();
+ const status = error?.response?.status;
+ if (status !== 401 && status !== 403) {
+ setSessionError(
+ error?.response?.data?.message || "Ambari Server could not validate the current session.",
+ );
}
-
- setIsLoading(false);
- return false;
- } catch (error) {
- console.error('Failed to initialize user:', error);
+ } finally {
setIsLoading(false);
- return false;
}
- }, [loadUserData, loadAuthorizations]);
+ }, [clearSession, loadSession]);
- // Login method
const login = useCallback(async (username: string, password: string): Promise<boolean> => {
+ setIsLoading(true);
+ setLoginError(null);
try {
- setIsLoading(true);
-
- // Authenticate user
await LoginApi.authenticate(username, password);
-
- // Store initial data
- const initialAmbariLsData = {
- app: {
- loginName: encodeURIComponent(username),
- authenticated: true
- }
- };
-
- db.setItem('ambari', JSON.stringify(initialAmbariLsData));
-
- // Load user data and authorizations
- const [userData] = await Promise.all([
- loadUserData(username),
- loadAuthorizations(username)
- ]);
-
- if (userData) {
- // Update stored data with user info
- const updatedData = {
- app: {
- loginName: encodeURIComponent(username),
- authenticated: true,
- user: userData
- }
- };
- db.setItem('ambari', JSON.stringify(updatedData));
-
- setIsAuthenticated(true);
- setIsLoading(false);
- setLoginError(null);
- return true;
+ await loadSession(username);
+ return true;
+ } catch (error: any) {
+ clearSession();
+ const status = error?.response?.status;
+ if (status === 403) {
+ setLoginError(error?.response?.data?.message || "Invalid username or password.");
+ } else if (status === 500) {
+ setLoginError(error?.response?.data?.message || "Ambari Server could not complete the login request.");
+ } else {
+ setLoginError(error?.response?.data?.message || "Unable to sign in to Ambari.");
}
- setIsLoading(false);
return false;
- } catch (error:any) {
- setLoginError(error?.response?.data?.message || 'Login failed');
+ } finally {
setIsLoading(false);
- return false;
}
- }, [loadUserData, loadAuthorizations]);
+ }, [clearSession, loadSession]);
+
+ const logout = useCallback(async () => {
+ clearSession();
+ db.cleanUp();
+ sessionStorage.clear();
+ localStorage.removeItem("lastVisitedURL");
+ void LoginApi.logout().catch(() => {
+ // Client cleanup and navigation must not depend on server logoff success.
+ });
+ }, [clearSession]);
- // Logout method
- const logout = useCallback(async (): Promise<void> => {
- try {
- await LoginApi.logout();
- } catch (error) {
- console.error('Logout error:', error);
- } finally {
- // Clear all user data
- setUser(null);
- setIsAuthenticated(false);
- setAuthorizations([]);
- setPrivileges([]);
- setClusterPrivileges({});
- setViewPrivileges({});
- db.cleanUp();
+ const refreshUserData = useCallback(async () => {
+ if (user?.user_name) {
+ await loadSession(user.user_name);
}
- }, []);
+ }, [loadSession, user]);
+
+ const havePermissions = useCallback((authorizationIds: string): boolean => {
+ const requested = authorizationIds.split(",").map((value) => value.trim());
+ return requested.some((id) => authorizations.some(
+ (authorization) => authorization.authorization_id === id,
+ ));
+ }, [authorizations]);
+
+ const hasAuthorization = useCallback((authorizationId: string): boolean => {
+ if (authorizationId === "HOST.ADD_DELETE_COMPONENTS") {
+ return havePermissions(authorizationId) || havePermissions("CLUSTER.ADMINISTRATOR");
+ }
+ return havePermissions(authorizationId);
+ }, [havePermissions]);
- // Refresh user data
- const refreshUserData = useCallback(async (): Promise<void> => {
- if (user?.user_name) {
- await Promise.all([
- loadUserData(user.user_name),
- loadAuthorizations(user.user_name)
- ]);
+ const hasPrivilege = useCallback((permissionName: string, clusterName?: string): boolean => {
+ if (clusterName) {
+ return clusterPrivileges[clusterName]?.includes(permissionName) || false;
}
- }, [user, loadUserData, loadAuthorizations]);
+ return Object.values(clusterPrivileges).some((items) => items.includes(permissionName));
+ }, [clusterPrivileges]);
- // Initialize on mount
- useEffect(() => {
- initializeUser();
- }, [initializeUser]);
+ const isAdmin = useCallback(() => privileges.some(
+ (privilege) => privilege.permission_name === "AMBARI.ADMINISTRATOR",
+ ) || hasAuthorization("CLUSTER.ADMINISTRATOR"), [hasAuthorization, privileges]);
+ const isOperator = useCallback(
+ () => hasAuthorization("CLUSTER.ADMINISTRATOR"),
+ [hasAuthorization],
+ );
+ const isClusterUser = useCallback(() => !isOperator(), [isOperator]);
+ const isClusterOperator = useCallback(() => isOperator() && !privileges.some(
+ (privilege) => privilege.permission_name === "AMBARI.ADMINISTRATOR",
+ ), [isOperator, privileges]);
- // Update parsed privileges when privileges change
useEffect(() => {
- parsePrivileges(privileges);
- }, [privileges, parsePrivileges]);
+ const expireSession = () => {
+ clearSession();
+ setSessionError(null);
+ setIsLoading(false);
+ };
+ window.addEventListener(SESSION_EXPIRED_EVENT, expireSession);
+ return () => window.removeEventListener(SESSION_EXPIRED_EVENT, expireSession);
+ }, [clearSession]);
- const contextValue: UserContextType = {
- user,
- isAuthenticated,
- isLoading,
- authorizations,
- privileges,
- clusterPrivileges,
- viewPrivileges,
- havePermissions,
- hasAuthorization,
- hasPrivilege,
- isAdmin,
- isOperator,
- isClusterUser,
- isClusterOperator,
- login,
- logout,
- refreshUserData,
- loginError
- };
+ useEffect(() => {
+ void initializeUser();
+ }, [initializeUser]);
return (
- <UserContext.Provider value={contextValue}>
+ <UserContext.Provider value={{
+ user,
+ isAuthenticated,
+ isLoading,
+ sessionError,
+ authorizations,
+ privileges,
+ clusterPrivileges,
+ viewPrivileges,
+ havePermissions,
+ hasAuthorization,
+ hasPrivilege,
+ isAdmin,
+ isOperator,
+ isClusterUser,
+ isClusterOperator,
+ login,
+ logout,
+ refreshUserData,
+ retrySession: initializeUser,
+ loginError,
+ loginMessage,
+ acknowledgeLoginMessage: () => setLoginMessage(null),
+ }}>
{children}
</UserContext.Provider>
);
diff --git a/ambari-web/latest/src/store/context.tsx b/ambari-web/latest/src/store/context.tsx
index 12a88acd9b..e3349678b8 100644
--- a/ambari-web/latest/src/store/context.tsx
+++ b/ambari-web/latest/src/store/context.tsx
@@ -29,12 +29,12 @@ import { Client } from "@stomp/stompjs";
import ClusterApi from "../api/clusterApi";
import { ChooseServicesApi } from "../api/chooseServicesApi";
import { ServicesApi } from "../api/servicesApi";
-import { get, isEmpty, isString, isUndefined, map, set } from "lodash";
+import { get, isEmpty, isUndefined, 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 { mapStackConfigProperties } from "../Utils/Utility";
import useAuth from "../hooks/useAuth";
+import { parsePersistedValue, persistedPayload } from "../Utils/persistedSettings";
+import { DEFAULT_SUPPORTS } from "../constants";
// import {LocalStorageOps} from "../Utils/LocalStorageOps";
interface AppContextProps {
@@ -47,11 +47,16 @@ interface AppContextProps {
services: any[];
cluster: any;
isAppLoaded: boolean;
+ initializationError: string | null;
+ retryInitialization: () => void;
serviceComponentInfo: any;
isKerberosEnabled: boolean;
stackConfigurations: any;
allHostNames: string[];
ambariProperties: any;
+ ambariServerVersion?: string;
+ supports: Record<string, boolean>;
+ setSupports: (supports: Record<string, boolean>) => void;
upgradeState: string;
setUpgradeState: (state: string) => void;
upgradeDirection: string;
@@ -73,6 +78,7 @@ interface AppContextProps {
clusterState: any;
userBgPreferences: boolean;
setUserBgPreferences: (value: boolean) => void;
+ syncUserBgPreferences: (value: boolean) => void;
// Background Operations - persistent cache like Ember.js singleton
backgroundOperations: any[];
setBackgroundOperations: (operations: any[]) => void;
@@ -87,6 +93,7 @@ interface AppContextProps {
upgradeSuspended: boolean;
upgradeIsRunning: boolean;
wizardIsNotFinished: boolean;
+ isNonWizardUser: boolean;
isClusterInstalled?: boolean;
}
@@ -100,11 +107,16 @@ export const AppContext = createContext<AppContextProps>({
services: [],
cluster: {},
isAppLoaded: false,
+ initializationError: null,
+ retryInitialization: () => {},
serviceComponentInfo: {},
isKerberosEnabled: false,
stackConfigurations: [],
allHostNames: [],
ambariProperties: {},
+ ambariServerVersion: "",
+ supports: DEFAULT_SUPPORTS,
+ setSupports: () => {},
upgradeState: "",
setUpgradeState: () => {},
upgradeDirection: "",
@@ -123,6 +135,7 @@ export const AppContext = createContext<AppContextProps>({
clusterState: {},
userBgPreferences: false,
setUserBgPreferences: () => {},
+ syncUserBgPreferences: () => {},
// Background Operations defaults
backgroundOperations: [],
setBackgroundOperations: () => {},
@@ -139,6 +152,7 @@ export const AppContext = createContext<AppContextProps>({
upgradeIsFinalizeItem: false,
setUpgradeIsFinalizeItem: () => {},
wizardIsNotFinished: false,
+ isNonWizardUser: false,
isClusterInstalled: false,
});
@@ -149,6 +163,8 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
const [isSocketConnected, setIsSocketConnected] = useState(false);
const [socketClient, setSocketClient] = useState(null);
const [isAppLoaded, setAppLoaded] = useState(false);
+ const [initializationError, setInitializationError] = useState<string | null>(null);
+ const [initializationAttempt, setInitializationAttempt] = useState(0);
const [parsedSocketMessages, setParsedSocketMessages] = useState<any[]>([]);
const [clusterName, setClusterName] = useState<string>("");
const [isKerberosEnabled, setIsKerberosEnabled] = useState(false);
@@ -166,21 +182,25 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
useState<string>("");
const [currentStackVersion, setCurrentStackVersion] = useState<string>("");
const [ambariProperties, setAmbariProperties] = useState({});
- const [sessionsValidated, setSessionsValidated] = useState(false);
- const [sessionExists, setSessionExists] = useState(false);
+ const [ambariServerVersion, setAmbariServerVersion] = useState("");
+ const [supports, setSupports] = useState(DEFAULT_SUPPORTS);
+ const [wizardUser, setWizardUser] = useState("");
const [clusterState, setClusterState] = useState({});
const [userUrl, setUserUrl] = useState("");
- const { user } = useAuth();
+ const { authorizations, user } = useAuth();
const loginName = user?.user_name;
- 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 isOnlyViewUser = authorizations.length === 0
+ || (authorizations.length === 1
+ && authorizations[0].authorization_id === "VIEW.USE");
+ const [client] = useState(() => 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 [userBgPreferences, setUserBgPreferences] = useState(false);
@@ -257,6 +277,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
setAppLoaded(true);
} catch (err) {
setServices([]);
+ setInitializationError("Ambari could not load the installed cluster services.");
}
};
@@ -270,10 +291,16 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
};
useEffect(() => {
- fetchClusterState();
- }, []);
+ if (!isOnlyViewUser) {
+ void fetchClusterState();
+ }
+ }, [isOnlyViewUser]);
useEffect(() => {
+ if (isOnlyViewUser) {
+ setAppLoaded(true);
+ return;
+ }
if (clusterName && isClusterInstalled) {
fetchClusterServices();
fetchAllHostNames();
@@ -282,7 +309,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
} else if (!isUndefined(isClusterInstalled) && !isClusterInstalled) {
setAppLoaded(true);
}
- }, [clusterName, isClusterInstalled]);
+ }, [clusterName, isClusterInstalled, isOnlyViewUser, initializationAttempt]);
useEffect(() => {
if (!clusterName || !isClusterInstalled) return;
@@ -334,16 +361,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
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,
@@ -356,16 +374,14 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
get(clusterData, "items.[0].Clusters.version", "")?.split("-")[1]
);
const clusterInfo = clusterData?.items[0]?.Clusters;
- setCluster(clusterInfo);
+ setCluster(clusterInfo || {});
+ setClusterName(clusterInfo?.cluster_name || "");
setIsKerberosEnabled(
clusterData?.items?.[0]?.Clusters?.security_type === "KERBEROS"
);
const isInstalled = clusterInfo?.provisioning_state === "INSTALLED";
setIsClusterInstalled(isInstalled);
- } catch (error) {
- setIsClusterInstalled(false);
- }
};
const fetchServiceComponentInfo = async () => {
@@ -392,65 +408,8 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
};
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;
- }
+ setAmbariProperties(response?.RootServiceComponents?.properties || {});
+ setAmbariServerVersion(response?.RootServiceComponents?.component_version || "");
};
const fetchUpgradeStates = async () => {
@@ -516,11 +475,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
const persistedData = await ClusterApi.getPersistData(
"admin-settings-show-bg-" + loginName
);
- if (!!persistedData) {
- setUserBgPreferences(true);
- } else {
- setUserBgPreferences(false);
- }
+ setUserBgPreferences(parsePersistedValue(persistedData, true));
} catch (err) {
console.error("Could not fetch user bg preferences", err);
}
@@ -528,11 +483,9 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
async function setUserBgPreferencesData(value: boolean) {
try {
setUserBgPreferences(value);
- await ClusterApi.postPersistData(
- JSON.stringify({
- ["admin-settings-show-bg-" + loginName]: `${value}`,
- })
- );
+ await ClusterApi.postPersistData(persistedPayload({
+ ["admin-settings-show-bg-" + loginName]: value,
+ }));
} catch (err) {
console.error("Could not fetch user bg preferences", err);
}
@@ -540,21 +493,36 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
useEffect(() => {
async function moveAppToReadyState() {
- const isAuthenticated = await fetchUserInfo();
- setSessionsValidated(true);
- setSessionExists(true);
- if (isAuthenticated) {
- await fetchClusterName();
- await fetchClusterData();
+ setAppLoaded(false);
+ setInitializationError(null);
+ const supportsKey = `user-pref-${loginName || ""}-supports`;
+ try {
+ const [savedSupports, wizardData] = await Promise.all([
+ ClusterApi.getPersistData(supportsKey).catch(() => null),
+ ClusterApi.getPersistData("wizard-data").catch(() => null),
+ ]);
+ setSupports({
+ ...DEFAULT_SUPPORTS,
+ ...parsePersistedValue(savedSupports, {}),
+ });
+ setWizardUser(parsePersistedValue<{ userName?: string }>(wizardData, {}).userName || "");
+
await getAmbariProperties();
- try {
- await getUserUrl();
- } catch (err) {}
- } else {
+ if (isOnlyViewUser) {
+ setAppLoaded(true);
+ return;
+ }
+
+ await fetchClusterData();
+ await getUserUrl().catch(() => undefined);
+ } catch (error: any) {
+ setInitializationError(
+ error?.response?.data?.message || "Ambari could not initialize the application shell.",
+ );
}
}
- moveAppToReadyState();
- }, []);
+ void moveAppToReadyState();
+ }, [initializationAttempt, isOnlyViewUser, loginName]);
useEffect(() => {
if (!isEmpty(cluster) && cluster?.versionNum && cluster?.stack) {
@@ -637,22 +605,6 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
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) {
@@ -735,10 +687,24 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
console.error("Broker reported error: " + frame.headers["message"]);
console.error("Additional details: " + frame.body);
};
+ client.onDisconnect = function () {
+ setSocketClient(null);
+ setIsSocketConnected(false);
+ };
+ client.onWebSocketClose = function () {
+ setSocketClient(null);
+ setIsSocketConnected(false);
+ };
useEffect(() => {
+ if (isOnlyViewUser) {
+ return;
+ }
client.activate();
- }, []);
+ return () => {
+ void client.deactivate();
+ };
+ }, [client, isOnlyViewUser]);
// Ember.js upgrade computed properties - implementing the same logic as ui/app/app.js
const upgradeInit = upgradeState === "NOT_REQUIRED";
@@ -750,8 +716,8 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
const upgradeAborted = upgradeState === "ABORTED" && !upgradeSuspend;
const upgradeSuspended = upgradeState === "ABORTED" && upgradeSuspend;
const upgradeIsRunning = upgradeInProgress || upgradeHolding;
- // TODO: Add wizardWatcherController.isNonWizardUser check when available
- const wizardIsNotFinished = upgradeIsRunning || upgradeSuspended;
+ const isNonWizardUser = Boolean(wizardUser && wizardUser !== loginName);
+ const wizardIsNotFinished = upgradeIsRunning || upgradeSuspended || isNonWizardUser;
return (
<AppContext.Provider
@@ -764,9 +730,14 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
clusterName,
cluster,
isAppLoaded,
+ initializationError,
+ retryInitialization: () => setInitializationAttempt((value) => value + 1),
services,
serviceComponentInfo,
ambariProperties,
+ ambariServerVersion,
+ supports,
+ setSupports,
isKerberosEnabled,
stackConfigurations,
allHostNames,
@@ -784,11 +755,12 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
upgradeVersionDisplayName,
setUpgradeVersionDisplayName,
userUrl,
- sessionExists,
- sessionsValidated,
+ sessionExists: true,
+ sessionsValidated: true,
clusterState,
userBgPreferences,
setUserBgPreferences: setUserBgPreferencesData,
+ syncUserBgPreferences: setUserBgPreferences,
// Background Operations
backgroundOperations,
setBackgroundOperations,
@@ -804,6 +776,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
upgradeIsFinalizeItem,
setUpgradeIsFinalizeItem,
wizardIsNotFinished,
+ isNonWizardUser,
isClusterInstalled,
}}
>
diff --git a/ambari-web/latest/src/components/UserSettingsModal.tsx b/ambari-web/latest/src/test/setup.ts
similarity index 53%
copy from ambari-web/latest/src/components/UserSettingsModal.tsx
copy to ambari-web/latest/src/test/setup.ts
index 4d574303a2..3eb6781b61 100644
--- a/ambari-web/latest/src/components/UserSettingsModal.tsx
+++ b/ambari-web/latest/src/test/setup.ts
@@ -16,29 +16,39 @@
* limitations under the License.
*/
-import Modal from "./Modal";
-
-type UserSettingsModalProps = {
- isOpen: boolean;
- onClose: () => void;
-};
-
-const UserSettingsModal = ({ isOpen, onClose }: UserSettingsModalProps) => {
- return (
- <Modal
- isOpen={isOpen}
- onClose={onClose}
- modalTitle="User Settings"
- modalBody={<div/>}
- successCallback={() => {}}
- options={{
- modalSize: "modal-lg",
- okButtonText: "SAVE",
- cancelButtonText: "CANCEL",
- okButtonVariant: "success"
- }}
- />
- )
+class MemoryStorage implements Storage {
+ private values = new Map<string, string>();
+
+ get length() {
+ return this.values.size;
+ }
+
+ clear() {
+ this.values.clear();
+ }
+
+ getItem(key: string) {
+ return this.values.get(key) ?? null;
+ }
+
+ key(index: number) {
+ return Array.from(this.values.keys())[index] ?? null;
+ }
+
+ removeItem(key: string) {
+ this.values.delete(key);
+ }
+
+ setItem(key: string, value: string) {
+ this.values.set(key, String(value));
+ }
}
-export default UserSettingsModal;
\ No newline at end of file
+Object.defineProperty(globalThis, "localStorage", {
+ configurable: true,
+ value: new MemoryStorage(),
+});
+Object.defineProperty(globalThis, "sessionStorage", {
+ configurable: true,
+ value: new MemoryStorage(),
+});
diff --git a/ambari-web/latest/src/types/auth.ts b/ambari-web/latest/src/types/auth.ts
index cfa5cc8a65..7e3481ec26 100644
--- a/ambari-web/latest/src/types/auth.ts
+++ b/ambari-web/latest/src/types/auth.ts
@@ -53,6 +53,7 @@ export interface UserContextType {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
+ sessionError: string | null;
// Authorization data
authorizations: Authorization[];
@@ -75,11 +76,17 @@ export interface UserContextType {
isClusterUser: () => boolean;
isClusterOperator: () => boolean;
loginError: string | null;
+ loginMessage: {
+ text: string;
+ buttonText: string;
+ } | null;
// Actions
login: (username: string, password: string) => Promise<boolean>;
logout: () => Promise<void>;
refreshUserData: () => Promise<void>;
+ retrySession: () => Promise<void>;
+ acknowledgeLoginMessage: () => void;
}
export interface LoginResponse {
diff --git a/ambari-web/latest/vite.config.ts b/ambari-web/latest/vite.config.ts
index 929c08ff59..abf5bd97c5 100755
--- a/ambari-web/latest/vite.config.ts
+++ b/ambari-web/latest/vite.config.ts
@@ -16,10 +16,14 @@
* limitations under the License.
*/
-import { defineConfig } from 'vite'
+import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
- base:"./"
+ base:"./",
+ test: {
+ environment: 'jsdom',
+ setupFiles: ['./src/test/setup.ts'],
+ },
})
diff --git a/ambari-web/pom.xml b/ambari-web/pom.xml
index a9d61682da..8dabfb03a6 100755
--- a/ambari-web/pom.xml
+++ b/ambari-web/pom.xml
@@ -138,6 +138,9 @@
<configuration>
<arguments>run build</arguments>
<workingDirectory>${ui2dir}</workingDirectory>
+ <environmentVariables>
+ <VITE_AMBARI_VERSION>${ambariVersion}</VITE_AMBARI_VERSION>
+ </environmentVariables>
</configuration>
</execution>
</executions>
diff --git a/docs/frontend-refactor/ember-baseline/01-auth-shell.md b/docs/frontend-refactor/ember-baseline/01-auth-shell.md
new file mode 100644
index 0000000000..b5a65955ab
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/01-auth-shell.md
@@ -0,0 +1,50 @@
+# 认证、应用外壳与全局导航
+
+## 功能清单
+
+| ID | 功能与行为 | 前置/边界 | 后端请求 | 主要证据 |
+| --- | --- | --- | --- | --- |
+| AUTH-001 | 访问 `/` 自动跳转登录页;登录页清空主界面标题并连接 login outlet | 未认证 | `router.login.clusters` | `app/router.js`,`app/templates/login.hbs` |
+| AUTH-002 | 用户名密码使用 UTF-8 Base64 组成 Basic Authorization,先验证身份,再加载当前用户、privileges 和集群 | 失败时 403 显示认证错误,500 显示服务错误,其他错误显示通用失败 | `router.login`、`router.afterLogin` | `app/router.js`,`app/controllers/login_controller.js` |
+| AUTH-003 | 支持 Knox/外部 JWT 登录跳转;401/403 响应中的 `jwtProviderUrl` 附加当前 URL 作为返回地址 | `/login/local` 永不跳外部认证;重定向次数超限显示错误弹窗 | `router.login.clusters` | `app/router.js` |
+| AUTH-004 | 已被服务端认证但 localStorage 未标记时,从响应 `User` header 恢复用户会话 | 用户数据加载后调用 authorization 请求,但旧代码使用 `.complete()` 串联后续流程,权限加载失败也会继续加载登录消息并尝试进入主界面;这是迁移时不能复刻的失败路径缺陷 | `router.afterLogin`、authorization load | `app/router.js#afterLoginSuccessCallback` |
+| AUTH-005 | 登录成功加载登录消息设置;启用的消息以 modal 展示,用户确认后继续 | 设置不存在、JSON 非法或请求失败时直接继续 | `router.login.message` | `app/router.js` |
+| AUTH-006 | 根据集群和权限决定首屏:已安装集群进入 Dashboard/恢复路径,无集群进入 Admin View,仅 View 用户进入 Views,未完成集群进入 Installer | preferred target 只按首字符接受 `/` 或 `#` 并排除 login;`//host/path` 因此前缀检查仍会被接受,构成经典 UI 的 protocol-relative 跳转缺陷 | `router.login.clusters`、`ambari.service.load_server_version` | `app/router.js#restorePreferedPath` |
+| AUTH-007 | 退出时停止主界面 polling、清 localStorage/向导状态/权限、发送 logoff,并回登录页 | 用户主动退出才向服务端发请求;已有集群数据时 complete 后刷新页面;keep-alive 标志只在 logoff 成功回调中清除,失败回调为空 | `router.logoff` | `app/router.js#logOff`、`app/router.js#logOffSuccessCallback` |
+| AUTH-008 | 维持服务端会话 keep-alive,主路由进入后启动 | updater 由 `isPollerRunning` 控制;正常 logoff 成功会停止,但 logoff 失败时标志可能保持为 true,不能把“退出必定停止”作为等价要求 | 见“认证与应用外壳”模块接口目录 | `app/controllers/application.js#startKeepAlivePoller`,`app/routes/main.js`,`app/router.js#logOffErrorCallback` |
+| SHELL-001 | 主应用进入时升级 localStorage schema,加载 supports、Ambari properties、cluster name、Views,再初始化全局模型和 polling | 未认证保存当前位置并跳登录;仅 View 用户不加载完整集群运维数据 | 多个 router/cluster 初始化请求 | `app/routes/main.js`,`app/controllers/main.js`,`app/controllers/global/cluster_controller.js` |
+| SHELL-002 | 全局导航包含 Dashboard、Services、Hosts、Alerts、Admin、Views,并由权限、安装状态、已安装服务和 feature flag 控制 | 菜单可见不等于 route 可进入;route 仍执行权限重定向 | 无单一请求 | `app/templates/main.hbs`,`app/views/main/menu.js`,`app/routes/main.js` |
+| SHELL-003 | 保存用户 preferred path,登录后恢复到原 route | 接受以 `/` 或 `#` 开头且不是 login 的值;经典实现错误接受 `//host`,React 必须使用同源/规范化 URL 校验而不是复刻该缺陷 | 无 | `app/router.js#savePreferedPath`、`app/router.js#restorePreferedPath` |
+| SHELL-004 | 页面标题、breadcrumb、侧边服务菜单、全局 spinner/loading overlay 随 route 和模型加载状态更新 | 数据未加载时 route 等待 `dataLoading()` 或相应 deferred | 全局加载请求 | `app/routes/main.js`,`app/views/common/breadcrumbs_view.js` |
+| SHELL-005 | Inactivity timeout 按 admin 与 readonly Ambari property 选择时长,监听 window/iframe 的 mousemove、keypress、click,每秒检查活跃时间,并在超时前 60 秒显示继续会话/退出倒计时 | wizard step 与 `/stack/upgrade` 期间跳过超时检查;超时或用户选择退出都会调用 logoff;设置来源是 Ambari properties,不是 User Settings popup | Ambari properties load;超时本身无额外读取请求 | `app/controllers/main.js#monitorInactivity`、`app/controllers/main.js#checkActiveness` |
+| SHELL-006 | About 弹窗显示 controller 中已缓存的 Ambari Server 版本;没有 click-time 版本请求 | 缓存来自 Installer 或 Main controller;均没有值时显示 N/A,因此不存在 About 点击请求失败流程 | 无 click-time 请求 | `app/controllers/application.js#ambariVersion`、`app/controllers/application.js#showAboutPopup` |
+| SHELL-007 | Experimental 页面读取支持开关,允许逐项修改并 Save/Cancel;Reset UI States 会清本地 DB/cache/向导/cluster status,并向服务端持久化 `wizard-data={}` | route 仅 `AMBARI.MANAGE_SETTINGS` 可进入;Reset 按钮另需 `CLUSTER.MANAGE_USER_PERSISTED_DATA` 且 non-wizard user 时禁用 | user supports preference、`persist.post` | `app/router.js#experimental`,`app/controllers/experimental.js`,`app/templates/experimental.hbs` |
+| SHELL-008 | 版本不一致检查在 Installer 进入前阻止继续并提示 server/web client mismatch | 真实行为依赖打包版本,`CONDITIONAL` | Server 版本请求 | `app/controllers/installer.js`,`app/routes/installer.js` |
+| SHELL-009 | User Settings popup 可保存是否自动展示 Background Operations 和 timezone,并列出当前用户的 cluster/View privileges | 菜单由 `AMBARI.MANAGE_SETTINGS` 控制,handler 又要求 `CLUSTER.UPGRADE_DOWNGRADE_STACK`,持久化最终要求 `CLUSTER.MANAGE_USER_PERSISTED_DATA`;这些不一致 gate 必须分别保留为基线事实 | `persist.get`、`persist.post`、`router.user.privileges` | `app/templates/application.hbs`,`app/controllers/global/user_settings_controller.js`,`app/templates/common/settings.hbs`,`app/mixins/common/persist.js` |
+
+## 权限模型
+
+登录后的 `privileges[].PrivilegeInfo.permission_name` 决定全局角色:
+
+- `AMBARI.ADMINISTRATOR` 设置 `App.isAdmin`。
+- 当前集群的 `CLUSTER.ADMINISTRATOR` 同时设置 `isAdmin` 和 `isOperator`。
+- Cluster Operator、Service Administrator、Service Operator、Cluster User 等细粒度动作最终使用 `App.isAuthorized(permission)` 判断。
+- 只有 View 权限、没有集群运维权限的用户进入 Views,不初始化完整运维页面。
+
+详细 permission 与功能关联见 [13-permissions-flags.md](13-permissions-flags.md)。
+
+## 全局 HTTP 语义
+
+- 所有命名请求默认 timeout 为 `App.timeout`,默认 `Content-Type: text/plain`,用于避免 Knox 修改 JSON body。
+- 没有自定义 error callback 时,500、401、407、413 等状态使用全局错误 modal,展示 method、URL、status 和服务端 message。
+- KDC 相关 400 错误会被识别并改用 Invalid KDC popup。
+- 同一 request 可以设置 loading popup;超过 500ms 才显示,complete 时清除。
+- 超长 GET 自动转换为带 `X-Http-Method-Override: GET` 的 POST。
+
+[generated/api-by-module/auth-shell.md](generated/api-by-module/auth-shell.md) 只是按请求名和 caller path 宽匹配的候选索引,可能混入或漏掉请求。权威网络核对必须联合 [AJAX 定义](generated/ajax-endpoints.md)、[AJAX 调用点](generated/ajax-calls.md)、[direct HTTP](generated/direct-http-calls.md)、[browser entrypoints](generated/browser-network-entrypoints.md) 和 [realtime channels](generated/realtime-channels.md)。
+
+## 占位与边界
+
+- `/main/admin/authentication`、`/main/admin/advanced`、`/main/admin/audit` 在 `routes/main.js` 有 outlet 路由,但经典 Ember 树中未找到对应完整 controller/template,标记为 `PLACEHOLDER`,不能作为 React 必须复刻的已确认页面。
+- `/main/test` 只在 `routes/main.js` 中连接 `mainTest` outlet;经典 Ember 树中没有对应 controller、template 或 view,标记为 `PLACEHOLDER`。该 route fragment 仍保留在静态目录中,但不生成 React 功能需求。
+- 用户、组、角色和 View 实例管理由独立 AngularJS Admin Console 提供,不属于本文 Ember 基线。
diff --git a/docs/frontend-refactor/ember-baseline/13-permissions-flags.md b/docs/frontend-refactor/ember-baseline/13-permissions-flags.md
new file mode 100644
index 0000000000..28535521b8
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/13-permissions-flags.md
@@ -0,0 +1,158 @@
+# 权限、Feature Flag 与运行时条件索引
+
+本文不是权限名称清单,而是经典 Ember 对功能入口和操作能力的实际判定基线。React 必须同时复刻 RBAC、全局长流程互斥、feature flag、stack/service metadata、组件状态和 maintenance 条件;仅在页面加载后依赖后端返回 403 不等价。
+
+## 权限判定语义
+
+| ID | 规则 | 旧版精确行为 | 主要证据 |
+| --- | --- | --- | --- |
+| GATE-AUTH-001 | 逗号分隔权限是 OR | `App.havePermissions("A, B")` trim 后逐项检查,只要 `App.auth` 包含任意一个即为 true;React 不得解释为全部满足 | `app/app.js#havePermissions`、`test/app_test.js` |
+| GATE-AUTH-002 | `isAuthorized` 增加向导所有权限制 | `App.isAuthorized(x) = App.havePermissions(x) && !wizardWatcherController.isNonWizardUser`;另一用户占有长向导时,即使当前用户有 RBAC 权限也不能修改 | `app/app.js#isAuthorized`、`app/utils/helper.js#isAuthorized` |
+| GATE-AUTH-003 | `havePermissions` 不受向导所有权限制 | 模板 `havePermissions` 适合只读可见性;因此相同 permission 在 `havePermissions` 与 `isAuthorized` 下可能有不同结果 | `app/utils/helper.js` |
+| GATE-AUTH-004 | Upgrade 全局互斥及 OR 表达式污染 | upgrade 非 `NOT_REQUIRED/COMPLETED`、非 suspended,且 `supports.opsDuringRollingUpgrade=false` 时,一条权限表达式只有在整个字符串既不包含 `CLUSTER.UPGRADE_DOWNGRADE_STACK` 也不包含 `CLUSTER.MANAGE_USER_PERSISTED_DATA` 时才被提前拒绝;只要组合串包含任一例外,整条 OR 绕过 upgrade deny,随后命中串中任意其他权限也返回 true。例外不是逐 permission 判断 | `app/app.js#havePermissions`、`app/views/main/menu.js` |
+| GATE-AUTH-005 | 无权限模型即全部拒绝 | `App.auth` 尚未加载时 `havePermissions` 返回 false,UI 保持隐藏/禁用而不是先闪现操作 | `app/app.js`、登录初始化 |
+| GATE-AUTH-006 | 只有 View 权限的用户走独立导航 | `App.auth` 空数组,或权限恰好只有 `VIEW.USE` 时 `isOnlyViewUser=true`,登录后进入 Views,不加载完整集群运维外壳 | `AUTH-006`、`app/app.js#isOnlyViewUser`、`app/router.js` |
+| GATE-AUTH-007 | Cluster Administrator 提升前端角色标志 | 当前 cluster privilege 含 `CLUSTER.ADMINISTRATOR` 时同时设置 `App.isAdmin=true`、`isOperator=true`;Ambari Administrator 只由 `AMBARI.ADMINISTRATOR` 确认 | `app/router.js#loginGetClustersSuccessCallback` |
+| GATE-AUTH-008 | UI 权限不是服务端授权替代品 | Ember 只为已知动作隐藏/禁用入口;所有 mutation 仍需服务端验证。React 对照必须同时测试无权限深链和直接 API 调用 | 所有 route/template/controller、Ambari REST RBAC |
+
+## Ambari 级权限
+
+| Permission | 旧版 Ember 作用 | 关联功能 ID | 闸门类型/边界 |
+| --- | --- | --- | --- |
+| `AMBARI.ADD_DELETE_CLUSTERS` | 无集群时进入 Installer;安装 Step 7 中决定全新集群特有配置行为 | `AUTH-006`、`INST-FLOW-001`、`INST-MODE-001`、`INST-0-*` 到 `INST-10-*` | Installer route 硬重定向;没有权限转 Views |
+| `AMBARI.MANAGE_SETTINGS` | 进入 `/experimental`、编辑 supports;Background Operations footer 的管理动作 | `SHELL-007`、`BG-001` 到 `BG-005` | route guard + template action;非权限用户转 Views/Dashboard |
+| `AMBARI.MANAGE_STACK_VERSIONS` | Versions 页的 Manage Versions 外链、out-of-sync reinstall/remove 入口及 repository URL 编辑 | `VER-LIST-001` 到 `VER-LIST-009`、`VIEW-ADMIN-003` | 不能与 version install/reinstall/upgrade/hide-discard 混为一项;后者由 `CLUSTER.UPGRADE_DOWNGRADE_STACK` 禁用逻辑控制 |
+| `AMBARI.EDIT_STACK_REPOS` | repository URL 编辑的权限模型成员 | `STACK-SVC-003` 到 `STACK-SVC-005`、`INST-1-*` | application Admin Console 链接包含此权限;经典 stack 页面还依赖 cluster upgrade/version 权限,真实角色组合需运行态验证 |
+| `AMBARI.ASSIGN_ROLES` | 显示独立 Ambari Admin Console 入口 | `SHELL-002` | Ember 只负责跳转;用户/角色页面属于 AngularJS,`OUT_OF_SCOPE` |
+| `AMBARI.MANAGE_USERS` | 显示独立 Ambari Admin Console 入口 | `SHELL-002` | AngularJS Admin Console,`OUT_OF_SCOPE` |
+| `AMBARI.MANAGE_GROUPS` | 显示独立 Ambari Admin Console 入口 | `SHELL-002` | AngularJS Admin Console,`OUT_OF_SCOPE` |
+| `AMBARI.MANAGE_VIEWS` | 显示独立 Ambari Admin Console 入口 | `SHELL-002`、Views 文档的 Admin View 跳转 | View instance 管理由 AngularJS 完成,Ember 仅发现版本并跳转 |
+| `AMBARI.RENAME_CLUSTER` | 显示独立 Ambari Admin Console 入口 | `SHELL-002` | 当前 Ember 树没有 rename form,`OUT_OF_SCOPE` |
+| `AMBARI.ADMINISTRATOR` | 设置全局 `App.isAdmin` 并通常由服务端 privilege 集合赋予其他能力 | `AUTH-002`、`AUTH-006` | 不是所有按钮直接检查的万能 bypass;前端仍按返回的具体 permission names 判断 |
+
+## Cluster 级权限
+
+| Permission | 旧版 Ember 作用 | 关联功能 ID | 闸门类型/边界 |
+| --- | --- | --- | --- |
+| `CLUSTER.ADMINISTRATOR` | 设置 `isAdmin/isOperator` 角色标志 | `AUTH-002`、`SHELL-001` | 前端角色派生;动作仍按细粒度权限 |
+| `CLUSTER.USER` | 标识只有只读 cluster user 权限的用户 | `AUTH-006`、`SHELL-002` | mapper 分类;无直接 mutation 按钮 |
+| `CLUSTER.VIEW_CONFIGS` | Service Config tab 可见 | `SVC-CONFIG-001`、`SVC-CONFIG-002`、`HOST-TAB-001` | 使用 `havePermissions`,长向导占用时仍可只读查看 |
+| `CLUSTER.VIEW_STACK_DETAILS` | Admin 主入口和 Stack/Versions 只读页面可见 | `STACK-SVC-*`、`VER-LIST-*` | 与 upgrade permission OR;Admin route 本身另有 routePath 限制,深链需逐路由测试 |
+| `CLUSTER.TOGGLE_KERBEROS` | Kerberos 页面、Enable/Disable/Edit、长流程恢复 | `KRB-ENTRY-*` 到 `KRB-REC-*` | route guard + button guard +恢复 guard;还需 `supports.enableToggleKerberos` |
+| `CLUSTER.UPGRADE_DOWNGRADE_STACK` | Admin/Stack/Upgrade route、发起和控制 upgrade、Admin View 跳转、Kerberos CSV | `UPG-START-*`、`UPG-RUN-*`、`VER-LIST-*`、`KRB-MGMT-008` | route guard + mutation;upgrade 中该权限是全局互斥例外 |
+| `CLUSTER.MANAGE_ALERT_NOTIFICATIONS` | Actions 中 Manage Notifications,创建/编辑/删除 targets | `ALERT-LIST-005`、`ALERT-NOTIFY-*`、`ALERT-GROUP-005` | controller action 构造时过滤 |
+| `CLUSTER.TOGGLE_ALERTS` | Definition detail 中 enable/disable cluster-scope alert | `ALERT-LIST-004`、`ALERT-DEF-004`、`ALERT-SET-003` | template guard;service-scope 另用 `SERVICE.TOGGLE_ALERTS` |
+| `CLUSTER.MODIFY_CONFIGS` | Admin menu/Auto Start 的组合可见性以及 cluster-scope config 能力 | `SHELL-002`、`ADMIN-AUTO-*`、部分 `SVC-CONFIG-*` | 常与 service permission OR;具体 Service Config 编辑主要检查 `SERVICE.MODIFY_CONFIGS` |
+| `CLUSTER.MANAGE_AUTO_START` | 进入 Service Auto Start,并实际启用全局开关、全选和每个 component recovery checkbox | `ADMIN-AUTO-001` 到 `ADMIN-AUTO-004` | route 与 `SERVICE.MANAGE_AUTO_START` OR,但整个页面的编辑控件统一只看此 cluster permission |
+| `CLUSTER.MANAGE_USER_PERSISTED_DATA` | 允许写 UI persist/user preferences;upgrade 时仍可用 | `INST-FLOW-002`、所有长流程恢复、用户 UI 设置 | persist mixin guard;无权限时不能把客户端状态写到服务端 |
+| `CLUSTER.MANAGE_WIDGETS` | Dashboard/Service Metrics Widget 编辑 | `OUT_OF_SCOPE` | 因用户明确排除 Metrics,不作为 React 验收项 |
+
+## Service 与 Host 级权限
+
+| Permission | 旧版 Ember 作用 | 关联功能 ID | 闸门类型/边界 |
+| --- | --- | --- | --- |
+| `SERVICE.START_STOP` | service、component、host bulk start/stop/restart;abort eligible request;restart-required 操作 | `SVC-ALL-002` 到 `SVC-ALL-004`、`SVC-ACT-001`、`SVC-CONFIG-009`、`HOST-BULK-001`、`HOST-COMP-001` | 菜单构造 +模板;仍按 current/desired state、maintenance、upgrade过滤 |
+| `SERVICE.RUN_SERVICE_CHECK` | Run Service Check 的语义权限 | `SVC-ACT-002`、安装/安全/HA流程中的测试结果 | Service Actions 旧实现不是该项的独立 gate;宽 OR 分支可能因其他 service permission而加入 smoke test,最终依赖后端授权 |
+| `SERVICE.RUN_CUSTOM_COMMAND` | stack custom commands、Refresh Queues、Rebalance、特定 service 命令的语义权限 | `SVC-ACT-006` 到 `SVC-ACT-009`、`HOST-COMP-007`、`HOST-COMP-008` | metadata/status过滤存在,但 Service Actions 宽 OR 后多数命令没有对应 permission的第二次前端检查;最终依赖后端授权 |
+| `SERVICE.ADD_DELETE_SERVICES` | Add Service、Delete Service、Stack Services 中添加 | `SVC-ALL-001`、`SVC-ACT-005`、`SVC-ADD-*`、`STACK-SVC-002` | 同时要求 `supports.enableAddDeleteServices`;Add Service route 硬保护 |
+| `SERVICE.MODIFY_CONFIGS` | Service Config 编辑/保存/revert、override、DB test关联编辑、部分 restart UI | `SVC-CONFIG-003` 到 `SVC-CONFIG-010`、`CFG-GROUP-006`、`HOST-TAB-001` | 编辑控件检查 + save controller;旧版本/compare 模式始终只读 |
+| `SERVICE.COMPARE_CONFIGS` | 配置版本 Compare | `SVC-CONFIG-006` | `havePermissions`,只读能力 |
+| `SERVICE.MANAGE_CONFIG_GROUPS` | Manage Config Groups、group/host/override 管理 | `CFG-GROUP-001` 到 `CFG-GROUP-006`、`HOST-ADD-004` | template action;实际 config 保存还可能需 modify configs |
+| `SERVICE.TOGGLE_MAINTENANCE` | service/component maintenance 动作的语义权限 | `SVC-ACT-003`、`SVC-SUM-003`、`HOST-COMP-006` | Service Actions 的 toggle 在宽 OR 分支中直接加入,无独立前端 permission复查;组件路径也可能由 Host maintenance 权限显示,最终仍需后端按 scope授权 |
+| `SERVICE.TOGGLE_ALERTS` | Alerts 页面入口动作、创建向导 route、service-scope definition enable/disable/edit/delete | `ALERT-LIST-004`、`ALERT-DEF-002` 到 `ALERT-DEF-006`、`ALERT-CREATE-*` | Create 还要求 `supports.createAlerts`;Metric 类型创建排除 |
+| `SERVICE.DECOMMISSION_RECOMMISSION` | DataNode/NodeManager/RegionServer 等批量与单组件退役/重新服役 | `HOST-BULK-004`、`HOST-COMP-005` | 还要求 component 在 `components.decommissionAllowed` 且状态安全 |
+| `SERVICE.ENABLE_HA` | NN/RM/Ranger HA、JournalNode Management、Federation、HAWQ standby 入口及恢复 | `SVC-ACT-010`、`NNHA-*`、`JN-*`、`RMHA-*`、`RAHA-*`、`FED-*`、`RBF-*`、`HAWQ-*` | service action + `serviceTypes`/feature/组件前置;向导所有权会统一撤销操作能力 |
+| `SERVICE.MOVE` | Reassign Master 入口 | `SVC-MOVE-*`、`HOST-COMP-009` | 仅 `components.reassignable` master;所有 hosts 已有该 master 时禁用 |
+| `SERVICE.SET_SERVICE_USERS_GROUPS` | Admin Service Accounts 页面 | `ADMIN-ACCT-001`、`ADMIN-ACCT-002` | route 硬保护;与 Config edit 权限是两套能力 |
+| `SERVICE.MANAGE_AUTO_START` | 允许进入 Auto Start route | `ADMIN-AUTO-*` | 与 cluster auto-start permission OR;但页面没有按授权 service过滤,且所有开关/checkbox统一因缺 `CLUSTER.MANAGE_AUTO_START` 而 disabled,所以 service-only 用户只能查看 |
+| `SERVICE.VIEW_OPERATIONAL_LOGS` | Host Logs tab、task Log Search 链接 | `HOST-TAB-004`、`BG-003` | 还需 LOGSEARCH service、`supports.logSearch` 和目标 host/log metadata |
+| `HOST.ADD_DELETE_HOSTS` | Add Host、单/批删除 host | `HOST-ADD-*`、`HOST-DETAIL-004`、`HOST-BULK-008` | Add Host 入口还受 wizard/upgrade互斥;删除前做组件安全检查 |
+| `HOST.ADD_DELETE_COMPONENTS` | 添加、安装、删除、重新安装 host components | `HOST-BULK-005`、`HOST-BULK-009`、`HOST-COMP-002` 到 `HOST-COMP-004` | stack cardinality、最后 master、component state 是额外硬条件 |
+| `HOST.TOGGLE_MAINTENANCE` | host maintenance、部分 host/component maintenance 菜单 | `HOST-BULK-002`、`HOST-BULK-003`、`HOST-DETAIL-003`、`HOST-COMP-006` | scope 不同使用不同 endpoint |
+| `SERVICE.VIEW_METRICS` | Metrics 列/视图 | `OUT_OF_SCOPE` | 明确排除 |
+
+## `App.supports` Feature Flags
+
+本节权威枚举生成器可识别的 `App.supports.*` flags。它们的初始默认值在 `app/config.js`,进入 main/installer 时由 `experimentalController.loadSupports()` 从服务端覆盖。React 不得将当前默认值编译成永久行为;非 `App.supports` 的 runtime UI gates 另见下一节。
+
+| Flag(classic 默认) | 功能影响 | 关联功能 ID | 边界 |
+| --- | --- | --- | --- |
+| `enableAddDeleteServices=true` | Add/Delete Service 与 Stack Services add link | `SVC-ALL-001`、`SVC-ACT-005`、`SVC-ADD-*`、`STACK-SVC-002` | 与 `SERVICE.ADD_DELETE_SERVICES` AND |
+| `enableToggleKerberos=true` | Kerberos Admin menu/route/Enable/Disable/Edit | `KRB-ENTRY-002`、`KRB-MGMT-*` | 与 `CLUSTER.TOGGLE_KERBEROS` AND;Windows stack menu 另有排除 |
+| `preKerberizeCheck=false` | Enable Kerberos 前 server checks | `KRB-ENTRY-004` | false 时直接进入向导 |
+| `kerberosStackAdvisor=true` | Configure Identities recommendations | `KRB-4-002` | 仅没有 stored values 时调用 |
+| `regenerateKeytabsOnSingleHost=false` | Host action Regenerate Keytabs | `HOST-DETAIL-007`、`KRB-MGMT-006` | Kerberos enabled AND |
+| `autoRollbackHA=false` | NN HA 关键阶段 Close/rollback 模式 | `NNHA-REC-*` | true 隐藏关键 step 关闭按钮并进入自动 rollback;false 给 manual rollback instructions |
+| `manageJournalNode=true` | HDFS Service Action 的 Manage JournalNodes | `JN-ENTRY-*` | 还需 HA_MODE 且 host/JN count 可调整 |
+| `preInstallChecks=false` | Installer Customize Services 的 Pre-Install Checks | `INST-7-006` | 只用于全新 installer,不应误加到所有复用 Step 7 场景 |
+| `customizeAgentUserAccount=false` | SSH install options 的 Ambari Agent OS user | `INST-MODE-007`、`INST-2-002`、`HOST-ADD-001` | false 时 bootstrap payload 强制 `userRunAs=root` |
+| `skipComponentStartAfterInstall=false` | Install/Add Host/Add Service Step 9 状态机可在 install 后跳过 start/check | `INST-9-001`、`INST-9-004`、`HOST-ADD-006`、`SVC-ADD-007` | 改变进度权重、可完成状态和 retry路径;有专门 controller tests |
+| `disableCredentialsAutocompleteForRepoUrls=true` | repository URL 变化时是否保留浏览器 credential/autocomplete 相关输入 | `INST-1-004`、`STACK-SVC-004` | 仅交互细节,仍需保留避免凭据意外复用 |
+| `alwaysEnableManagedMySQLForHive=false` | 安装/Add Service 的 Hive managed MySQL option 可见性 | `INST-7-002`、`SVC-ADD-004` | 在普通 Service Configs route 与安装向导行为不同 |
+| `createAlerts=false` | Create Alert action | `ALERT-LIST-005`、`ALERT-CREATE-*` | 还需 toggle alert 权限;Metric type 排除 |
+| `preUpgradeCheck=true` | Pre-Upgrade checks 与 custom cluster check 流程 | `UPG-START-004`、`UPG-START-005` | 服务端仍决定具体 checks/bypassability |
+| `enabledWizardForHostOrderedUpgrade=true` | Host Ordered upgrade wizard 可选/受限 | `UPG-START-002`、`UPG-RUN-*` | false 时 controller `isWizardRestricted` 禁止相关 mutations |
+| `displayOlderVersions=false` | Hosts/Versions 对旧 stack version 的显示 | `HOST-TAB-003`、`VER-LIST-001` 到 `VER-LIST-004` | false 时过滤低于 current 的旧版本记录 |
+| `opsDuringRollingUpgrade=false` | Upgrade 期间是否允许普通 permission 操作 | `GATE-AUTH-004`、`UPG-RUN-010` | 位于全局 `havePermissions`,影响所有模块 |
+| `serviceAutoStart=true` | Admin Auto Start menu/page | `ADMIN-AUTO-*` | 仍需 cluster/service auto-start permission |
+| `enableNewServiceRestartOptions=false` | Service actions 中新的 Restart Service 选项 | `SVC-ACT-001`、`SVC-ACT-004` | 旧 Restart All/rolling actions仍可能存在 |
+| `logSearch=true` | Host Logs tab、task/host Log Search link和额外 logging resource load | `HOST-TAB-004`、`BG-003` | 还需 LOGSEARCH installed 和 operational logs permission |
+| `logCountVizualization=false` | Host Summary 的 log count visualization | `HOST-DETAIL-001` | 属于 operational logs,不是 Metrics;需 LOGSEARCH运行态验证 |
+| `installGanglia=false` | 是否保留旧 GANGLIA service model | `OUT_OF_SCOPE` | Ganglia 是旧 Metrics service,本文不作为安装功能要求 |
+| `customizedWidgetLayout=false` | Metrics widgets | `OUT_OF_SCOPE` | 明确排除 |
+| `showPageLoadTime=false` | 开发/实验 page load timer | `OUT_OF_SCOPE` | 不构成最终用户业务功能 |
+| `redhatSatellite=false` | config 中有默认定义,当前 classic `app/` 无消费调用 | 无 | `STATIC_ONLY` 遗留 capability,不作为已确认功能 |
+| `addingNewRepository=false` | config 中有默认定义,当前 classic `app/` 无消费调用 | 无 | `STATIC_ONLY` 遗留 capability |
+| `kerberosAutomated` | Add Service route 只有 TODO 注释,当前运行代码不读取 | 无 | 不得据注释创建 React 功能开关 |
+
+## 其他 Runtime UI Gates
+
+| ID | Gate(classic 默认) | 运行时来源与功能影响 | 边界 |
+| --- | --- | --- | --- |
+| GATE-RUNTIME-001 | `App.stackVersionsAvailable=true` | 初始加载所有 upgrades/repository versions 后重算为 `App.StackVersion` 是否非空;控制 Admin Versions tab 与 Host Stack Versions route | 不是 per-user supports,也不是 RBAC;无 version 时 Host深链回 Summary |
+| GATE-RUNTIME-002 | `App.upgradeHistoryAvailable=false` | `restoreUpgradeState()` 根据是否存在已完成/非运行 upgrade重算;控制 Upgrade History tab | 运行中/暂停的唯一一条 upgrade本身不使 history tab可见 |
+| GATE-RUNTIME-003 | `App.enableDigitalClock=false` | 本地 config直接控制顶栏 Clock view | 当前未从 supports覆盖;属于 shell UI gate,不是业务授权 |
+
+## Stack、Service 与 Component Metadata 条件
+
+| ID | 条件来源 | 旧版影响 | 关联功能 |
+| --- | --- | --- | --- |
+| GATE-META-001 | `StackService.isInstallable`、已安装 service names、service dependencies | Installer/Add Service 可选项,Delete Service 依赖限制 | `INST-4-*`、`SVC-ADD-001`、`SVC-ACT-005` |
+| GATE-META-002 | service `serviceTypes` 包含 `HA_MODE` | HDFS/YARN/RANGER/HAWQ 显示对应 HA action | `NNHA-*`、`JN-*`、`RMHA-*`、`RAHA-*`、`HAWQ-*` |
+| GATE-META-003 | service `serviceTypes` 包含 `FEDERATION` | HDFS 显示 NameNode Federation | `FED-*` |
+| GATE-META-004 | service `serviceTypes` 包含 `DFSRouter` | HDFS 显示 Router-based Federation | `RBF-*` |
+| GATE-META-005 | component cardinality/min/max、`isMaster`、`isClient`、`isSlave`、`isHAComponentOnly` | host assignment、add/delete component、skip master/slave steps | `INST-5-*`、`INST-6-*`、`HOST-COMP-002` 到 `HOST-COMP-004`、`SVC-ADD-*` |
+| GATE-META-006 | `components.reassignable` | Move Master action与目标 host | `SVC-MOVE-*`、`HOST-COMP-009` |
+| GATE-META-007 | `components.decommissionAllowed` | Decommission菜单 | `HOST-BULK-004`、`HOST-COMP-005` |
+| GATE-META-008 | stack component custom commands | Refresh/Rebalance/Knox/HBase及任意 command 可见性 | `SVC-ACT-006` 到 `SVC-ACT-009`、`HOST-COMP-007`、`HOST-COMP-008` |
+| GATE-META-009 | config types/themes/value attributes/dependencies | Config tab是否存在、控件、必填/只读/override/recommendations | `SVC-CONFIG-*`、`INST-7-*` |
+| GATE-META-010 | Windows stack/stack family/version | Kerberos menu、部分 services/commands、旧 HAWQ 能力 | `KRB-ENTRY-002`、`HAWQ-*` |
+
+## 状态、Maintenance 与长流程互斥
+
+| ID | 条件 | 必须保持的行为 | 关联功能 |
+| --- | --- | --- | --- |
+| GATE-STATE-001 | service/component desired/current state | 已 STARTED 不再显示 Start;已 INSTALLED 不再 Stop;transition/request中禁重复提交 | `SVC-ACT-001`、`HOST-COMP-001`、所有 progress wizard |
+| GATE-STATE-002 | host/component/service maintenance | start/stop、delete、HA前置等动作按 scope 禁用或跳过;maintenance 本身仍可由有权用户切换 | `HOST-BULK-*`、`HOST-COMP-*`、`SVC-ACT-*`、HA文档 |
+| GATE-STATE-003 | heartbeat/host health | Add Host registration、Recover、delete、HA host selection和decommission安全判断 | `INST-3-*`、`HOST-DETAIL-004`、`HOST-DETAIL-005`、HA文档 |
+| GATE-STATE-004 | stale configs/restart required | Restart Required/Refresh Configs 只对受影响 components | `SVC-ALL-004`、`SVC-ACT-004`、`SVC-CONFIG-009`、`HOST-COMP-007` |
+| GATE-STATE-005 | background request/schedule already active | 避免同一 action 重复发起,显示现有 operation或禁用 | `BG-*`、`HOST-BULK-010`、`SVC-ACT-001` |
+| GATE-STATE-006 | `wizardWatcherController.isNonWizardUser` | 全局 mutation权限临时失效;当前向导所有者继续,其他窗口只读或路由到流程 | `INST-FLOW-003`、`INST-FLOW-006`、`KRB-REC-*`、所有 HA recovery |
+| GATE-STATE-007 | upgrade state | 普通操作默认失效;upgrade owner/authorized user可控制 pause/retry/finalize | `UPG-RUN-*`、`GATE-AUTH-004` |
+| GATE-STATE-008 | Kerberos security type/KDC session | Add Host/Add Service/Reassign/HA等创建 component前验证或更新 identities | `INST-MODE-009`、`INST-MODE-010`、`KRB-X-*`、HA文档 |
+| GATE-STATE-009 | cluster provisioning/wizard `clusterState` | 刷新/崩溃后恢复当前 step/request,禁止从入口重新并发创建 | `INST-FLOW-*`、`KRB-REC-*`、所有 HA/Federation recovery |
+
+## 已知优先级与迁移风险
+
+| ID | 旧版表达式 | 风险/基线结论 |
+| --- | --- | --- |
+| GATE-RISK-001 | `!isWindows && isAuthorized(KERBEROS) \|\| upgradeRunning` | JavaScript 中 `&&` 优先于 `\|\|`,所以 upgrade running 时即使 Windows/无 Kerberos 权限也可能显示 Admin Kerberos category;这是旧版静态行为,不应无意当作安全授权。React 矩阵标 `BEHAVIOR_DIFF` 后由维护者决定是否修复 |
+| GATE-RISK-002 | Admin parent `enter` 接受四种权限 OR,但 `routePath` 主要要求 upgrade permission | 菜单可见、父 route 与子 route条件不完全一致;每个深链必须单测,不可只实现一个统一 Admin guard |
+| GATE-RISK-003 | Service actions 外层用多个 permissions OR | 旧代码在宽 OR 后直接加入 Refresh Configs、Restart All、Run Smoke Test、Toggle Maintenance以及多种 custom command;多数 action/controller没有按语义 permission二次过滤,只有 HA、Move、Add Component等局部另查。用户仅有 OR 中任一权限即可看到/触发若干其他语义动作,最终由 server授权。React 必须逐 action + route + server授权,不复制漏闸 |
+| GATE-RISK-006 | upgrade例外按整条 permission字符串判断 | 组合串只要包含 upgrade/persist例外,就会让串中其他权限一起绕过全局 upgrade deny;React 若按逐权限修正,应在矩阵标 `BEHAVIOR_DIFF`,不得误称经典已有严格隔离 |
+| GATE-RISK-004 | Client feature flags 可在 Experimental 页面修改 | flags 是服务端/用户环境事实,不是可信安全边界;后端必须继续授权 |
+| GATE-RISK-005 | 某些权限只出现在独立 Admin Console入口 | 不得因 permission 名存在就推断经典 Ember 有相应 CRUD 页面 |
+
+## Metrics 权限边界
+
+`SERVICE.VIEW_METRICS`、`CLUSTER.MANAGE_WIDGETS`、Metrics Widget 相关 feature flags 全部为 `OUT_OF_SCOPE`。HA checkpoint、decommission 或 component health 为安全判定读取 `metrics/...` 字段不受此排除影响,因为它们关联的是运维状态机而非指标展示。
diff --git a/docs/frontend-refactor/ember-baseline/14-react-gap-matrix.md b/docs/frontend-refactor/ember-baseline/14-react-gap-matrix.md
new file mode 100644
index 0000000000..d671269eca
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/14-react-gap-matrix.md
@@ -0,0 +1,101 @@
+# React 功能差异矩阵规范
+
+本文定义后续 `ambari-web/latest` 对照 `ambari-web/classic` 的验收格式。当前阶段只建立规则和模板,不提前判断 React 覆盖状态。旧版事实保存在模块文档,React 状态是可反复更新的派生数据。
+
+## 对照单位
+
+矩阵一行对应一个稳定功能 ID,而不是一个 Ember route、React component 或 REST URL。同一个页面中的查看、修改、失败重试和恢复可能是不同功能;同一个接口也可能服务多个功能。
+
+对每个功能必须核对以下维度:
+
+| 维度 | `COVERED` 的最低要求 |
+| --- | --- |
+| 入口 | route、菜单、按钮、弹窗或自动触发点存在,深链和返回路径等价 |
+| 可见条件 | permission、feature flag、stack/service/component/status/maintenance/upgrade/wizard 条件等价 |
+| 正常行为 | 用户输入、校验、确认、请求顺序、成功结果和模型刷新等价 |
+| API | method、URL、predicate、fields、payload、header、operation level、context 和 response 分支等价 |
+| 异步行为 | request ID、polling/STOMP、task detail、日志、终止状态和进度展示等价 |
+| 异常行为 | 禁用、错误信息、retry、skip、rollback、cancel、partial failure 和重复提交保护等价 |
+| 恢复 | 刷新、崩溃、另一窗口、向导所有权和服务端状态恢复等价 |
+| 测试 | 至少有自动化测试;`STATIC_ONLY/CONDITIONAL` 还需记录真实集群场景证据 |
+
+任何一个必需维度不等价,都不能标 `COVERED`。
+
+## 状态枚举
+
+| 状态 | 定义 |
+| --- | --- |
+| `COVERED` | UI、API、条件、异常、恢复和测试均与旧版基线等价 |
+| `PARTIAL` | 主路径存在,但一个或多个必需维度尚未实现或缺少证据 |
+| `MISSING` | React 没有可达入口/行为,或只有无功能的页面壳 |
+| `BEHAVIOR_DIFF` | React 有意或无意改变旧版行为;必须记录差异、影响和维护者决策 |
+| `NOT_APPLICABLE` | 经维护者明确决定不迁移;必须给出理由/issue,不得用于隐藏缺失 |
+| `NEEDS_RUNTIME_VALIDATION` | 静态代码看似覆盖,但需真实 stack、KDC、HA、外部 DB/LDAP/Log Search 等环境确认 |
+| `BLOCKED` | 被明确的后端、依赖或基础设施问题阻塞;必须关联 issue,不能代替 `MISSING` |
+
+`PLACEHOLDER` 和 `OUT_OF_SCOPE` 是旧版证据等级/范围,不是 React 实现状态。旧版为 `PLACEHOLDER` 的条目通常不进入必做矩阵;Metrics `OUT_OF_SCOPE` 永远不作为缺口。
+
+## 矩阵字段
+
+| 字段 | 必填 | 内容 |
+| --- | --- | --- |
+| `feature_id` | 是 | 模块文档中的稳定 ID |
+| `legacy_doc` | 是 | 旧版模块文档和锚点/标题 |
+| `legacy_evidence` | 是 | `CONFIRMED/STATIC_ONLY/CONDITIONAL/PLACEHOLDER/OUT_OF_SCOPE` |
+| `react_status` | 是 | 上述状态枚举 |
+| `react_route` | 条件 | 可达 route/入口;自动行为写触发位置 |
+| `react_ui` | 条件 | component/hook/store 文件 |
+| `react_api` | 条件 | API client/query/mutation 文件和请求名/endpoint |
+| `condition_evidence` | 是 | 权限、flags、状态等价性的代码/测试证据 |
+| `happy_path_test` | 是 | unit/component/e2e/真实集群测试引用 |
+| `failure_recovery_test` | 是 | error/retry/cancel/refresh/ownership 等测试引用;不适用需说明 |
+| `differences` | 条件 | 缺失维度或有意行为变化的精确描述 |
+| `runtime_scenario` | 条件 | stack、service、KDC、HA topology、权限角色等复现条件 |
+| `issue` | 条件 | 修复/决策 issue 或 PR |
+| `reviewed_commit` | 是 | React 对照时使用的完整 Git commit |
+| `reviewed_by` | 是 | 审核人及日期 |
+
+推荐用 CSV/JSON 保存可编辑矩阵,Markdown 只作为自动渲染结果。旧版功能索引应由文档生成,禁止人工复制 ID 清单。
+
+## 行模板
+
+```csv
+feature_id,legacy_doc,legacy_evidence,react_status,react_route,react_ui,react_api,condition_evidence,happy_path_test,failure_recovery_test,differences,runtime_scenario,issue,reviewed_commit,reviewed_by
+INST-MODE-008,07-cluster-installation.md,CONFIRMED,MISSING,,,,,,,,,,,
+KRB-MODE-003,08-kerberos.md,CONDITIONAL,NEEDS_RUNTIME_VALIDATION,/main/admin/kerberos,...,...,...,...,...,,Existing IPA with Kerberos enabled,...,...,...
+```
+
+## 对照工作流
+
+1. 在固定 Ember baseline commit 上重跑提取器和基线检查,冻结功能 ID 与接口目录。
+2. 在固定 React commit 上扫描 routes、UI actions、API clients、permissions 和 tests,先建立静态候选关联。
+3. 逐功能核对全部维度;不能仅因 route 或 endpoint 存在就标覆盖。
+4. 对安装、Kerberos、HA/Federation、upgrade、external DB/LDAP/Log Search 等场景执行真实集群测试。
+5. 由第二位 reviewer 复查所有 `COVERED` 和 `NOT_APPLICABLE`;复杂长流程至少再做一次故障注入/刷新恢复。
+6. 每次 React PR 只更新受影响行的状态和证据,不改旧版事实;发现旧版基线错误时单独提交基线修正及源码证据。
+
+## 复杂流程场景矩阵
+
+下列场景不能由单一 happy path 代表:
+
+| 场景组 | 至少覆盖的变量 |
+| --- | --- |
+| Cluster Installation | Public/Local repository;VDF file/URL;SSH/manual Agent;warning acceptance;bootstrap/registration failure;install/start/check retry;刷新恢复 |
+| Add Host/Add Service | Kerberos off/automatic/manual;有/无 master/slave/config step;config group;descriptor/CSV;partial deployment failure |
+| Kerberos | MIT/AD/IPA/Manual;KDC connection failure;lost heartbeat;descriptor create/update;Step 7 force retry;Disable skip;credential persistent/temporary;keytab all/missing/restart |
+| NameNode/JournalNode HA | secure/non-secure;依赖 service动态任务;checkpoint等待;JN add/remove/delete-only;auto/manual rollback;刷新/另窗口 |
+| RM/Ranger HA | service/topology前置失败;host冲突;Stack Advisor changes;条件 HAWQ/HDFS配置;progress failure/retry |
+| Federation/HAWQ | NameNode/Router federation;format/bootstrap failure;Kerberos;旧 HAWQ stack 的 add/remove/activate standby |
+| Upgrade | Rolling/Express/Host Ordered;precheck required/warning/bypass;pause/resume/retry/skip/abort/downgrade/finalize;non-owner |
+| Permissions | 至少 read-only、service operator、cluster admin、Ambari admin、View-only;upgrade/wizard互斥下再次验证 |
+
+## 评审门禁
+
+一个模块可以宣布迁移完成,必须满足:
+
+1. 该模块所有纳入范围的 feature IDs 都有矩阵行且没有空白状态。
+2. `MISSING/PARTIAL/BLOCKED/BEHAVIOR_DIFF` 均有 issue 或明确维护决策。
+3. 所有 `COVERED` 都有正常路径和异常/恢复证据;无异常路径的简单只读项需明确写 `not applicable` 原因。
+4. 所有 `CONDITIONAL/STATIC_ONLY/NEEDS_RUNTIME_VALIDATION` 都有运行场景和测试结果。
+5. API 反向扫描没有未关联 mutation,route/action 反向扫描没有未关联可达用户行为。
+6. Metrics 排除项没有被错误计入完成率。
diff --git a/docs/frontend-refactor/ember-baseline/generated/ajax-calls.md b/docs/frontend-refactor/ember-baseline/generated/ajax-calls.md
new file mode 100644
index 0000000000..222693932b
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/generated/ajax-calls.md
@@ -0,0 +1,440 @@
+# Ember 非 Metrics AJAX 调用点
+
+> 由 `tools/extract-ember-baseline.mjs` 生成。每行是一个 `App.ajax.send(...)` 调用点;动态请求按 `tools/contracts/dynamic-ajax-resolutions.mjs` 的人工审计契约解析。参数键只列调用对象中 `data: {...}` 的顶层键。
+
+共 394 个调用点,其中 364 个命中纳入范围的注册请求,3 个静态请求名未在注册表定义,27 个使用动态表达式。
+
+| 请求名/表达式 | 注册状态 | 调用参数 | 回调 | 位置 |
+| --- | --- | --- | --- | --- |
+| `router.login.clusters` | `REGISTERED` | 未发现内联 `data` | `callback` | [source](../../../../ambari-web/classic/app/controllers/application.js#L124) |
+| DYNAMIC: `queryParams.name` | `DYNAMIC` | 表达式:`queryParams.data` | `success`, `callback` | [source](../../../../ambari-web/classic/app/controllers/global/background_operations_controller.js#L178) |
+| `cluster.load_cluster_name` | `REGISTERED` | `args`, `callback`, `errorLogMessage`, `reloadPopupText`, `shouldUseDefaultHandler` | `success`, `error`, `callback` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L110) |
+| `components.get.staleConfigs` | `REGISTERED` | 未发现内联 `data` | `success`, `callback` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L263) |
+| `wizard.service_components` | `REGISTERED` | `stackUrl`, `stackVersion` | `success` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L398) |
+| `ambari.service` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L410) |
+| `router.user.authorizations` | `REGISTERED` | `userName` | `success` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L419) |
+| `hosts.all` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L460) |
+| `cluster.load_detailed_repo_version` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L501) |
+| `cluster.load_last_upgrade` | `REGISTERED` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L532) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/global/configuration_controller.js#L144) |
+| `hosts.metrics.lazy_load` | `REGISTERED` | `parameters`, `url` | `success` | [source](../../../../ambari-web/classic/app/controllers/global/update_controller.js#L404) |
+| `hosts.host_components.pre_load` | `REGISTERED` | `callback`, `url` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/global/update_controller.js#L472) |
+| `config.tags.site` | `REGISTERED` | `site` | `callback` | [source](../../../../ambari-web/classic/app/controllers/global/update_controller.js#L703) |
+| `host.logging` | `REGISTERED` | `callback`, `fields`, `hostName` | `success` | [source](../../../../ambari-web/classic/app/controllers/global/update_controller.js#L729) |
+| `router.user.privileges` | `REGISTERED` | `userName` | `success` | [source](../../../../ambari-web/classic/app/controllers/global/user_settings_controller.js#L201) |
+| `wizard.stacks` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/installer.js#L237) |
+| `wizard.stacks_versions_definitions` | `REGISTERED` | `dfd`, `stackName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/installer.js#L271) |
+| `ambari.service` | `REGISTERED` | `fields` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/installer.js#L425) |
+| DYNAMIC: `name` | `DYNAMIC` | `data`, `dfd` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/installer.js#L590) |
+| DYNAMIC: `name` | `DYNAMIC` | `data`, `dfd` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/installer.js#L656) |
+| `wizard.step1.get_supported_os_types` | `REGISTERED` | `dfd`, `stackInfo`, `stackName`, `stackVersion`, `versionDefinition` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/installer.js#L701) |
+| `admin.stack_versions.edit.repo` | `REGISTERED` | `repoVersion`, `repoVersionId`, `stackName`, `stackVersion` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/installer.js#L822) |
+| `wizard.advanced_repositories.valid_url` | `REGISTERED` | `data`, `dfd`, `osId`, `osType`, `repoId`, `stackName`, `stackVersion` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/installer.js#L909) |
+| `cluster.save_provisioning_state` | `REGISTERED` | `state` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/installer.js#L1123) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js#L50) |
+| `admin.get.all_configurations` | `REGISTERED` | `urlParams` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js#L62) |
+| `common.service.configurations` | `REGISTERED` | `desired_config` | `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js#L96) |
+| `common.service.multiConfigurations` | `REGISTERED` | `configs` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step4_controller.js#L54) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/step3_controller.js#L53) |
+| `admin.get.all_configurations` | `REGISTERED` | `urlParams` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/step3_controller.js#L72) |
+| `common.service.multiConfigurations` | `REGISTERED` | `configs` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js#L76) |
+| `nameNode.federation.formatNameNode` | `REGISTERED` | `host` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js#L118) |
+| `nameNode.federation.formatZKFC` | `REGISTERED` | `host` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js#L130) |
+| `nameNode.federation.bootstrapNameNode` | `REGISTERED` | `host` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js#L164) |
+| `restart.custom.filter` | `REGISTERED` | `context`, `filter` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js#L184) |
+| `service.item.executeCustomCommand` | `REGISTERED` | `command`, `componentName`, `context`, `hosts`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js#L42) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js#L62) |
+| `reassign.load_configs` | `REGISTERED` | `type`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js#L71) |
+| `common.service.configurations` | `REGISTERED` | `desired_config` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js#L93) |
+| `config.tags` | `REGISTERED` | `serviceConfig` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js#L81) |
+| `reassign.load_configs` | `REGISTERED` | `serviceConfig`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js#L95) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step4_controller.js#L43) |
+| `reassign.load_configs` | `REGISTERED` | `type`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step4_controller.js#L52) |
+| `common.service.configurations` | `REGISTERED` | `desired_config` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step4_controller.js#L72) |
+| `service.item.executeCustomCommand` | `REGISTERED` | `command`, `componentName`, `context`, `hosts`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/removeStandby/step3_controller.js#L41) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/removeStandby/step3_controller.js#L61) |
+| `reassign.load_configs` | `REGISTERED` | `type`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/removeStandby/step3_controller.js#L70) |
+| `common.service.configurations` | `REGISTERED` | `desired_config` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/removeStandby/step3_controller.js#L87) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js#L65) |
+| `admin.get.all_configurations` | `REGISTERED` | `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js#L80) |
+| `admin.high_availability.getNnCheckPointsStatuses` | `REGISTERED` | 表达式:`{ hostNames }` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/journalNode/step3_controller.js#L50) |
+| `common.service.configurations` | `REGISTERED` | `desired_config` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/journalNode/step4_controller.js#L71) |
+| `admin.high_availability.getNnCheckPointStatus` | `REGISTERED` | `hostName` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollbackHA/step2_controller.js#L26) |
+| `common.services.update` | `REGISTERED` | `ServiceInfo`, `context` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L222) |
+| `admin.high_availability.load_hbase_configs` | `REGISTERED` | `hbaseSiteTag` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L238) |
+| `admin.high_availability.load_accumulo_configs` | `UNREGISTERED` | `accumuloSiteTag` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L251) |
+| `admin.high_availability.load_hawq_configs` | `UNREGISTERED` | `tagName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L265) |
+| `common.services.update` | `REGISTERED` | `ServiceInfo`, `context` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L347) |
+| `admin.save_configs` | `REGISTERED` | `properties`, `siteName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L363) |
+| `admin.save_configs` | `REGISTERED` | `properties`, `siteName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L376) |
+| `admin.save_configs` | `REGISTERED` | `properties`, `siteName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L390) |
+| `admin.high_availability.load_configs` | `REGISTERED` | `coreSiteTag`, `hdfsSiteTag` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L413) |
+| `admin.save_configs` | `REGISTERED` | `properties`, `siteName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L427) |
+| `admin.save_configs` | `REGISTERED` | `properties`, `siteName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L437) |
+| `common.delete.host_component` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L460) |
+| `common.host.host_component.passive` | `REGISTERED` | `callback`, `componentName`, `hostName`, `passive_state`, `taskNum` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L478) |
+| `admin.high_availability.getHostComponent` | `REGISTERED` | `callback`, `componentName`, `hostName`, `taskNum` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L500) |
+| `common.delete.host_component` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js#L549) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js#L64) |
+| `admin.get.all_configurations` | `REGISTERED` | `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js#L213) |
+| `admin.high_availability.getNnCheckPointStatus` | `REGISTERED` | `hostName` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step4_controller.js#L37) |
+| `common.host.host_component.passive` | `REGISTERED` | `componentName`, `hostName`, `passive_state` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step5_controller.js#L51) |
+| `common.service.configurations` | `REGISTERED` | `desired_config` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step5_controller.js#L82) |
+| `admin.high_availability.getJnCheckPointStatus` | `REGISTERED` | `hostName` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step6_controller.js#L79) |
+| `common.service.multiConfigurations` | `REGISTERED` | `configs` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step9_controller.js#L227) |
+| `common.service.configurations` | `REGISTERED` | `desired_config` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step9_controller.js#L246) |
+| `common.delete.host_component` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step9_controller.js#L310) |
+| DYNAMIC: `name` | `DYNAMIC` | `requestId`, `stageId` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/progress_popup_controller.js#L122) |
+| `background_operations.get_by_task` | `REGISTERED` | `requestId`, `taskId` | `success`, `callback` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/progress_popup_controller.js#L274) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/rangerAdmin/step4_controller.js#L45) |
+| `reassign.load_configs` | `REGISTERED` | `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/rangerAdmin/step4_controller.js#L61) |
+| `common.service.multiConfigurations` | `REGISTERED` | `configs` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/rangerAdmin/step4_controller.js#L87) |
+| `config.tags` | `REGISTERED` | `serviceConfig` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js#L69) |
+| `reassign.load_configs` | `REGISTERED` | `serviceConfig`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js#L85) |
+| `config.recommendations` | `REGISTERED` | `dataToSend`, `stackVersionUrl` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js#L201) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step4_controller.js#L65) |
+| `reassign.load_configs` | `REGISTERED` | `type`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step4_controller.js#L74) |
+| `reassign.load_configs` | `REGISTERED` | `type`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step4_controller.js#L87) |
+| `reassign.load_configs` | `REGISTERED` | `type`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step4_controller.js#L100) |
+| `common.service.configurations` | `REGISTERED` | `desired_config` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step4_controller.js#L120) |
+| `admin.kerberos_security.regenerate_keytabs` | `REGISTERED` | `type`, `withAutoRestart` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos.js#L182) |
+| `admin.kerberos_security.checks` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos.js#L234) |
+| `admin.security_status` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos.js#L307) |
+| `kerberos.session.state` | `REGISTERED` | `callback` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos.js#L422) |
+| `admin.security.cluster_configs.kerberos` | `REGISTERED` | `additionalCallback`, `clusterName` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos.js#L449) |
+| `admin.kerberos.cluster.artifact.update` | `REGISTERED` | `artifactName`, `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos.js#L556) |
+| `admin.kerberos.cluster.artifact.create` | `REGISTERED` | 表达式:`{ artifactName, data }` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos.js#L583) |
+| `admin.unkerberize.cluster` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/disable_controller.js#L47) |
+| `admin.unkerberize.cluster.skip` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/disable_controller.js#L56) |
+| `common.delete.service` | `REGISTERED` | `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/disable_controller.js#L65) |
+| `common.across.services.configurations` | `REGISTERED` | `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step2_controller.js#L228) |
+| `common.cluster.update` | `REGISTERED` | `clusterName`, `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step2_controller.js#L312) |
+| `common.services.update` | `REGISTERED` | `ServiceInfo`, `context`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step3_controller.js#L42) |
+| `hosts.heartbeat_lost` | `REGISTERED` | `clusterName` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step3_controller.js#L66) |
+| `common.service_component.info` | `REGISTERED` | `componentName`, `serviceName`, `urlParams` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step3_controller.js#L76) |
+| `service.item.smoke` | `REGISTERED` | `actionName`, `displayName`, `operationLevel`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step3_controller.js#L89) |
+| `config.recommendations` | `REGISTERED` | `dataToSend`, `stackVersionUrl` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step4_controller.js#L342) |
+| `admin.kerberos.cluster.csv` | `REGISTERED` | `skipDownload` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step5_controller.js#L63) |
+| `admin.unkerberize.cluster` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step5_controller.js#L103) |
+| `common.services.update` | `REGISTERED` | `ServiceInfo`, `context` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step6_controller.js#L25) |
+| `common.delete.host_component` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step6_controller.js#L59) |
+| `admin.unkerberize.cluster` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step7_controller.js#L69) |
+| `common.services.update` | `REGISTERED` | `ServiceInfo`, `context`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/step8_controller.js#L26) |
+| `wizard.step8.create_selected_services` | `REGISTERED` | `cluster`, `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/wizard_controller.js#L203) |
+| `common.delete.service` | `REGISTERED` | `serviceName` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/wizard_controller.js#L229) |
+| `admin.unkerberize.cluster` | `REGISTERED` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/wizard_controller.js#L244) |
+| `wizard.step8.register_host_to_component` | `REGISTERED` | `cluster`, `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/kerberos/wizard_controller.js#L274) |
+| `components.get_category` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/service_auto_start.js#L133) |
+| `admin.save_configs` | `REGISTERED` | `properties`, `siteName` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/service_auto_start.js#L151) |
+| `components.update` | `REGISTERED` | `ServiceComponentInfo`, `query` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/service_auto_start.js#L163) |
+| DYNAMIC: `(onlyState) ? 'admin.upgrade.state' : 'admin.upgrade.data'` | `DYNAMIC` | `id` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L461) |
+| `admin.upgrade.get_compatible_versions` | `REGISTERED` | `stackName`, `stackVersion` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L500) |
+| `admin.upgrade.upgrade_item` | `REGISTERED` | `groupId`, `stageId`, `upgradeId` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L626) |
+| `admin.upgrade.upgrade_task` | `REGISTERED` | `groupId`, `stageId`, `task`, `taskId`, `upgradeId` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L680) |
+| `admin.upgrade.abort` | `REGISTERED` | `isDowngrade`, `upgradeId` | `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L773) |
+| `admin.downgrade.start` | `REGISTERED` | `id`, `isDowngrade`, `label`, `upgradeType`, `value` | `success`, `callback` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L790) |
+| `admin.upgrade.suspend` | `REGISTERED` | `isDowngrade`, `upgradeId` | `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L812) |
+| `admin.upgrade.retry` | `REGISTERED` | `upgradeId` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L861) |
+| `admin.upgrade.start` | `REGISTERED` | 表达式:`version` | `success`, `error`, `callback` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L876) |
+| `admin.upgrade.update.options` | `REGISTERED` | `skipComponentFailures`, `skipSCFailures`, `upgradeId` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1233) |
+| `admin.upgrade.pre_upgrade_check` | `REGISTERED` | 表达式:`data` | `success`, `error`, `callback` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1295) |
+| `admin.upgrade.get_supported_upgradeTypes` | `REGISTERED` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1318) |
+| `admin.upgrade.pre_upgrade_check` | `REGISTERED` | 表达式:`params` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1462) |
+| `admin.stack_version.install.repo_version` | `REGISTERED` | 表达式:`data` | `success`, `error`, `callback` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1595) |
+| `host.host_component.delete_components` | `REGISTERED` | `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1654) |
+| `common.host_components.update` | `REGISTERED` | `HostRoles`, `context`, `query` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1679) |
+| `admin.stack_versions.edit.repo` | `REGISTERED` | `repoVersion`, `repoVersionId`, `stackName`, `stackVersion` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1769) |
+| `admin.stack_versions.validate.repo` | `REGISTERED` | `baseUrl`, `osType`, `repo`, `repoId`, `repoName`, `stackName`, `stackVersion` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1832) |
+| `admin.upgrade.finalizeContext` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L1962) |
+| `admin.upgrade.upgradeItem.setState` | `REGISTERED` | `groupId`, `itemId`, `status`, `upgradeId` | `callback` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L2056) |
+| `cluster.load_repositories` | `REGISTERED` | `stackName`, `stackVersion` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L2089) |
+| `admin.upgrade.revert` | `REGISTERED` | `id`, `isDowngrade`, `label`, `upgradeId`, `value` | `success`, `error`, `callback` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L2218) |
+| `admin.stack_versions.discard` | `REGISTERED` | `id`, `stackName`, `stackVersion` | `callback` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L2253) |
+| `admin.upgrade.data` | `REGISTERED` | `id` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_upgrade_history_controller.js#L83) |
+| `admin.upgrade.upgrade_item` | `REGISTERED` | `groupId`, `stageId`, `upgradeId` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_upgrade_history_controller.js#L160) |
+| `admin.upgrade.upgrade_task` | `REGISTERED` | `groupId`, `stageId`, `task`, `taskId`, `upgradeId` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_upgrade_history_controller.js#L213) |
+| `alerts.update_alert_definition` | `REGISTERED` | `data`, `id` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alert_definitions_controller.js#L109) |
+| `alerts.create_alert_definition` | `REGISTERED` | `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/add_alert_definition/add_alert_definition_controller.js#L37) |
+| `admin.save_configs` | `REGISTERED` | `properties`, `siteName` | `error` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/alert_definitions_actions_controller.js#L239) |
+| DYNAMIC: `ajaxData` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/alert_instances_controller.js#L118) |
+| `alerts.update_alert_definition` | `REGISTERED` | `data`, `id` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/definition_configs_controller.js#L653) |
+| `alerts.get_instances_history` | `REGISTERED` | `definitionName`, `timestamp` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js#L90) |
+| `alerts.update_alert_definition` | `REGISTERED` | `data`, `id` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js#L154) |
+| `alerts.delete_alert_definition` | `REGISTERED` | `id` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js#L188) |
+| `alerts.update_alert_definition` | `REGISTERED` | `data`, `id` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js#L245) |
+| `alerts.update_alert_definition` | `REGISTERED` | `data`, `id` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js#L264) |
+| `alerts.update_alert_definition` | `REGISTERED` | `data`, `id` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js#L308) |
+| `alerts.notifications` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js#L209) |
+| DYNAMIC: `sendData` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js#L493) |
+| DYNAMIC: `sendData` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js#L530) |
+| DYNAMIC: `sendData` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js#L561) |
+| `alerts.notifications` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js#L362) |
+| `alerts.create_alert_notification` | `REGISTERED` | `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js#L859) |
+| `alerts.update_alert_notification` | `REGISTERED` | `data`, `id` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js#L897) |
+| `alerts.delete_alert_notification` | `REGISTERED` | `id` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js#L938) |
+| `alerts.update_alert_notification` | `REGISTERED` | `data`, `id` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js#L976) |
+| `service.serviceConfigVersions.get.total` | `REGISTERED` | 表达式:`{}` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/dashboard/config_history_controller.js#L140) |
+| `service.serviceConfigVersions.get.suggestions` | `REGISTERED` | 表达式:`{ key }` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/dashboard/config_history_controller.js#L209) |
+| `config_groups.all_fields` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/addHost/step4_controller.js#L27) |
+| `config_groups.update_config_group` | `REGISTERED` | `configGroup`, `id` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/host/add_controller.js#L269) |
+| `common.host_components.update` | `REGISTERED` | `HostRoles`, `context`, `level`, `query` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/add_controller.js#L445) |
+| `common.host_components.update` | `REGISTERED` | `HostRoles`, `context`, `hostName`, `noOpsMessage`, `query` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L149) |
+| `common.host_components.update` | `REGISTERED` | `HostRoles`, `context`, `noOpsMessage`, `query` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L250) |
+| `common.hosts.delete` | `REGISTERED` | `hosts`, `query` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L374) |
+| `bulk_request.hosts.passive_state` | `REGISTERED` | `hostNames`, `passive_state`, `requestInfo` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L501) |
+| `host.host_component.add_new_components` | `REGISTERED` | `context`, `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L591) |
+| `common.host_components.update` | `REGISTERED` | `HostRoles`, `context`, `query` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L618) |
+| `host.host_component.delete_components` | `REGISTERED` | `componentName`, `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L717) |
+| `common.host_components.update` | `REGISTERED` | `HostRoles`, `context`, `level`, `noOpsMessage`, `query` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L830) |
+| `bulk_request.decommission` | `REGISTERED` | `componentName`, `context`, `noOpsMessage`, `parameters`, `serviceName` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L921) |
+| `host_components.hbase_regionserver.active` | `REGISTERED` | `hostNames` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L953) |
+| `hosts.bulk.operations` | `REGISTERED` | `operationData`, `parameters` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L1096) |
+| `hosts.with_searchTerm` | `REGISTERED` | `facet`, `page_size`, `searchTerm` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/combo_search_box.js#L27) |
+| DYNAMIC: `(Array.isArray(component)) ? 'common.host.host_components.update' : 'common.host.host_component.update'` | `DYNAMIC` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L337) |
+| `common.host_component.getNnCheckPointTime` | `REGISTERED` | `host` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L421) |
+| DYNAMIC: `(Em.isNone(componentName)) ? 'common.delete.host' : 'common.delete.host_component'` | `DYNAMIC` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L606) |
+| `host.host_component.upgrade` | `UNREGISTERED` | `component`, `componentName`, `data`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L654) |
+| `admin.get.all_configurations` | `REGISTERED` | `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L926) |
+| `admin.get.all_configurations` | `REGISTERED` | `urlParams` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L960) |
+| `admin.get.all_configurations` | `REGISTERED` | `urlParams` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L979) |
+| `admin.get.all_configurations` | `REGISTERED` | `webHCat` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1180) |
+| `admin.get.all_configurations` | `REGISTERED` | 表达式:`{ urlParams }` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1202) |
+| `common.service.configurations` | `REGISTERED` | `componentName`, `desired_config` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1335) |
+| `admin.get.all_configurations` | `REGISTERED` | 表达式:`{ urlParams }` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1423) |
+| `host.host_component.refresh_configs` | `REGISTERED` | `context`, `resource_filters` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1661) |
+| `config.tags` | `REGISTERED` | `callback` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1688) |
+| `reassign.load_configs` | `REGISTERED` | `urlParams` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1718) |
+| `common.host.host_component.update` | `REGISTERED` | `HostRoles`, `component`, `componentName`, `context`, `hostName`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1838) |
+| `host.host_component.decommission_slave` | `REGISTERED` | `command`, `componentName`, `context`, `hostName`, `serviceName`, `slaveType` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1955) |
+| `host.region_servers.in_inservice` | `REGISTERED` | `hostNames` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L1992) |
+| `common.batch.request_schedules` | `REGISTERED` | `batches`, `intervalTimeSeconds`, `tolerateSize` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L2142) |
+| `common.batch.request_schedules` | `REGISTERED` | `batches`, `intervalTimeSeconds`, `tolerateSize` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L2250) |
+| `bulk_request.hosts.passive_state` | `REGISTERED` | `hostNames`, `passive_state`, `requestInfo` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L2341) |
+| `common.host.host_component.passive` | `REGISTERED` | `component`, `componentName`, `context`, `hostName`, `passive_state` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L2374) |
+| `common.delete.host` | `REGISTERED` | `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L2828) |
+| `service.item.executeCustomCommand` | `REGISTERED` | `command`, `componentName`, `context`, `hosts`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L3046) |
+| `common.batch.request_schedules` | `REGISTERED` | `batches`, `intervalTimeSeconds`, `tolerateSize` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L3194) |
+| `admin.kerberos_security.regenerate_keytabs.host` | `REGISTERED` | `clusterName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L3267) |
+| `common.services.update` | `REGISTERED` | `ServiceInfo`, `context`, `query` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service.js#L163) |
+| `restart.allServices` | `REGISTERED` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/service.js#L183) |
+| `common.services.update` | `REGISTERED` | `ServiceInfo`, `context` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service.js#L202) |
+| `common.services.update` | `REGISTERED` | `ServiceInfo`, `context` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service.js#L253) |
+| `restart.staleConfigs` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service.js#L348) |
+| DYNAMIC: `name` | `DYNAMIC` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/add_controller.js#L468) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `callback` | [source](../../../../ambari-web/classic/app/controllers/main/service/info/summary.js#L148) |
+| `reassign.load_configs` | `REGISTERED` | `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/info/summary.js#L178) |
+| `service.flume.agent.command` | `REGISTERED` | `agentName`, `context`, `host`, `state` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/info/summary.js#L317) |
+| `common.service.hdfs.getNnCheckPointTime` | `REGISTERED` | 表达式:`{ clusterIdValue }` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L405) |
+| `common.service.host_component.update` | `REGISTERED` | `query`, `serviceName`, `state` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L572) |
+| `common.service.update` | `REGISTERED` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L599) |
+| `service.item.refreshQueueYarnRequest` | `REGISTERED` | `command`, `componentName`, `context`, `forceRefreshConfigTags`, `hosts`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L645) |
+| `service.item.startStopLdapKnox` | `REGISTERED` | `command`, `componentName`, `context`, `host`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L692) |
+| `service.item.executeCustomCommand` | `REGISTERED` | `command`, `componentName`, `context`, `hosts`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L740) |
+| `common.batch.request_schedules` | `REGISTERED` | `batches`, `intervalTimeSeconds`, `tolerateSize` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L788) |
+| `service.item.rebalanceHdfsNodes` | `REGISTERED` | `hosts`, `threshold` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L842) |
+| `admin.kerberos_security.regenerate_keytabs.service` | `REGISTERED` | `clusterName`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L889) |
+| `service.item.updateHBaseReplication` | `REGISTERED` | `hosts`, `replication_cluster_keys`, `replication_peers` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L975) |
+| `service.item.stopHBaseReplication` | `REGISTERED` | `hosts`, `replication_peers` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L1040) |
+| `service.item.smoke` | `REGISTERED` | 表达式:`requestData` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L1292) |
+| `service.item.executeCustomCommand` | `REGISTERED` | `command`, `componentName`, `context`, `hosts`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L1544) |
+| `service.item.executeCustomCommand` | `REGISTERED` | `command`, `componentName`, `context`, `forceRefreshConfigTags`, `hosts`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L1568) |
+| `common.delete.service` | `REGISTERED` | `serviceName`, `servicesToDeleteNext` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L2057) |
+| `hosts.config_groups` | `REGISTERED` | 表达式:`{}` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/manage_config_groups_controller.js#L307) |
+| `service.load_config_groups` | `REGISTERED` | `serviceName` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/manage_config_groups_controller.js#L445) |
+| `config.host_overrides` | `REGISTERED` | `params`, `typeTagToGroupMap` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/manage_config_groups_controller.js#L539) |
+| `hosts.info.install` | `REGISTERED` | `hostNames` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/manage_config_groups_controller.js#L1057) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step1_controller.js#L44) |
+| `reassign.load_configs` | `REGISTERED` | `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step1_controller.js#L78) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step3_controller.js#L292) |
+| `reassign.load_configs` | `REGISTERED` | `urlParams` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step3_controller.js#L315) |
+| `common.host.host_component.passive` | `REGISTERED` | `componentName`, `hostName`, `passive_state` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L254) |
+| `common.across.services.configurations` | `REGISTERED` | `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L293) |
+| `common.delete.host_component` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L442) |
+| `service.mysql.clean` | `REGISTERED` | `host` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L485) |
+| `service.mysql.configure` | `REGISTERED` | `host` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L506) |
+| `common.host.host_component.update` | `REGISTERED` | `HostRoles`, `componentName`, `context`, `hostName`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L518) |
+| `restart.hostComponents` | `REGISTERED` | `context`, `operation_level`, `resource_filters` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L551) |
+| `common.host.host_component.update` | `REGISTERED` | `HostRoles`, `componentName`, `context`, `hostName`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L565) |
+| `cluster.custom_action.create` | `REGISTERED` | `filteredHosts`, `requestInfo` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L663) |
+| `custom_action.request` | `REGISTERED` | `requestId` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L681) |
+| `custom_action.request` | `REGISTERED` | `requestId`, `taskId` | `success` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js#L705) |
+| `common.delete.host_component` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step6_controller.js#L133) |
+| `common.host.host_component.passive` | `REGISTERED` | `componentName`, `hostName`, `passive_state` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step6_controller.js#L160) |
+| `common.host.host_component.update` | `REGISTERED` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step6_controller.js#L201) |
+| `common.host.host_component.passive` | `REGISTERED` | `componentName`, `hostName`, `passive_state` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step7_controller.js#L65) |
+| `common.delete.host_component` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/service/reassign/step7_controller.js#L84) |
+| `views.info` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/views_controller.js#L49) |
+| `views.instances` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/main/views_controller.js#L60) |
+| DYNAMIC: `isRetry ? 'common.host_components.update' : 'common.services.update'` | `DYNAMIC` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard.js#L426) |
+| `wizard.launch_bootstrap` | `REGISTERED` | `bootStrapData`, `popup` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard.js#L514) |
+| `wizard.service_components` | `REGISTERED` | `stackUrl`, `stackVersion` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard.js#L629) |
+| `cluster.load_current_repo_stack_services` | `REGISTERED` | `clusterName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard.js#L686) |
+| `hosts.confirmed` | `REGISTERED` | 表达式:`{}` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard.js#L1281) |
+| `ambari.service` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step2_controller.js#L522) |
+| `wizard.step3.bootstrap` | `REGISTERED` | `bootRequestId`, `callback`, `numPolls`, `shouldUseDefaultHandler`, `timeout` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step3_controller.js#L484) |
+| `wizard.step3.is_hosts_registered` | `REGISTERED` | `callback`, `shouldUseDefaultHandler`, `timeout` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step3_controller.js#L574) |
+| `wizard.step3.is_hosts_registered` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step3_controller.js#L669) |
+| `ambari.service` | `REGISTERED` | `fields` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step3_controller.js#L706) |
+| `wizard.step3.jdk_check` | `REGISTERED` | `host_names`, `java_home`, `jdk_location` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step3_controller.js#L731) |
+| `wizard.step3.jdk_check.get_results` | `REGISTERED` | `requestIndex` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step3_controller.js#L750) |
+| `request_schedule.get.pending` | `REGISTERED` | `hostComponent` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step7/assign_master_controller.js#L85) |
+| `service.serviceConfigVersions.get.current.not.default` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step7_controller.js#L420) |
+| `config.tags` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step7_controller.js#L1175) |
+| `ambari.service` | `REGISTERED` | `fields` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step7_controller.js#L1565) |
+| `admin.kerberize.cluster_descriptor_artifact` | `REGISTERED` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/wizard/step7_controller.js#L1883) |
+| `cluster.load_repo_version` | `REGISTERED` | `repositoryVersion`, `stackName` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step8_controller.js#L346) |
+| `cluster.load_repositories` | `REGISTERED` | `stackName`, `stackVersion` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step8_controller.js#L403) |
+| `wizard.step8.existing_cluster_names` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step8_controller.js#L718) |
+| `common.delete.cluster` | `REGISTERED` | `isLast`, `name` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step8_controller.js#L753) |
+| `wizard.get_version_definitions` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step8_controller.js#L835) |
+| `wizard.delete_repository_versions` | `REGISTERED` | `id`, `stackName`, `stackVersion` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step8_controller.js#L862) |
+| DYNAMIC: `ajaxOpts` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/controllers/wizard/step8_controller.js#L906) |
+| DYNAMIC: `name` | `DYNAMIC` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step9_controller.js#L511) |
+| `background_operations.get_by_task` | `REGISTERED` | `clusterName`, `requestId`, `taskId` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step9_controller.js#L989) |
+| `wizard.step9.load_log` | `REGISTERED` | `args`, `callback`, `cluster`, `numPolls`, `polling`, `requestId`, `timeout` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step9_controller.js#L1040) |
+| `wizard.step9.installer.get_host_status` | `REGISTERED` | `cluster` | `success`, `error` | [source](../../../../ambari-web/classic/app/controllers/wizard/step9_controller.js#L1108) |
+| `ambari.service` | `REGISTERED` | `fields` | `success` | [source](../../../../ambari-web/classic/app/controllers/wizard/step9_controller.js#L1225) |
+| `hosts.ips` | `REGISTERED` | `hostNames` | 默认处理 | [source](../../../../ambari-web/classic/app/mappers/service_metrics_mapper.js#L334) |
+| `hiveServerInteractive.getStatus` | `REGISTERED` | `hsiHost`, `port` | 默认处理 | [source](../../../../ambari-web/classic/app/mappers/service_metrics_mapper.js#L352) |
+| `service.serviceConfigVersions.get.multiple` | `REGISTERED` | `serviceConfigVersions`, `serviceName` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_comparator.js#L392) |
+| `service.serviceConfigVersions.get` | `REGISTERED` | `serviceName` | `success` | [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_loader.js#L48) |
+| `service.serviceConfigVersions.get.current` | `REGISTERED` | `serviceNames` | `success` | [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_loader.js#L109) |
+| `service.serviceConfigVersions.get.multiple` | `REGISTERED` | `additionalParams`, `serviceConfigVersions`, `serviceName` | `success` | [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_loader.js#L176) |
+| DYNAMIC: `ajaxOptions` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_saver.js#L595) |
+| DYNAMIC: `ajaxOptions` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_saver.js#L617) |
+| DYNAMIC: `ajaxData` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_saver.js#L644) |
+| `components.filter_by_status` | `REGISTERED` | `clusterName`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_saver.js#L880) |
+| `config.recommendations` | `REGISTERED` | `dataToSend`, `stackVersionUrl` | `success`, `error`, `callback` | [source](../../../../ambari-web/classic/app/mixins/common/configs/enhanced_configs.js#L335) |
+| `wizard.loadrecommendations` | `REGISTERED` | 表达式:`recommendationData` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/common/hosts/host_component_recommendation_mixin.js#L116) |
+| `config.validations` | `REGISTERED` | 表达式:`validationData` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/common/hosts/host_component_validation_mixin.js#L95) |
+| `persist.get` | `REGISTERED` | `data`, `key` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/common/persist.js#L48) |
+| `persist.get.text` | `REGISTERED` | `key` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/common/persist.js#L67) |
+| `persist.post` | `REGISTERED` | `keyValuePair` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/common/persist.js#L128) |
+| `config.validations` | `REGISTERED` | 表达式:`validationData` | `success` | [source](../../../../ambari-web/classic/app/mixins/common/serverValidator.js#L196) |
+| `preinstalled.checks` | `REGISTERED` | `RequestInfo`, `resource_filters` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/actions/check_host.js#L144) |
+| `preinstalled.checks.tasks` | `REGISTERED` | `requestId` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/actions/check_host.js#L188) |
+| `wizard.step3.host_info` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/actions/check_host.js#L253) |
+| `wizard.step3.rerun_checks` | `REGISTERED` | 未发现内联 `data` | `success`, `error`, `callback` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/actions/check_host.js#L443) |
+| `host.stack_versions.install` | `REGISTERED` | `hostName`, `version` | `success` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/actions/install_new_version.js#L41) |
+| `host.host_component.slave_desired_admin_state` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/host_components/decommissionable.js#L157) |
+| `host.host_component.decommission_status` | `REGISTERED` | `componentName`, `hostName`, `serviceName` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/host_components/decommissionable.js#L200) |
+| `host.host_component.add_new_component` | `REGISTERED` | `component`, `data`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/host_components/install_component.js#L29) |
+| `common.host.host_component.update` | `REGISTERED` | `HostRoles`, `component`, `componentName`, `context`, `hostName`, `serviceName`, `urlParams` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/host/details/host_components/install_component.js#L67) |
+| `common.create_component` | `REGISTERED` | `componentName`, `serviceName` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/main/host/details/host_components/install_component.js#L133) |
+| DYNAMIC: `config_action.get('popupProperties').primaryButton.metaData.name` | `DYNAMIC` | `command`, `componentName`, `context`, `forceRefreshConfigTags`, `hosts`, `serviceName` | `error` | [source](../../../../ambari-web/classic/app/mixins/main/service/configs/component_actions_by_configs.js#L112) |
+| `common.batch.request_schedules` | `REGISTERED` | `batches`, `displayName`, `hostName`, `intervalTimeSeconds`, `tolerateSize` | `success` | [source](../../../../ambari-web/classic/app/mixins/main/service/configs/component_actions_by_configs.js#L232) |
+| `common.batch.request_schedules` | `REGISTERED` | `batches`, `intervalTimeSeconds`, `tolerateSize` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/main/service/configs/component_actions_by_configs.js#L296) |
+| `config_groups.create` | `REGISTERED` | `data`, `modelData` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/service/configs/config_overridable.js#L259) |
+| DYNAMIC: `sendData` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/main/service/configs/config_overridable.js#L329) |
+| DYNAMIC: `sendData` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/main/service/configs/config_overridable.js#L439) |
+| `service.serviceConfigVersions.get.current` | `REGISTERED` | `serviceNames` | `success` | [source](../../../../ambari-web/classic/app/mixins/main/service/configs/hive_interactive_check.js#L25) |
+| `configs.config_groups.load.services` | `REGISTERED` | `dfd`, `serviceNames` | `success` | [source](../../../../ambari-web/classic/app/mixins/main/service/groups_mapping.js#L40) |
+| `configs.theme` | `REGISTERED` | `serviceName`, `stackVersionUrl` | `success` | [source](../../../../ambari-web/classic/app/mixins/main/service/themes_mapping.js#L39) |
+| `configs.theme.services` | `REGISTERED` | `serviceNames`, `stackVersionUrl` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/main/service/themes_mapping.js#L73) |
+| `admin.kerberize.cluster_descriptor` | `REGISTERED` | `queryParams` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js#L421) |
+| `admin.kerberize.cluster_descriptor.stack` | `REGISTERED` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js#L431) |
+| `config.recommendations` | `REGISTERED` | `dataToSend`, `stackVersionUrl` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js#L758) |
+| `admin.kerberos.cluster.artifact.create` | `REGISTERED` | `artifactName`, `data` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js#L859) |
+| `admin.kerberos.cluster.artifact.update` | `REGISTERED` | `artifactName`, `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js#L877) |
+| DYNAMIC: `isInstaller ? 'hosts.info.install' : 'hosts.high_availability.wizard'` | `DYNAMIC` | `hostNames` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/wizard/assign_master_components.js#L604) |
+| `hosts.high_availability.wizard` | `REGISTERED` | 表达式:`{}` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardHostsLoading.js#L43) |
+| DYNAMIC: `this.get('request.ajaxName')` | `DYNAMIC` | 表达式:`this.get('request.ajaxData')` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L129) |
+| `admin.poll.kerberize.cluster.request` | `REGISTERED` | `requestId` | `success` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L158) |
+| `host_component.installed.on_hosts` | `REGISTERED` | `componentName`, `hostNames` | 默认处理 | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L417) |
+| `common.services.update` | `REGISTERED` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L462) |
+| `common.services.update` | `REGISTERED` | 表达式:`data` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L510) |
+| `wizard.step8.register_host_to_component` | `REGISTERED` | `cluster`, `componentName`, `data`, `hostName`, `serviceName`, `taskNum` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L568) |
+| `common.host_components.update` | `REGISTERED` | `HostRoles`, `componentName`, `context`, `hostName`, `query`, `serviceName`, `taskNum` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L625) |
+| `common.host.host_component.update` | `REGISTERED` | `HostRoles`, `componentName`, `hostName`, `serviceName`, `taskNum` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L652) |
+| `background_operations.get_by_request` | `REGISTERED` | `requestId` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L688) |
+| `common.delete.host_component` | `REGISTERED` | `componentName`, `hostName` | `success`, `error` | [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L768) |
+| `router.login.clusters` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/router.js#L274) |
+| `router.afterLogin` | `REGISTERED` | `loginName`, `usr` | `success`, `error` | [source](../../../../ambari-web/classic/app/router.js#L296) |
+| `router.login` | `REGISTERED` | `auth`, `loginName`, `usr` | `success`, `error` | [source](../../../../ambari-web/classic/app/router.js#L421) |
+| `router.afterLogin` | `REGISTERED` | `loginName`, `usr` | `success`, `error` | [source](../../../../ambari-web/classic/app/router.js#L442) |
+| `router.login.message` | `REGISTERED` | 表达式:`requestData` | `success`, `error` | [source](../../../../ambari-web/classic/app/router.js#L463) |
+| `router.login.clusters` | `REGISTERED` | 表达式:`requestData` | `success` | [source](../../../../ambari-web/classic/app/router.js#L571) |
+| `ambari.service.load_server_version` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/router.js#L631) |
+| `router.logoff` | `REGISTERED` | 未发现内联 `data` | `success`, `error` | [source](../../../../ambari-web/classic/app/router.js#L737) |
+| `ambari.service.load_server_version` | `REGISTERED` | 未发现内联 `data` | `success` | [source](../../../../ambari-web/classic/app/router.js#L976) |
+| DYNAMIC: `queue.shift()` | `DYNAMIC` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax_queue.js#L151) |
+| `host.host_components.filtered` | `REGISTERED` | `callback`, `fields`, `parameters` | `success` | [source](../../../../ambari-web/classic/app/utils/batch_scheduled_requests.js#L187) |
+| `restart.hostComponents` | `REGISTERED` | `context`, `operation_level`, `query`, `resource_filters` | `success`, `error` | [source](../../../../ambari-web/classic/app/utils/batch_scheduled_requests.js#L255) |
+| `common.service.passive` | `REGISTERED` | `passive_state`, `requestInfo`, `serviceName` | `success` | [source](../../../../ambari-web/classic/app/utils/batch_scheduled_requests.js#L299) |
+| `common.batch.request_schedules` | `REGISTERED` | `batches`, `intervalTimeSeconds`, `tolerateSize` | `success`, `error` | [source](../../../../ambari-web/classic/app/utils/batch_scheduled_requests.js#L332) |
+| `common.batch.request_schedules` | `REGISTERED` | `batches`, `intervalTimeSeconds`, `tolerateSize` | `success` | [source](../../../../ambari-web/classic/app/utils/batch_scheduled_requests.js#L631) |
+| `request_schedule.get` | `REGISTERED` | `request_schedule_id` | `success`, `error` | [source](../../../../ambari-web/classic/app/utils/batch_scheduled_requests.js#L713) |
+| `common.delete.request_schedule` | `REGISTERED` | `request_schedule_id` | `success`, `error` | [source](../../../../ambari-web/classic/app/utils/batch_scheduled_requests.js#L741) |
+| `config.on_site` | `REGISTERED` | `params` | 默认处理 | [source](../../../../ambari-web/classic/app/utils/config.js#L818) |
+| `configs.stack_configs.load.cluster_configs` | `REGISTERED` | `stackVersionUrl` | `success` | [source](../../../../ambari-web/classic/app/utils/config.js#L1044) |
+| DYNAMIC: `name` | `DYNAMIC` | `serviceList`, `stackVersionUrl` | `success` | [source](../../../../ambari-web/classic/app/utils/config.js#L1063) |
+| `credentials.create` | `REGISTERED` | `alias`, `clusterName`, `resource` | `error` | [source](../../../../ambari-web/classic/app/utils/credentials.js#L68) |
+| `credentials.get` | `REGISTERED` | `alias`, `callback`, `clusterName` | `success`, `error` | [source](../../../../ambari-web/classic/app/utils/credentials.js#L126) |
+| `credentials.update` | `REGISTERED` | `alias`, `clusterName`, `resource` | 默认处理 | [source](../../../../ambari-web/classic/app/utils/credentials.js#L157) |
+| `credentials.list` | `REGISTERED` | `callback`, `clusterName` | `success` | [source](../../../../ambari-web/classic/app/utils/credentials.js#L176) |
+| `credentials.delete` | `REGISTERED` | `alias`, `clusterName` | 默认处理 | [source](../../../../ambari-web/classic/app/utils/credentials.js#L194) |
+| `credentials.store.info` | `REGISTERED` | `callback`, `clusterName` | `success` | [source](../../../../ambari-web/classic/app/utils/credentials.js#L212) |
+| `background_operations.abort_request` | `REGISTERED` | `requestId`, `requestName`, `serviceInfo` | `success`, `error` | [source](../../../../ambari-web/classic/app/utils/host_progress_popup.js#L254) |
+| `bulk_request.hosts.update_rack_id` | `REGISTERED` | `hostNames`, `hostNamesArray`, `rackId`, `requestInfo` | `success`, `error` | [source](../../../../ambari-web/classic/app/utils/hosts.js#L247) |
+| `background_operations.get_by_task` | `REGISTERED` | `requestId`, `taskId` | `success` | [source](../../../../ambari-web/classic/app/utils/polling.js#L117) |
+| `background_operations.get_by_request` | `REGISTERED` | `callback`, `requestId` | `success`, `error` | [source](../../../../ambari-web/classic/app/utils/polling.js#L148) |
+| `service.serviceConfigVersion.revert` | `REGISTERED` | `data` | `success` | [source](../../../../ambari-web/classic/app/views/common/configs/config_versions_control_view.js#L200) |
+| DYNAMIC: `(isServiceInstalled) ? 'cluster.custom_action.create' : 'custom_action.create'` | `DYNAMIC` | `filteredHosts`, `requestInfo` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/common/configs/widgets/test_db_connection_widget_view.js#L226) |
+| `custom_action.request` | `REGISTERED` | `requestId` | `success` | [source](../../../../ambari-web/classic/app/views/common/configs/widgets/test_db_connection_widget_view.js#L246) |
+| `custom_action.request` | `REGISTERED` | `requestId`, `taskId` | `success` | [source](../../../../ambari-web/classic/app/views/common/configs/widgets/test_db_connection_widget_view.js#L267) |
+| `admin.kerberos_security.test_connection` | `REGISTERED` | `kdcHostname` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/common/controls_view.js#L1332) |
+| DYNAMIC: `(isServiceInstalled) ? 'cluster.custom_action.create' : 'custom_action.create'` | `DYNAMIC` | `filteredHosts`, `requestInfo` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/common/controls_view.js#L1368) |
+| `custom_action.request` | `REGISTERED` | `requestId` | `success` | [source](../../../../ambari-web/classic/app/views/common/controls_view.js#L1388) |
+| `custom_action.request` | `REGISTERED` | `requestId`, `taskId` | `success` | [source](../../../../ambari-web/classic/app/views/common/controls_view.js#L1409) |
+| `cancel.background.operation` | `REGISTERED` | `requestId` | 默认处理 | [source](../../../../ambari-web/classic/app/views/common/host_progress_popup_body_view.js#L1017) |
+| `logtail.get` | `REGISTERED` | `hostName`, `logComponentName`, `pageSize`, `startIndex` | `error` | [source](../../../../ambari-web/classic/app/views/common/log_tail_view.js#L133) |
+| `configs.quicklinksconfig` | `REGISTERED` | `serviceName`, `stackVersionUrl` | `success` | [source](../../../../ambari-web/classic/app/views/common/quick_view_link_view.js#L132) |
+| `hosts.for_quick_links` | `REGISTERED` | `clusterName`, `hosts`, `urlParams` | `success` | [source](../../../../ambari-web/classic/app/views/common/quick_view_link_view.js#L212) |
+| `hosts.all` | `REGISTERED` | 表达式:`{}` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/main/admin/highAvailability/nameNode/rollbackHA/step1_view.js#L34) |
+| `bulk_request.hosts.passive_state` | `REGISTERED` | `hostNames`, `passive_state`, `requestInfo` | 默认处理 | [source](../../../../ambari-web/classic/app/views/main/admin/stack_upgrade/custom_cluster_checks/custom_cluster_checks_maintenance_view.js#L29) |
+| `common.services.update` | `REGISTERED` | 表达式:`data` | `success` | [source](../../../../ambari-web/classic/app/views/main/admin/stack_upgrade/custom_cluster_checks/custom_cluster_checks_view.js#L49) |
+| `wizard.advanced_repositories.valid_url` | `REGISTERED` | `data`, `osType`, `repoId`, `stackName`, `stackVersion` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/main/admin/stack_upgrade/services_view.js#L240) |
+| `admin.upgrade.service_checks` | `REGISTERED` | `upgradeId` | `success` | [source](../../../../ambari-web/classic/app/views/main/admin/stack_upgrade/upgrade_wizard_view.js#L310) |
+| `ambari.service.load_server_version` | `REGISTERED` | 未发现内联 `data` | 默认处理 | [source](../../../../ambari-web/classic/app/views/main/admin/stack_upgrade/versions_view.js#L220) |
+| `hosts.confirmed.minimal` | `REGISTERED` | 表达式:`{}` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/main/host/add_view.js#L40) |
+| `host.host_component.decommission_status_datanode` | `REGISTERED` | `componentName`, `hostNames` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/main/host/details/host_component_views/datanode_view.js#L34) |
+| `host.host_component.decommission_status_regionserver` | `REGISTERED` | 表达式:`{ hostName, desiredAdminState }` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/main/host/details/host_component_views/regionserver_view.js#L31) |
+| `hosts.high_availability.wizard` | `REGISTERED` | 表达式:`{}` | `success`, `error` | [source](../../../../ambari-web/classic/app/views/main/service/reassign_view.js#L37) |
+
+## 动态调用解析
+
+动态解析:27/27 个调用点已绑定契约,45 个唯一候选请求名,0 个候选未命中纳入注册表。
+
+| 位置/表达式 | 解析状态 | 分派种类 | 候选请求名 | 分派条件 | 运行时边界 |
+| --- | --- | --- | --- | --- | --- |
+| [source](../../../../ambari-web/classic/app/controllers/global/background_operations_controller.js#L178)<br>`queryParams.name` | `RESOLVED_CLOSED` | `HELPER_RETURN_PROPERTY` | `background_operations.get_most_recent`<br>`background_operations.get_by_task`<br>`background_operations.get_by_request` | getQueryParams() defaults to get_most_recent; TASK_DETAILS selects get_by_task; TASKS_LIST or HOSTS_LIST selects get_by_request. | The current production set is closed. Unknown levelInfo.name [...]
+| [source](../../../../ambari-web/classic/app/controllers/installer.js#L590)<br>`name` | `RESOLVED_CLOSED` | `LOCAL_TERNARY` | `wizard.step1.post_version_definition_file.xml`<br>`wizard.step1.post_version_definition_file.url` | A truthy isXMLdata selects the XML dry-run request; otherwise the URL/JSON dry-run request is used. | The current production set is closed.<br>证据:`ambari-web/classic/app/controllers/installer.js:586-600`<br>`ambari-web/classic/app/utils/ajax/ajax.js:2025,2039` |
+| [source](../../../../ambari-web/classic/app/controllers/installer.js#L656)<br>`name` | `RESOLVED_CLOSED` | `LOCAL_TERNARY` | `wizard.step8.post_version_definition_file.xml`<br>`wizard.step8.post_version_definition_file` | isXMLdata == true selects the XML request; otherwise the JSON request is used. | The current production set is closed. The source intentionally uses loose equality.<br>证据:`ambari-web/classic/app/controllers/installer.js:653-666`<br>`ambari-web/classic/app/utils/ajax/a [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/admin/highAvailability/progress_popup_controller.js#L122)<br>`name` | `RESOLVED_CLOSED` | `LOCAL_BRANCH` | `background_operations.get_by_request`<br>`common.request.polling` | A null or undefined stageId selects get_by_request; every other value selects common.request.polling. Numeric zero is normalized to string "0". | The current production set is closed. One request is sent for every requestId with the same selected name.< [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L461)<br>`(onlyState) ? 'admin.upgrade.state' : 'admin.upgrade.data'` | `RESOLVED_CLOSED` | `INLINE_TERNARY` | `admin.upgrade.state`<br>`admin.upgrade.data` | A truthy onlyState loads only Upgrade fields; otherwise the full upgrade group and item data is loaded. | The current production set is closed.<br>证据:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:452 [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/alerts/alert_instances_controller.js#L118)<br>`ajaxData` | `RESOLVED_CLOSED` | `OPTIONS_OBJECT_SWITCH` | `alerts.instances.by_host`<br>`alerts.instances.by_definition`<br>`alerts.instances` | sourceType HOST selects by_host; ALERT_DEFINITION selects by_definition; all other values select all alert instances. | The current production set is closed. Scheduled refreshes reuse the previously selected sourceType and sourceName.<br [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js#L493)<br>`sendData` | `RESOLVED_CLOSED` | `STATIC_OPTIONS_OBJECT` | `alert_groups.create` | postNewAlertGroup constructs an options object with a fixed request name. | This is semantically static; it is classified as dynamic only because the complete options object is passed by variable.<br>证据:`ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js:464-493`<br [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js#L530)<br>`sendData` | `RESOLVED_CLOSED` | `STATIC_OPTIONS_OBJECT` | `alert_groups.update` | updateAlertGroup constructs an options object with a fixed request name. | This is semantically static; it is classified as dynamic only because the complete options object is passed by variable.<br>证据:`ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js:506-530`<br> [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js#L561)<br>`sendData` | `RESOLVED_CLOSED` | `STATIC_OPTIONS_OBJECT` | `alert_groups.delete` | removeAlertGroup constructs an options object with a fixed request name. | This is semantically static; it is classified as dynamic only because the complete options object is passed by variable.<br>证据:`ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js:540-561`<br> [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L337)<br>`(Array.isArray(component)) ? 'common.host.host_components.update' : 'common.host.host_component.update'` | `RESOLVED_CLOSED` | `INLINE_TERNARY` | `common.host.host_components.update`<br>`common.host.host_component.update` | An array selects the multi-component update on one host; a non-array component selects the single host-component update. | The current production set is closed. The non-array bran [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/host/details.js#L606)<br>`(Em.isNone(componentName)) ? 'common.delete.host' : 'common.delete.host_component'` | `RESOLVED_CLOSED` | `INLINE_TERNARY` | `common.delete.host`<br>`common.delete.host_component` | A null or undefined componentName deletes the host; every other value deletes a host component. | The current production set is closed. An empty string is not Em.isNone and therefore selects the host-component endpoint.<b [...]
+| [source](../../../../ambari-web/classic/app/controllers/main/service/add_controller.js#L468)<br>`name` | `RESOLVED_OPEN_BOUNDARY` | `PARAMETER_WRAPPER` | `common.services.update` | The only production caller invokes installServicesRequest with common.services.update. | The wrapper can accept any future request name. A test injects wizard.step3.host_info, but that is not a production call path.<br>证据:`ambari-web/classic/app/controllers/main/service/add_controller.js:459-474`<br>`ambari- [...]
+| [source](../../../../ambari-web/classic/app/controllers/wizard.js#L426)<br>`isRetry ? 'common.host_components.update' : 'common.services.update'` | `RESOLVED_CLOSED` | `INLINE_TERNARY` | `common.host_components.update`<br>`common.services.update` | A retry installs unfinished host components; an initial run installs services in INIT state. | The current production set is closed.<br>证据:`ambari-web/classic/app/controllers/wizard.js:401-432`<br>`ambari-web/classic/app/utils/ajax/ajax.js:4 [...]
+| [source](../../../../ambari-web/classic/app/controllers/wizard/step8_controller.js#L906)<br>`ajaxOpts` | `RESOLVED_CLOSED` | `OPTIONS_OBJECT_TERNARY` | `admin.kerberos.cluster.artifact.update`<br>`admin.kerberos.cluster.artifact.create` | descriptorExists === true selects update; otherwise create. A truthy instant sends here, while a falsy instant enqueues the same options object. | The current production set is closed. The same pair also appears in the upstream set for queue.shift().< [...]
+| [source](../../../../ambari-web/classic/app/controllers/wizard/step9_controller.js#L511)<br>`name` | `RESOLVED_CLOSED` | `LOCAL_SWITCH` | `common.host_components.update`<br>`common.services.update` | addHostController selects host_components; addServiceController and the default branch select services. | The current production set is closed. Unknown controllerName values retain the common.services.update default.<br>证据:`ambari-web/classic/app/controllers/wizard/step9_controller.js:466- [...]
+| [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_saver.js#L595)<br>`ajaxOptions` | `RESOLVED_CLOSED` | `STATIC_OPTIONS_OBJECT` | `wizard.step8.apply_configuration_groups` | createConfigGroup constructs an options object with a fixed request name. | This is semantically static. An optional success callback does not change the request name.<br>证据:`ambari-web/classic/app/mixins/common/configs/configs_saver.js:584-595`<br>`ambari-web/classic/app/utils/ajax/ajax.js:2185` |
+| [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_saver.js#L617)<br>`ajaxOptions` | `RESOLVED_CLOSED` | `STATIC_OPTIONS_OBJECT` | `config_groups.update_config_group` | updateConfigGroup constructs an options object with a fixed request name. | This is semantically static. An optional success callback does not change the request name.<br>证据:`ambari-web/classic/app/mixins/common/configs/configs_saver.js:605-617`<br>`ambari-web/classic/app/utils/ajax/ajax.js:915` |
+| [source](../../../../ambari-web/classic/app/mixins/common/configs/configs_saver.js#L644)<br>`ajaxData` | `RESOLVED_CLOSED` | `STATIC_OPTIONS_OBJECT` | `common.across.services.configurations` | putChangedConfigurations constructs an options object with a fixed request name. | This is semantically static. Optional success and always callbacks do not change the request name.<br>证据:`ambari-web/classic/app/mixins/common/configs/configs_saver.js:629-644`<br>`ambari-web/classic/app/utils/ajax [...]
+| [source](../../../../ambari-web/classic/app/mixins/main/service/configs/component_actions_by_configs.js#L112)<br>`config_action.get('popupProperties').primaryButton.metaData.name` | `RESOLVED_OPEN_BOUNDARY` | `MODEL_METADATA_LOOKUP` | `service.item.refreshQueueYarnRequest` | The code selects ConfigAction records with actionType showPopup. The only current showPopup fixture supplies service.item.refreshQueueYarnRequest. | The current data set is closed, but ConfigAction is an extension [...]
+| [source](../../../../ambari-web/classic/app/mixins/main/service/configs/config_overridable.js#L329)<br>`sendData` | `RESOLVED_CLOSED` | `STATIC_OPTIONS_OBJECT` | `config_groups.update` | updateConfigurationGroup constructs an options object with a fixed request name. | This is semantically static; callbacks stored on the options object do not change the request name.<br>证据:`ambari-web/classic/app/mixins/main/service/configs/config_overridable.js:308-329`<br>`ambari-web/classic/app/util [...]
+| [source](../../../../ambari-web/classic/app/mixins/main/service/configs/config_overridable.js#L439)<br>`sendData` | `RESOLVED_CLOSED` | `STATIC_OPTIONS_OBJECT` | `common.delete.config_group` | deleteConfigurationGroup constructs an options object with a fixed request name. | This is semantically static; callbacks stored on the options object do not change the request name.<br>证据:`ambari-web/classic/app/mixins/main/service/configs/config_overridable.js:416-439`<br>`ambari-web/classic/ap [...]
+| [source](../../../../ambari-web/classic/app/mixins/wizard/assign_master_components.js#L604)<br>`isInstaller ? 'hosts.info.install' : 'hosts.high_availability.wizard'` | `RESOLVED_CLOSED` | `LOCAL_TERNARY` | `hosts.info.install`<br>`hosts.high_availability.wizard` | installerController in wizardController.name or content.controllerName selects install host information; all other wizards select existing-cluster HA host information. | The current production set is closed.<br>证据:`ambari-we [...]
+| [source](../../../../ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js#L129)<br>`this.get('request.ajaxName')` | `RESOLVED_OPEN_BOUNDARY` | `MIXIN_REQUEST_PROPERTY` | `admin.kerberize.cluster`<br>`admin.kerberize.cluster.force` | submitRequest is reached only on an isSingleRequestPage path. Kerberos Step 7 is the only current production controller setting that flag; initial execution selects the normal request and retry selects force. | The base mixin remains an exte [...]
+| [source](../../../../ambari-web/classic/app/utils/ajax/ajax_queue.js#L151)<br>`queue.shift()` | `RESOLVED_OPEN_BOUNDARY` | `FIFO_OPTIONS_QUEUE` | `wizard.step8.create_cluster`<br>`wizard.step8.create_selected_services`<br>`wizard.step8.create_components`<br>`wizard.step8.register_host_to_cluster`<br>`wizard.step8.register_host_to_component`<br>`common.across.services.configurations`<br>`wizard.step8.apply_configuration_groups`<br>`config_groups.update_config_group`<br>`alerts.create_al [...]
+| [source](../../../../ambari-web/classic/app/utils/config.js#L1063)<br>`name` | `RESOLVED_CLOSED` | `LOCAL_TERNARY` | `configs.stack_configs.load.services`<br>`configs.stack_configs.load.all` | serviceNames is normalized to an array; a nonempty array selects specified services and an empty array selects all services. | The current production set is closed.<br>证据:`ambari-web/classic/app/utils/config.js:1060-1071`<br>`ambari-web/classic/app/utils/ajax/ajax.js:814,819` |
+| [source](../../../../ambari-web/classic/app/views/common/configs/widgets/test_db_connection_widget_view.js#L226)<br>`(isServiceInstalled) ? 'cluster.custom_action.create' : 'custom_action.create'` | `RESOLVED_CLOSED` | `INLINE_TERNARY` | `cluster.custom_action.create`<br>`custom_action.create` | A loaded service model selects the cluster-scoped custom action; otherwise the top-level pre-cluster custom action is used. | The current production set is closed. The runtime boundary is the s [...]
+| [source](../../../../ambari-web/classic/app/views/common/controls_view.js#L1368)<br>`(isServiceInstalled) ? 'cluster.custom_action.create' : 'custom_action.create'` | `RESOLVED_CLOSED` | `INLINE_TERNARY` | `cluster.custom_action.create`<br>`custom_action.create` | A loaded parent service model selects the cluster-scoped custom action; otherwise the top-level pre-cluster custom action is used. | The current production set is closed. This is a separate legacy database-control implementat [...]
+
+`RESOLVED_CLOSED` 表示当前生产分支可穷举;`RESOLVED_OPEN_BOUNDARY` 表示当前候选已穷举,但通用 wrapper、model metadata、mixin property 或 FIFO queue 仍可被未来代码扩展。
+
+`UNREGISTERED` 是经典代码实际调用但 `app/utils/ajax/ajax.js` 没有同名定义的遗留行为。它不是有效后端契约;迁移时应先确认预期 endpoint,再决定修复或保留兼容行为。
diff --git a/docs/frontend-refactor/ember-baseline/generated/ajax-endpoints.md b/docs/frontend-refactor/ember-baseline/generated/ajax-endpoints.md
new file mode 100644
index 0000000000..fec3c5bdcb
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/generated/ajax-endpoints.md
@@ -0,0 +1,632 @@
+# Ember 非 Metrics 命名 AJAX 接口目录
+
+> 由 `tools/extract-ember-baseline.mjs` 生成,请勿手工编辑。URL 默认追加 `/api/v1`;Method 中的 `DYNAMIC` 只表示 HTTP method 由运行时表达式决定,动态 URL 单独标为 `DYNAMIC_URL`。调用者计数为经典前端 `app/` 目录中的字符串引用数。
+
+- 纳入定义:288
+- 排除 Metrics 定义:95
+- 有经典前端调用证据:235
+- 未发现经典前端调用证据:53
+
+| 请求名 | Method | URL(不含默认 prefix) | format 输入键 | Prefix | 模块 | 调用者 | 定义 |
+| --- | --- | --- | --- | --- | --- | ---: | --- |
+| `common.cluster.update` | `PUT` | `/clusters/{clusterName}` | `data` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L34) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/step2_controller.js:313` | | | | | |
+| `common.services.update` | `PUT` | `/clusters/{clusterName}/services?{urlParams}` | `ServiceInfo`, `clusterName`, `context` | `/api/v1 (default)` | 安装向导、服务与配置、Stack 与升级、安全、高可用与联邦、Views | 15 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L46) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:223`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:348`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/step3_controller.js:43`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/step6_controller.js:26`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/step8_controller.js:27`<br>`ambari-web/classic/ap [...]
+| `common.service.update` | `PUT` | `/clusters/{clusterName}/services/{serviceName}` | `ServiceInfo`, `clusterName`, `context`, `serviceName` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L68) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:600` | | | | | |
+| `common.service_component.info` | `GET` | `/clusters/{clusterName}/services/{serviceName}/components/{componentName}?{urlParams}` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L91) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/step3_controller.js:77` | | | | | |
+| `common.service.hdfs.getNnCheckPointTime` | `GET` | `/clusters/{clusterName}/services/HDFS/components/NAMENODE?fields=host_components/metrics/dfs/FSNamesystem/HAState,host_components/metrics/dfs/FSNamesystem/LastCheckpointTime,host_components/metrics/dfs/namenode/ClusterId` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L96) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:406` | | | | | |
+| `common.host_component.getNnCheckPointTime` | `GET` | `/clusters/{clusterName}/hosts/{host}/host_components/NAMENODE?fields=metrics/dfs/FSNamesystem/HAState,metrics/dfs/FSNamesystem/LastCheckpointTime` | 无 `format()` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L101) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:422` | | | | | |
+| `common.host_component.update` | `PUT` | `/clusters/{clusterName}/host_components` | `HostRoles`, `context`, `query` | `/api/v1 (default)` | 主机、服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L106) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/add_controller.js:506` | | | | | |
+| `common.host.host_components.create` | `POST` | `/clusters/{clusterName}/hosts` | `host_components`, `query` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L125) |
+| `common.host.host_components.update` | `PUT` | `/clusters/{clusterName}/hosts/{hostName}/host_components?{urlParams}` | `HostRoles`, `clusterName`, `context`, `hostName`, `query` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L143) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:338` | | | | | |
+| `common.host.host_component.update` | `PUT` | `/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}?{urlParams}` | `HostRoles`, `clusterName`, `context`, `hostName`, `serviceName` | `/api/v1 (default)` | 主机、服务与配置 | 7 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L167) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:338`<br>`ambari-web/classic/app/controllers/main/host/details.js:1839`<br>`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:519`<br>`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:566`<br>`ambari-web/classic/app/controllers/main/service/reassign/step6_controller.js:202`<br>`ambari-web/classic/app/mixins/main/host/details/host_components/install_component.js:68`< [...]
+| `common.hosts.all` | `GET` | `/clusters/{clusterName}/host_components?{urlParams}&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L191) |
+| `common.service.configurations` | `PUT` | `/clusters/{clusterName}` | `desired_config` | `/api/v1 (default)` | 认证与应用外壳、主机、安全、高可用与联邦 | 9 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L196) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js:97`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js:94`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step4_controller.js:73`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/removeStandby/step3_controller.js:88`<br>`ambari-web/classic/app/controllers/main/ad [...]
+| `common.service.multiConfigurations` | `PUT` | `/clusters/{clusterName}` | `configs` | `/api/v1 (default)` | 认证与应用外壳、安全、高可用与联邦 | 4 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L211) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step4_controller.js:55`<br>`ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js:77`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step9_controller.js:228`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/rangerAdmin/step4_controller.js:88` | | | | | |
+| `common.across.services.configurations` | `PUT` | `/clusters/{clusterName}` | `data` | `/api/v1 (default)` | 安装向导、服务与配置、安全、高可用与联邦 | 4 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L222) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/step2_controller.js:229`<br>`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:294`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:1549`<br>`ambari-web/classic/app/mixins/common/configs/configs_saver.js:631` | | | | | |
+| `common.request.polling` | `GET` | `/clusters/{clusterName}/requests/{requestId}?fields=tasks/Tasks/request_id,tasks/Tasks/command,tasks/Tasks/command_detail,tasks/Tasks/ops_display_name,tasks/Tasks/start_time,tasks/Tasks/end_time,tasks/Tasks/exit_code,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,tasks/Tasks/status,tasks/Tasks/structured_out,Requests/*&tasks/Tasks/stage_id={stageId}` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦、后台操作与通用能力 | 1 | [source](../../../../ambari-w [...]
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/progress_popup_controller.js:113` | | | | | |
+| `ambari.service.load_server_version` | `GET` | `/services/AMBARI?fields=components/RootServiceComponents/component_version&components/RootServiceComponents/component_name=AMBARI_SERVER&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 认证与应用外壳、Stack 与升级、Views | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L239) |
+| | | 调用位置:`ambari-web/classic/app/router.js:632`<br>`ambari-web/classic/app/router.js:977`<br>`ambari-web/classic/app/views/main/admin/stack_upgrade/versions_view.js:221` | | | | | |
+| `service.flume.agent.command` | `PUT` | `/clusters/{clusterName}/hosts/{host}/host_components/FLUME_HANDLER` | `agentName`, `clusterName`, `context`, `host`, `state` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L245) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/info/summary.js:318` | | | | | |
+| `common.host_components.update` | `PUT` | `/clusters/{clusterName}/host_components?{urlParams}` | `HostRoles`, `clusterName`, `context`, `level`, `query` | `/api/v1 (default)` | 安装向导、主机、Stack 与升级 | 9 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L272) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1680`<br>`ambari-web/classic/app/controllers/main/host/add_controller.js:446`<br>`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:150`<br>`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:251`<br>`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:619`<br>`ambari-web/classic/app/controllers/main/host/bulk_operations_contr [...]
+| `common.hosts.delete` | `DELETE` | `/clusters/{clusterName}/hosts{urlParams}` | `query` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L295) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:375` | | | | | |
+| `common.service.passive` | `PUT` | `/clusters/{clusterName}/services/{serviceName}` | `passive_state`, `requestInfo` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L309) |
+| | | 调用位置:`ambari-web/classic/app/utils/batch_scheduled_requests.js:300` | | | | | |
+| `common.service.host_component.update` | `PUT` | `/clusters/{clusterName}/host_components` | `clusterName`, `context`, `query`, `serviceName`, `state` | `/api/v1 (default)` | 主机、服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L329) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:573` | | | | | |
+| `common.host.host_component.passive` | `PUT` | `/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}` | `context`, `passive_state` | `/api/v1 (default)` | 主机、服务与配置、安全、高可用与联邦 | 6 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L355) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:479`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step5_controller.js:52`<br>`ambari-web/classic/app/controllers/main/host/details.js:2375`<br>`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:255`<br>`ambari-web/classic/app/controllers/main/service/reassign/step6_controller.js:161`<br>`ambari-web/classic/app/controllers/m [...]
+| `common.host.with_host_component` | `GET` | `/clusters/{clusterName}/hosts?host_components/HostRoles/component_name={componentName}&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L375) |
+| `common.batch.request_schedules` | `POST` | `/clusters/{clusterName}/request_schedules` | `batches`, `intervalTimeSeconds`, `tolerateSize` | `/api/v1 (default)` | 主机、服务与配置、后台操作与通用能力 | 8 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L380) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:2143`<br>`ambari-web/classic/app/controllers/main/host/details.js:2251`<br>`ambari-web/classic/app/controllers/main/host/details.js:3195`<br>`ambari-web/classic/app/controllers/main/service/item.js:789`<br>`ambari-web/classic/app/mixins/main/service/configs/component_actions_by_configs.js:233`<br>`ambari-web/classic/app/mixins/main/service/configs/component_actions_by_configs.js:297`<br>`ambari-web/classic/app/utils/ba [...]
+| `common.delete.host` | `DELETE` | `/clusters/{clusterName}/hosts/{hostName}` | 无 `format()` | `/api/v1 (default)` | 主机 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L402) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:607`<br>`ambari-web/classic/app/controllers/main/host/details.js:2829` | | | | | |
+| `common.delete.host_component` | `DELETE` | `/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}` | 无 `format()` | `/api/v1 (default)` | 主机、服务与配置、安全、高可用与联邦 | 9 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L406) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:461`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:550`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step9_controller.js:311`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/step6_controller.js:60`<br>`ambari-web/classic/app/controllers/main/host/details.js:607`<br>`ambari-web/classic/ap [...]
+| `common.delete.user` | `DELETE` | `/users/{user}` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L410) |
+| `common.delete.config_group` | `DELETE` | `/clusters/{clusterName}/config_groups/{id}` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L414) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/service/configs/config_overridable.js:419` | | | | | |
+| `common.delete.cluster` | `DELETE` | `/clusters/{name}` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L418) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:754` | | | | | |
+| `common.delete.service` | `DELETE` | `/clusters/{clusterName}/services/{serviceName}` | 无 `format()` | `/api/v1 (default)` | 服务与配置、安全、高可用与联邦 | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L422) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/disable_controller.js:66`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/wizard_controller.js:230`<br>`ambari-web/classic/app/controllers/main/service/item.js:2058` | | | | | |
+| `common.delete.request_schedule` | `DELETE` | `/clusters/{clusterName}/request_schedules/{request_schedule_id}` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L427) |
+| | | 调用位置:`ambari-web/classic/app/utils/batch_scheduled_requests.js:742` | | | | | |
+| `common.get.request.status` | `GET` | `/clusters/{clusterName}/requests/{requestId}?fields=Requests/request_status` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L431) |
+| `alerts.load_alert_groups` | `GET` | `/clusters/{clusterName}/alert_groups?fields=*` | 无 `format()` | `/api/v1 (default)` | 告警 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L435) |
+| `alerts.load_an_alert_group` | `GET` | `/clusters/{clusterName}/alert_groups/{group_id}` | 无 `format()` | `/api/v1 (default)` | 告警 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L439) |
+| `alert_groups.create` | `POST` | `/clusters/{clusterName}/alert_groups` | `definitions`, `name`, `targets` | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L443) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js:476` | | | | | |
+| `alert_groups.update` | `PUT` | `/clusters/{clusterName}/alert_groups/{group_id}` | `definitions`, `name`, `targets` | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L459) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js:508` | | | | | |
+| `alert_groups.delete` | `DELETE` | `/clusters/{clusterName}/alert_groups/{group_id}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L475) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js:542` | | | | | |
+| `alerts.load_all_alert_definitions` | `GET` | `/clusters/{clusterName}/alert_definitions?fields=*` | 无 `format()` | `/api/v1 (default)` | 告警 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L484) |
+| `alerts.notifications` | `GET` | `/alert_targets?fields=*` | 无 `format()` | `/api/v1 (default)` | 告警 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L488) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/manage_alert_groups_controller.js:210`<br>`ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js:363` | | | | | |
+| `alerts.instances` | `GET` | `/clusters/{clusterName}/alerts?fields=*` | 无 `format()` | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L492) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/alert_instances_controller.js:113` | | | | | |
+| `alerts.instances.unhealthy` | `GET` | `/clusters/{clusterName}/alerts?fields=*&Alert/state.in(CRITICAL,WARNING)&{paginationInfo}` | 无 `format()` | `/api/v1 (default)` | 告警 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L496) |
+| `alerts.instances.by_definition` | `GET` | `/clusters/{clusterName}/alerts?fields=*&Alert/definition_id={definitionId}` | 无 `format()` | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L500) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/alert_instances_controller.js:104` | | | | | |
+| `alerts.instances.by_host` | `GET` | `/clusters/{clusterName}/alerts?fields=*&Alert/host_name={hostName}` | 无 `format()` | `/api/v1 (default)` | 主机、告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L504) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/alert_instances_controller.js:95` | | | | | |
+| `alerts.update_alert_definition` | `PUT` | `/clusters/{clusterName}/alert_definitions/{id}` | `data` | `/api/v1 (default)` | 告警 | 6 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L508) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alert_definitions_controller.js:110`<br>`ambari-web/classic/app/controllers/main/alerts/definition_configs_controller.js:654`<br>`ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js:155`<br>`ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js:246`<br>`ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js:265`<br>`ambari-web/classic/app/controllers/main/a [...]
+| `alerts.create_alert_definition` | `POST` | `/clusters/{clusterName}/alert_definitions/` | `data` | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L518) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/add_alert_definition/add_alert_definition_controller.js:38` | | | | | |
+| `alerts.delete_alert_definition` | `DELETE` | `/clusters/{clusterName}/alert_definitions/{id}` | 无 `format()` | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L528) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js:189` | | | | | |
+| `alerts.create_alert_notification` | `POST` | `/alert_targets?{urlParams}` | `data` | `/api/v1 (default)` | 安装向导、告警 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L533) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js:860`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:1706` | | | | | |
+| `alerts.update_alert_notification` | `PUT` | `/alert_targets/{id}` | `data` | `/api/v1 (default)` | 告警 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L543) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js:898`<br>`ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js:977` | | | | | |
+| `alerts.delete_alert_notification` | `DELETE` | `/alert_targets/{id}` | 无 `format()` | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L553) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/manage_alert_notifications_controller.js:939` | | | | | |
+| `alerts.get_instances_history` | `GET` | `/clusters/{clusterName}/alert_history?(AlertHistory/definition_name={definitionName})&(AlertHistory/timestamp>={timestamp})` | 无 `format()` | `/api/v1 (default)` | 告警 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L558) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/alerts/definition_details_controller.js:91` | | | | | |
+| `background_operations.get_most_recent` | `GET` | `/clusters/{clusterName}/requests?to=end&page_size={operationsCount}&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` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L562) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/background_operations_controller.js:208` | | | | | |
+| `background_operations.get_by_request` | `GET` | `/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` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦、后台操作与通用能力 | 4 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L569) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/background_operations_controller.js:222`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/progress_popup_controller.js:111`<br>`ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js:689`<br>`ambari-web/classic/app/utils/polling.js:149` | | | | | |
+| `background_operations.get_by_task` | `GET` | `/clusters/{clusterName}/requests/{requestId}/tasks/{taskId}` | 无 `format()` | `/api/v1 (default)` | 安装向导、安全、高可用与联邦、后台操作与通用能力 | 4 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L574) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/background_operations_controller.js:215`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/progress_popup_controller.js:275`<br>`ambari-web/classic/app/controllers/wizard/step9_controller.js:990`<br>`ambari-web/classic/app/utils/polling.js:118` | | | | | |
+| `background_operations.abort_request` | `PUT` | `/clusters/{clusterName}/requests/{requestId}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 主机、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L579) |
+| | | 调用位置:`ambari-web/classic/app/utils/host_progress_popup.js:255` | | | | | |
+| `service.item.smoke` | `POST` | `/clusters/{clusterName}/requests` | `actionName`, `displayName`, `operationLevel`, `serviceName` | `/api/v1 (default)` | 服务与配置、安全、高可用与联邦 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L594) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/step3_controller.js:90`<br>`ambari-web/classic/app/controllers/main/service/item.js:1293` | | | | | |
+| `service.item.rebalanceHdfsNodes` | `POST` | `/clusters/{clusterName}/requests` | `hosts`, `threshold` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L614) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:843` | | | | | |
+| `cancel.background.operation` | `PUT` | `/clusters/{clusterName}/requests/{requestId}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 主机、Views、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L636) |
+| | | 调用位置:`ambari-web/classic/app/views/common/host_progress_popup_body_view.js:1018` | | | | | |
+| `service.item.refreshQueueYarnRequest` | `POST` | `/clusters/{clusterName}/requests` | `command`, `componentName`, `context`, `forceRefreshConfigTags`, `hosts`, `serviceName` | `/api/v1 (default)` | 服务与配置、后台操作与通用能力 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L657) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:646`<br>`ambari-web/classic/app/models/configs/theme/config_action.js:110` | | | | | |
+| `service.item.startStopLdapKnox` | `POST` | `/clusters/{clusterName}/requests` | `command`, `componentName`, `context`, `host`, `serviceName` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L679) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:693` | | | | | |
+| `service.item.updateHBaseReplication` | `POST` | `/clusters/{clusterName}/requests` | `hosts`, `replication_cluster_keys`, `replication_peers` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L700) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:976` | | | | | |
+| `service.item.stopHBaseReplication` | `POST` | `/clusters/{clusterName}/requests` | `hosts`, `replication_peers` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L725) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:1041` | | | | | |
+| `service.item.executeCustomCommand` | `POST` | `/clusters/{clusterName}/requests` | `command`, `componentName`, `context`, `hosts`, `serviceName` | `/api/v1 (default)` | 主机、服务与配置、安全、高可用与联邦 | 6 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L751) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js:43`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/removeStandby/step3_controller.js:42`<br>`ambari-web/classic/app/controllers/main/host/details.js:3047`<br>`ambari-web/classic/app/controllers/main/service/item.js:741`<br>`ambari-web/classic/app/controllers/main/service/item.js:1545`<br>`ambari-web/classic/app/controllers/main/service/item.js:156 [...]
+| `configs.theme` | `GET` | `{stackVersionUrl}/services/{serviceName}/themes?ThemeInfo/default=true&fields=*` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L773) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/service/themes_mapping.js:40` | | | | | |
+| `configs.theme.services` | `GET` | `{stackVersionUrl}/services?StackServices/service_name.in({serviceNames})&themes/ThemeInfo/default=true&fields=themes/*` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L778) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/service/themes_mapping.js:74` | | | | | |
+| `configs.quicklinksconfig` | `GET` | `{stackVersionUrl}/services/{serviceName}/quicklinks?QuickLinkInfo/default=true&fields=*` | 无 `format()` | `/api/v1 (default)` | Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L785) |
+| | | 调用位置:`ambari-web/classic/app/views/common/quick_view_link_view.js:133` | | | | | |
+| `configs.quicklinksconfig.services` | `GET` | `{stackVersionUrl}/services?StackServices/service_name.in({serviceNames})&quicklinks/QuickLinkInfo/default=true&fields=quicklinks/*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L790) |
+| `configs.config_groups.load.all` | `GET` | `/clusters/{clusterName}/config_groups?fields=*` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L797) |
+| `configs.config_groups.load.services` | `GET` | `/clusters/{clusterName}/config_groups?ConfigGroup/tag.in({serviceNames})&fields=*` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L802) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/service/groups_mapping.js:41` | | | | | |
+| `configs.stack_configs.load.cluster_configs` | `GET` | `{stackVersionUrl}?fields=configurations/*,Versions/config_types/*` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L809) |
+| | | 调用位置:`ambari-web/classic/app/utils/config.js:1045` | | | | | |
+| `configs.stack_configs.load.all` | `GET` | `{stackVersionUrl}/services?fields=configurations/*,StackServices/config_types/*` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L814) |
+| | | 调用位置:`ambari-web/classic/app/utils/config.js:1062` | | | | | |
+| `configs.stack_configs.load.services` | `GET` | `{stackVersionUrl}/services?StackServices/service_name.in({serviceList})&fields=configurations/*,configurations/dependencies/*,StackServices/config_types/*` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L819) |
+| | | 调用位置:`ambari-web/classic/app/utils/config.js:1062` | | | | | |
+| `configs.stack_configs.load.service` | `GET` | `{stackVersionUrl}/services/{serviceName}?fields=configurations/*,StackServices/config_types/*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L824) |
+| `configs.config_versions.load` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?service_name={serviceName}&service_config_version={configVersion}&fields=*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L831) |
+| `configs.config_versions.load.group` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?service_name={serviceName}&group_id={id}&fields=*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L836) |
+| `configs.config_versions.load.current_versions` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?service_name.in({serviceNames})&is_current=true&fields=*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L841) |
+| `service.load_config_groups` | `GET` | `/clusters/{clusterName}/config_groups?ConfigGroup/tag={serviceName}&fields=*` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L847) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/manage_config_groups_controller.js:446` | | | | | |
+| `reassign.load_configs` | `GET` | `/clusters/{clusterName}/configurations?{urlParams}` | 无 `format()` | `/api/v1 (default)` | 主机、服务与配置、安全、高可用与联邦 | 13 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L851) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js:72`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js:96`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step4_controller.js:53`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/removeStandby/step3_controller.js:71`<br>`ambari-web/classic/app/controllers/main/ad [...]
+| `reassign.save_configs` | `PUT` | `/clusters/{clusterName}` | `properties`, `service_config_version_note`, `siteName` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L856) |
+| `config.cluster` | `GET` | `{stackVersionUrl}/configurations?fields=*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L875) |
+| `config.advanced` | `GET` | `{stackVersionUrl}/services/{serviceName}/configurations?fields=*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L879) |
+| `config.advanced.multiple.services` | `GET` | `{stackVersionUrl}/services?StackServices/service_name.in({serviceNames})&fields=configurations/*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L883) |
+| `config.advanced.partial` | `GET` | `{stackVersionUrl}/services/?StackServices/service_name.in({serviceList})&fields=configurations/*{queryFilter}` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L887) |
+| `config.config_types` | `GET` | `{stackVersionUrl}/services/{serviceName}?fields=StackServices/config_types` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L891) |
+| `config.tags` | `GET` | `/clusters/{clusterName}?fields=Clusters/desired_configs` | 无 `format()` | `/api/v1 (default)` | 认证与应用外壳、安装向导、主机、服务与配置、安全、高可用与联邦 | 17 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L895) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/configuration_controller.js:145`<br>`ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js:51`<br>`ambari-web/classic/app/controllers/main/admin/federation/step3_controller.js:54`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js:63`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller [...]
+| `config.tags.site` | `GET` | `/clusters/{clusterName}?fields=Clusters/desired_configs/{site}` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L899) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/update_controller.js:704` | | | | | |
+| `config.tags_and_groups` | `GET` | `/clusters/{clusterName}?fields=Clusters/desired_configs,config_groups/*{urlParams}` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L903) |
+| `config_groups.all_fields` | `GET` | `/clusters/{clusterName}/config_groups?fields=*` | 无 `format()` | `/api/v1 (default)` | 主机、服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L907) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/addHost/step4_controller.js:28` | | | | | |
+| `config_groups.get_config_group_by_id` | `GET` | `/clusters/{clusterName}/config_groups/{id}` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L911) |
+| `config_groups.update_config_group` | `PUT` | `/clusters/{clusterName}/config_groups/{id}` | `configGroup` | `/api/v1 (default)` | 安装向导、主机、服务与配置 | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L915) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/add_controller.js:270`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:1598`<br>`ambari-web/classic/app/mixins/common/configs/configs_saver.js:607` | | | | | |
+| `config.on_site` | `GET` | `/clusters/{clusterName}/configurations?{params}` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L929) |
+| | | 调用位置:`ambari-web/classic/app/utils/config.js:819` | | | | | |
+| `config.host_overrides` | `GET` | `/clusters/{clusterName}/configurations?{params}` | 无 `format()` | `/api/v1 (default)` | 主机、服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L933) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/manage_config_groups_controller.js:540` | | | | | |
+| `config.tags.selected` | `GET` | `/clusters/{clusterName}/configurations?type.in({tags})` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L938) |
+| `credentials.store.info` | `GET` | `/clusters/{clusterName}?fields=Clusters/credential_store_properties` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L943) |
+| | | 调用位置:`ambari-web/classic/app/utils/credentials.js:214` | | | | | |
+| `credentials.list` | `GET` | `/clusters/{clusterName}/credentials?fields=Credential/*` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L948) |
+| | | 调用位置:`ambari-web/classic/app/utils/credentials.js:178` | | | | | |
+| `credentials.get` | `GET` | `/clusters/{clusterName}/credentials/{alias}` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L953) |
+| | | 调用位置:`ambari-web/classic/app/utils/credentials.js:128` | | | | | |
+| `credentials.create` | `POST` | `/clusters/{clusterName}/credentials/{alias}` | `resource` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L958) |
+| | | 调用位置:`ambari-web/classic/app/utils/credentials.js:70` | | | | | |
+| `credentials.update` | `PUT` | `/clusters/{clusterName}/credentials/{alias}` | `resource` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L971) |
+| | | 调用位置:`ambari-web/classic/app/utils/credentials.js:159` | | | | | |
+| `credentials.delete` | `DELETE` | `/clusters/{clusterName}/credentials/{alias}` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L984) |
+| | | 调用位置:`ambari-web/classic/app/utils/credentials.js:196` | | | | | |
+| `host.host_component.add_new_component` | `POST` | `/clusters/{clusterName}/hosts?Hosts/host_name={hostName}` | `data` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L990) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/host_components/install_component.js:30` | | | | | |
+| `host.host_component.add_new_components` | `POST` | `/clusters/{clusterName}/hosts` | `data` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1001) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:592` | | | | | |
+| `host.host_component.delete_components` | `DELETE` | `/clusters/{clusterName}/host_components` | `data` | `/api/v1 (default)` | 主机、Stack 与升级 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1012) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1655`<br>`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:718` | | | | | |
+| `host.host_component.slave_desired_admin_state` | `GET` | `/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}/?fields=HostRoles/desired_admin_state` | 无 `format()` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1022) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/host_components/decommissionable.js:158` | | | | | |
+| `host.host_component.decommission_status` | `GET` | `/clusters/{clusterName}/services/{serviceName}/components/{componentName}/?fields=ServiceComponentInfo,host_components/HostRoles/state` | 无 `format()` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1026) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/host_components/decommissionable.js:201` | | | | | |
+| `host_components.hbase_regionserver.active` | `GET` | `/clusters/{clusterName}/host_components?HostRoles/component_name=HBASE_REGIONSERVER&HostRoles/maintenance_state=OFF&HostRoles/desired_admin_state=INSERVICE&HostRoles/host_name.in({hostNames})` | 无 `format()` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1030) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:954` | | | | | |
+| `host.host_component.decommission_status_datanode` | `GET` | `/clusters/{clusterName}/host_components?HostRoles/component_name=NAMENODE&HostRoles/host_name.in({hostNames})&fields=metrics/dfs/namenode` | 无 `format()` | `/api/v1 (default)` | 主机、Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1034) |
+| | | 调用位置:`ambari-web/classic/app/views/main/host/details/host_component_views/datanode_view.js:35` | | | | | |
+| `host.host_component.decommission_status_regionserver` | `GET` | `/clusters/{clusterName}/host_components?HostRoles/component_name=HBASE_MASTER&HostRoles/host_name={hostName}&fields=metrics/hbase/master/liveRegionServersHosts,metrics/hbase/master/deadRegionServersHosts&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1038) |
+| | | 调用位置:`ambari-web/classic/app/views/main/host/details/host_component_views/regionserver_view.js:32` | | | | | |
+| `host.region_servers.in_inservice` | `GET` | `/clusters/{clusterName}/host_components?HostRoles/component_name=HBASE_REGIONSERVER&HostRoles/desired_admin_state=INSERVICE&fields=HostRoles/host_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1041) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:1993` | | | | | |
+| `host.host_component.decommission_slave` | `POST` | `/clusters/{clusterName}/requests` | `clusterName`, `command`, `componentName`, `context`, `hostName`, `serviceName`, `slaveType` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1045) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:1956` | | | | | |
+| `host.host_component.refresh_configs` | `POST` | `/clusters/{clusterName}/requests` | `context`, `resource_filters` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1072) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:1662` | | | | | |
+| `admin.security_status` | `GET` | `/clusters/{clusterName}?fields=Clusters/security_type` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1420) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos.js:308` | | | | | |
+| `cluster.load_cluster_name` | `GET` | `/clusters?fields=Clusters/security_type,Clusters/version,Clusters/cluster_id` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1429) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/cluster_controller.js:111` | | | | | |
+| `cluster.load_last_upgrade` | `GET` | `/clusters/{clusterName}/upgrades?fields=Upgrade/*` | 无 `format()` | `/api/v1 (default)` | Stack 与升级、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1433) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/cluster_controller.js:533` | | | | | |
+| `cluster.update_upgrade_version` | `GET` | `/stacks/{stackName}/versions?fields=services/StackServices,Versions` | `data` | `/api/v1 (default)` | Stack 与升级 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1437) |
+| `cluster.load_repositories` | `GET` | `/stacks/{stackName}/versions/{stackVersion}/operating_systems?fields=repositories/*,OperatingSystems/*` | `data` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1446) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:2090`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:404` | | | | | |
+| `cluster.load_repo_version` | `GET` | `/stacks/{stackName}/versions?fields=repository_versions/operating_systems/repositories/*,repository_versions/operating_systems/OperatingSystems/*,repository_versions/RepositoryVersions/display_name&repository_versions/RepositoryVersions/repository_version={repositoryVersion}` | 无 `format()` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1455) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:347` | | | | | |
+| `cluster.load_detailed_repo_version` | `GET` | `/clusters/{clusterName}/stack_versions?fields=repository_versions/RepositoryVersions/repository_version,ClusterStackVersions/stack,ClusterStackVersions/version&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | Stack 与升级、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1459) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/cluster_controller.js:502` | | | | | |
+| `cluster.load_current_repo_stack_services` | `GET` | `/clusters/{clusterName}/stack_versions?fields=repository_versions/RepositoryVersions/stack_services,ClusterStackVersions/stack,ClusterStackVersions/version` | 无 `format()` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1463) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard.js:687` | | | | | |
+| `cluster.save_provisioning_state` | `PUT` | `/clusters/{clusterName}` | `state` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1467) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:1124` | | | | | |
+| `cluster.logging.searchEngine` | `GET` | `/clusters/{clusterName}/logging/searchEngine?{query}` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1481) |
+| `admin.high_availability.polling` | `GET` | `/clusters/{clusterName}/requests/{requestId}?fields=tasks/*,Requests/*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1485) |
+| `admin.high_availability.getNnCheckPointStatus` | `GET` | `/clusters/{clusterName}/hosts/{hostName}/host_components/NAMENODE` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1489) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollbackHA/step2_controller.js:27`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step4_controller.js:38` | | | | | |
+| `admin.high_availability.getNnCheckPointsStatuses` | `GET` | `/clusters/{clusterName}/host_components?HostRoles/component_name=NAMENODE&HostRoles/host_name.in({hostNames})&fields=HostRoles/desired_state,metrics/dfs/namenode&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1493) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/journalNode/step3_controller.js:51` | | | | | |
+| `admin.high_availability.getJnCheckPointStatus` | `GET` | `/clusters/{clusterName}/hosts/{hostName}/host_components/JOURNALNODE?fields=metrics` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1497) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step6_controller.js:80` | | | | | |
+| `admin.high_availability.getHostComponent` | `GET` | `/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}` | 无 `format()` | `/api/v1 (default)` | 主机、安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1501) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:501` | | | | | |
+| `common.create_component` | `POST` | `/clusters/{clusterName}/services?ServiceInfo/service_name={serviceName}` | `componentName` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1505) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/host_components/install_component.js:134` | | | | | |
+| `admin.high_availability.load_configs` | `GET` | `/clusters/{clusterName}/configurations?(type=core-site&tag={coreSiteTag})\|(type=hdfs-site&tag={hdfsSiteTag})` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1523) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:414` | | | | | |
+| `admin.save_configs` | `PUT` | `/clusters/{clusterName}` | `properties`, `siteName` | `/api/v1 (default)` | 告警、安全、高可用与联邦 | 7 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1527) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:364`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:377`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:391`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:428`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability [...]
+| `admin.high_availability.load_hbase_configs` | `GET` | `/clusters/{clusterName}/configurations?type=hbase-site&tag={hbaseSiteTag}` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1544) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js:239` | | | | | |
+| `admin.security.cluster_configs` | `GET` | `/clusters/{clusterName}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安全、高可用与联邦 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1548) |
+| `admin.security.cluster_configs.kerberos` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?service_name=KERBEROS&is_current=true` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1557) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos.js:450` | | | | | |
+| `admin.get.all_configurations` | `GET` | `/clusters/{clusterName}/configurations?{urlParams}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 认证与应用外壳、主机、安全、高可用与联邦 | 10 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1566) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js:63`<br>`ambari-web/classic/app/controllers/main/admin/federation/step3_controller.js:73`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js:81`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js:214`<br>`ambari-web/classic/app/controllers/main/host/details.js:927`<br>`ambari-web/classi [...]
+| `kerberos.session.state` | `GET` | `/clusters/{clusterName}/services/KERBEROS?fields=Services/attributes/kdc_validation_result,Services/attributes/kdc_validation_failure_details` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1576) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos.js:423` | | | | | |
+| `admin.kerberize.cluster` | `PUT` | `/clusters/{clusterName}` | `data` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1581) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/step7_controller.js:42` | | | | | |
+| `admin.unkerberize.cluster` | `PUT` | `/clusters/{clusterName}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安全、高可用与联邦 | 4 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1591) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/disable_controller.js:48`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/step5_controller.js:104`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/step7_controller.js:70`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/wizard_controller.js:245` | | | | | |
+| `admin.kerberize.cluster.force` | `PUT` | `/clusters/{clusterName}?force_toggle_kerberos=true` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1605) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/step7_controller.js:55` | | | | | |
+| `admin.unkerberize.cluster.skip` | `PUT` | `/clusters/{clusterName}?manage_kerberos_identities=false` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1620) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/disable_controller.js:57` | | | | | |
+| `get.cluster.artifact` | `GET` | `/clusters/{clusterName}/artifacts/{artifactName}?fields=artifact_data` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1635) |
+| `admin.kerberize.stack_descriptor` | `GET` | `/clusters/{clusterName}/kerberos_descriptors/STACK` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1639) |
+| `admin.kerberize.cluster_descriptor_artifact` | `GET` | `/clusters/{clusterName}/artifacts/kerberos_descriptor?fields=artifact_data` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1643) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step7_controller.js:1885` | | | | | |
+| `admin.kerberize.cluster_descriptor` | `GET` | `/clusters/{clusterName}/kerberos_descriptors/COMPOSITE{queryParams}` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1647) |
+| | | 调用位置:`ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js:423` | | | | | |
+| `admin.kerberize.cluster_descriptor.stack` | `GET` | `/clusters/{clusterName}/kerberos_descriptors/STACK` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1651) |
+| | | 调用位置:`ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js:433` | | | | | |
+| `admin.kerberos.cluster.artifact.create` | `POST` | `/clusters/{clusterName}/artifacts/{artifactName}` | `data` | `/api/v1 (default)` | 安装向导、安全、高可用与联邦 | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1655) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos.js:584`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:895`<br>`ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js:860` | | | | | |
+| `admin.kerberos.cluster.artifact.update` | `PUT` | `/clusters/{clusterName}/artifacts/{artifactName}` | `data` | `/api/v1 (default)` | 安装向导、安全、高可用与联邦 | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1664) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos.js:557`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:895`<br>`ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js:878` | | | | | |
+| `admin.kerberos.cluster.csv` | `GET` | `/clusters/{clusterName}/kerberos_identities?fields=*&format=csv` | `data` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1673) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/step5_controller.js:64` | | | | | |
+| `admin.poll.kerberize.cluster.request` | `GET` | `/clusters/{clusterName}/requests/{requestId}?fields=stages/Stage/context,stages/Stage/status,stages/Stage/progress_percent,stages/tasks/*,Requests/*` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1683) |
+| | | 调用位置:`ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js:159` | | | | | |
+| `admin.stack_upgrade.run_upgrade` | `PUT` | `/clusters/{clusterName}` | `data` | `/api/v1 (default)` | Stack 与升级 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1687) |
+| `admin.user.create` | `POST` | `/users/{user}` | `data` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1697) |
+| `admin.user.edit` | `PUT` | `/users/{user}` | `data` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1708) |
+| `admin.stack_upgrade.do_poll` | `GET` | `/clusters/{cluster}/requests/{requestId}?fields=tasks/*` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1719) |
+| `admin.upgrade.data` | `GET` | `/clusters/{clusterName}/upgrades/{id}?upgrade_groups/UpgradeGroup/status!=PENDING&fields=Upgrade/progress_percent,Upgrade/request_context,Upgrade/request_status,Upgrade/direction,Upgrade/downgrade_allowed,upgrade_groups/UpgradeGroup,Upgrade/*,upgrade_groups/upgrade_items/UpgradeItem/status,upgrade_groups/upgrade_items/UpgradeItem/display_status,upgrade_groups/upgrade_items/UpgradeItem/context,upgrade_groups/upgrade_items/UpgradeItem/group_id,upgrade_grou [...]
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:462`<br>`ambari-web/classic/app/controllers/main/admin/stack_upgrade_history_controller.js:84` | | | | | |
+| `admin.upgrade.state` | `GET` | `/clusters/{clusterName}/upgrades/{id}?fields=Upgrade/*` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1740) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:462` | | | | | |
+| `admin.upgrade.finalizeContext` | `GET` | `/clusters/{clusterName}/upgrades/{id}?upgrade_groups/upgrade_items/UpgradeItem/status=HOLDING&fields=upgrade_groups/upgrade_items/UpgradeItem/context` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1744) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1963` | | | | | |
+| `admin.upgrade.upgrade_item` | `GET` | `/clusters/{clusterName}/upgrades/{upgradeId}/upgrade_groups/{groupId}/upgrade_items/{stageId}?fields=UpgradeItem/group_id,UpgradeItem/stage_id,tasks/Tasks/command_detail,tasks/Tasks/host_name,tasks/Tasks/role,tasks/Tasks/request_id,tasks/Tasks/stage_id,tasks/Tasks/status,tasks/Tasks/structured_out&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1748) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:627`<br>`ambari-web/classic/app/controllers/main/admin/stack_upgrade_history_controller.js:161` | | | | | |
+| `admin.upgrade.upgrade_task` | `GET` | `/clusters/{clusterName}/upgrades/{upgradeId}/upgrade_groups/{groupId}/upgrade_items/{stageId}/tasks/{taskId}` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1762) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:681`<br>`ambari-web/classic/app/controllers/main/admin/stack_upgrade_history_controller.js:214` | | | | | |
+| `admin.upgrade.service_checks` | `GET` | `/clusters/{clusterName}/upgrades/{upgradeId}/upgrade_groups?upgrade_items/UpgradeItem/status=COMPLETED&upgrade_items/tasks/Tasks/status.in(FAILED,ABORTED,TIMEDOUT)&upgrade_items/tasks/Tasks/command=SERVICE_CHECK&fields=upgrade_items/tasks/Tasks/command_detail,tasks/Tasks/ops_display_name,upgrade_items/tasks/Tasks/status&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | Stack 与升级、Views | 1 | [source](../../../../ambari-web/classic/ap [...]
+| | | 调用位置:`ambari-web/classic/app/views/main/admin/stack_upgrade/upgrade_wizard_view.js:311` | | | | | |
+| `admin.upgrade.update.options` | `PUT` | `/clusters/{clusterName}/upgrades/{upgradeId}` | `skipComponentFailures`, `skipSCFailures` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1769) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1234` | | | | | |
+| `admin.upgrade.start` | `POST` | `/clusters/{clusterName}/upgrades` | `id`, `skipComponentFailures`, `skipSCFailures`, `type` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1784) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:877` | | | | | |
+| `admin.downgrade.start` | `POST` | `/clusters/{clusterName}/upgrades` | `upgradeType` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1803) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:791` | | | | | |
+| `admin.upgrade.abort` | `PUT` | `/clusters/{clusterName}/upgrades/{upgradeId}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1818) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:774` | | | | | |
+| `admin.upgrade.suspend` | `PUT` | `/clusters/{clusterName}/upgrades/{upgradeId}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1833) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:813` | | | | | |
+| `admin.upgrade.retry` | `PUT` | `/clusters/{clusterName}/upgrades/{upgradeId}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1848) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:862` | | | | | |
+| `admin.upgrade.revert` | `POST` | `/clusters/{clusterName}/upgrades` | `upgradeId` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1862) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:2219` | | | | | |
+| `admin.upgrade.upgradeItem.setState` | `PUT` | `/clusters/{clusterName}/upgrades/{upgradeId}/upgrade_groups/{groupId}/upgrade_items/{itemId}` | `status` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1877) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:2057` | | | | | |
+| `admin.stack_versions.all` | `GET` | `/clusters/{clusterName}/stack_versions?fields=ClusterStackVersions/*,repository_versions/RepositoryVersions/*&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1891) |
+| `admin.stack_version.install.repo_version` | `POST` | `/clusters/{clusterName}/stack_versions` | `ClusterStackVersions` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1895) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1596` | | | | | |
+| `admin.stack_versions.edit.repo` | `PUT` | `/stacks/{stackName}/versions/{stackVersion}/repository_versions/{repoVersionId}` | `repoVersion` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1908) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:823`<br>`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1770` | | | | | |
+| `admin.stack_versions.validate.repo` | `POST` | `/stacks/{stackName}/versions/{stackVersion}/operating_systems/{osType}/repositories/{repoId}?validate_only=true` | `baseUrl`, `repoName` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1918) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1833` | | | | | |
+| `admin.stack_versions.discard` | `PUT` | `/stacks/{stackName}/versions/{stackVersion}/repository_versions/{id}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1934) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:2254` | | | | | |
+| `admin.upgrade.pre_upgrade_check` | `GET` | `/clusters/{clusterName}/rolling_upgrades_check?fields=*&UpgradeChecks/repository_version_id={id}&UpgradeChecks/upgrade_type={type}` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1949) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1296`<br>`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1463` | | | | | |
+| `admin.upgrade.get_supported_upgradeTypes` | `GET` | `/stacks/{stackName}/versions/{stackVersion}/compatible_repository_versions?CompatibleRepositoryVersions/repository_version={toVersion}` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1954) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:1319` | | | | | |
+| `admin.upgrade.get_compatible_versions` | `GET` | `/stacks/{stackName}/versions/{stackVersion}/compatible_repository_versions?fields=CompatibleRepositoryVersions/repository_version&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1959) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js:501` | | | | | |
+| `admin.kerberos_security.checks` | `GET` | `''` | 无 `format()` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1964) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos.js:235` | | | | | |
+| `admin.kerberos_security.test_connection` | `GET` | `/kdc_check/{kdcHostname}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安全、高可用与联邦、Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1970) |
+| | | 调用位置:`ambari-web/classic/app/views/common/controls_view.js:1333` | | | | | |
+| `admin.kerberos_security.regenerate_keytabs` | `PUT` | `/clusters/{clusterName}?regenerate_keytabs={type}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1980) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos.js:183` | | | | | |
+| `admin.kerberos_security.regenerate_keytabs.service` | `PUT` | `/clusters/{clusterName}?regenerate_keytabs=all®enerate_components={serviceName}&config_update_policy=none` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 服务与配置、安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1995) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/item.js:890` | | | | | |
+| `admin.kerberos_security.regenerate_keytabs.host` | `PUT` | `/clusters/{clusterName}?regenerate_keytabs=all®enerate_hosts={hostName}&config_update_policy=none` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 主机、安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2010) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/details.js:3268` | | | | | |
+| `wizard.step1.post_version_definition_file.xml` | `POST` | `/version_definitions?dry_run=true` | `data` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2025) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:588` | | | | | |
+| `wizard.step1.post_version_definition_file.url` | `POST` | `/version_definitions?dry_run=true` | `data` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2039) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:588` | | | | | |
+| `wizard.step8.post_version_definition_file.xml` | `POST` | `/version_definitions` | `data` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2049) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:655` | | | | | |
+| `wizard.step8.post_version_definition_file` | `POST` | `/version_definitions` | `data` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2063) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:655` | | | | | |
+| `wizard.step1.get_repo_version_by_id` | `GET` | `/stacks/{stackName}/versions?fields=repository_versions/operating_systems/repositories/*,repository_versions/RepositoryVersions/*&repository_versions/RepositoryVersions/id={repoId}&Versions/stack_version={stackVersion}` | 无 `format()` | `/api/v1 (default)` | Stack 与升级 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2073) |
+| `wizard.step1.get_supported_os_types` | `GET` | `/stacks/{stackName}/versions/{stackVersion}?fields=operating_systems/repositories/Repositories` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2080) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:702` | | | | | |
+| `wizard.advanced_repositories.valid_url` | `PUT` | `/stacks/{stackName}/versions/{stackVersion}/operating_systems/{osType}/repositories/{repoId}` | `data` | `/api/v1 (default)` | 安装向导、Stack 与升级、Views | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2085) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:910`<br>`ambari-web/classic/app/views/main/admin/stack_upgrade/services_view.js:241` | | | | | |
+| `wizard.get_version_definitions` | `GET` | `/version_definitions` | 无 `format()` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2095) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:836` | | | | | |
+| `wizard.delete_repository_versions` | `DELETE` | `/stacks/{stackName}/versions/{stackVersion}/repository_versions/{id}` | 无 `format()` | `/api/v1 (default)` | 安装向导、Stack 与升级 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2098) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:863` | | | | | |
+| `wizard.service_components` | `GET` | `{stackUrl}/services?fields=StackServices/*,components/*,components/dependencies/Dependencies/scope,components/dependencies/Dependencies/service_name,components/dependencies/Dependencies/type,artifacts/Artifacts/artifact_name` | 无 `format()` | `/api/v1 (default)` | 安装向导、后台操作与通用能力 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2102) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/cluster_controller.js:399`<br>`ambari-web/classic/app/controllers/wizard.js:630` | | | | | |
+| `wizard.step9.installer.get_host_status` | `GET` | `/clusters/{cluster}/hosts?fields=Hosts/host_state,host_components/HostRoles/state` | 无 `format()` | `/api/v1 (default)` | 安装向导、主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2106) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step9_controller.js:1109` | | | | | |
+| `wizard.step9.load_log` | `GET` | `/clusters/{cluster}/requests/{requestId}?fields=tasks/Tasks/command,tasks/Tasks/command_detail,tasks/Tasks/ops_display_name,tasks/Tasks/exit_code,tasks/Tasks/start_time,tasks/Tasks/end_time,tasks/Tasks/host_name,tasks/Tasks/id,tasks/Tasks/role,tasks/Tasks/status&minimal_response=true` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2110) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step9_controller.js:1041` | | | | | |
+| `wizard.step8.existing_cluster_names` | `GET` | `/clusters` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2120) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:719` | | | | | |
+| `wizard.step8.create_cluster` | `POST` | `/clusters/{cluster}` | `data` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2125) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:992` | | | | | |
+| `wizard.step8.create_selected_services` | `POST` | `/clusters/{cluster}/services` | `data` | `/api/v1 (default)` | 安装向导、安全、高可用与联邦 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2137) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/wizard_controller.js:204`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:1014` | | | | | |
+| `wizard.step8.create_components` | `POST` | `/clusters/{cluster}/services?ServiceInfo/service_name={serviceName}` | `data` | `/api/v1 (default)` | 安装向导 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2149) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:1088`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:1108` | | | | | |
+| `wizard.step8.register_host_to_cluster` | `POST` | `/clusters/{cluster}/hosts` | `data` | `/api/v1 (default)` | 安装向导、主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2161) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:1133` | | | | | |
+| `wizard.step8.register_host_to_component` | `POST` | `/clusters/{cluster}/hosts` | `data` | `/api/v1 (default)` | 安装向导、主机、安全、高可用与联邦 | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2173) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/wizard_controller.js:275`<br>`ambari-web/classic/app/controllers/wizard/step8_controller.js:1463`<br>`ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js:569` | | | | | |
+| `wizard.step8.apply_configuration_groups` | `POST` | `/clusters/{cluster}/config_groups` | `data` | `/api/v1 (default)` | 安装向导、服务与配置 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2185) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step8_controller.js:1582`<br>`ambari-web/classic/app/mixins/common/configs/configs_saver.js:586` | | | | | |
+| `wizard.step8.set_local_repos` | `PUT` | `{stackVersionURL}/operating_systems/{osType}/repositories/{repoId}` | `data` | `/api/v1 (default)` | Stack 与升级 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2197) |
+| `wizard.step3.jdk_check` | `POST` | `/requests` | `host_names`, `java_home`, `jdk_location` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2208) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step3_controller.js:732` | | | | | |
+| `wizard.step3.jdk_check.get_results` | `GET` | `/requests/{requestIndex}?fields=*,tasks/Tasks/host_name,tasks/Tasks/status,tasks/Tasks/structured_out` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2232) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step3_controller.js:751` | | | | | |
+| `wizard.step3.host_info` | `GET` | `/hosts?fields=Hosts/total_mem,Hosts/cpu_count,Hosts/disk_info,Hosts/last_agent_env,Hosts/host_name,Hosts/os_type,Hosts/os_arch,Hosts/os_family,Hosts/ip` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2236) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/actions/check_host.js:254` | | | | | |
+| `wizard.loadrecommendations` | `POST` | `{stackVersionUrl}/recommendations` | `hosts`, `recommend`, `recommendations`, `services` | `/api/v1 (default)` | 安装向导、主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2247) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/hosts/host_component_recommendation_mixin.js:117` | | | | | |
+| `config.recommendations` | `POST` | `{stackVersionUrl}/recommendations` | `dataToSend` | `/api/v1 (default)` | 安装向导、服务与配置、安全、高可用与联邦 | 4 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2270) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js:202`<br>`ambari-web/classic/app/controllers/main/admin/kerberos/step4_controller.js:343`<br>`ambari-web/classic/app/mixins/common/configs/enhanced_configs.js:336`<br>`ambari-web/classic/app/mixins/wizard/addSecurityConfigs.js:759` | | | | | |
+| `config.validations` | `POST` | `{stackVersionUrl}/validations` | `hosts`, `recommendations`, `services`, `validate` | `/api/v1 (default)` | 主机 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2282) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/hosts/host_component_validation_mixin.js:96`<br>`ambari-web/classic/app/mixins/common/serverValidator.js:197` | | | | | |
+| `preinstalled.checks` | `POST` | `/requests` | `RequestInfo`, `resource_filters` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2299) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/actions/check_host.js:145` | | | | | |
+| `preinstalled.checks.tasks` | `GET` | `/requests/{requestId}?fields=Requests/inputs,Requests/request_status,tasks/Tasks/host_name,tasks/Tasks/structured_out/host_resolution_check/hosts_with_failures,tasks/Tasks/structured_out/host_resolution_check/failed_count,tasks/Tasks/structured_out/installed_packages,tasks/Tasks/structured_out/last_agent_env_check,tasks/Tasks/structured_out/transparentHugePage,tasks/Tasks/stdout,tasks/Tasks/stderr,tasks/Tasks/error_log,tasks/Tasks/command_detail,t [...]
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/actions/check_host.js:189` | | | | | |
+| `persist.get.text` | `GET` | `/persist/{key}` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 跨模块与待人工归类 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2329) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/persist.js:68` | | | | | |
+| `persist.get` | `GET` | `/persist/{key}` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2340) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/persist.js:49` | | | | | |
+| `persist.post` | `POST` | `/persist` | `keyValuePair` | `/api/v1 (default)` | 跨模块与待人工归类 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2345) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/persist.js:129` | | | | | |
+| `wizard.step3.rerun_checks` | `GET` | `/hosts?fields=Hosts/last_agent_env` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2356) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/actions/check_host.js:444` | | | | | |
+| `wizard.step3.bootstrap` | `GET` | `/bootstrap/{bootRequestId}` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2365) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step3_controller.js:485` | | | | | |
+| `wizard.step3.is_hosts_registered` | `GET` | `/hosts?fields=Hosts/host_status` | 无 `format()` | `/api/v1 (default)` | 安装向导、主机 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2369) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step3_controller.js:575`<br>`ambari-web/classic/app/controllers/wizard/step3_controller.js:670` | | | | | |
+| `wizard.stacks` | `GET` | `/stacks` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2373) |
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:238` | | | | | |
+| `wizard.stacks_versions` | `GET` | `/stacks/{stackName}/versions?fields=Versions,operating_systems/repositories/Repositories` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2377) |
+| `wizard.stacks_versions_definitions` | `GET` | `/version_definitions?fields=VersionDefinition/stack_default,VersionDefinition/stack_repo_update_link_exists,VersionDefinition/max_jdk,VersionDefinition/min_jdk,operating_systems/repositories/Repositories/*,operating_systems/OperatingSystems/*,VersionDefinition/stack_services,VersionDefinition/repository_version&VersionDefinition/show_available=true&VersionDefinition/stack_name={stackName}` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [...]
+| | | 调用位置:`ambari-web/classic/app/controllers/installer.js:272` | | | | | |
+| `wizard.launch_bootstrap` | `POST` | `/bootstrap` | `bootStrapData`, `popup` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2387) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard.js:515` | | | | | |
+| `router.login` | `POST` | `/auth` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 认证与应用外壳 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2399) |
+| | | 调用位置:`ambari-web/classic/app/router.js:422` | | | | | |
+| `router.afterLogin` | `GET` | `/users/{loginName}?fields=*,privileges/PrivilegeInfo/cluster_name,privileges/PrivilegeInfo/permission_name` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 认证与应用外壳 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2413) |
+| | | 调用位置:`ambari-web/classic/app/router.js:297`<br>`ambari-web/classic/app/router.js:443` | | | | | |
+| `users.all` | `GET` | `/users/?fields=*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2426) |
+| `users.privileges` | `GET` | `/privileges?fields=*` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2430) |
+| `router.user.privileges` | `GET` | `/users/{userName}/privileges?fields=*` | 无 `format()` | `/api/v1 (default)` | 认证与应用外壳 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2434) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/user_settings_controller.js:202` | | | | | |
+| `router.user.authorizations` | `GET` | `/users/{userName}/authorizations?fields=*` | 无 `format()` | `/api/v1 (default)` | 认证与应用外壳、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2438) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/cluster_controller.js:420` | | | | | |
+| `router.login.clusters` | `GET` | `/clusters?fields=Clusters/provisioning_state,Clusters/security_type,Clusters/version,Clusters/cluster_id` | 无 `format()` | `/api/v1 (default)` | 认证与应用外壳 | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2442) |
+| | | 调用位置:`ambari-web/classic/app/controllers/application.js:125`<br>`ambari-web/classic/app/router.js:275`<br>`ambari-web/classic/app/router.js:572` | | | | | |
+| `router.login.message` | `GET` | `/settings/motd` | 无 `format()` | `/api/v1 (default)` | 认证与应用外壳 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2446) |
+| | | 调用位置:`ambari-web/classic/app/router.js:464` | | | | | |
+| `router.logoff` | `GET` | `/logout` | 无静态 `data.*` 引用 | `/api/v1 (default)` | 认证与应用外壳 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2450) |
+| | | 调用位置:`ambari-web/classic/app/router.js:738` | | | | | |
+| `ambari.service` | `GET` | `/services/AMBARI/components/AMBARI_SERVER{fields}` | 无 `format()` | `/api/v1 (default)` | 安装向导、后台操作与通用能力 | 6 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2461) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/cluster_controller.js:411`<br>`ambari-web/classic/app/controllers/installer.js:426`<br>`ambari-web/classic/app/controllers/wizard/step2_controller.js:523`<br>`ambari-web/classic/app/controllers/wizard/step3_controller.js:707`<br>`ambari-web/classic/app/controllers/wizard/step7_controller.js:1566`<br>`ambari-web/classic/app/controllers/wizard/step9_controller.js:1226` | | | | | |
+| `config_groups.create` | `POST` | `/clusters/{clusterName}/config_groups` | `data` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2466) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/service/configs/config_overridable.js:260` | | | | | |
+| `config_groups.update` | `PUT` | `/clusters/{clusterName}/config_groups/{id}` | `data` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2476) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/service/configs/config_overridable.js:310` | | | | | |
+| `request_schedule.get` | `GET` | `/clusters/{clusterName}/request_schedules/{request_schedule_id}` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2486) |
+| | | 调用位置:`ambari-web/classic/app/utils/batch_scheduled_requests.js:714` | | | | | |
+| `request_schedule.get.pending` | `GET` | `/clusters/{clusterName}/request_schedules?fields=*&(RequestSchedule/status.in(SCHEDULED,IN_PROGRESS))` | 无 `format()` | `/api/v1 (default)` | 安装向导、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2490) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step7/assign_master_controller.js:86` | | | | | |
+| `restart.hostComponents` | `POST` | `/clusters/{clusterName}/requests` | `context`, `operation_level`, `resource_filters` | `/api/v1 (default)` | 主机、服务与配置、后台操作与通用能力 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2494) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:552`<br>`ambari-web/classic/app/utils/batch_scheduled_requests.js:256` | | | | | |
+| `restart.allServices` | `POST` | `/clusters/{clusterName}/requests` | `clusterName` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2512) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service.js:184` | | | | | |
+| `restart.staleConfigs` | `POST` | `/clusters/{clusterName}/requests` | `clusterName` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2534) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service.js:349` | | | | | |
+| `restart.custom.filter` | `POST` | `/clusters/{clusterName}/requests` | `context`, `filter` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2556) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js:185` | | | | | |
+| `bulk_request.decommission` | `POST` | `/clusters/{clusterName}/requests` | `clusterName`, `componentName`, `context`, `parameters`, `serviceName` | `/api/v1 (default)` | 主机、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2578) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:922` | | | | | |
+| `bulk_request.hosts.passive_state` | `PUT` | `/clusters/{clusterName}/hosts` | `hostNames`, `passive_state`, `requestInfo` | `/api/v1 (default)` | 主机、Stack 与升级、Views、后台操作与通用能力 | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2600) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:502`<br>`ambari-web/classic/app/controllers/main/host/details.js:2342`<br>`ambari-web/classic/app/views/main/admin/stack_upgrade/custom_cluster_checks/custom_cluster_checks_maintenance_view.js:30` | | | | | |
+| `bulk_request.hosts.update_rack_id` | `PUT` | `/clusters/{clusterName}/hosts` | `hostNames`, `rackId`, `requestInfo` | `/api/v1 (default)` | 主机、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2622) |
+| | | 调用位置:`ambari-web/classic/app/utils/hosts.js:248` | | | | | |
+| `bulk_request.hosts.all_components.passive_state` | `PUT` | `/clusters/{clusterName}/host_components` | `passive_state`, `query`, `requestInfo` | `/api/v1 (default)` | 主机、后台操作与通用能力 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2643) |
+| `views.info` | `GET` | `/views` | 无 `format()` | `/api/v1 (default)` | Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2663) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/views_controller.js:50` | | | | | |
+| `views.instances` | `GET` | `/views?fields=versions/instances/ViewInstanceInfo,versions/ViewVersionInfo/label&versions/ViewVersionInfo/system=false` | 无 `format()` | `/api/v1 (default)` | Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2670) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/views_controller.js:61` | | | | | |
+| `host.host_components.filtered` | `POST` | `/clusters/{clusterName}/hosts?{fields}` | `parameters` | `/api/v1 (default)` | 主机、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2687) |
+| | | 调用位置:`ambari-web/classic/app/utils/batch_scheduled_requests.js:188` | | | | | |
+| `host.stack_versions.install` | `POST` | `/clusters/{clusterName}/hosts/{hostName}/stack_versions` | `version` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2702) |
+| | | 调用位置:`ambari-web/classic/app/mixins/main/host/details/actions/install_new_version.js:42` | | | | | |
+| `host.logging` | `GET` | `/clusters/{clusterName}/hosts/{hostName}?fields=host_components/logging,host_components/HostRoles/service_name{fields}{query}&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2719) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/update_controller.js:730` | | | | | |
+| `components.filter_by_status` | `GET` | `/clusters/{clusterName}/components?fields=host_components/HostRoles/host_name,ServiceComponentInfo/component_name,ServiceComponentInfo/started_count{urlParams}&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2723) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/configs/configs_saver.js:881` | | | | | |
+| `components.get_category` | `GET` | `/clusters/{clusterName}/components?fields=ServiceComponentInfo/component_name,ServiceComponentInfo/service_name,ServiceComponentInfo/category,ServiceComponentInfo/recovery_enabled,ServiceComponentInfo/total_count&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2727) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/service_auto_start.js:134` | | | | | |
+| `components.get.staleConfigs` | `GET` | `/clusters/{clusterName}/components?host_components/HostRoles/stale_configs=true&fields=host_components/HostRoles/host_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2731) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/cluster_controller.js:264` | | | | | |
+| `components.update` | `PUT` | `/clusters/{clusterName}/components?{urlParams}` | `ServiceComponentInfo`, `query` | `/api/v1 (default)` | 跨模块与待人工归类 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2736) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/service_auto_start.js:164` | | | | | |
+| `hosts.all.install` | `GET` | `/hosts?minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2751) |
+| `hosts.all` | `GET` | `/clusters/{clusterName}/hosts?minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、安全、高可用与联邦、Views、后台操作与通用能力 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2755) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/cluster_controller.js:461`<br>`ambari-web/classic/app/views/main/admin/highAvailability/nameNode/rollbackHA/step1_view.js:35` | | | | | |
+| `hosts.with_public_host_names` | `GET` | `/clusters/{clusterName}/hosts?fields=Hosts/public_host_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2759) |
+| `hosts.for_quick_links` | `GET` | `/clusters/{clusterName}/hosts?Hosts/host_name.in({hosts})&fields=Hosts/public_host_name{urlParams}&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2763) |
+| | | 调用位置:`ambari-web/classic/app/views/common/quick_view_link_view.js:213` | | | | | |
+| `hosts.confirmed.install` | `GET` | `/hosts?fields=Hosts/cpu_count,Hosts/disk_info,Hosts/total_mem,Hosts/maintenance_state&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2767) |
+| `hosts.confirmed` | `GET` | `/clusters/{clusterName}/hosts?fields=host_components/HostRoles/state&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 安装向导、主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2771) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard.js:1282` | | | | | |
+| `hosts.with_searchTerm` | `POST` | `/clusters/{clusterName}/hosts?fields=Hosts/{facet}&minimal_response=true&page_size={page_size}` | `facet`, `searchTerm` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2775) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/combo_search_box.js:28` | | | | | |
+| `hosts.confirmed.minimal` | `GET` | `/clusters/{clusterName}/hosts?fields=host_components/HostRoles/state&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2790) |
+| | | 调用位置:`ambari-web/classic/app/views/main/host/add_view.js:41` | | | | | |
+| `hosts.heartbeat_lost` | `GET` | `/clusters/{clusterName}/hosts?Hosts/host_state=HEARTBEAT_LOST` | 无 `format()` | `/api/v1 (default)` | 主机、安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2794) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/kerberos/step3_controller.js:67` | | | | | |
+| `host_components.all` | `GET` | `/clusters/{clusterName}/host_components?fields=HostRoles/host_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2798) |
+| `host_components.with_services_names` | `GET` | `/clusters/{clusterName}/host_components?fields=component/ServiceComponentInfo/service_name,HostRoles/host_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2802) |
+| `components.get_installed` | `GET` | `/clusters/{clusterName}/components` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2806) |
+| `custom_action.create` | `POST` | `/requests` | `filteredHosts`, `requestInfo` | `/api/v1 (default)` | 服务与配置、Views | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2819) |
+| | | 调用位置:`ambari-web/classic/app/views/common/configs/widgets/test_db_connection_widget_view.js:227`<br>`ambari-web/classic/app/views/common/controls_view.js:1369` | | | | | |
+| `cluster.custom_action.create` | `POST` | `/clusters/{clusterName}/requests` | `filteredHosts`, `requestInfo` | `/api/v1 (default)` | 服务与配置、Views | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2841) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:664`<br>`ambari-web/classic/app/views/common/configs/widgets/test_db_connection_widget_view.js:227`<br>`ambari-web/classic/app/views/common/controls_view.js:1369` | | | | | |
+| `custom_action.request` | `GET` | `/requests/{requestId}/tasks/{taskId}` | `requestId`, `taskId` | `/api/v1 (default)` | 服务与配置、Views、后台操作与通用能力 | 6 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2863) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:682`<br>`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:706`<br>`ambari-web/classic/app/views/common/configs/widgets/test_db_connection_widget_view.js:247`<br>`ambari-web/classic/app/views/common/configs/widgets/test_db_connection_widget_view.js:268`<br>`ambari-web/classic/app/views/common/controls_view.js:1389`<br>`ambari-web/classic/app/views/common/controls_view.js: [...]
+| `hosts.high_availability.wizard` | `GET` | `/clusters/{clusterName}/hosts?fields=Hosts/cpu_count,Hosts/disk_info,Hosts/total_mem,Hosts/maintenance_state&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、服务与配置、Views | 3 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2873) |
+| | | 调用位置:`ambari-web/classic/app/mixins/wizard/assign_master_components.js:605`<br>`ambari-web/classic/app/mixins/wizard/wizardHostsLoading.js:44`<br>`ambari-web/classic/app/views/main/service/reassign_view.js:38` | | | | | |
+| `hosts.security.wizard` | `GET` | `/clusters/{clusterName}/hosts?fields=host_components/HostRoles/service_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、安全、高可用与联邦 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2877) |
+| `host_component.installed.on_hosts` | `GET` | `/clusters/{clusterName}/host_components?HostRoles/component_name={componentName}&HostRoles/host_name.in({hostNames})&fields=HostRoles/host_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2881) |
+| | | 调用位置:`ambari-web/classic/app/mixins/wizard/wizardProgressPageController.js:418` | | | | | |
+| `hosts.by_component.one` | `GET` | `/clusters/{clusterName}/hosts?host_components/HostRoles/component_name.in({componentNames})&fields=host_components,Hosts/cpu_count,Hosts/disk_info,Hosts/total_mem,Hosts/ip,Hosts/os_type,Hosts/os_arch,Hosts/public_host_name&page_size=1&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2885) |
+| `hosts.by_component.all` | `GET` | `/clusters/{clusterName}/hosts?host_components/HostRoles/component_name.in({componentNames})&fields=host_components,Hosts/cpu_count,Hosts/disk_info,Hosts/total_mem,Hosts/ip,Hosts/os_type,Hosts/os_arch,Hosts/public_host_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2889) |
+| `hosts.config_groups` | `GET` | `/clusters/{clusterName}/hosts?fields=Hosts/cpu_count,Hosts/disk_info,Hosts/total_mem,Hosts/ip,Hosts/os_type,Hosts/os_arch,Hosts/public_host_name,host_components&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2893) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/manage_config_groups_controller.js:308` | | | | | |
+| `hosts.info.install` | `GET` | `/hosts?Hosts/host_name.in({hostNames})&fields=Hosts/cpu_count,Hosts/disk_info,Hosts/total_mem,Hosts/ip,Hosts/os_type,Hosts/os_arch,Hosts/public_host_name&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 主机、服务与配置 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2897) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/manage_config_groups_controller.js:1058`<br>`ambari-web/classic/app/mixins/wizard/assign_master_components.js:605` | | | | | |
+| `hosts.ips` | `GET` | `/hosts?Hosts/host_name.in({hostNames})&fields=Hosts/ip` | 无 `format()` | `/api/v1 (default)` | 主机 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2901) |
+| | | 调用位置:`ambari-web/classic/app/mappers/service_metrics_mapper.js:335` | | | | | |
+| `hosts.host_components.pre_load` | `GET` | `data.url [DYNAMIC_URL]` | `url` | `/api/v1 (default)` | 主机、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2905) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/update_controller.js:473` | | | | | |
+| `hosts.metrics.lazy_load` | `POST` | `data.url [DYNAMIC_URL]` | `parameters`, `url` | `/api/v1 (default)` | 主机、后台操作与通用能力 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2914) |
+| | | 调用位置:`ambari-web/classic/app/controllers/global/update_controller.js:405` | | | | | |
+| `hosts.bulk.operations` | `POST` | `/clusters/{clusterName}/hosts?fields=Hosts/host_name,Hosts/host_state,Hosts/maintenance_state,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,Hosts/total_mem,stack_versions/HostStackVersions,stack_versions/repository_versions/RepositoryVersions/repository_version,stack_versions/repository_versions/RepositoryVersions/id,host_components/HostRoles/stale_configshost_components/HostRoles/service_name&minimal_response=true` | `p [...]
+| | | 调用位置:`ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js:1097` | | | | | |
+| `logtail.get` | `GET` | `/clusters/{clusterName}/logging/searchEngine?component_name={logComponentName}&host_name={hostName}&pageSize={pageSize}&startIndex={startIndex}` | 无 `format()` | `/api/v1 (default)` | Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2951) |
+| | | 调用位置:`ambari-web/classic/app/views/common/log_tail_view.js:135` | | | | | |
+| `service.serviceConfigVersions.get` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?service_name={serviceName}&fields=service_config_version,user,hosts,group_id,group_name,is_current,createtime,service_name,service_config_version_note,stack_id,is_cluster_compatible&sortBy=service_config_version.desc&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2956) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/configs/configs_loader.js:49` | | | | | |
+| `service.serviceConfigVersions.get.current` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?service_name.in({serviceNames})&is_current=true&fields=*` | 无 `format()` | `/api/v1 (default)` | 服务与配置 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2960) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/configs/configs_loader.js:110`<br>`ambari-web/classic/app/mixins/main/service/configs/hive_interactive_check.js:26` | | | | | |
+| `service.serviceConfigVersions.get.current.not.default` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?is_current=true&group_id>0&fields=*` | 无 `format()` | `/api/v1 (default)` | 安装向导 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2964) |
+| | | 调用位置:`ambari-web/classic/app/controllers/wizard/step7_controller.js:421` | | | | | |
+| `service.serviceConfigVersions.get.total` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?page_size=1&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2968) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/dashboard/config_history_controller.js:141` | | | | | |
+| `service.serviceConfigVersion.get` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?service_name={serviceName}&service_config_version={serviceConfigVersion}` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2972) |
+| `service.serviceConfigVersions.get.multiple` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?(service_name={serviceName}%26service_config_version.in({serviceConfigVersions})){additionalParams}` | `additionalParams`, `serviceConfigVersions` | `/api/v1 (default)` | 服务与配置 | 2 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2976) |
+| | | 调用位置:`ambari-web/classic/app/mixins/common/configs/configs_comparator.js:393`<br>`ambari-web/classic/app/mixins/common/configs/configs_loader.js:177` | | | | | |
+| `service.serviceConfigVersions.get.suggestions` | `GET` | `/clusters/{clusterName}/configurations/service_config_versions?fields={key}&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2986) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/dashboard/config_history_controller.js:210` | | | | | |
+| `service.serviceConfigVersion.revert` | `PUT` | `/clusters/{clusterName}` | `data` | `/api/v1 (default)` | 服务与配置、Views | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2990) |
+| | | 调用位置:`ambari-web/classic/app/views/common/configs/config_versions_control_view.js:201` | | | | | |
+| `service.mysql.clean` | `POST` | `/clusters/{clusterName}/requests` | `host` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3001) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:486` | | | | | |
+| `service.mysql.configure` | `POST` | `/clusters/{clusterName}/requests` | `host` | `/api/v1 (default)` | 服务与配置 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3021) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/service/reassign/step4_controller.js:507` | | | | | |
+| `service.mysql.testHiveConnection` | `POST` | `/requests` | `db_connection_url`, `db_name`, `db_pass`, `db_user`, `hosts`, `java_home`, `jdk_location`, `jdk_name` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3041) |
+| `service.components.load` | `GET` | `/clusters/{clusterName}/services?fields=components&minimal_response=true` | 无 `format()` | `/api/v1 (default)` | 跨模块与待人工归类 | 0 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3216) |
+| `nameNode.federation.formatNameNode` | `POST` | `/clusters/{clusterName}/requests` | `host` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3221) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js:119` | | | | | |
+| `nameNode.federation.formatZKFC` | `POST` | `/clusters/{clusterName}/requests` | `host` | `/api/v1 (default)` | 安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3241) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js:131` | | | | | |
+| `nameNode.federation.bootstrapNameNode` | `POST` | `/clusters/{clusterName}/requests` | `host` | `/api/v1 (default)` | 安装向导、安全、高可用与联邦 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3260) |
+| | | 调用位置:`ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js:165` | | | | | |
+| `hiveServerInteractive.getStatus` | `GET` | `http://:/leader [dynamic: 'http://' + data.hsiHost + ':' + data.port + '/leader'] [DYNAMIC_URL]` | `hsiHost`, `port` | `/api/v1 (default)` | 跨模块与待人工归类 | 1 | [source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3279) |
+| | | 调用位置:`ambari-web/classic/app/mappers/service_metrics_mapper.js:353` | | | | | |
+
+## 已排除的 Metrics 请求
+
+- `hosts.metrics` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1089))
+- `hosts.metrics.host_component` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1093))
+- `service.metrics.flume.channel_fill_percent` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1097))
+- `service.metrics.flume.channel_size` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1102))
+- `service.metrics.flume.sink_drain_success` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1107))
+- `service.metrics.flume.sink_connection_failed` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1112))
+- `service.metrics.flume.source_accepted` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1117))
+- `service.metrics.flume.channel_size_for_all` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1122))
+- `service.metrics.flume.channel_size_for_all.mma` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1125))
+- `service.metrics.flume.channel_size_for_all.sum` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1128))
+- `service.metrics.flume.gc` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1131))
+- `service.metrics.flume.jvm_heap_used` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1136))
+- `service.metrics.flume.jvm_threads_runnable` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1141))
+- `service.metrics.flume.cpu_user` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1146))
+- `service.metrics.flume.incoming_event_put_successCount` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1151))
+- `service.metrics.flume.incoming_event_put_successCount.mma` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1154))
+- `service.metrics.flume.incoming_event_put_successCount.sum` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1157))
+- `service.metrics.flume.outgoing_event_take_success_count` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1160))
+- `service.metrics.flume.outgoing_event_take_success_count.mma` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1163))
+- `service.metrics.flume.outgoing_event_take_success_count.sum` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1166))
+- `service.metrics.hbase.cluster_requests` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1169))
+- `service.metrics.hbase.hlog_split_size` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1174))
+- `service.metrics.hbase.hlog_split_time` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1179))
+- `service.metrics.hbase.regionserver_queuesize` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1184))
+- `service.metrics.hbase.regionserver_regions` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1189))
+- `service.metrics.hbase.regionserver_rw_requests` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1194))
+- `service.metrics.ambari_metrics.master.average_load` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1199))
+- `service.metrics.ambari_metrics.region_server.store_files` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1204))
+- `service.metrics.ambari_metrics.region_server.regions` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1209))
+- `service.metrics.ambari_metrics.region_server.request` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1214))
+- `service.metrics.ambari_metrics.region_server.block_cache_hit_percent` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1219))
+- `service.metrics.ambari_metrics.region_server.compaction_queue_size` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1224))
+- `service.metrics.ambari_metrics.aggregated` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1229))
+- `service.metrics.hdfs.block_status` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1234))
+- `service.metrics.hdfs.file_operations` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1239))
+- `service.metrics.hdfs.gc` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1244))
+- `service.metrics.hdfs.io` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1249))
+- `service.metrics.hdfs.jvm_heap` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1254))
+- `service.metrics.hdfs.jvm_threads` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1259))
+- `service.metrics.hdfs.rpc` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1264))
+- `service.metrics.hdfs.space_utilization` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1269))
+- `service.metrics.yarn.gc` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1274))
+- `service.metrics.yarn.jobs_threads` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1279))
+- `service.metrics.yarn.rpc` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1284))
+- `service.metrics.yarn.jobs_heap` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1289))
+- `service.metrics.yarn.queue.allocated` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1294))
+- `service.metrics.yarn.queue.allocated.container` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1299))
+- `service.metrics.yarn.node.manager.statuses` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1304))
+- `service.metrics.yarn.queue.memory.resource` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1309))
+- `service.metrics.yarn.queue.apps.states.current` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1328))
+- `service.metrics.yarn.queue.apps.states.finished` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1333))
+- `service.metrics.kafka.broker.topic` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1338))
+- `service.metrics.kafka.controller.KafkaController` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1342))
+- `service.metrics.kafka.controller.ControllerStats` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1346))
+- `service.metrics.kafka.log.LogFlushStats` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1350))
+- `service.metrics.kafka.server.ReplicaManager` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1354))
+- `service.metrics.kafka.server.ReplicaFetcherManager` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1358))
+- `service.metrics.storm.nimbus` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1362))
+- `dashboard.cluster_metrics.cpu` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1366))
+- `dashboard.cluster_metrics.load` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1371))
+- `dashboard.cluster_metrics.memory` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1376))
+- `dashboard.cluster_metrics.network` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1381))
+- `host.metrics.aggregated` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1386))
+- `host.metrics.cpu` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1390))
+- `host.metrics.disk` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1395))
+- `host.metrics.load` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1400))
+- `host.metrics.memory` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1405))
+- `host.metrics.network` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1410))
+- `host.metrics.processes` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L1415))
+- `host.host_component.flume.metrics` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2674))
+- `host.host_component.flume.metrics.timeseries` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2678))
+- `hosts.heatmaps` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2810))
+- `namenode.cpu_wio` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L2814))
+- `widgets.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3069))
+- `widgets.all.shared.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3074))
+- `widgets.all.mine.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3079))
+- `widgets.layout.stackDefined.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3084))
+- `widget.layout.id.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3089))
+- `widget.layout.name.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3094))
+- `widget.layout.delete` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3099))
+- `widget.layout.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3104))
+- `widget.layout.edit` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3109))
+- `widget.layout.create` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3120))
+- `widgets.layout.userDefined.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3131))
+- `widgets.layouts.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3136))
+- `widgets.layouts.active.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3141))
+- `widgets.layouts.all.active.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3146))
+- `widget.activelayouts.edit` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3152))
+- `widget.action.delete` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3164))
+- `widgets.serviceComponent.metrics.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3174))
+- `widgets.hostComponent.metrics.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3179))
+- `widgets.hosts.metrics.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3184))
+- `widgets.wizard.metrics.get` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3189))
+- `widgets.wizard.add` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3194))
+- `widgets.wizard.edit` ([source](../../../../ambari-web/classic/app/utils/ajax/ajax.js#L3205))
diff --git a/docs/frontend-refactor/ember-baseline/generated/api-by-module/auth-shell.md b/docs/frontend-refactor/ember-baseline/generated/api-by-module/auth-shell.md
new file mode 100644
index 0000000000..a968f95189
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/generated/api-by-module/auth-shell.md
@@ -0,0 +1,21 @@
+# 认证与应用外壳:Ember 非 Metrics 命名 AJAX 候选索引
+
+> 由 `tools/extract-ember-baseline.mjs` 生成。该页只按请求名和调用者路径的宽正则启发式归类:共享请求可能跨模块混入或重复,模块请求也可能漏列或归到其他页。它不是模块接口全集;权威核对必须联合 `../ajax-endpoints.json`、`../ajax-calls.json`、`../direct-http-calls.json`、`../browser-network-entrypoints.json` 和 `../realtime-channels.json`。
+
+共 12 个命名请求候选。
+候选内容 SHA-256:`9a1933e4507e2d4ce46b4203ec35925e485fcaab947a02a1f92b81dd280cd464`。
+
+| 请求名 | Method | URL(不含默认 prefix) | format 输入键 | 调用位置 |
+| --- | --- | --- | --- | --- |
+| `common.service.configurations` | `PUT` | `/clusters/{clusterName}` | `desired_config` | `ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js:97`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js:94`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/addStandby/step4_controller.js:73`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/remove [...]
+| `common.service.multiConfigurations` | `PUT` | `/clusters/{clusterName}` | `configs` | `ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step4_controller.js:55`<br>`ambari-web/classic/app/controllers/main/admin/federation/step4_controller.js:77`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step9_controller.js:228`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/rangerAdmin/step4_controller.js:88` |
+| `ambari.service.load_server_version` | `GET` | `/services/AMBARI?fields=components/RootServiceComponents/component_version&components/RootServiceComponents/component_name=AMBARI_SERVER&minimal_response=true` | 无 `format()` | `ambari-web/classic/app/router.js:632`<br>`ambari-web/classic/app/router.js:977`<br>`ambari-web/classic/app/views/main/admin/stack_upgrade/versions_view.js:221` |
+| `config.tags` | `GET` | `/clusters/{clusterName}?fields=Clusters/desired_configs` | 无 `format()` | `ambari-web/classic/app/controllers/global/configuration_controller.js:145`<br>`ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js:51`<br>`ambari-web/classic/app/controllers/main/admin/federation/step3_controller.js:54`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/hawq/activateStandby/step3_controller.js:63`<br>`ambari- [...]
+| `admin.get.all_configurations` | `GET` | `/clusters/{clusterName}/configurations?{urlParams}` | 无静态 `data.*` 引用 | `ambari-web/classic/app/controllers/main/admin/federation/routerBasedFederation/step3_controller.js:63`<br>`ambari-web/classic/app/controllers/main/admin/federation/step3_controller.js:73`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js:81`<br>`ambari-web/classic/app/controllers/main/admin/highAvailability/nameNode/step3_co [...]
+| `router.login` | `POST` | `/auth` | 无静态 `data.*` 引用 | `ambari-web/classic/app/router.js:422` |
+| `router.afterLogin` | `GET` | `/users/{loginName}?fields=*,privileges/PrivilegeInfo/cluster_name,privileges/PrivilegeInfo/permission_name` | 无静态 `data.*` 引用 | `ambari-web/classic/app/router.js:297`<br>`ambari-web/classic/app/router.js:443` |
+| `router.user.privileges` | `GET` | `/users/{userName}/privileges?fields=*` | 无 `format()` | `ambari-web/classic/app/controllers/global/user_settings_controller.js:202` |
+| `router.user.authorizations` | `GET` | `/users/{userName}/authorizations?fields=*` | 无 `format()` | `ambari-web/classic/app/controllers/global/cluster_controller.js:420` |
+| `router.login.clusters` | `GET` | `/clusters?fields=Clusters/provisioning_state,Clusters/security_type,Clusters/version,Clusters/cluster_id` | 无 `format()` | `ambari-web/classic/app/controllers/application.js:125`<br>`ambari-web/classic/app/router.js:275`<br>`ambari-web/classic/app/router.js:572` |
+| `router.login.message` | `GET` | `/settings/motd` | 无 `format()` | `ambari-web/classic/app/router.js:464` |
+| `router.logoff` | `GET` | `/logout` | 无静态 `data.*` 引用 | `ambari-web/classic/app/router.js:738` |
diff --git a/docs/frontend-refactor/ember-baseline/generated/browser-network-entrypoints.md b/docs/frontend-refactor/ember-baseline/generated/browser-network-entrypoints.md
new file mode 100644
index 0000000000..8331b0c905
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/generated/browser-network-entrypoints.md
@@ -0,0 +1,66 @@
+# Ember 非 Metrics 浏览器网络与下载入口
+
+> 由 `tools/extract-ember-baseline.mjs` 生成。每个命令式入口、整页 reload/redirect、View iframe/icon 和非 Metrics 声明式导航链接均已人工分类。生成器遇到新入口而没有语义契约时会直接失败。普通启动脚本、样式表、favicon 和固定 UI 图片属于构建/静态资源,不在本目录范围。
+
+共 56 个候选调用点。
+
+| 语义 | 网络效果 | URL / 内容契约 | 表达式 | 边界 | 位置 |
+| --- | --- | --- | --- | --- | --- |
+| `STATIC_EXTERNAL_LINK`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `http://www.apache.org/licenses/LICENSE-2.0` | `<a data-qa="license-link" href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank" rel="noopener noreferrer">` | 经典应用 footer 的 Apache License 外链。 | [source](../../../../ambari-web/classic/app/assets/index.html#L48) |
+| `STATIC_NOTICE_DOCUMENT`<br>target=_blank anchor | `REMOTE_REQUEST` | `GET /licenses/NOTICE.txt` | `<a data-qa="third-party-link" href="/licenses/NOTICE.txt" target="_blank" rel="noopener noreferrer">` | 经典应用 footer 打开同源第三方 NOTICE 静态文档。 | [source](../../../../ambari-web/classic/app/assets/index.html#L49) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after show_bg/timezone preference requests settle` | `location.reload();` | 两个 preference mutation 都用 \`.always()\` 串联;成功或失败均可 reload 以应用 timezone。 | [source](../../../../ambari-web/classic/app/controllers/global/user_settings_controller.js#L327) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `delayed reload of current Ambari document after route/status change` | `location.reload();` | cluster 已安装时,route 或 install status observer 延迟 \`App.pageReloadTime\` 后 reload。 | [source](../../../../ambari-web/classic/app/controllers/main.js#L122) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes generated HTML configuration diff` | `window.open();` | 新窗口只接收内存中生成的 HTML,不请求后端。 | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L2329) |
+| `PAGE_RELOAD`<br>window.location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after starting repeat-tolerance config save` | `window.location.reload();` | 配置 PUT 不被 await;保存请求仍在途时立即整页 reload,可能中断或隐藏失败结果。 | [source](../../../../ambari-web/classic/app/controllers/main/alerts/alert_definitions_actions_controller.js#L251) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after bulk host-delete result closes` | `location.reload();` | 成功/部分失败结果都整页刷新,先清除已删除 host selection。 | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L453) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after bulk host-component-delete result primary action` | `location.reload();` | 结果 popup 的 Primary 与 Close 各有独立 reload 调用。 | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L792) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after bulk host-component-delete result closes` | `location.reload();` | 结果 popup 的 Close 分支整页重建 host/component 模型。 | [source](../../../../ambari-web/classic/app/controllers/main/host/bulk_operations_controller.js#L797) |
+| `PAGE_RELOAD`<br>window.location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after service deletion confirmation` | `window.location.reload();` | 用户关闭删除成功确认后整页 reload,以 REST 启动链重新构建 cluster/service 模型。 | [source](../../../../ambari-web/classic/app/controllers/main/service/item.js#L2042) |
+| `INTERNAL_AMBARI_ROUTE`<br>window.open | `NAVIGATION_REQUEST` | `#/main/view/{viewName}/{shortUrl} or #/main/views/{viewName}/{version}/{instanceName}` | `window.open(event.context.get('internalAmbariUrl'));` | 新浏览上下文打开 Ember hash route,随后由 View details iframe 请求 context。 | [source](../../../../ambari-web/classic/app/controllers/main/views_controller.js#L112) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after generic long-running wizard exit cleanup` | `location.reload();` | HA/Federation 等共享 \`exitWizard()\` 在 status persist settle 后 transition 并 reload。 | [source](../../../../ambari-web/classic/app/controllers/wizard.js#L1528) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document from the stale-page popup link` | `location.reload();'>"` | reload 调用位于运行时编译的 inline onclick;用户点击时执行。 | [source](../../../../ambari-web/classic/app/mixins/common/reload_popup.js#L63) |
+| `AMBARI_REST_DOWNLOAD`<br>window.open | `REMOTE_REQUEST` | `GET one of the five generated client-config contracts (\`?format=client_config_tar\`)` | `window.open(url);` | 精确 resource-scope URL 见 client-config-downloads 清单。 | [source](../../../../ambari-web/classic/app/mixins/main/host/details/support_client_configs_download.js#L39) |
+| `JWT_PROVIDER_REDIRECT`<br>redirectByURL call | `NAVIGATION_REQUEST` | `{401/403 response jwtProviderUrl}{encoded current window URL}{redirected query flag}` | `this.redirectByURL(responseJson.jwtProviderUrl + encodeURIComponent(this.getCurrentLocationUrl()));` | 认证失败响应驱动的外部 IdP 整页跳转;redirect counter 仅限制循环,不约束 provider origin。 | [source](../../../../ambari-web/classic/app/router.js#L349) |
+| `ADMIN_VIEW_REDIRECT`<br>replaceWindowLocation call | `NAVIGATION_REQUEST` | `{appURLRoot}views/ADMIN_VIEW/{lexicallyLatestServerComponentVersion}/INSTANCE/#/` | `App.replaceWindowLocation(App.appURLRoot + 'views/ADMIN_VIEW/' + latestVersion + '/INSTANCE/#/')` | 无 cluster 的 Admin View 分流;location.replace 整页离开 Ember。 | [source](../../../../ambari-web/classic/app/router.js#L669) |
+| `PAGE_RELOAD`<br>window.location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after logout when cluster model was loaded` | `window.location.reload();` | 先 transition 到 login,下一 run-loop 再整页 reload。 | [source](../../../../ambari-web/classic/app/router.js#L773) |
+| `PREFERRED_PATH_REDIRECT`<br>window.location assignment | `NAVIGATION_REQUEST` | `{saved preferredPath beginning with / or #, except a path containing #/login}` | `window.location = preferredPath` | \`startsWith("/")\` 也接受 \`//host/path\`,因此旧实现可能发生 protocol-relative 跨源导航;React 应按已知安全缺陷处理。 | [source](../../../../ambari-web/classic/app/router.js#L806) |
+| `WINDOW_LOCATION_SETTER`<br>window.location assignment | `NAVIGATION_REQUEST` | `window.location={callerSuppliedUrl}` | `window.location = url` | JWT redirect helper 的最终可测试 setter;实际 provider 数据入口另列。 | [source](../../../../ambari-web/classic/app/router.js#L889) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after closing an unfinished Activate HAWQ Standby wizard` | `location.reload();` | cluster status persist 以 \`alwaysCallback\` 收尾,失败也 transition 后 reload。 | [source](../../../../ambari-web/classic/app/routes/activate_hawq_standby_routes.js#L56) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after completing Activate HAWQ Standby` | `location.reload();` | 完成清理 persist 以 \`alwaysCallback\` 收尾。 | [source](../../../../ambari-web/classic/app/routes/activate_hawq_standby_routes.js#L178) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after Add Alert Definition completes` | `location.reload();` | 清理向导状态后 transition 到 Alerts;status persist 失败也 reload。 | [source](../../../../ambari-web/classic/app/routes/add_alert_definition_routes.js#L141) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after closing an unfinished Add HAWQ Standby wizard` | `location.reload();` | cluster status persist 以 \`alwaysCallback\` 收尾,失败也 reload。 | [source](../../../../ambari-web/classic/app/routes/add_hawq_standby_routes.js#L57) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after completing Add HAWQ Standby` | `location.reload();` | 完成清理 persist 以 \`alwaysCallback\` 收尾。 | [source](../../../../ambari-web/classic/app/routes/add_hawq_standby_routes.js#L202) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after Disable Kerberos closes` | `location.reload();` | Disable 清理和 cluster status persist 以 \`alwaysCallback\` 收尾,随后返回 Kerberos 页并 reload。 | [source](../../../../ambari-web/classic/app/routes/main.js#L549) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after Ranger Admin HA completes` | `location.reload();` | 完成清理 persist 以 \`alwaysCallback\` 收尾。 | [source](../../../../ambari-web/classic/app/routes/ra_high_availability_routes.js#L179) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after Reassign Master completes` | `location.reload();` | cluster status persist 以 \`alwaysCallback\` 收尾,失败也 transition 后 reload。 | [source](../../../../ambari-web/classic/app/routes/reassign_master_routes.js#L314) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after confirmed close of an active Remove HAWQ Standby wizard` | `location.reload();` | 确认关闭后清理状态;persist 成功或失败都 reload。 | [source](../../../../ambari-web/classic/app/routes/remove_hawq_standby_routes.js#L63) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after closing an inactive Remove HAWQ Standby wizard` | `location.reload();` | 非活动阶段无需确认,但同样在 persist settle 后 reload。 | [source](../../../../ambari-web/classic/app/routes/remove_hawq_standby_routes.js#L81) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after completing Remove HAWQ Standby` | `location.reload();` | 完成清理 persist 以 \`alwaysCallback\` 收尾。 | [source](../../../../ambari-web/classic/app/routes/remove_hawq_standby_routes.js#L189) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `reload current Ambari document after NameNode HA rollback completes` | `location.reload();` | 本分支不等待 cluster status persist,transition 后下一 run-loop 直接 reload。 | [source](../../../../ambari-web/classic/app/routes/rollbackHA_routes.js#L154) |
+| `PAGE_RELOAD`<br>location.reload | `NAVIGATION_REQUEST` | `conditionally reload current Ambari document when closing an upgrade wizard` | `location.reload();` | 只在全局 upgrade state 为 NOT_REQUIRED 或 COMPLETED 时 reload 并 reset wizard owner。 | [source](../../../../ambari-web/classic/app/routes/stack_upgrade_routes.js#L83) |
+| `NEW_UI_NAVIGATION`<br>new UI anchor | `NAVIGATION_REQUEST` | `GET /latest/#` | `<a href="/latest/#">` | 用户菜单的 Switch Experience 整页导航到新版前端入口。 | [source](../../../../ambari-web/classic/app/templates/application.hbs#L70) |
+| `STATIC_EXTERNAL_LINK`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `http://ambari.apache.org/` | `<a href="http://ambari.apache.org/" target="_blank" rel="noopener noreferrer">` | About 弹窗静态项目链接。 | [source](../../../../ambari-web/classic/app/templates/common/about.hbs#L30) |
+| `STATIC_EXTERNAL_LINK`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `http://www.apache.org/licenses/LICENSE-2.0` | `<a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank" rel="noopener noreferrer">` | About 弹窗静态 license 链接。 | [source](../../../../ambari-web/classic/app/templates/common/about.hbs#L32) |
+| `EXTERNAL_LOG_SEARCH`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `{Log Search UI quick-link URL}{hostLog.linkTail}` | `<a {{bindAttr href="view.formatedLink" class=":pull-right view.isLodaded::disabled"}} target="_blank" rel="noopener noreferrer">` | 依赖 Log Search service quick-link descriptor 和已加载日志上下文。 | [source](../../../../ambari-web/classic/app/templates/common/host_progress_popup.hbs#L345) |
+| `EXTERNAL_LOG_SEARCH`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `{Log Search quick-link template}/#/logs/serviceLogs;{component/host/file query}` | `<a class="open-in-log-search" {{bindAttr href="view.logSearchUrl"}} target="_blank" rel="noopener noreferrer">` | 在 Log Search service 和 quick-link 可用时显示。 | [source](../../../../ambari-web/classic/app/templates/common/modal_popups/log_tail_popup.hbs#L32) |
+| `EXTERNAL_ALERT_HELP`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `{AlertDefinition.help_url}` | `<a {{bindAttr href="controller.content.helpUrl"}} target="_blank" rel="noopener noreferrer">` | 只在定义有 help URL 时显示,URL 由后端数据提供。 | [source](../../../../ambari-web/classic/app/templates/main/alerts/definition_details.hbs#L196) |
+| `EXTERNAL_LOG_SEARCH`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `{Log Search UI quick-link URL}{file.linkTail}` | `<a {{bindAttr href="view.formatedLink"}} target="_blank" rel="log-file-name-tooltip noopener noreferrer" {{translateAttr title="popup.logTail.openInLogSearch"}} class="pull-righ...` | 主机日志页按文件构造 Log Search 深链。 | [source](../../../../ambari-web/classic/app/templates/main/host/logs.hbs#L46) |
+| `EXTERNAL_QUICK_LINK`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `{stack quicklinks descriptor + current configs + selected public host/protocol/port}` | `<a {{bindAttr href="url"}} target="_blank" rel="noopener noreferrer">` | 多 master/group 的服务 Quick Links。 | [source](../../../../ambari-web/classic/app/templates/main/service/info/summary.hbs#L103) |
+| `EXTERNAL_QUICK_LINK`<br>target=_blank anchor | `NAVIGATION_REQUEST` | `{stack quicklinks descriptor + current configs + selected public host/protocol/port}` | `<a {{bindAttr href="url"}} target="_blank" rel="noopener noreferrer">` | 单 host/平铺的服务 Quick Links。 | [source](../../../../ambari-web/classic/app/templates/main/service/info/summary.hbs#L111) |
+| `INTERNAL_AMBARI_ROUTE`<br>target=_blank anchor | `CONDITIONAL` | `{ViewInstance.internalAmbariUrl}` | `<a href="#" target="_blank" {{action goToView link.viewInstance target="controller"}}>` | 遗留 Hive-to-View hook;当前 \`viewsToShow\` 为空,默认无可见链接,有 runtime extension 时才路由。 | [source](../../../../ambari-web/classic/app/templates/main/service/services/hive.hbs#L54) |
+| `VIEW_ICON_RESOURCE`<br>dynamic image src | `REMOTE_REQUEST` | `GET {ViewInstanceInfo.icon_path}` | `<img {{bindAttr src="instance.iconPath"}}>` | 由后端 View instance metadata 驱动的动态图片请求;不是固定构建图片。 | [source](../../../../ambari-web/classic/app/templates/main/views.hbs#L28) |
+| `URL_PARSER_NO_NETWORK`<br>anchor href | `NO_NETWORK` | `none; assigns caller URL to a detached anchor to read \`.hostname\`` | `.href = url` | 只借浏览器 URL parser 解析 hostname。 | [source](../../../../ambari-web/classic/app/utils/configs/database.js#L223) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes caller-provided text/HTML` | `window.open('');` | 本地内容预览。 | [source](../../../../ambari-web/classic/app/utils/file_utils.js#L76) |
+| `LOCAL_DOWNLOAD`<br>anchor href | `LOCAL_ONLY` | `data:attachment/{fileType};charset=utf-8,{encodedData}` | `.href = file` | Safari fallback 的本地 data URL 下载。 | [source](../../../../ambari-web/classic/app/utils/file_utils.js#L91) |
+| `ADMIN_VIEW_REDIRECT_HELPER`<br>location.replace | `NAVIGATION_REQUEST` | `window.location.replace({callerSuppliedLocation})` | `window.location.replace(location);` | 可测试的 helper 实现;真实业务调用点另列。 | [source](../../../../ambari-web/classic/app/utils/helper.js#L1115) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes selected task/component log text` | `window.open();` | 不发新后端请求。 | [source](../../../../ambari-web/classic/app/views/common/host_progress_popup_body_view.js#L1044) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes already-loaded log tail` | `window.open();` | 不发新后端请求。 | [source](../../../../ambari-web/classic/app/views/common/modal_popups/log_tail_popup.js#L57) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes task log HTML already in the DOM` | `window.open();` | 不发新后端请求。 | [source](../../../../ambari-web/classic/app/views/common/modal_popups/logs_popup.js#L43) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes failed-host JSON` | `window.open().document;` | 不发新后端请求。 | [source](../../../../ambari-web/classic/app/views/main/admin/stack_upgrade/failed_hosts_modal_view.js#L77) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes already-loaded upgrade task log` | `window.open(),` | 不发新后端请求。 | [source](../../../../ambari-web/classic/app/views/main/admin/stack_upgrade/upgrade_task_view.js#L174) |
+| `ADMIN_VIEW_REDIRECT`<br>replaceWindowLocation call | `NAVIGATION_REQUEST` | `{appURLRoot}views/ADMIN_VIEW/{lexicallyLatestServerComponentVersion}/INSTANCE/#/` | `App.replaceWindowLocation(App.appURLRoot + 'views/ADMIN_VIEW/' + latestVersion + '/INSTANCE/#/')` | Manage Versions 确认后整页跳转 Admin View。 | [source](../../../../ambari-web/classic/app/views/main/admin/stack_upgrade/versions_view.js#L233) |
+| `VIEW_WEB_CONTEXT`<br>iframe computed src | `REMOTE_REQUEST` | `{window.location.protocol}//{window.location.host}{ViewInstanceInfo.context_path}/{viewPath}` | `src: function() {` | 同源 View application iframe;不是 \`/api/v1\` REST。 | [source](../../../../ambari-web/classic/app/views/main/views/details.js#L85) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes bootstrap warning details` | `window.open('');` | 不发新后端请求。 | [source](../../../../ambari-web/classic/app/views/wizard/step3/hostWarningPopupBody_view.js#L480) |
+| `LOCAL_DOCUMENT`<br>window.open | `LOCAL_ONLY` | `about:blank; writes already-loaded install task logs` | `window.open();` | 不发新后端请求。 | [source](../../../../ambari-web/classic/app/views/wizard/step9/hostLogPopupBody_view.js#L197) |
+
+网络效果分类:`CONDITIONAL`、`LOCAL_ONLY`、`NAVIGATION_REQUEST`、`NO_NETWORK`、`REMOTE_REQUEST`。`LOCAL_ONLY` 和 `NO_NETWORK` 不是后端接口,但仍是 React 需保留或明确变更的用户行为。
diff --git a/docs/frontend-refactor/ember-baseline/generated/direct-http-calls.md b/docs/frontend-refactor/ember-baseline/generated/direct-http-calls.md
new file mode 100644
index 0000000000..d379bfda04
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/generated/direct-http-calls.md
@@ -0,0 +1,29 @@
+# Ember 非 Metrics 直接 HTTP 调用
+
+> 由 `tools/extract-ember-baseline.mjs` 生成。这些调用绕过 `App.ajax` 命名注册表;每条已人工还原业务语义、URL 契约和本基线保留的运维字段。生成器遇到新调用而没有语义契约时会直接失败。
+
+共 19 个静态调用点。
+
+| 语义 | Method / 范围 | URL 契约 | 保留字段 | 行为/边界 | 位置 |
+| --- | --- | --- | --- | --- | --- |
+| `CLUSTER_MODEL_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}?fields=Clusters` | `Clusters/*` | 初始加载 cluster identity/security/credential-store 等基础模型;complete 在 mapper 后刷新 isCredentialStorePersistent。<br>调用:`App.HttpClient.get(clusterUrl, App.clusterMapper, {` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L194) |
+| `DYNAMIC_HOST_MODEL_LOAD_HELPER`<br>HttpClient | `GET` / `NON_METRICS` | `GET {callerSuppliedRealUrl}; test mode uses /data/hosts/HDP2/hosts.json` | `caller supplied` | \`requestHosts()\` 在经典 app 树中无生产调用者,只有 controller unit test;保留为 STATIC_ONLY 遗留 helper。<br>调用:`App.HttpClient.get(url, App.hostsMapper, {` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L384) |
+| `CLUSTER_MODEL_REFRESH`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}?fields=Clusters` | `Clusters/*` | 运行期刷新 cluster mapper;complete callback 为空。<br>调用:`App.HttpClient.get(clusterUrl, App.clusterMapper, {` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L449) |
+| `ORIGINAL_REQUEST_REPLAY`<br>jQuery AJAX | `DYNAMIC` / `NON_METRICS` | `{ajaxOpt.type} {ajaxOpt.url}; payload/headers/callbacks are the original failed jQuery request` | `original request dependent` | 先保存 KDC credential,再原样重放 \`ajaxOpt\`;不是新 endpoint,也不得丢失原 success/error/statusCode 处理。<br>调用:`$.ajax(ajaxOpt);` | [source](../../../../ambari-web/classic/app/controllers/global/cluster_controller.js#L485) |
+| `HOST_MODEL_LOAD`<br>HttpClient | `GET` / `MIXED` | `GET-over-POST /api/v1/clusters/{clusterName}/hosts?fields={runtimeFields}&minimal_response=true&{pagination}&{sort}; RequestInfo.query={hostPredicate}` | `Hosts rack/name/maintenance/public_host/cpu_count/ph_cpu_count/status/state/heartbeat/ip/os/agent-env`<br>`host component state/maintenance/stale-config/service/display/desired-admin-state`<br>`stack/repository version identity`<br>`logging metadata when supports.logSearch`<br>`Nam [...]
+| `COMPONENT_TOPOLOGY_LOAD`<br>HttpClient | `GET` / `MIXED` | `GET /api/v1/clusters/{clusterName}/components/?{runtimeComponentPredicates}&fields={runtimeFields}&minimal_response=true` | `service/component identity and master/client topology`<br>`host/display/public-host/state/maintenance/stale-config/ha-state/desired-admin-state`<br>`HDFS ClusterId and HBase IsActiveMaster operational selectors` | 共用 serviceMetricsMapper 且后续请求会携带大量指标;本基线只保留 topology/state/HA/Active-Standby 等运维语义。<br>调用: [...]
+| `SERVICE_STATE_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/services?fields=ServiceInfo/state,ServiceInfo/maintenance_state,ServiceInfo/desired_repository_version_id,components/ServiceComponentInfo/component_name&minimal_response=true` | `service state/maintenance/desired repository version`<br>`component names` | 集群初始化的 service 模型加载。<br>调用:`App.HttpClient.get(componentConfigUrl, App.serviceMapper, {` | [source](../../../../ambari-web/classic/app/co [...]
+| `COMPONENT_STATE_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/components/?fields=ServiceComponentInfo/{service_name,category,installed_count,started_count,init_count,install_failed_count,unknown_count,total_count,display_name},host_components/HostRoles/host_name&minimal_response=true` | `component category and aggregate state counts`<br>`host component host names` | 用于服务组件状态和拓扑映射。<br>调用:`App.HttpClient.get(url, App.componentsStateMapper, {` | [sourc [...]
+| `ALERT_DEFINITION_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/alert_definitions?fields=AlertDefinition/{component_name,description,enabled,repeat_tolerance,repeat_tolerance_enabled,id,ignore_host,interval,label,name,scope,service_name,source,help_url}` | `alert definition configuration and source` | 初始加载全部告警定义。<br>调用:`App.HttpClient.get(url, App.alertDefinitionsMapper, {` | [source](../../../../ambari-web/classic/app/controllers/global/update_contr [...]
+| `UNHEALTHY_ALERT_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/alerts?fields={Alert operational fields}&Alert/state.in(CRITICAL,WARNING)&Alert/maintenance_state.in(OFF)&from={from}&page_size={pageSize}` | `critical/warning non-maintenance alert instances`<br>`repeat-tolerance remaining`<br>`timestamps/text/host/service/component` | 只载入非健康且 maintenance OFF 的分页实例。<br>调用:`App.HttpClient.get(url, App.alertInstanceMapper, {` | [source](../../../../ambari- [...]
+| `ALERT_GROUPED_SUMMARY_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/alerts?format=groupedSummary` | `server grouped alert summary` | 为告警定义摘要 mapper 加载服务端分组统计。<br>调用:`App.HttpClient.get(url, App.alertDefinitionSummaryMapper, {` | [source](../../../../ambari-web/classic/app/controllers/global/update_controller.js#L665) |
+| `ALERT_GROUP_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/alert_groups?fields=AlertGroup/{default,definitions,id,name,targets}` | `alert group membership and targets` | 初始加载告警组。<br>调用:`App.HttpClient.get(url, App.alertGroupsMapper, {` | [source](../../../../ambari-web/classic/app/controllers/global/update_controller.js#L676) |
+| `ALERT_TARGET_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/alert_targets?fields=*` | `all alert target/notification fields` | 根级 alert target 资源,不带 cluster path。<br>调用:`App.HttpClient.get(App.get('apiPrefix') + '/alert_targets?fields=*', App.alertNotificationMapper, {` | [source](../../../../ambari-web/classic/app/controllers/global/update_controller.js#L682) |
+| `STACK_VERSION_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/stack_versions?fields={full: *,repository_versions/*,... \| update: ClusterStackVersions/*}` | `cluster stack versions`<br>`repository/OS/repository details on full load` | \`fullLoad\` 选择初始全量 URL 或运行期轻量 URL。<br>调用:`App.HttpClient.get(this.getUrl(true, fullLoad), App.stackVersionMapper, {` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_and_upgrade_controller.js#L2025) |
+| `REPOSITORY_VERSION_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/stacks?fields=versions/repository_versions/RepositoryVersions,versions/repository_versions/operating_systems/*,versions/repository_versions/operating_systems/repositories/*` | `stack repository versions`<br>`OS and repository definitions` | 全量 repository-version model 加载。<br>调用:`App.HttpClient.get(this.getUrl(false, true), App.repoVersionMapper, {` | [source](../../../../ambari-web/classic/app/controllers/ma [...]
+| `UPGRADE_HISTORY_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/upgrades?fields=Upgrade` | `Upgrade/*` | 历史列表 complete 时即 resolve;HttpClient error 不调 complete,可留下未解决 promise。<br>调用:`App.HttpClient.get(this.get('upgradeHistoryUrl'), App.stackUpgradeHistoryMapper, {` | [source](../../../../ambari-web/classic/app/controllers/main/admin/stack_upgrade_history_controller.js#L69) |
+| `CONFIG_HISTORY_LOAD`<br>HttpClient | `GET` / `NON_METRICS` | `GET /api/v1/clusters/{clusterName}/configurations/service_config_versions?{filterAndSortPredicates}fields=service_config_version,user,group_id,group_name,is_current,createtime,service_name,hosts,service_config_version_note,is_cluster_compatible,stack_id&minimal_response=true` | `service config version history and compatibility metadata` | 过滤、排序条件由 table mixin 动态生成。<br>调用:`App.HttpClient.get(this.getUrl(queryParams), App.ser [...]
+| `GENERIC_MUTATION_POLL_HELPER`<br>jQuery AJAX | `DYNAMIC` / `NON_METRICS` | `PUT {App.Poll.url}; body={App.Poll.data}; response is text or JSON with Requests.id` | `Requests.id when asynchronous`<br>`caller-defined task/request polling data` | 经典 app 树未发现 \`App.Poll.create()\` 生产调用,只有 unit test;空/非 JSON 成功会直接标记 success,有 request ID 才转轮询。<br>调用:`$.ajax({` | [source](../../../../ambari-web/classic/app/utils/polling.js#L67) |
+| `HOST_DELETE_PREFLIGHT_LOAD`<br>HttpClient | `GET` / `MIXED` | `GET /api/v1/clusters/{clusterName}/hosts/?Hosts/host_name.in({hostName})&fields={host,host-component,stack-version,logging and metric fields}&minimal_response=true&page_size=10&from=0&sortBy=Hosts/host_name.asc` | `host and host-component state/maintenance/stale-config/desired-admin-state`<br>`stack versions and logging`<br>`NameNode ClusterId/HAState safety selectors` | 升级前 heartbeat check 中的安全删除流程;响应携带 disk/load/cpu/memo [...]
+
+`MIXED` 调用同时返回指标字段和非 Metrics 运维字段。本文只纳入表中列明的 topology/state/maintenance/stale config/HA/Active-Standby/安全判断字段;指标数值仍排除。`ORIGINAL_REQUEST_REPLAY` 是保存 KDC credential 后重放原请求,不能当成一个固定 endpoint。
diff --git a/docs/frontend-refactor/ember-baseline/generated/realtime-channels.md b/docs/frontend-refactor/ember-baseline/generated/realtime-channels.md
new file mode 100644
index 0000000000..a0e5a9d41f
--- /dev/null
+++ b/docs/frontend-refactor/ember-baseline/generated/realtime-channels.md
@@ -0,0 +1,360 @@
+# Ember 非 Metrics 实时通道契约
+
+> 由 `tools/extract-ember-baseline.mjs` 从已审计的静态 contract 生成。本文冻结 classic UI 的 STOMP/WebSocket/SockJS 行为,不包含 Metrics 时序数据通道。
+
+## 可校验摘要
+
+- transports:2
+- destinations:11(10 static + 1 dynamic)
+- subscribe sites:11
+- addHandler sites:1
+- removeHandler sites:1
+- business unsubscribe sites:1
+- lifecycle contracts:4
+
+## Transport
+
+| ID / 类型 | URL | 协议与心跳 | Fallback / 重连 | 风险边界 | Source / Test |
+| --- | --- | --- | --- | --- | --- |
+| `RT-TRANSPORT-001`<br>`NATIVE_WEBSOCKET` | `{ws\|wss}://{window.location.hostname}{:window.location.port}/api/stomp/v1/websocket` | STOMP 1.1/1.0<br>heartbeat 10000/10000 ms<br>schemes ws/wss | fallback: Initial native WebSocket connection error before any successful connection.<br>reconnect: After any previously successful connection is lost, reconnect with the same transport and restore the captured in-memory subscriptions. | There is no REST polling substitute when both native WebSo [...]
+| `RT-TRANSPORT-002`<br>`SOCKJS_FALLBACK` | `{http\|https}://{window.location.hostname}{:window.location.port}/api/stomp/v1` | STOMP 1.1/1.0<br>heartbeat 10000/10000 ms<br>SockJS eventsource, xhr-polling, iframe-xhr-polling, jsonp-polling | fallback: Native WebSocket is unavailable or its initial connection fails.<br>reconnect: A previously successful SockJS connection reconnects through SockJS and restores the captured subscriptions. | The fallback deliberately excludes WebSocket, XHR s [...]
+
+## Destination 契约
+
+| ID / Destination | Event | Ember 消费字段 | Handler chain | Lifecycle | REST reconcile | 风险边界 | Source |
+| --- | --- | --- | --- | --- | --- | --- | --- |
+| `RT-SUB-001`<br>`/events/hostcomponents` | `HostComponentsUpdateEvent` | `hostComponents[].componentName`<br>`hostComponents[].hostName`<br>`hostComponents[].currentState`<br>`hostComponents[].previousState`<br>`hostComponents[].maintenanceState`<br>`hostComponents[].staleConfigs` | `App.hostComponentStatusMapper.map`<br>`App.ServerDataMapper.updatePropertiesByConfig`<br>`App.hostComponentStatusMapper.updateComponentsWithStaleConfigs`<br>`App.componentsStateMapper.updateComponentCountO [...]
+| `RT-SUB-002`<br>`/events/alerts` | `AlertUpdateEvent` | `summaries[App.clusterId][*].definition_id`<br>`summaries[App.clusterId][*].definition_name`<br>`summaries[App.clusterId][*].summary` | `App.alertSummaryMapper.map`<br>`App.alertDefinitionSummaryMapper.map` | Global application-lifetime subscription restored after reconnect. | Alert pages also load alert data through REST, but the socket client does not request a missed-event snapshot after reconnect. | A missing current-cluster e [...]
+| `RT-SUB-003`<br>`/events/ui_topologies` | `TopologyUpdateEvent` | `eventType`<br>`clusters[App.clusterId].components[].componentName`<br>`clusters[App.clusterId].components[].serviceName`<br>`clusters[App.clusterId].components[].displayName`<br>`clusters[App.clusterId].components[].hostNames`<br>`clusters[App.clusterId].components[].publicHostNames`<br>`clusters[App.clusterId].components[].commandParams.version`<br>`clusters[App.clusterId].components[].lastComponentState`<br>`clusters[ [...]
+| `RT-SUB-004`<br>`/events/configs` | `ConfigsUpdateEvent` | `configs[].type` | `default: App.UpdateController.configsChangedHandler`<br>`default: ConfigurationController.updateConfigTags`<br>`default when cluster-env changed: App.UpdateController.updateClusterEnv`<br>`history: MainConfigHistoryController.load(true)` | The global default handler is permanent. The named history handler is added when config history renders and removed when its view is destroyed. | Every event triggers a RE [...]
+| `RT-SUB-005`<br>`/events/services` | `ServiceUpdateEvent` | `service_name`<br>`maintenance_state`<br>`state` | `App.serviceStateMapper.map`<br>`App.ServerDataMapper.updatePropertiesByConfig`<br>`App.cache.services update` | Global application-lifetime subscription restored after reconnect. | Periodic service/component REST refreshes can converge service state, but missed events are not replayed. | Updates for a Service record that is not loaded are ignored by updatePropertiesByConfig.< [...]
+| `RT-SUB-006`<br>`/events/hosts` | `HostUpdateEvent` | `host_name`<br>`host_status`<br>`host_state`<br>`last_heartbeat_time`<br>`maintenance_state`<br>`alerts_summary` | `App.hostStateMapper.map`<br>`App.ServerDataMapper.updatePropertiesByConfig` | Global application-lifetime subscription restored after reconnect. | Host list/detail REST refreshes can replace missed status, heartbeat, maintenance, and alert-summary values; the channel itself has no snapshot request. | Updates for an unl [...]
+| `RT-SUB-007`<br>`/events/alert_definitions` | `AlertDefinitionsUIUpdateEvent` | `eventType`<br>`clusters[App.clusterId].alertDefinitions[]`<br>`clusters[App.clusterId].alertDefinitions[].definitionId`<br>`clusters[App.clusterId].alertDefinitions[].componentName`<br>`clusters[App.clusterId].alertDefinitions[].serviceName` | `App.alertDefinitionsMapperAdapter.map`<br>`UPDATE: normalize id/component_name/service_name and call App.alertDefinitionsMapper.map`<br>`DELETE: remove App.AlertDef [...]
+| `RT-SUB-008`<br>`/events/alert_group` | `AlertGroupsUpdateEvent` | `updateType`<br>`groups[].cluster_id`<br>`groups[].default`<br>`groups[].definitions`<br>`groups[].id`<br>`groups[].name`<br>`groups[].service_name`<br>`groups[].targets` | `App.alertGroupsMapperAdapter.map`<br>`CREATE/UPDATE: convert definition and target IDs to {id} and call App.alertGroupsMapper.map`<br>`DELETE: remove App.AlertGroup record`<br>`toggle ManageAlertGroupsController.changeTrigger` | Global application-l [...]
+| `RT-SUB-009`<br>`/events/upgrade` | `UpgradeUpdateEvent` | `type`<br>`request_id`<br>`request_status`<br>`suspended`<br>`all CREATE fields through restoreLastUpgrade` | `App.upgradeStateMapper.map`<br>`CREATE: MainAdminStackAndUpgradeController.restoreLastUpgrade({Upgrade:event})`<br>`matching UPDATE: persist upgradeState and isSuspended in controller/DB` | Global application-lifetime subscription; CREATE restores a new/current upgrade and UPDATE mutates only the matching request. | Up [...]
+| `RT-SUB-010`<br>`/events/requests` | `RequestUpdateEvent` | `requestId`<br>`requestContext`<br>`progressPercent`<br>`requestStatus`<br>`userName`<br>`startTime`<br>`endTime`<br>`Tasks[].id`<br>`Tasks[].requestId`<br>`Tasks[].status`<br>`Tasks[].hostName` | `BackgroundOperationsController.subscribeToUpdates`<br>`BackgroundOperationsController.updateRequests`<br>`parseRequestContext and generateTasksMapOfRequest`<br>`update or insert the visible background-operation model` | Subscribed o [...]
+| `RT-SUB-011`<br>`/events/tasks/{taskId}` | `NamedTaskUpdateEvent` | `id`<br>`requestId`<br>`hostName`<br>`status`<br>`errorLog`<br>`outLog`<br>`stderr`<br>`stdout`<br>`structured_out` | `BackgroundOperationsController.handleTaskUpdates`<br>`BackgroundOperationsController.updateTask`<br>`unsubscribe when the client classifies status as finished` | Dynamic subscription is created on entry to TASK_DETAILS for a task outside the client's FAILED/ABORTED/COMPLETED terminal set. A received me [...]
+
+## Payload Schema
+
+<details>
+<summary><code>RT-SUB-001</code> <code>/events/hostcomponents</code></summary>
+
+```json
+{
+ "type": "object",
+ "fields": {
+ "hostComponents": {
+ "type": "array",
+ "required": true,
+ "items": {
+ "clusterId": "integer",
+ "serviceName": "string",
+ "hostName": "string",
+ "componentName": "string",
+ "currentState": "State; omitted when null",
+ "previousState": "State; omitted when null",
+ "maintenanceState": "MaintenanceState; omitted when null",
+ "staleConfigs": "boolean; omitted when null"
+ }
+ }
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-002</code> <code>/events/alerts</code></summary>
+
+```json
+{
+ "type": "object",
+ "fields": {
+ "summaries": {
+ "type": "map<clusterId,map<definitionName,AlertDefinitionSummary>>",
+ "required": true,
+ "AlertDefinitionSummary": {
+ "definition_id": "integer",
+ "definition_name": "string",
+ "summary": {
+ "OK": "AlertStateValues",
+ "WARNING": "AlertStateValues",
+ "CRITICAL": "AlertStateValues",
+ "UNKNOWN": "AlertStateValues"
+ },
+ "AlertStateValues": {
+ "count": "integer",
+ "maintenance_count": "integer",
+ "original_timestamp": "integer",
+ "latest_text": "string; omitted when null"
+ }
+ }
+ }
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-003</code> <code>/events/ui_topologies</code></summary>
+
+```json
+{
+ "type": "object",
+ "fields": {
+ "clusters": {
+ "type": "map<clusterId,TopologyCluster>; omitted when null",
+ "TopologyCluster": {
+ "components": "array<TopologyComponent>; omitted when null",
+ "hosts": "array<TopologyHost>; omitted when null"
+ },
+ "TopologyComponent": {
+ "componentName": "string",
+ "serviceName": "string",
+ "displayName": "string",
+ "version": "string",
+ "hostIds": "array<integer>",
+ "hostNames": "array<string>",
+ "publicHostNames": "array<string>",
+ "componentLevelParams": "map<string,string>",
+ "commandParams": "map<string,string>",
+ "lastComponentState": "State"
+ },
+ "TopologyHost": {
+ "hostId": "integer",
+ "hostName": "string",
+ "rackName": "string",
+ "ipv4": "string"
+ }
+ },
+ "eventType": "CREATE|UPDATE|DELETE; omitted when null",
+ "hash": "string; omitted when null"
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-004</code> <code>/events/configs</code></summary>
+
+```json
+{
+ "type": "object",
+ "omittedWhenEmpty": true,
+ "fields": {
+ "serviceConfigId": "integer",
+ "clusterId": "integer",
+ "serviceName": "string",
+ "groupId": "integer",
+ "version": "integer",
+ "user": "string",
+ "note": "string",
+ "hostNames": "array<string>",
+ "createTime": "integer",
+ "groupName": "string",
+ "configs": "array<{clusterId: integer, type: string, tag: string, version: integer}>",
+ "changedConfigTypes": "array<string>"
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-005</code> <code>/events/services</code></summary>
+
+```json
+{
+ "type": "object",
+ "fields": {
+ "cluster_name": "string",
+ "maintenance_state": "MaintenanceState; omitted when null",
+ "service_name": "string",
+ "state": "State; omitted when null"
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-006</code> <code>/events/hosts</code></summary>
+
+```json
+{
+ "type": "object",
+ "fields": {
+ "cluster_name": "string",
+ "host_name": "string",
+ "host_status": "string; omitted when null",
+ "host_state": "HostState; omitted when null",
+ "last_heartbeat_time": "integer; omitted when null",
+ "maintenance_state": "MaintenanceState; omitted when null",
+ "alerts_summary": "AlertSummaryDTO; omitted when null"
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-007</code> <code>/events/alert_definitions</code></summary>
+
+```json
+{
+ "type": "object",
+ "fields": {
+ "eventType": "CREATE|UPDATE|DELETE",
+ "clusters": {
+ "type": "map<clusterId,AlertCluster>",
+ "AlertCluster": {
+ "alertDefinitions": "array<AlertDefinition>",
+ "hostName": "string; omitted when null",
+ "staleIntervalMultiplier": "integer; omitted when null"
+ },
+ "AlertDefinition": {
+ "clusterId": "integer",
+ "definitionId": "integer",
+ "serviceName": "string",
+ "componentName": "string",
+ "name": "string",
+ "scope": "Scope",
+ "interval": "integer",
+ "enabled": "boolean",
+ "source": "Alert Source object",
+ "label": "string",
+ "description": "string",
+ "uuid": "string",
+ "ignore_host": "boolean",
+ "help_url": "string",
+ "repeat_tolerance": "integer",
+ "repeat_tolerance_enabled": "boolean"
+ }
+ }
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-008</code> <code>/events/alert_group</code></summary>
+
+```json
+{
+ "type": "object",
+ "omittedWhenEmpty": true,
+ "fields": {
+ "updateType": "CREATE|UPDATE|DELETE",
+ "groups": {
+ "type": "array",
+ "items": {
+ "cluster_id": "integer",
+ "default": "boolean",
+ "definitions": "array<integer>",
+ "id": "integer",
+ "name": "string",
+ "service_name": "string",
+ "targets": "array<integer>"
+ }
+ }
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-009</code> <code>/events/upgrade</code></summary>
+
+```json
+{
+ "type": "object",
+ "omittedWhenEmpty": true,
+ "fields": {
+ "type": "CREATE|UPDATE",
+ "associated_version": "string; full CREATE event",
+ "cluster_id": "integer",
+ "direction": "UPGRADE|DOWNGRADE; full CREATE event",
+ "downgrade_allowed": "boolean; full CREATE event",
+ "request_id": "integer",
+ "request_status": "HostRoleStatus",
+ "skip_failures": "boolean; full CREATE event",
+ "skip_service_check_failures": "boolean; full CREATE event",
+ "upgrade_type": "UpgradeType; full CREATE event",
+ "start_time": "integer",
+ "end_time": "integer",
+ "upgrade_id": "integer; full CREATE event",
+ "suspended": "boolean",
+ "progress_percent": "number",
+ "revert_allowed": "boolean; full CREATE event"
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-010</code> <code>/events/requests</code></summary>
+
+```json
+{
+ "type": "object",
+ "fields": {
+ "clusterName": "string; omitted when null",
+ "endTime": "integer; omitted when null",
+ "requestId": "integer",
+ "progressPercent": "number; omitted when null",
+ "requestContext": "string; omitted when null",
+ "requestStatus": "HostRoleStatus; omitted when null",
+ "startTime": "integer; omitted when null",
+ "userName": "string; omitted when null",
+ "Tasks": {
+ "type": "array",
+ "items": {
+ "id": "integer",
+ "requestId": "integer",
+ "status": "HostRoleStatus",
+ "hostName": "string"
+ }
+ }
+ }
+}
+```
+
+</details>
+
+<details>
+<summary><code>RT-SUB-011</code> <code>/events/tasks/{taskId}</code></summary>
+
+```json
+{
+ "type": "object",
+ "fields": {
+ "id": "integer",
+ "requestId": "integer",
+ "hostName": "string",
+ "endTime": "integer; omitted when null",
+ "status": "HostRoleStatus",
+ "errorLog": "string; omitted when null",
+ "outLog": "string; omitted when null",
+ "stderr": "string; omitted when null",
+ "stdout": "string; omitted when null",
+ "structured_out": "string; omitted when null"
+ }
+}
+```
+
+</details>
+
+## Lifecycle
+
+| ID / 名称 | 行为 | 风险边界 | Source / Test |
+| --- | --- | --- | --- |
+| `RT-LIFE-001`<br>`APPLICATION_BOOTSTRAP` | MainController connects once, starts all global subscriptions on native success, or waits for the nested SockJS attempt to settle after native failure. Cluster REST loading starts independently. | Both transports failing still invokes startSubscriptions, but disconnected subscribe calls return null and are not queued.<br>There is no global unsubscribe or disconnect call on logout in classic application code. | 实现:[source](../../../../ambari-we [...]
+| `RT-LIFE-002`<br>`MESSAGE_DISPATCH` | Each broker message body is parsed once per registered handler iteration and delivered synchronously in handler insertion order. A destination has one default handler plus optional named handlers. | JSON.parse has no try/catch.<br>One throwing handler prevents later handlers for the same message and provides no isolation or replay.<br>A second default subscribe for an existing destination logs an error and retains the original handler. | 实现:[source [...]
+| `RT-LIFE-003`<br>`RECONNECT_AND_RESTORE` | After a previously successful connection fails, the wrapper waits 6000 ms, disconnects an open client, reconnects with the current transport, clears the subscription map, and recreates every captured default and named handler. | There is no replay cursor, sequence number, or forced REST snapshot after reconnect.<br>Subscription changes during the delay or reconnect can be resurrected or lost because restoration uses a shallow pre-delay snapsho [...]
+| `RT-LIFE-004`<br>`DYNAMIC_HANDLER_OWNERSHIP` | Config history owns a named handler on the permanent configs destination. Task detail owns an entire dynamic destination until selected client terminal states arrive. | Named handler removal assumes the destination exists.<br>Task ownership is not released merely by leaving TASK_DETAILS.<br>Client and server terminal-state definitions are inconsistent. | 实现:[source](../../../../ambari-web/classic/app/views/main/dashboard/config_history_vie [...]
diff --git a/docs/frontend-refactor/react-current/01-auth-shell-gap.md b/docs/frontend-refactor/react-current/01-auth-shell-gap.md
new file mode 100644
index 0000000000..305f8b0acc
--- /dev/null
+++ b/docs/frontend-refactor/react-current/01-auth-shell-gap.md
@@ -0,0 +1,113 @@
+# React 认证与应用外壳实现对照
+
+## 对照范围
+
+| 项目 | 值 |
+| --- | --- |
+| Ember 基准 | `ember-baseline/01-auth-shell.md` |
+| React 实现 | `ambari-web/latest`,分支 `auth-shell-module` |
+| React 基线提交 | `8ac5c5a1346687bc46b4651c42b07237fe0e9ca9` |
+| 模块功能 ID | `AUTH-001` 至 `AUTH-008`,`SHELL-001` 至 `SHELL-009` |
+| 复核日期 | 2026-08-13 |
+| Metrics 范围 | 排除;`/main/dashboard/metrics` 仅作为已有默认路由,不评价 Metrics 页面能力 |
+
+状态遵循 `ember-baseline/14-react-gap-matrix.md`。当前代码、单元测试和生产构建已通过静态验证,但尚未连接真实 Ambari Server、Knox 和 Admin View,因此 17 项均标记为 `NEEDS_RUNTIME_VALIDATION`,不能提前标记为 `COVERED`。
+
+## 当前结论
+
+| 状态 | 数量 |
+| --- | ---: |
+| `NEEDS_RUNTIME_VALIDATION` | 17 |
+| 合计 | 17 |
+
+认证与应用外壳的静态缺口已经补齐:Router 始终挂载,会话探测先于应用初始化,user/privileges/authorizations/MOTD 原子加载,四类 landing、统一 logout、keep-alive、inactivity、About、Experimental、User Settings 和 Installer 版本 gate 均已接线。剩余门禁是使用真实服务端完成本文列出的角色、SSO、错误和恢复场景。
+
+## 功能状态
+
+| ID | 状态 | React 实现与自动化证据 | 待运行态验证 |
+| --- | --- | --- | --- |
+| `AUTH-001` | `NEEDS_RUNTIME_VALIDATION` | `App.tsx` 始终挂载 hash router;`AuthenticatedApplication` 分隔公开登录与受保护应用;未认证深链保存安全目标后跳 `/login`;根 `/` 不作为 preferred path | 未登录直接访问根、Dashboard、Installer、View 深链;服务端 session 过期后的跳转与恢复 |
+| `AUTH-002` | `NEEDS_RUNTIME_VALIDATION` | `loginApi.authenticate()` 发送 UTF-8 Basic;用户名仅在 API URL 边界编码;`loadSession()` 要求 user 和 authorizations 全部成功;测试覆盖 UTF-8、单次编码和 authorization failure | 本地用户成功登录;403、500、网络错误的服务端实际 response body 与 UI 文案 |
+| `AUTH-003` | `NEEDS_RUNTIME_VALIDATION` | `/login/local`、`jwtProviderUrl`、返回 URL、三次循环限制和 local recovery 已实现;plain business 403 不再误判 session 失效 | Knox JWT 成功返回;Knox 401/403 payload;代理下完整 return URL;循环超限后的 local 登录 |
+| `AUTH-004` | `NEEDS_RUNTIME_VALIDATION` | `/clusters` session probe 读取 `User` header;本地状态缺失时恢复;authorization failure 不建 session;401 会同步清 React 内存 session | 容器/SSO 已认证且 localStorage 为空;header 大小写与代理透传;session 过期后的所有 lifecycle teardown |
+| `AUTH-005` | `NEEDS_RUNTIME_VALIDATION` | user、authorization 与 MOTD 并行加载;启用且有文本时在 AppProvider 前阻断;缺失、禁用、非法和请求失败时继续 | `/settings/motd` 的合法、换行、非法 JSON、disabled、404、500 实际响应 |
+| `AUTH-006` | `NEEDS_RUNTIME_VALIDATION` | `selectLandingPath()` 覆盖 installed、incomplete、View-only、no-cluster;Admin View 生成版本化浏览器 URL;测试覆盖四类 landing | 四种角色/集群组合;Admin View 部署版本选择;未完成 Installer 与进行中 wizard 的实际恢复 |
+| `AUTH-007` | `NEEDS_RUNTIME_VALIDATION` | Navbar 和 inactivity 共用 context logout;先清内存、本地 DB 和 preferred path,再 best-effort `/logout`;服务端失败不阻塞导航;测试覆盖 rejected logout | `/logout` 成功、500、网络断开和长时间 pending;cookie 删除;STOMP、polling 和 iframe teardown |
+| `AUTH-008` | `NEEDS_RUNTIME_VALIDATION` | 认证应用挂载后每 60 秒串行调用 `/clusters`,卸载或 logout 后停止 | 长会话保持;请求跨 60 秒时无并发;session 过期 response;登出后 Network 面板无后续请求 |
+| `SHELL-001` | `NEEDS_RUNTIME_VALIDATION` | AppProvider 加载 supports、wizard owner、Ambari properties/version、cluster;运维用户再加载 services/hosts/upgrade/background/STOMP;View-only 跳过运维初始化并使用最小壳层;初始化失败可 Retry | installed/incomplete/no-cluster/View-only 的实际请求清单;每个初始化 API 故障和 Retry;STOMP reconnect/deactivate |
+| `SHELL-002` | `NEEDS_RUNTIME_VALIDATION` | `/main`、`/installer`、`/experimental`、`/adminView` 位于统一认证边界;Admin 子菜单和 route 分开授权;Auto Start 菜单保留两组 AND、route 保留 Manage OR;Kerberos/Auto Start 使用 feature guard | read-only、service operator、cluster admin、Ambari admin、View-only;upgrade 与 non-wizard owner;直接输入各受限 route |
+| `SHELL-003` | `NEEDS_RUNTIME_VALIDATION` | `normalizeInternalPath()` 只接受单斜杠内部路径,排除根、login、absolute 和 `//host`;值恢复后消费;测试覆盖安全/拒绝样例 | 浏览器刷新、SSO 往返、session expiry 和主动 logout;各 wizard 写入/清除自己的服务端恢复键 |
+| `SHELL-004` | `NEEDS_RUNTIME_VALIDATION` | `DocumentTitleUpdater` 只更新 `document.title`;认证和 shell 初始化分别显示 loading/error/retry;不再渲染标题占位 DOM | cluster 名、Installer、Views、Experimental 和深链 title;慢请求进度;初始化失败后 Retry 成功 |
+| `SHELL-005` | `NEEDS_RUNTIME_VALIDATION` | admin/readonly property 分流;大于 0 才启用;60 秒 warning;Continue/Sign Out;window 和动态 iframe activity;wizard/upgrade 排除;测试覆盖 timeout 选择、倒计时和排除路径 | admin/readonly 实际 property 单位;同源和跨域 View iframe;动态 iframe;warning 两个按钮;路由切换和 timeout=0 |
+| `SHELL-006` | `NEEDS_RUNTIME_VALIDATION` | Ambari version 初始化时缓存;About 只读 context,缺失显示 `N/A`,无 click-time 请求 | Main、Installer、View-only 和 no-cluster;properties 请求失败;重复打开 About 的 Network 记录 |
+| `SHELL-007` | `NEEDS_RUNTIME_VALIDATION` | `/experimental` 由 `AMBARI.MANAGE_SETTINGS` 保护;经典 supports key;Save 同步共享 context;Reset 另需 persisted-data 权限且 non-owner 禁用;服务端 reset 成功后才清本地并整页 reload | supports 存在/缺失/非法;Save/Cancel;Reset 成功/失败;另一用户持有 Installer、Kerberos、HA wizard |
+| `SHELL-008` | `NEEDS_RUNTIME_VALIDATION` | Maven 注入 `VITE_AMBARI_VERSION`;client/server 都非空且不一致才阻断 Installer;测试覆盖 empty/match/mismatch | Maven packaged artifact;相同版本、不一致版本和开发构建空版本;阻断页面显示两端版本 |
+| `SHELL-009` | `NEEDS_RUNTIME_VALIDATION` | 菜单 `AMBARI.MANAGE_SETTINGS`、handler `CLUSTER.UPGRADE_DOWNGRADE_STACK`、保存 `CLUSTER.MANAGE_USER_PERSISTED_DATA` 三层 gate 保留;background/timezone/default persist、cluster/View privileges 和错误反馈已实现 | 三层不同权限组合;首次登录默认写回;字符串/JSON persist;timezone reload;无 privileges、Ambari admin 和 API failure |
+
+## 后端接口对照
+
+| Ember 契约 | React 实现 | 静态结论 | 运行态门禁 |
+| --- | --- | --- | --- |
+| `POST /api/v1/auth` | `LoginApi.authenticate()` | UTF-8 Basic、`Content-Type: text/plain`、`skipAuthRedirect` 已实现 | Knox/proxy header 和 403/500 body |
+| `GET /api/v1/users/{userName}?fields=*,privileges/...` | `LoginApi.handleSuccessfulLogin()` | username 单次编码;user 与 privilege 原子建 session | 特殊字符用户名和真实 privilege shape |
+| `GET /api/v1/users/{userName}/authorizations?fields=*` | `LoginApi.loadAuthorizationsCallback()` | 失败时整个 session 失败,不复刻 Ember `.complete()` 缺陷 | 不同角色 authorization 集合和 401/403/500 |
+| `GET /api/v1/users/{userName}/privileges?fields=*` | `LoginApi.loadPrivileges()` | User Settings 独立读取 cluster/View 字段 | View instance/version/name 和无 privilege response |
+| `GET /api/v1/clusters?...` | `probeSession()`、`getClusterData()`、`noopPolling()` | session probe、cluster 初始化和 keep-alive 职责分开;probe 可读 `User` header | SSO header、无集群、未完成集群、session expiry |
+| `GET /api/v1/settings/motd` | `LoginApi.loadLoginMessage()` | 容错解析和阻断确认已实现 | 合法/非法/404/500 payload |
+| `GET /api/v1/logout` | `LoginApi.logout()` | 统一调用;客户端清理不等待服务端成功 | cookie、proxy、pending/失败请求 |
+| `GET /api/v1/services/AMBARI/components/AMBARI_SERVER` | `ClusterApi.loadAmbariProperties()` | properties 和 component version 一次初始化并缓存 | 各部署版本的 response shape |
+| `GET/POST /api/v1/persist[/key]` | `ClusterApi.getPersistData()`、`postPersistData()` | User Settings、supports、wizard owner 使用经典 JSON-string value 契约 | 404/default、权限拒绝、并发 wizard owner |
+| `GET /api/v1/services/AMBARI?...component_version...` | `ServiceApi.getAmbariServerVersion()` | Admin View 选择最新版本并构造 browser URL | 多版本排序、空 response、Admin View 未安装 |
+
+## 五轮实现审计
+
+| 轮次 | 独立入口 | 本轮发现 | 修正结果 |
+| --- | --- | --- | --- |
+| 1. Route 与 landing | Router、公开/受保护边界、四类首屏、深链 | 根 `/` 被保存为 preferred path 会自重定向;`/adminView` 缺经典 route guard | 根路径不再保存;Admin View 对已有集群保留 Upgrade guard、无集群允许 Ambari admin |
+| 2. 认证与 session | Basic、JWT、User header、MOTD、logout、keep-alive | 全局 401 只改 hash但不清 React session;plain 403 会被误当 session expiry;logout 会等待服务端 | 增加 session-expired 事件;401/JWT challenge 与业务 403 分流;logout 先完成客户端清理并后台 logoff |
+| 3. API 与持久化 | 全部十类 Auth/Shell endpoint、URL 编码、persist value | session 探测失败会全量清 DB,破坏 Installer/HA/Kerberos 恢复;Reset 先清本地再请求服务端 | session-only cleanup 保留 wizard namespace;主动 logout/reset 才全量清理;Reset 改为 server-first |
+| 4. 权限与状态 | menu/route/handler/save gate、upgrade、wizard owner、feature flags | Settings 首次缺省值未写回;Navbar 在 upgrade 时显示无响应项;`wizardIsNotFinished` 漏 non-owner | 权限允许时写回默认值;受限菜单按经典语义隐藏;全局 wizard 状态包含 non-owner |
+| 5. 异步与失败恢复 | STOMP、polling、iframe、version、View-only、错误/Retry | STOMP client 每 render 新建;动态 iframe 未监听;Modal 泄漏内部 props;View-only 仍暴露集群控件 | 固定单一 STOMP client并 teardown;MutationObserver 管理 iframe;过滤 Modal props;View-only 使用最小导航 |
+
+## 有意不复刻的 Ember 缺陷
+
+| Ember 行为 | React 决定 |
+| --- | --- |
+| preferred path 只看首字符,错误接受 `//host` | 拒绝 protocol-relative 和 absolute URL |
+| authorization 请求通过 `.complete()` 串联,失败仍可能继续 | user、privileges、authorizations 任一失败均不建立 session |
+| logoff 失败可能保留 keep-alive 标志 | 客户端退出和 lifecycle teardown 不依赖服务端 logoff 结果 |
+| Experimental Reset 不等待服务端 persist 完成 | 先成功清服务端 wizard owner,再清本地并整页恢复 session |
+
+## 自动化证据
+
+`npm test` 当前运行 7 个 Vitest 文件、32 个测试,覆盖:
+
+- UTF-8 Basic Authorization 和 username 单次编码。
+- authorization failure 不建立 session,`User` header 恢复 session,logout failure cleanup。
+- JWT/local 分支、redirect limit、session-expired 事件和 plain 403 分流。
+- `//host`、absolute、login 和根路径拒绝,以及安全内部路径接受。
+- installed/default、preferred、incomplete、no-cluster 和 View-only landing。
+- client/server version empty、match、mismatch。
+- persist JSON values、fallback、admin/readonly inactivity、wizard/upgrade exclusion和倒计时。
+- Admin View existing-cluster guard 与 no-cluster Ambari admin landing。
+
+`npm run build` 已通过。现存 Sass deprecation、重复 switch、`eval` 和大 bundle 警告来自基线,不作为本模块完成证据,也不在本模块范围内修复。全仓 `npm run lint` 仍被 `frontend-refactor` 基线的大量 `any`、`@ts-ignore` 和 hook dependency 技术债阻断;本模块新增的策略、API、layout、guard、页面和测试文件无 ESLint error。
+
+## 运行态验收矩阵
+
+至少执行以下场景后才允许将对应 ID 改为 `COVERED`:
+
+1. 本地 Basic 登录:成功、错误密码、500、网络失败、特殊字符用户名。
+2. Knox JWT:成功往返、`/login/local` bypass、循环超限、代理 return URL。
+3. 服务端已有会话且 localStorage 为空;运行中 session 过期。
+4. installed cluster、incomplete cluster、no-cluster Ambari admin、View-only 四类 landing。
+5. admin timeout、readonly timeout、timeout=0、Continue、Sign Out、wizard/upgrade exclusion、同源/跨域动态 View iframe。
+6. MOTD enabled/disabled/malformed/404/500。
+7. logout 成功、失败、pending,确认无后续 keep-alive、polling 或 STOMP 工作。
+8. User Settings 三层权限、首次默认写回、timezone reload、cluster/View privilege 列表。
+9. Experimental Save/Cancel/Reset、persist failure、non-wizard owner。
+10. Admin View navigation、版本请求失败 fallback、Maven packaged version match/mismatch。
+
+## Issue 与 PR 粒度
+
+本模块只使用一个主 JIRA 和一个主 PR,不按单个功能 ID 拆分。JIRA 英文草稿位于 `react-current/issues/01-auth-shell.md`;PR 必须以 `apache/ambari:frontend-refactor` 为 base,并推送到 `JiaLiangC/ambari`。
diff --git a/docs/frontend-refactor/react-current/issues/01-auth-shell.md b/docs/frontend-refactor/react-current/issues/01-auth-shell.md
new file mode 100644
index 0000000000..2bea58b92c
--- /dev/null
+++ b/docs/frontend-refactor/react-current/issues/01-auth-shell.md
@@ -0,0 +1,85 @@
+## Problem
+
+The React frontend does not yet provide a reliable authentication and application-shell boundary equivalent to the classic Ember UI. Authentication, server-session recovery, protected routing, landing-page selection, session lifecycle handling, persisted user settings, and global shell behavior are either incomplete or split across unrelated components.
+
+The current implementation selects the login page from a one-time `window.location.hash` snapshot and mounts the router only after cluster initialization. An unauthenticated request to a protected URL can therefore remain on the loading screen. The `/main` and `/installer` trees do not share an authentication guard, authorization failures can still establish a client-side session, and usernames are URL-encoded by both the context and API layers.
+
+This issue covers the complete non-Metrics Auth/Shell module defined by the classic UI baseline. Metrics pages are not part of the scope; an existing Metrics route may only be referenced as a landing route.
+
+## Scope
+
+* Establish a single router-driven boundary between public login routes and the authenticated application.
+* Support local Basic authentication, Knox/external JWT redirection, `/login/local`, redirect-loop protection, and server-authenticated session recovery through the `User` response header.
+* Load the user, privileges, and authorizations before declaring the session authenticated. Treat any required authorization-loading failure as a failed session.
+* Load and display the login message of the day when enabled, while allowing login to continue when the setting is absent, invalid, or unavailable.
+* Select the initial destination for installed clusters, incomplete installation, users with only View privileges, and Ambari administrators with no cluster.
+* Preserve and restore only normalized same-origin application paths. Reject protocol-relative, absolute, and login targets.
+* Consolidate logout, local-state cleanup, polling teardown, and server logoff behavior.
+* Add the authenticated-session keep-alive lifecycle.
+* Load global shell state in the correct order and avoid initializing full cluster operations for View-only users.
+* Apply authentication and authorization checks independently to navigation visibility and direct route access.
+* Implement document-title updates, cached About information, and actionable global loading/error states.
+* Implement administrator and read-only inactivity timeouts, the 60-second warning, continue/sign-out actions, activity monitoring, and wizard/upgrade exclusions.
+* Implement the Experimental page, persisted support flags, and Reset UI States with its separate persisted-data permission.
+* Complete User Settings for background-operation preference, timezone, and current cluster/View privileges while preserving the classic UI's distinct visibility, handler, and persistence permission checks.
+* Block the Installer on a packaged web-client/server version mismatch when a client version is available.
+
+## Classic UI Baseline
+
+The acceptance baseline is `docs/frontend-refactor/ember-baseline/01-auth-shell.md`, feature IDs `AUTH-001` through `AUTH-008` and `SHELL-001` through `SHELL-009`. The React gap analysis is recorded in `docs/frontend-refactor/react-current/01-auth-shell-gap.md`.
+
+The authoritative network comparison must include all of the following classic contracts rather than only matching API file names:
+
+* `POST /api/v1/auth`
+* `GET /api/v1/users/{userName}` with privileges
+* `GET /api/v1/users/{userName}/authorizations`
+* `GET /api/v1/users/{userName}/privileges`
+* `GET /api/v1/clusters` for authentication, cluster state, and keep-alive
+* `GET /api/v1/settings/motd`
+* `GET /api/v1/logout`
+* `GET /api/v1/services/AMBARI/components/AMBARI_SERVER` for properties and version
+* `GET` and `POST /api/v1/persist[/key]`
+* Ambari Admin View browser navigation
+
+## Acceptance Criteria
+
+* Visiting `/`, `/main/...`, or `/installer/...` without an authenticated server session routes to `/login` and safely preserves an eligible target.
+* A valid local login sends UTF-8 Basic credentials and URL-encodes the username exactly once at the URL boundary.
+* User data, privileges, and authorizations must all load before protected content is rendered.
+* A server-authenticated user with empty local storage is restored from the `User` response header.
+* Authentication responses with `jwtProviderUrl` redirect to the provider with the current URL as the return target, except on `/login/local`; repeated redirects stop with a visible recovery path.
+* An enabled message of the day requires acknowledgment before navigation. Missing, malformed, disabled, or failed MOTD retrieval does not block login.
+* Landing behavior is correct for installed clusters, incomplete installation, no-cluster Ambari administrators, and View-only users.
+* Restored targets are internal normalized hash paths and never protocol-relative or absolute URLs.
+* Logout stops session-owned work, clears local UI state, attempts server logoff, and reaches login even when logoff fails.
+* Keep-alive starts only for an authenticated application and stops on unmount or logout.
+* Navigation visibility and direct route entry each enforce their required permissions.
+* Inactivity handling selects the administrator or read-only timeout, warns 60 seconds before expiration, supports continue/sign out, monitors the window and accessible iframes, and skips wizard and stack-upgrade routes.
+* About uses the server version cached during initialization and displays `N/A` when unavailable without a click-time request.
+* User Settings loads and saves background-operation preference and timezone and displays cluster/View privileges under the documented permission gates.
+* Experimental support flags can be loaded, edited, saved, canceled, and reset under the documented permission gates.
+* A packaged client/server version mismatch blocks the Installer and displays both versions.
+* Unit tests cover authentication branches, authorization failure, URL normalization, landing selection, permission gates, logout failure, inactivity behavior, and persisted settings.
+* Runtime validation covers local Basic auth, Knox JWT, server-session recovery, each landing mode, administrator/read-only inactivity, MOTD variants, logout failure, Admin View navigation, and a packaged version mismatch.
+
+## Out of Scope
+
+* Metrics dashboards, charts, widgets, and Metrics data APIs.
+* AngularJS Ambari Admin Console implementation. This issue only covers navigation into the existing Admin View.
+* Feature work belonging to Hosts, Services, Alerts, Upgrades, Installer steps, Kerberos, or HA beyond the shell entry and lifecycle behavior described above.
+
+## Compatibility Decisions
+
+The React implementation must not reproduce known unsafe or failure-prone classic UI behavior:
+
+* Protocol-relative preferred paths such as `//host/path` are rejected even though the classic prefix check accepts them.
+* User and privilege data are not sufficient to establish a session when the authorization request fails.
+* A plain business-operation `403 Forbidden` is not treated as proof that the authenticated session expired.
+* Client logout and lifecycle teardown do not wait for successful server logoff.
+* Reset UI States does not erase recoverable local wizard data until the server-side wizard owner reset succeeds.
+
+These are intentional compatibility corrections, not missing parity.
+
+## Verification Boundary
+
+The module is not complete based on static code or unit tests alone. All feature IDs remain `NEEDS_RUNTIME_VALIDATION` until the runtime matrix in `docs/frontend-refactor/react-current/01-auth-shell-gap.md` is executed against real Ambari Server, Knox, Admin View, packaged web-client versions, and representative permission roles.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ambari.apache.org
For additional commands, e-mail: commits-help@ambari.apache.org