This is an automated email from the ASF dual-hosted git repository.
arshad 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 fa26f7958a AMBARI-26370 :: Ambari Web React: Integrate Hosts tabs -
Summary, Configs, Alerts and Versions (#4097)
fa26f7958a is described below
commit fa26f7958a83ebada9370834a98842683b7a6ac7
Author: Himanshu Maurya <[email protected]>
AuthorDate: Fri Dec 19 16:14:41 2025 +0530
AMBARI-26370 :: Ambari Web React: Integrate Hosts tabs - Summary, Configs,
Alerts and Versions (#4097)
---
.../useAuth.ts => Utils/ComponentInProgress.tsx} | 18 +-
ambari-web/latest/src/hooks/useAuth.ts | 10 +-
ambari-web/latest/src/router/RoutesList.tsx | 19 +-
ambari-web/latest/src/screens/Hosts/details.tsx | 938 +++++++++++++++++++++
ambari-web/latest/src/screens/Hosts/index.tsx | 513 +++++++++++
.../screens/Hosts/supportClientConfigsDownload.ts | 73 ++
6 files changed, 1550 insertions(+), 21 deletions(-)
diff --git a/ambari-web/latest/src/hooks/useAuth.ts
b/ambari-web/latest/src/Utils/ComponentInProgress.tsx
similarity index 66%
copy from ambari-web/latest/src/hooks/useAuth.ts
copy to ambari-web/latest/src/Utils/ComponentInProgress.tsx
index c7d39f2554..4034b30d84 100644
--- a/ambari-web/latest/src/hooks/useAuth.ts
+++ b/ambari-web/latest/src/Utils/ComponentInProgress.tsx
@@ -16,18 +16,6 @@
* limitations under the License.
*/
-//TODO: This will be implemented soon to fetch auth info
-
-export const useAuth = () => {
- const hasAuthorization = (authId: string) => {
- return authId ? true : false; //TODO: will be implemented soon
- };
-
- const havePermissions = (authRoles: string) => {
- return authRoles ? true : false; //TODO: will be implemented soon
- };
-
- return { hasAuthorization, havePermissions };
-}
-
-export default useAuth;
+export function ComponentInProgress() {
+ return <h1>Component In Progress</h1>;
+}
\ No newline at end of file
diff --git a/ambari-web/latest/src/hooks/useAuth.ts
b/ambari-web/latest/src/hooks/useAuth.ts
index c7d39f2554..56684f6c9c 100644
--- a/ambari-web/latest/src/hooks/useAuth.ts
+++ b/ambari-web/latest/src/hooks/useAuth.ts
@@ -27,7 +27,15 @@ export const useAuth = () => {
return authRoles ? true : false; //TODO: will be implemented soon
};
- return { hasAuthorization, havePermissions };
+ const isAdmin = () => {
+ return true; //TODO: will be implemented soon
+ }
+
+ const isOperator = () => {
+ return true; //TODO: will be implemented soon
+ }
+
+ return { hasAuthorization, havePermissions, isAdmin, isOperator };
}
export default useAuth;
diff --git a/ambari-web/latest/src/router/RoutesList.tsx
b/ambari-web/latest/src/router/RoutesList.tsx
index 7dac38c646..680b436b1d 100644
--- a/ambari-web/latest/src/router/RoutesList.tsx
+++ b/ambari-web/latest/src/router/RoutesList.tsx
@@ -15,11 +15,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { RouteObject, Navigate, Outlet } from "react-router-dom";
import StackAndVersions from
"../screens/ClusterAdmin/StackAndVersions/StackAndVersions";
-function ComponentInProgress() {
- return <h1>Component In Progress</h1>;
-}
+import { ComponentInProgress } from "../Utils/ComponentInProgress";
+import HostsList from "../screens/Hosts/HostsList";
+import { Hosts } from "../screens/Hosts";
const RoutesList: RouteObject[] = [
{
@@ -86,11 +87,19 @@ const RoutesList: RouteObject[] = [
},
{
path: "hosts",
- element: <ComponentInProgress />,
+ element: <HostsList />,
+ },
+ {
+ path: "hosts/component/:componentName",
+ element: <HostsList />,
+ },
+ {
+ path: "hosts/version/:versionName/:versionStatus",
+ element: <HostsList />,
},
{
path: "hosts/:hostname/:tab",
- element: <ComponentInProgress />,
+ element: <Hosts />,
},
{
path: "host/add/:stepNumber",
diff --git a/ambari-web/latest/src/screens/Hosts/details.tsx
b/ambari-web/latest/src/screens/Hosts/details.tsx
new file mode 100644
index 0000000000..2d2a9d73ba
--- /dev/null
+++ b/ambari-web/latest/src/screens/Hosts/details.tsx
@@ -0,0 +1,938 @@
+/**
+ * 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 { get, set } from "lodash";
+import {
+ addDeleteComponentsMap,
+ getComponentDisplayName,
+ getComponentName,
+ getHostComponentsInfo,
+ serviceNonClientActiveComponents,
+ setRackInfo,
+ showConfirmationPopup,
+} from "./utils";
+import modalManager from "../../store/ModalManager";
+import { HostsApi } from "../../api/hostsApi";
+import {
+ showAlertModal,
+ showErrorModal,
+ translate,
+ translateWithVariables,
+} from "../../Utils/Utility";
+import {
+ defaultSuccessCallback,
+ infoPassiveState,
+ restartHostComponents,
+} from "./batchUtils";
+import { IHostStackVersion } from "../../models/hostStackVersion";
+import { Alert } from "react-bootstrap";
+import { IHostComponent } from "../../models/hostComponent";
+import { ComponentStatus } from "./enums";
+import { checkNnLastCheckpointTime } from "./actions";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faWarning } from "@fortawesome/free-solid-svg-icons";
+import { IHost } from "../../models/host";
+import {
+ downloadClientConfigsCall,
+ ResourceTypeEnum,
+} from "./supportClientConfigsDownload";
+//TODO: uncomment below lines and their usages after these components are
available
+// import BackgroundOperations from "../BackgroundOperations";
+// import RecommendationModal from "../../components/RecommendationModal";
+
+export const doAction = (option: any) => {
+ switch (option.action) {
+ case "deleteHost":
+ validateAndDeleteHost(option);
+ break;
+ case "startAllComponents":
+ doStartAllComponents(option);
+ break;
+ case "stopAllComponents":
+ doStopAllComponents(option);
+ break;
+ case "restartAllComponents":
+ doRestartAllComponents(option);
+ break;
+ case "onOffPassiveModeForHost":
+ onOffPassiveModeForHost(option);
+ break;
+ case "setRackId":
+ setRackIdForHost(option);
+ break;
+ case "downloadClientConfigs":
+ downloadClientConfigs(option);
+ break;
+ case "downloadAllClientConfigs":
+ downloadAllClientConfigs(option);
+ break;
+ case "regenerateKeytabFileOperations":
+ regenerateKeytabFileOperations(option);
+ break;
+ default:
+ break;
+ }
+};
+
+const setRackIdForHost = (option: any) => {
+ const operationData = {
+ message: translate("hosts.host.details.setRackId"),
+ };
+ setRackInfo(
+ operationData,
+ [
+ {
+ hostName: option.hostName,
+ },
+ ],
+ option.clusterName,
+ option.callback,
+ option.rack
+ );
+};
+
+const onOffPassiveModeForHost = (context: any) => {
+ const state = context.active ? "ON" : "OFF";
+ const message = translateWithVariables("hosts.host.details.for.postfix", {
+ "0": context.label,
+ }) as string;
+ let popupInfo = translateWithVariables("hosts.passiveMode.popup", {
+ "0": context.active ? "On" : "Off",
+ "1": context.hostName,
+ });
+
+ let popupAlert = "";
+
+ if (state === "OFF") {
+ const currentHostVersion = get(context, "host.stackVersions", []).find(
+ (version: IHostStackVersion) => version.isCurrent()
+ )?.repoVersion;
+ const currentClusterVersion = get(context, "clusterStackVersion", []).find(
+ (version: any) => version.state === "CURRENT"
+ )?.repository_version;
+ if (currentHostVersion !== currentClusterVersion) {
+ popupAlert = translateWithVariables(
+ "hosts.passiveMode.popup.version.mismatch",
+ {
+ "0": context.hostName,
+ "1": currentClusterVersion,
+ }
+ ) as string;
+ }
+ }
+ let modalProps = {
+ isOpen: true,
+ onClose: () => {},
+ modalTitle: translate("popup.confirmation.commonHeader"),
+ modalBody: (
+ <div>
+ <div>{popupInfo}</div>
+ {popupAlert ? (
+ <Alert variant="warning" className="mt-2">
+ {popupAlert}
+ </Alert>
+ ) : null}
+ </div>
+ ),
+ successCallback: () => {
+ hostPassiveModeRequest(
+ state,
+ message,
+ context.hostName,
+ context.clusterName
+ );
+ modalManager.hide();
+ },
+ options: {
+ buttonSize: "sm" as "sm" | "lg" | undefined,
+ cancelableViaIcon: true,
+ cancelableViaBtn: true,
+ okButtonVariant: "primary",
+ },
+ };
+ modalManager.show(modalProps);
+};
+
+const hostPassiveModeRequest = async (
+ state: string,
+ message: string,
+ hostNames: string,
+ clusterName: string
+) => {
+ const data = {
+ RequestInfo: {
+ context: message,
+ query: "Hosts/host_name.in(" + hostNames + ")",
+ },
+ Body: {
+ Hosts: {
+ maintenance_state: state,
+ },
+ },
+ };
+
+ try {
+ await HostsApi.updateHost(clusterName, data);
+ updateHost(state);
+ } catch (error) {
+ showErrorModal(message + get(error, "message", ""));
+ }
+};
+
+const updateHost = (state: string) => {
+ infoPassiveState(state);
+ // setTimeout(() => {
+ // window.location.reload();
+ // }, 2000);
+};
+
+const doStartAllComponents = (context: any) => {
+ const hostComponents: IHostComponent[] = get(
+ context,
+ "host.hostComponents",
+ []
+ );
+ const components = serviceNonClientActiveComponents(hostComponents);
+ if (components.length) {
+ showConfirmationPopup(translate("question.sure.startAll") as string, () =>
+ sendComponentsCommand(
+ components,
+ translate(
+ "hosts.host.maintainance.startAllComponents.context"
+ ) as string,
+ ComponentStatus.STARTED
+ )
+ );
+ }
+};
+
+export const sendComponentsCommand = async (
+ component: IHostComponent[],
+ context: string,
+ state: string
+) => {
+ const clusterName = get(component, "[0].clusterName", "");
+ const hostName = get(component, "[0].hostName", "");
+ const query =
+ "HostRoles/component_name.in(" +
+ component.map(getComponentName).join(",") +
+ ")";
+ const data = {
+ RequestInfo: {
+ context: context,
+ operation_level: {
+ level: "HOST",
+ cluster_name: clusterName,
+ host_name: hostName,
+ },
+ query: query,
+ },
+ Body: {
+ HostRoles: {
+ state: state,
+ },
+ },
+ };
+
+ const response = await HostsApi.updateHostComponentsForHost(
+ clusterName,
+ hostName,
+ query,
+ data
+ );
+ const requestId = get(response, "Requests.id", -1);
+ if (requestId !== -1) {
+ // modalManager.show(
+ // <BackgroundOperations
+ // isOpen={true}
+ // onClose={() => {
+ // modalManager.hide();
+ // }}
+ // requestId={requestId}
+ // />
+ // );
+ }
+};
+
+const doStopAllComponents = (context: any) => {
+ const hostComponents: IHostComponent[] = get(
+ context,
+ "host.hostComponents",
+ []
+ );
+ const hostName = context.hostName;
+ const clusterName = get(context, "clusterName", "");
+ const components: IHostComponent[] =
+ serviceNonClientActiveComponents(hostComponents);
+ if (components.length) {
+ if (
+ components.find(
+ (component: IHostComponent) =>
+ getComponentName(component) === "NAMENODE"
+ )?.workStatus === ComponentStatus.STARTED
+ ) {
+ checkNnLastCheckpointTime(
+ () =>
+ showConfirmationPopup(
+ translate("question.sure.stopAll") as string,
+ () =>
+ sendComponentsCommand(
+ components,
+ translate(
+ "hosts.host.maintainance.stopAllComponents.context"
+ ) as string,
+ ComponentStatus.STOPPED
+ )
+ ),
+ hostName,
+ clusterName
+ );
+ } else {
+ showConfirmationPopup(translate("question.sure.stopAll") as string, () =>
+ sendComponentsCommand(
+ components,
+ translate(
+ "hosts.host.maintainance.stopAllComponents.context"
+ ) as string,
+ ComponentStatus.STOPPED
+ )
+ );
+ }
+ }
+};
+
+const doRestartAllComponents = (context: any) => {
+ const hostComponents: IHostComponent[] = get(
+ context,
+ "host.hostComponents",
+ []
+ );
+ const hostName = context.hostName;
+ const clusterName = get(context, "clusterName", "");
+ const components: IHostComponent[] =
+ serviceNonClientActiveComponents(hostComponents);
+ if (components.length) {
+ if (
+ components.find(
+ (component: IHostComponent) =>
+ getComponentName(component) === "NAMENODE"
+ )?.workStatus === ComponentStatus.STARTED
+ ) {
+ checkNnLastCheckpointTime(
+ () =>
+ showConfirmationPopup(
+ translate("question.sure.restartAll") as string,
+ () =>
+ restartHostComponents(
+ components,
+ translateWithVariables(
+ "rollingrestart.context.allOnSelectedHost",
+ {
+ "0": hostName,
+ }
+ ) as string,
+ "HOST"
+ )
+ ),
+ hostName,
+ clusterName
+ );
+ } else {
+ showConfirmationPopup(
+ translate("question.sure.restartAll") as string,
+ () =>
+ restartHostComponents(
+ components,
+ translateWithVariables("rollingrestart.context.allOnSelectedHost",
{
+ "0": hostName,
+ }) as string,
+ "HOST"
+ )
+ );
+ }
+ }
+};
+
+const downloadClientConfigs = (context: any) => {
+ const data = {
+ clusterName: context.clusterName,
+ hostName: context.hostName,
+ componentName: context.componentName,
+ resourceType: ResourceTypeEnum.HOST_COMPONENT,
+ };
+ downloadClientConfigsCall(data);
+};
+
+const downloadAllClientConfigs = (context: any) => {
+ const data = {
+ clusterName: context.clusterName,
+ hostName: context.hostName,
+ resourceType: ResourceTypeEnum.HOST,
+ };
+ downloadClientConfigsCall(data);
+};
+
+export const confirmRecoverHost = (context: any) => {
+ const host: IHost = context.host;
+ const hostComponents: IHostComponent[] = get(host, "hostComponents", []);
+ const allowedStates = [
+ ComponentStatus.STOPPED,
+ ComponentStatus.INSTALL_FAILED,
+ ComponentStatus.INIT,
+ ];
+ let componentsNotStopped: IHostComponent[] = [];
+ hostComponents.forEach((component: IHostComponent) => {
+ if (!allowedStates.includes(component.workStatus as ComponentStatus)) {
+ componentsNotStopped.push(component);
+ }
+ });
+ if (componentsNotStopped.length) {
+ let body = translate("hosts.recover.error.popup.body") as string;
+ if (body.includes("{0}")) {
+ body = body.replace(
+ "{0}",
+ componentsNotStopped
+ .map((component: IHostComponent) => getComponentName(component))
+ .join(", ")
+ );
+ }
+ const modalProps = {
+ isOpen: true,
+ onClose: () => {},
+ modalTitle: translate("hosts.recover.error.popup.title"),
+ modalBody: (
+ <Alert variant="warning">
+ <FontAwesomeIcon icon={faWarning} className="text-warning" />
+ {body}
+ </Alert>
+ ),
+ successCallback: () => {
+ modalManager.hide();
+ },
+ options: {
+ buttonSize: "sm" as "sm" | "lg" | undefined,
+ cancelableViaIcon: true,
+ cancelableViaBtn: false,
+ okButtonVariant: "primary",
+ },
+ };
+ modalManager.show(modalProps);
+ } else {
+ const modalProps = {
+ isOpen: true,
+ onClose: () => {},
+ modalTitle: translate("hosts.recover.popup.title"),
+ modalBody: translate("hosts.recover.popup.body"),
+ successCallback: () => {
+ recoverHost(context);
+ modalManager.hide();
+ },
+ options: {
+ buttonSize: "sm" as "sm" | "lg" | undefined,
+ cancelableViaIcon: true,
+ cancelableViaBtn: true,
+ okButtonVariant: "primary",
+ okButtonText: "YES",
+ cancelButtonText: "NO",
+ },
+ };
+ modalManager.show(modalProps);
+ }
+};
+
+const recoverHost = async (context: any) => {
+ const host: IHost = context.host;
+ const components = host.hostComponents;
+ const hostName = host.hostName;
+ const clusterName = get(context, "clusterName", "");
+ const isKerberosEnabled = get(context, "isKerberosEnabled", false);
+ const batches: any[] = [
+ {
+ order_id: 1,
+ type: "PUT",
+ uri: `/clusters/${clusterName}/hosts/${hostName}/host_components`,
+ RequestBodyInfo: {
+ RequestInfo: {
+ context: translate("hosts.host.recover.initAllComponents.context"),
+ operation_level: {
+ level: "HOST",
+ cluster_name: clusterName,
+ host_name: hostName,
+ },
+ query: `HostRoles/component_name.in(${components
+ .map((c) => c.componentName)
+ .join(",")})`,
+ },
+ Body: {
+ HostRoles: {
+ state: "INIT",
+ },
+ },
+ },
+ },
+ ];
+
+ batches.push({
+ order_id: 2,
+ type: "PUT",
+ uri: `/clusters/${clusterName}/hosts/${hostName}/host_components`,
+ RequestBodyInfo: {
+ RequestInfo: {
+ context: translate("hosts.host.recover.installAllComponents.context"),
+ operation_level: {
+ level: "HOST",
+ cluster_name: clusterName,
+ host_name: hostName,
+ },
+ query: `HostRoles/component_name.in(${components
+ .map((c) => c.componentName)
+ .join(",")})`,
+ },
+ Body: {
+ HostRoles: {
+ state: "INSTALLED",
+ },
+ },
+ },
+ });
+
+ if (isKerberosEnabled) {
+ batches.push({
+ order_id: 3,
+ type: "PUT",
+ uri: `/clusters/${clusterName}`,
+ RequestBodyInfo: {
+ RequestInfo: {
+ context: translate("hosts.host.recover.regenerateKeytabs.context"),
+ query:
`regenerate_keytabs=all®enerate_hosts=${hostName}&config_update_policy=none`,
+ },
+ Body: {
+ Clusters: {
+ security_type: "KERBEROS",
+ },
+ },
+ },
+ });
+ }
+
+ await context.getKDCSessionState(() => {
+ doRecoverHost(batches, clusterName);
+ });
+};
+
+const doRecoverHost = async (batches: any[], clusterName: string) => {
+ const data = {
+ intervalTimeSeconds: 1,
+ tolerateSize: 0,
+ batches: batches,
+ };
+ try {
+ const respone = await HostsApi.batchRequest(clusterName, data);
+ recoverHostSuccessCallback(respone);
+ } catch (error) {
+ showErrorModal(get(error, "message", ""));
+ }
+};
+
+const recoverHostSuccessCallback = (response: any) => {
+ const requestId = get(response, "data.resources.[0].RequestSchedule.id", -1);
+ if (requestId !== -1) {
+ // modalManager.show(
+ // <BackgroundOperations
+ // isOpen={true}
+ // onClose={() => {
+ // modalManager.hide();
+ // }}
+ // requestId={requestId}
+ // />
+ // );
+ }
+};
+
+const validateAndDeleteHost = (context: any) => {
+ const clusterComponents = get(context, "clusterComponents", []);
+ const serviceModels = get(context, "serviceModels", {});
+ const container = getHostComponentsInfo(
+ get(context, "host.hostComponents", []),
+ clusterComponents,
+ serviceModels
+ );
+ const properties = {};
+ const hostData = get(context, "host", {} as IHost);
+ if (container.nonDeletableComponents.length > 0) {
+ raiseDeleteComponentsError(container, "nonDeletableList", hostData);
+ return;
+ } else if (container.nonAddableMasterComponents.length > 0) {
+ raiseDeleteComponentsError(container, "masterList", hostData);
+ return;
+ } else if (container.runningComponents.length > 0) {
+ raiseDeleteComponentsError(container, "runningList", hostData);
+ return;
+ } else if (container.lastMasterComponents.length > 0) {
+ raiseDeleteComponentsError(container, "lastMasterList", hostData);
+ return;
+ }
+
+ set(properties, "fromDeleteHost", true);
+
+ if (container.isReconfigureRequired) {
+ reconfigureAndDeleteHost(container, context, properties);
+ } else {
+ confirmDeleteHost(container, context);
+ }
+};
+
+const raiseDeleteComponentsError = (
+ container: any,
+ type: string,
+ hostData: IHost
+) => {
+ let components = [];
+ if (type === "nonDeletableList") {
+ components = container.nonDeletableComponents;
+ } else if (type === "masterList") {
+ components = container.nonAddableMasterComponents;
+ } else if (type === "runningList") {
+ components = container.runningComponents;
+ } else if (type === "lastMasterList") {
+ components = container.lastMasterComponents;
+ }
+
+ let componentsBody = translate(`hosts.cant.do.popup.${type}.body`) as string;
+ if (componentsBody.includes("{0}")) {
+ componentsBody = componentsBody.replace("{0}", components.length);
+ }
+
+ const hostComponents = hostData.hostComponents.filter(
+ (component: IHostComponent) =>
+ components.includes(getComponentDisplayName(component))
+ );
+ const decommissionableComponents = hostComponents.filter(
+ (component: IHostComponent) => component.decommissionAllowed
+ );
+
+ const showBodyEnd = ["runningList", "masterList", "lastMasterList"].includes(
+ type
+ );
+ let componentsBodyEnd = "";
+ if (showBodyEnd) {
+ componentsBodyEnd = translate(
+ `hosts.cant.do.popup.${type}.body.end`
+ ) as string;
+ if (componentsBodyEnd.includes("{0}")) {
+ componentsBodyEnd = componentsBodyEnd.replace(
+ "{0}",
+ decommissionableComponents.map(getComponentDisplayName).join(", ")
+ );
+ }
+ }
+
+ const modalProps = {
+ isOpen: true,
+ onClose: () => {},
+ modalTitle: translate("hosts.cant.do.popup.title"),
+ modalBody: (
+ <div>
+ <Alert variant="warning" className="mt-2">
+ <div className="d-flex">
+ <div className="me-4">
+ <FontAwesomeIcon icon={faWarning} className="text-warn" />
+ </div>
+ <div>
+ <div className="text-dark fw-bold mb-2">{componentsBody}</div>
+ <div>{components.join(", ")}</div>
+ {showBodyEnd ? (
+ <div className="mt-4">{componentsBodyEnd}</div>
+ ) : null}
+ </div>
+ </div>
+ </Alert>
+ </div>
+ ),
+ successCallback: () => {
+ modalManager.hide();
+ },
+ options: {
+ buttonSize: "sm" as "sm" | "lg" | undefined,
+ cancelableViaIcon: true,
+ cancelableViaBtn: false,
+ okButtonVariant: "primary",
+ },
+ };
+
+ modalManager.show(modalProps);
+};
+
+const reconfigureAndDeleteHost = (
+ _container: any,
+ context: any,
+ properties: any
+) => {
+ const hostName = get(context, "hostName");
+ let reconfiguredComponents: any = [];
+ const loadComponentRelatedConfigs = get(
+ context,
+ "loadComponentRelatedConfigs"
+ );
+
+ get(context, "host.hostComponents", []).forEach(
+ (component: IHostComponent) => {
+ const componentsMapItem =
addDeleteComponentsMap[component.componentName];
+ if (componentsMapItem) {
+ reconfiguredComponents.push(component.displayName);
+ if (componentsMapItem.hostPropertyName) {
+ set(properties, componentsMapItem.hostPropertyName, hostName);
+ }
+ if (componentsMapItem.addPropertyName) {
+ set(properties, componentsMapItem.addPropertyName, true);
+ }
+ loadComponentRelatedConfigs(
+ componentsMapItem.configTagsCallbackName,
+ componentsMapItem.configsCallbackName,
+ properties
+ );
+ }
+ }
+ );
+
+ // modalManager.show(
+ // <RecommendationModal
+ // isOpen={true}
+ // onClose={() => {
+ // modalManager.hide();
+ // }}
+ // componentDisplayName={reconfiguredComponents.join(", ")}
+ // add={false}
+ // callback={() => confirmDeleteHost(container, context)}
+ // commonMessage={
+ // translateWithVariables(
+ // "hosts.host.delete.componentsRequireReconfigure",
+ // {
+ // "0": reconfiguredComponents.join(", "),
+ // }
+ // ) as string
+ // }
+ // />
+ // );
+};
+
+const confirmDeleteHost = (container: any, context: any) => {
+ const publicHostName = get(context, "host.publicHostName", "");
+
+ const header = translate("hosts.delete.popup.title");
+ const deletePopupBody = translateWithVariables("hosts.delete.popup.body", {
+ "0": publicHostName,
+ });
+
+ let lastComponentError = "";
+ if (container.lastComponents.length > 0) {
+ lastComponentError = translateWithVariables(
+ "hosts.delete.popup.body.msg4",
+ {
+ "0": container.lastComponents.join(", "),
+ }
+ ) as string;
+ }
+
+ let unknownComponents = "";
+ if (container.unknownComponents.length > 0) {
+ unknownComponents = container.unknownComponents.join(", ");
+ }
+
+ let decommissionWarning = "";
+ if (container.toDecommissionComponents.length > 0) {
+ decommissionWarning = translateWithVariables(
+ "hosts.delete.popup.body.msg7",
+ {
+ "0": container.toDecommissionComponents.join(", "),
+ }
+ ) as string;
+ }
+
+ const modalProps = {
+ isOpen: true,
+ onClose: () => {},
+ modalTitle: header,
+ modalBody: (
+ <div>
+ {unknownComponents ? (
+ <div>
+ <div>{translate("hosts.delete.popup.unknownComponents")}</div>
+ <div className="mt-1">{unknownComponents}</div>
+ </div>
+ ) : null}
+ <div>
+ <FontAwesomeIcon icon={faWarning} className="text-warning me-2" />
+ {deletePopupBody}
+ </div>
+ {lastComponentError ? (
+ <div className="mt-2">
+ <Alert variant="warning" className="mt-2">
+ {lastComponentError}
+ </Alert>
+ </div>
+ ) : null}
+ {decommissionWarning ? (
+ <div className="mt-2">
+ <Alert variant="warning" className="mt-2">
+ {decommissionWarning}
+ </Alert>
+ </div>
+ ) : null}
+ <Alert variant="warning" className="mt-2">
+ <div>{translate("common.important.strong")}</div>
+ {unknownComponents ? (
+ <div className="mt-2">
+ {translate("hosts.delete.popup.body.msg.unknownComponents")}
+ </div>
+ ) : null}
+ <div>{translate("hosts.delete.popup.body.msg1")}</div>
+ </Alert>
+ {!unknownComponents ? (
+ <Alert variant="warning" className="mt-2">
+ <span>{translate("hosts.delete.popup.body.msg5")}</span>
+ <span className="text-danger">
+ {translate("hosts.delete.popup.body.msg6")}
+ </span>
+ </Alert>
+ ) : null}
+ <Alert variant="warning" className="mt-2">
+ <div>{translate("common.important.strong")}</div>
+ <div>{translate("hosts.delete.popup.body.msg3")}</div>
+ </Alert>
+ </div>
+ ),
+ successCallback: () => {
+ doDeleteHost(context, container, {}, {});
+ modalManager.hide();
+ },
+ options: {
+ buttonSize: "sm" as "sm" | "lg" | undefined,
+ cancelableViaIcon: true,
+ cancelableViaBtn: true,
+ okButtonVariant: "primary",
+ },
+ };
+
+ modalManager.show(modalProps);
+};
+
+const doDeleteHost = (
+ context: any,
+ container: any,
+ groupedPropertiesToChange: any,
+ deletedHostComponentError: any
+) => {
+ const allComponents = get(context, "host.hostComponents", []);
+ const doDeleteHostComponent = get(context, "doDeleteHostComponent");
+ const applyConfigsCustomization = get(
+ context,
+ "applyConfigsCustomization",
+ () => {}
+ );
+ const putConfigsToServer = get(context, "putConfigsToServer", () => {});
+ const clearConfigsChanges = get(context, "clearConfigsChanges", () => {});
+ let deleteRequests = [];
+
+ const deleteHost = async () => {
+ if (allComponents.length > 0) {
+ for (const component of allComponents) {
+ deleteRequests.push(doDeleteHostComponent(component));
+ }
+ try {
+ await Promise.all(deleteRequests);
+ if (container.isReconfigureRequired) {
+ const reconfiguredComponents = allComponents
+ .filter(
+ (component: IHostComponent) =>
+ addDeleteComponentsMap[component.componentName]
+ )
+ .map((component: any) => component.displayName)
+ .join(", ");
+ applyConfigsCustomization();
+ putConfigsToServer(groupedPropertiesToChange,
reconfiguredComponents);
+ clearConfigsChanges();
+ }
+ await deleteHostCall(context);
+ } catch (e) {
+ set(
+ deletedHostComponentError,
+ "xhr.responseText",
+ `{"message": "${get(
+ deletedHostComponentError,
+ "xhr.statusText",
+ ""
+ )}"}`
+ );
+ showErrorModal(get(deletedHostComponentError, "xhr.responseText", ""));
+ }
+ }
+ };
+
+ return deleteHost();
+};
+
+const deleteHostCall = async (context: any) => {
+ const clusterName = get(context, "host.cluster", "");
+ const hostName = get(context, "host.hostName", "");
+ try {
+ await HostsApi.deleteHost(clusterName, hostName);
+ deleteHostCallSuccessCallback();
+ } catch (error) {
+ deleteHostCallErrorCallback(error);
+ }
+};
+
+const deleteHostCallSuccessCallback = () => {
+ window.location.href = "#/main/hosts";
+};
+
+const deleteHostCallErrorCallback = (error: any) => {
+ const errorMessage = get(error, "message", "");
+ showErrorModal(errorMessage);
+};
+
+const regenerateKeytabFileOperations = (context: any) => {
+ const clusterName = get(context, "clusterName", "");
+ const hostName = get(context, "hostName", "");
+ showConfirmationPopup(
+ translateWithVariables("question.sure.regenerateKeytab.host", {
+ "0": hostName,
+ }) as string,
+ async () => {
+ try {
+ const response = await HostsApi.regenerateKeytabsForHost(
+ clusterName,
+ hostName
+ );
+ const requestId = get(response, "Requests.id", -1);
+ defaultSuccessCallback(requestId);
+ } catch (error) {
+ showAlertModal(
+ translate("common.error"),
+ translateWithVariables(
+ "alerts.notifications.regenerateKeytab.host.error",
+ {
+ "0": hostName,
+ }
+ )
+ );
+ }
+ }
+ );
+};
diff --git a/ambari-web/latest/src/screens/Hosts/index.tsx
b/ambari-web/latest/src/screens/Hosts/index.tsx
new file mode 100644
index 0000000000..37a072e4e8
--- /dev/null
+++ b/ambari-web/latest/src/screens/Hosts/index.tsx
@@ -0,0 +1,513 @@
+/**
+ * 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.
+ */
+
+//TODO: uncomment the commented imports and their usages after those
components are available
+
+import { Button, Col, Nav, Row, Tab } from "react-bootstrap";
+import HostsSummary from "./HostSummary";
+// import HostConfigs from "./HostConfigs";
+// import HostAlerts from "../Alerts/HostAlerts";
+// import HostStackVersions from "../Alerts/HostStackVersions";
+import NestedDropdown from "../../components/NestedDropdown";
+import { forEach, get } from "lodash";
+import { useContext, useEffect, useMemo, useState } from "react";
+import IHost from "../../models/host";
+import { useHostConfigUpdater } from "../../hooks/useHostConfigUpdater";
+import { useParams, useNavigate } from "react-router-dom";
+import { IHostComponent } from "../../models/hostComponent";
+import { AppContext } from "../../store/context";
+import { supports } from "../../data/configs/services/config";
+import { confirmRecoverHost, doAction } from "./details";
+import { useHostChecks } from "../../hooks/useHostChecks";
+import HostChecks from "./HostChecks";
+import modalManager from "../../store/ModalManager";
+import useStackVersion from "../../hooks/useStackVersion";
+import { HostsApi } from "../../api/hostsApi";
+import Spinner from "../../components/Spinner";
+// import { ServiceContext } from "../../store/ServiceContext";
+import useKDCSessionState from "../../hooks/useKDCSessionState";
+// import useStackServices from "../../hooks/useStackServices";
+// import { useConfigs } from "../../hooks/useConfigs";
+// import useComponentAddDelete from "./hooks/useComponentAddDelete";
+import { translate, translateWithVariables } from "../../Utils/Utility";
+import classNames from "classnames";
+import { useAuth } from "../../hooks/useAuth";
+import { ComponentInProgress } from "../../Utils/ComponentInProgress";
+
+export function Hosts() {
+ const params = useParams();
+ const navigate = useNavigate();
+ const { isKerberosEnabled, clusterName, upgradeIsRunning } =
+ useContext(AppContext);
+ // const { allServiceModels: serviceModels } = useContext(ServiceContext);
+ const [allHostModels, setAllHostModels] = useState<IHost[]>([]);
+ const { stackVersionList } = useStackVersion();
+ const [showHostCheck, setShowHostCheck] = useState(false);
+ const [clusterComponents, setClusterComponents] = useState<any>({});
+ const [loading, setLoading] = useState(true);
+
+ // const { services: stackServices } = useStackServices();
+ // const { getConfigByName } = useConfigs([], stackServices as any);
+ const { getKDCSessionState } = useKDCSessionState(() => {});
+ const [activeTab, setActiveTab] = useState(params.tab || "summary");
+
+ // Authorization hooks - implementing Ember.js host authorization patterns
+ const { hasAuthorization, isAdmin, isOperator, havePermissions } = useAuth();
+
+ // Use computed upgrade property instead of utility function
+ const isUpgradeInProgress = upgradeIsRunning;
+
+ // Check specific authorizations for host operations
+ const canStartStopServices = hasAuthorization("SERVICE.START_STOP");
+ const canToggleHostMaintenance = hasAuthorization("HOST.TOGGLE_MAINTENANCE");
+ const canAddDeleteHosts = hasAuthorization("HOST.ADD_DELETE_HOSTS");
+ const canViewConfigs =
+ hasAuthorization("SERVICE.VIEW_CONFIGS") ||
+ hasAuthorization("CLUSTER.VIEW_CONFIGS");
+ const canRunHostChecks = isAdmin() || isOperator();
+
+ // Overall permission check for Host Actions menu - matches Ember template
logic
+ // {{#havePermissions "HOST.ADD_DELETE_COMPONENTS, HOST.TOGGLE_MAINTENANCE,
HOST.ADD_DELETE_HOSTS"}}
+ const canShowHostActions = havePermissions("HOST.ADD_DELETE_COMPONENTS,
HOST.TOGGLE_MAINTENANCE, HOST.ADD_DELETE_HOSTS");
+
+ const hostApiQueryParams = useMemo(() => {
+ return {
+ RequestInfo: {
+ query: `Hosts/host_name.in(${params.hostname})`,
+ },
+ };
+ }, []);
+
+ useHostConfigUpdater(hostApiQueryParams, allHostModels, setAllHostModels);
+ const { startHostCheck, stopHostCheck, isHostCheckRunning, hostCheckResult }
=
+ useHostChecks();
+
+ // const {
+ // _doDeleteHostComponent,
+ // applyConfigsCustomization,
+ // putConfigsToServer,
+ // clearConfigsChanges,
+ // loadComponentRelatedConfigs,
+ // } = useComponentAddDelete(
+ // clusterComponents,
+ // stackServices,
+ // getConfigByName,
+ // setAllHostModels
+ // );
+
+ const getBootHostsProp = () => {
+ let bootHosts = [];
+ const hostName = get(allHostModels, "[0].hostName", "");
+ const host = {
+ name: hostName,
+ };
+ bootHosts.push(host);
+ return bootHosts;
+ };
+
+ useEffect(() => {
+ if (params.tab && params.tab !== activeTab) {
+ setActiveTab(params.tab);
+ }
+ }, [params.tab]);
+
+ useEffect(() => {
+ getClusterComponents();
+ }, [get(allHostModels, "[0].hostComponents", []).length]);
+
+ useEffect(() => {
+ if (showHostCheck) {
+ startHostCheck(getBootHostsProp());
+ } else {
+ stopHostCheck();
+ }
+ }, [showHostCheck]);
+
+ const getAlerts = () => {
+ return {
+ critical: get(allHostModels, "[0].alertsSummary.CRITICAL", 0),
+ warning: get(allHostModels, "[0].alertsSummary.WARNING", 0),
+ };
+ };
+
+ const tabs = {
+ summary: {
+ title: "SUMMARY",
+ component: (
+ <HostsSummary
+ allHostModels={allHostModels}
+ setAllHostModels={setAllHostModels}
+ clusterComponents={clusterComponents}
+ />
+ ),
+ },
+ configs: {
+ title: "CONFIGS",
+ component: <ComponentInProgress />,
+ // component: <HostConfigs />,
+ },
+ alerts: {
+ title: (
+ <>
+ ALERTS{" "}
+ <Button
+ className={classNames("me-1 text-white fs-10 rounded-1 px-1 py-0",
{
+ "bg-danger": getAlerts().critical > 0,
+ "bg-orange":
+ getAlerts().warning > 0 && getAlerts().critical === 0,
+ "bg-light":
+ getAlerts().critical === 0 && getAlerts().warning === 0,
+ })}
+ >
+ {getAlerts().critical + getAlerts().warning}
+ </Button>
+ </>
+ ),
+ component: <ComponentInProgress />,
+ // component: <HostAlerts hostname={params.hostname} />,
+ },
+ versions: {
+ title: "VERSIONS",
+ component: <ComponentInProgress />,
+ // component: <HostStackVersions />,
+ },
+ };
+
+ const isActive = () => {
+ return get(allHostModels, "[0]")?.isActive();
+ };
+
+ const getClusterComponents = async () => {
+ setLoading(true);
+ const response = await HostsApi.getClusterComponents(
+ clusterName,
+
"ServiceComponentInfo/service_name,host_components/HostRoles/display_name,host_components/HostRoles/host_name,host_components/HostRoles/public_host_name,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,host_components/HostRoles/stale_configs,host_components/HostRoles/ha_state,host_components/HostRoles/desired_admin_state,&minimal_response=true"
+ );
+ setClusterComponents(response);
+ setLoading(false);
+ };
+
+ const getMaintenanceOptions = () => {
+ const isNotHeartBeating =
+ get(allHostModels, "[0].state", "") === "HEARTBEAT_LOST";
+ const hostName = get(allHostModels, "[0].hostName", "");
+ const isManualKerberos = false; // TODO: replace this hardcoded value with
App.get('router.mainAdminKerberosController.isManualKerberos')
+ let result: any[] = [];
+
+ // SERVICE.START_STOP authorization check for component operations
+ if (canStartStopServices && !isUpgradeInProgress) {
+ result = result.concat([
+ {
+ label: translate("hosts.host.details.startAllComponents"),
+ isDisabled: isNotHeartBeating,
+ icon: "play",
+ iconClass: "text-success",
+ onClick: () => {
+ if (!isNotHeartBeating) {
+ doAction({
+ action: "startAllComponents",
+ hostName: hostName,
+ host: get(allHostModels, "[0]"),
+ });
+ }
+ },
+ },
+ {
+ label: translate("hosts.host.details.stopAllComponents"),
+ isDisabled: isNotHeartBeating,
+ icon: "stop",
+ iconClass: "text-danger",
+ onClick: () => {
+ if (!isNotHeartBeating) {
+ doAction({
+ action: "stopAllComponents",
+ hostName: hostName,
+ host: get(allHostModels, "[0]"),
+ clusterName: clusterName,
+ });
+ }
+ },
+ },
+ {
+ label: translate("hosts.host.details.restartAllComponents"),
+ isDisabled: isNotHeartBeating,
+ icon: "repeat",
+ onClick: () => {
+ if (!isNotHeartBeating) {
+ doAction({
+ action: "restartAllComponents",
+ hostName: hostName,
+ host: get(allHostModels, "[0]"),
+ clusterName: clusterName,
+ });
+ }
+ },
+ },
+ ]);
+ }
+
+ // HOST.TOGGLE_MAINTENANCE authorization check for maintenance operations
+ if (canToggleHostMaintenance && !isUpgradeInProgress) {
+ result = result.concat([
+ {
+ label: translate("hosts.host.details.setRackId"),
+ icon: "cog",
+ onClick: () => {
+ doAction({
+ action: "setRackId",
+ hostName: hostName,
+ clusterName: clusterName,
+ rack: get(allHostModels, "[0].rack", ""),
+ callback: setAllHostModels,
+ });
+ },
+ },
+ {
+ label: translate("passiveState.turn" + (isActive() ? "On" : "Off")),
+ icon: "medkit",
+ onClick: () => {
+ doAction({
+ action: "onOffPassiveModeForHost",
+ hostName: hostName,
+ host: get(allHostModels, "[0]"),
+ clusterStackVersion: stackVersionList,
+ clusterName: clusterName,
+ active: isActive(),
+ label: translate(
+ "passiveState.turn" + (isActive() ? "On" : "Off")
+ ),
+ });
+ },
+ },
+ ]);
+ }
+
+ if (
+ isKerberosEnabled &&
+ supports.regenerateKeytabsOnSingleHost &&
+ canToggleHostMaintenance &&
+ !isUpgradeInProgress
+ ) {
+ result.push({
+ label: translate("admin.kerberos.button.regenerateKeytabs"),
+ isVisible: isKerberosEnabled || isManualKerberos,
+ icon: "repeat",
+ onClick: () => {
+ doAction({
+ action: "regenerateKeytabFileOperations",
+ hostName: hostName,
+ clusterName: clusterName,
+ });
+ },
+ });
+ }
+
+ // HOST.ADD_DELETE_HOSTS authorization check for delete host operation
+ if (canAddDeleteHosts && !isUpgradeInProgress) {
+ result.push({
+ label: translate("hosts.host.details.deleteHost"),
+ icon: "remove",
+ iconClass: "text-danger",
+ onClick: () => {
+ // doAction({
+ // action: "deleteHost",
+ // hostName: hostName,
+ // host: get(allHostModels, "[0]"),
+ // clusterComponents: get(clusterComponents, "items", []),
+ // serviceModels: serviceModels,
+ // doDeleteHostComponent: _doDeleteHostComponent,
+ // applyConfigsCustomization,
+ // putConfigsToServer,
+ // clearConfigsChanges,
+ // loadComponentRelatedConfigs,
+ // });
+ },
+ });
+ }
+
+ // Admin/Operator authorization check for host check operation
+ if (canRunHostChecks) {
+ result.push({
+ label: translate("host.host.details.checkHost"),
+ icon: "check",
+ onClick: () => {
+ if (allHostModels.length) {
+ let modalProps = {
+ isOpen: true,
+ onClose: () => {},
+ modalTitle: translate("popup.confirmation.commonHeader"),
+ modalBody: translateWithVariables("hosts.checkHost.popup", {
+ "0": hostName,
+ }),
+ successCallback: () => {
+ setShowHostCheck(true);
+ modalManager.hide();
+ },
+ options: {
+ buttonSize: "sm" as "sm" | "lg" | undefined,
+ cancelableViaIcon: true,
+ cancelableViaBtn: true,
+ okButtonVariant: "primary",
+ },
+ };
+ modalManager.show(modalProps);
+ }
+ },
+ });
+ }
+
+ return result;
+ };
+
+ const getClients = () => {
+ // Only return client configs if user has permission to view configs
+ if (!canViewConfigs) {
+ return [];
+ }
+
+ let clients: any[] = [];
+ const hostName = get(allHostModels, "[0].hostName", "");
+ const hostComponents = get(allHostModels, "[0].hostComponents", []);
+ const clientComponents = hostComponents.filter((component: any) =>
+ get(component, "isClient", false)
+ );
+
+ forEach(clientComponents, (component: IHostComponent) => {
+ clients.push({
+ label: component.displayName,
+ onClick: () => {
+ doAction({
+ action: "downloadClientConfigs",
+ componentName: component.componentName,
+ hostName: hostName,
+ clusterName: clusterName,
+ });
+ },
+ });
+ });
+ if (clients.length > 1) {
+ clients.unshift({
+ label: translate("host.host.details.downloadAllClients"),
+ onClick: () => {
+ doAction({
+ action: "downloadAllClientConfigs",
+ hostName: hostName,
+ clusterName: clusterName,
+ });
+ },
+ });
+ }
+ return clients;
+ };
+
+ const buildHostActionsSubmenu = () => {
+ const submenu = [...getMaintenanceOptions()];
+
+ // Add client configs download if user has permission and there are clients
+ const clients = getClients();
+ if (canViewConfigs && clients.length > 0) {
+ submenu.push({
+ label: translate("services.service.actions.downloadClientConfigs"),
+ submenu: clients,
+ icon: "download",
+ });
+ }
+
+ // Add recover host - no authorization check needed (matches Ember.js
pattern)
+ submenu.push({
+ action: "confirmRecoverHost",
+ label: translate("hosts.host.details.recoverHost"),
+ icon: "clockRotateLeft",
+ onClick: () => {
+ confirmRecoverHost({
+ host: get(allHostModels, "[0]"),
+ clusterName: clusterName,
+ isKerberosEnabled: isKerberosEnabled,
+ getKDCSessionState: getKDCSessionState,
+ });
+ },
+ });
+
+ return submenu;
+ };
+
+ const hostActionsMenu = {
+ label: String(translate("hosts.host.details.hostActions")).toUpperCase(),
+ submenu: buildHostActionsSubmenu(),
+ };
+
+ return (
+ <div className="p-4">
+ {showHostCheck ? (
+ <HostChecks
+ isOpen={showHostCheck}
+ onClose={() => {
+ setShowHostCheck(false);
+ }}
+ successCallback={() => {
+ startHostCheck(getBootHostsProp());
+ }}
+ loading={isHostCheckRunning}
+ hostCheckResult={hostCheckResult}
+ />
+ ) : null}
+ <Tab.Container activeKey={activeTab}>
+ <Row className="mx-5">
+ <Col>
+ <Nav
+ variant="underline"
+ onSelect={(selectedKey) => {
+ if (selectedKey && activeTab !== selectedKey) {
+ setActiveTab(selectedKey);
+ navigate(`/main/hosts/${params.hostname}/${selectedKey}`);
+ }
+ }}
+ >
+ {Object.entries(tabs).map(([key, tab]) => (
+ <Nav.Item>
+ <Nav.Link eventKey={key} className="ambari-nav">
+ {tab.title}
+ </Nav.Link>
+ </Nav.Item>
+ ))}
+ </Nav>
+ </Col>
+ <Col className="d-flex justify-content-end">
+ {canShowHostActions && (
+ <NestedDropdown menu={hostActionsMenu} dropDirection="start" />
+ )}
+ </Col>
+ </Row>
+ <Row>
+ {loading ? (
+ <Spinner />
+ ) : (
+ <Col className="p-4">
+ {Object.entries(tabs).map(([key, tab]) => {
+ if (activeTab === key) {
+ return tab.component;
+ }
+ return null;
+ })}
+ </Col>
+ )}
+ </Row>
+ </Tab.Container>
+ </div>
+ );
+}
diff --git
a/ambari-web/latest/src/screens/Hosts/supportClientConfigsDownload.ts
b/ambari-web/latest/src/screens/Hosts/supportClientConfigsDownload.ts
new file mode 100644
index 0000000000..26871b5d74
--- /dev/null
+++ b/ambari-web/latest/src/screens/Hosts/supportClientConfigsDownload.ts
@@ -0,0 +1,73 @@
+/**
+ * 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 { get } from "lodash";
+
+export enum ResourceTypeEnum {
+ CLUSTER = "ClusterResource",
+ HOST = "HostResource",
+ SERVICE = "ServiceResource",
+ SERVICE_COMPONENT = "ServiceComponentResource",
+ HOST_COMPONENT = "HostComponentResource",
+}
+
+export const downloadClientConfigsCall = (data: any) => {
+ const url = getUrl(
+ get(data, "clusterName"),
+ get(data, "hostName", ""),
+ get(data, "serviceName", ""),
+ get(data, "componentName", ""),
+ get(data, "resourceType")
+ );
+ const newWindow = window.open(url);
+ if (newWindow) {
+ newWindow.focus();
+ }
+};
+
+const getUrl = (
+ clusterName: string,
+ hostName: string,
+ serviceName: string,
+ componentName: string,
+ resourceType: ResourceTypeEnum
+) => {
+ const apiPrefix = "/api/v1";
+ let result = `${apiPrefix}/clusters/${clusterName}/`;
+
+ switch (resourceType) {
+ case ResourceTypeEnum.SERVICE_COMPONENT:
+ result += `services/${serviceName}/components/${componentName}`;
+ break;
+ case ResourceTypeEnum.HOST_COMPONENT:
+ result += `hosts/${hostName}/host_components/${componentName}`;
+ break;
+ case ResourceTypeEnum.HOST:
+ result += `hosts/${hostName}/host_components`;
+ break;
+ case ResourceTypeEnum.SERVICE:
+ result += `services/${serviceName}/components`;
+ break;
+ case ResourceTypeEnum.CLUSTER:
+ default:
+ result += "components";
+ }
+
+ result += "?format=client_config_tar";
+ return result;
+};
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]