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 5c5b80c830 AMBARI-26549 : Ambari Web React: Add Host Wizard (#4072)
5c5b80c830 is described below

commit 5c5b80c8306267d2b535f6f07a7b103d09482350
Author: Himanshu Maurya <[email protected]>
AuthorDate: Mon Sep 15 00:17:34 2025 +0530

    AMBARI-26549 : Ambari Web React: Add Host Wizard (#4072)
---
 ambari-web/latest/src/api/VersionsApi.ts           |   8 +
 .../latest/src/screens/ClusterWizard/constants.ts  |  23 ++
 .../Hosts/AddHostWizard/AddHostConfigurations.tsx  | 196 +++++++++++
 .../screens/Hosts/AddHostWizard/Step2Wrapper.tsx   |  27 ++
 .../Hosts/AddHostWizard/addHostWizardSteps.tsx     | 119 +++++++
 .../AddHostWizard/wizardDataStore/context.tsx      | 359 +++++++++++++++++++++
 .../Hosts/AddHostWizard/wizardDataStore/reducer.ts |  44 +++
 .../Hosts/AddHostWizard/wizardDataStore/types.ts   |  32 ++
 ambari-web/latest/src/screens/Hosts/utils.tsx      |  24 +-
 9 files changed, 831 insertions(+), 1 deletion(-)

diff --git a/ambari-web/latest/src/api/VersionsApi.ts 
b/ambari-web/latest/src/api/VersionsApi.ts
index 791a145301..9a3e2ed10e 100644
--- a/ambari-web/latest/src/api/VersionsApi.ts
+++ b/ambari-web/latest/src/api/VersionsApi.ts
@@ -182,6 +182,14 @@ const VersionsApi = {
     });
     return response.data; 
   },
+  getRepoDetails: async (stack: string, version: string) => {
+    const url = 
`/stacks/${stack}/versions?fields=repository_versions/operating_systems/repositories/*,repository_versions/operating_systems/OperatingSystems/*,repository_versions/RepositoryVersions/*&repository_versions/RepositoryVersions/repository_version=${version}`;
+    const response = await ambariApi.request({
+      url: url,
+      method: "GET",
+    });
+    return response.data;
+  },
 };
 
 export default VersionsApi;
diff --git a/ambari-web/latest/src/screens/ClusterWizard/constants.ts 
b/ambari-web/latest/src/screens/ClusterWizard/constants.ts
new file mode 100644
index 0000000000..4b35585d80
--- /dev/null
+++ b/ambari-web/latest/src/screens/ClusterWizard/constants.ts
@@ -0,0 +1,23 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const excludeServicesOnDisplay: string[] = [
+  "KERBEROS",
+  "GANGLIA",
+  "MAPREDUCE2",
+];
\ No newline at end of file
diff --git 
a/ambari-web/latest/src/screens/Hosts/AddHostWizard/AddHostConfigurations.tsx 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/AddHostConfigurations.tsx
new file mode 100644
index 0000000000..1b6f3fe8bc
--- /dev/null
+++ 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/AddHostConfigurations.tsx
@@ -0,0 +1,196 @@
+/**
+ * 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, useEffect, useState } from "react";
+import { AppContext } from "../../../store/context";
+import ConfigGroupApi from "../../../api/configGroupApi";
+import { cloneDeep, forEach, get, isEmpty, set } from "lodash";
+import Table from "../../../components/Table";
+import { Form } from "react-bootstrap";
+import WizardFooter from "../../../components/StepWizard/WizardFooter";
+import { ContextWrapper } from "../../ClusterWizard";
+import { ActionTypes } from "./wizardDataStore/types";
+import { translate } from "../../../Utils/Utility";
+
+export default function AddHostConfigurations() {
+  const { clusterName, services } = useContext(AppContext);
+  const { Context } = useContext(ContextWrapper);
+  const {
+    dispatch,
+    state,
+    flushStateToDb,
+    stepWizardUtilities: {
+      currentStep,
+      handleNextImperitive,
+      jumpToStep,
+      prevStepNumber,
+    },
+  }: any = useContext(Context);
+
+  const initialConfigs = get(
+    state,
+    `addHostSteps.CONFIGURATIONS.data.configurations`,
+    []
+  );
+
+  const [configGroups, setConfigGroups] = useState({});
+  const [formData, setFormData] = useState(initialConfigs);
+
+  useEffect(() => {
+    getConfigGroups();
+  }, []);
+
+  useEffect(() => {
+    if (!isEmpty(configGroups)) {
+      let data: any = [];
+      forEach(services, (service: any) => {
+        const serviceName = get(service, "ServiceInfo.service_name");
+        data.push({
+          serviceName: serviceName,
+          configGroups: get(configGroups, "items", [])
+            .filter(
+              (configGroup: any) =>
+                serviceName === get(configGroup, "ConfigGroup.tag")
+            )
+            .map((configGroup: any) => {
+              return {
+                ...get(configGroup, "ConfigGroup"),
+                isSelected: false,
+              };
+            }),
+        });
+      });
+      forEach(data, (service) => {
+        service?.configGroups.unshift({
+          cluster_name: clusterName,
+          description: "",
+          desired_configs: [],
+          group_name: "Default",
+          hosts: [],
+          tag: service?.serviceName,
+          isSelected: true,
+        });
+      });
+      setFormData(data);
+    }
+  }, [configGroups]);
+
+  const getConfigGroups = async () => {
+    const response = await ConfigGroupApi.getConfigGroups(clusterName, "*");
+    setConfigGroups(response);
+  };
+
+  const getSelectedConfigGroupName = (configGroups: any[]) => {
+    return configGroups.filter((cg: any) => cg.isSelected)?.[0]?.group_name;
+  };
+
+  const setSelectedConfigGroup = (
+    configGroupName: string,
+    serviceName: string
+  ) => {
+    const formDataCopy = cloneDeep(formData);
+    formDataCopy.forEach((service: any) => {
+      if (get(service, "serviceName") === serviceName) {
+        get(service, "configGroups", []).forEach((cg: any) => {
+          if (get(cg, "group_name") === configGroupName) {
+            set(cg, "isSelected", true);
+          } else {
+            set(cg, "isSelected", false);
+          }
+        });
+      }
+    });
+    setFormData(formDataCopy);
+  };
+
+  const columnInTable = [
+    {
+      header: translate("common.service"),
+      id: "service",
+      width: "40%",
+      cell: (info: any) => {
+        return get(info, "row.original.serviceName");
+      },
+    },
+    {
+      header: translate("common.conf.group"),
+      id: "configGroup",
+      width: "60%",
+      cell: (info: any) => {
+        const serviceName = get(info, "row.original.serviceName");
+        const configGroups = get(info, "row.original.configGroups");
+        return (
+          <Form.Select
+            className="custom-form-control fs-12 w-50"
+            value={getSelectedConfigGroupName(configGroups)}
+            onChange={(e) => {
+              setSelectedConfigGroup(e.target.value, serviceName);
+            }}
+          >
+            {configGroups.map((configGroup: any) => {
+              return (
+                <option
+                  key={get(configGroup, "group_name")}
+                  value={get(configGroup, "group_name")}
+                >
+                  {get(configGroup, "group_name")}
+                </option>
+              );
+            })}
+          </Form.Select>
+        );
+      },
+    },
+  ];
+
+  const moveToNextStep = () => {
+    dispatch({
+      type: ActionTypes.STORE_INFORMATION,
+      payload: {
+        step: currentStep.name,
+        data: { configurations: formData },
+      },
+    });
+    flushStateToDb("next");
+    handleNextImperitive();
+  };
+
+  return (
+    <div>
+      <h2 className="step-title">{translate("addHost.step4.header")}</h2>
+      <p className="make-all-grey step-description">
+        {translate("addHost.step4.title")}
+      </p>
+      <Table data={formData} columns={columnInTable} />
+      <WizardFooter
+        isNextEnabled={true}
+        step={currentStep}
+        onNext={() => {
+          moveToNextStep();
+        }}
+        onCancel={() => {
+          flushStateToDb("cancel");
+        }}
+        onBack={() => {
+          flushStateToDb("back");
+          jumpToStep(prevStepNumber);
+        }}
+      />
+    </div>
+  );
+}
diff --git a/ambari-web/latest/src/screens/Hosts/AddHostWizard/Step2Wrapper.tsx 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/Step2Wrapper.tsx
new file mode 100644
index 0000000000..81155a57fd
--- /dev/null
+++ b/ambari-web/latest/src/screens/Hosts/AddHostWizard/Step2Wrapper.tsx
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useContext } from "react";
+import Step2 from "../../ClusterWizard/Step2";
+import { AddHostContext } from "./wizardDataStore/context";
+
+export default function Step2Wrapper() {
+  const { installedHosts } = useContext(AddHostContext);
+
+  return <Step2 wizardName={"addHost"} installedHosts={installedHosts} />;
+}
diff --git 
a/ambari-web/latest/src/screens/Hosts/AddHostWizard/addHostWizardSteps.tsx 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/addHostWizardSteps.tsx
new file mode 100644
index 0000000000..2ac4789f79
--- /dev/null
+++ b/ambari-web/latest/src/screens/Hosts/AddHostWizard/addHostWizardSteps.tsx
@@ -0,0 +1,119 @@
+/**
+ * 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 Step10 from "../../ClusterWizard/Step10";
+// import Step3 from "../../ClusterWizard/Step3";
+// import Step8 from "../../ClusterWizard/Step8";
+// import Step9 from "../../ClusterWizard/Step9";
+import AddHostConfigurations from "./AddHostConfigurations";
+import Step2Wrapper from "./Step2Wrapper";
+// import Step6 from "../../ClusterWizard/Step6";
+import { translate } from "../../../Utils/Utility";
+
+function ComponentInProgress() {
+  return <h1>Component In Progress</h1>;
+}
+
+export default {
+  1: {
+    label: translate("installer.step2.header"),
+    completed: false,
+    Component: <Step2Wrapper />,
+    canGoBack: false,
+    isNextEnabled: false,
+    nextLabel: String(
+      translate("installer.step2.registerAndConfirm")
+    ).toUpperCase(),
+    name: "HOSTS",
+    keysToRemove: [
+      "HOST_STATUS",
+      "SLAVES_AND_CLIENTS",
+      "CONFIGURATIONS",
+      "REVIEW",
+      "INSTALL_START_TEST",
+    ],
+  },
+  2: {
+    label: translate("installer.step3.header"),
+    completed: false,
+    Component: <ComponentInProgress />,
+    // Component: <Step3 wizardName="addHost" />,
+    canGoBack: true,
+    isNextEnabled: true,
+    onNext: () => {
+      return new Promise((resolve) => resolve("Something"));
+    },
+    name: "HOST_STATUS",
+    keysToRemove: [
+      "SLAVES_AND_CLIENTS",
+      "CONFIGURATIONS",
+      "REVIEW",
+      "INSTALL_START_TEST",
+    ],
+  },
+  3: {
+    label: translate("installer.step6.header"),
+    completed: false,
+    Component: <ComponentInProgress />,
+    // Component: <Step6 wizardName="addHost" />,
+    canGoBack: true,
+    isNextEnabled: false,
+    name: "SLAVES_AND_CLIENTS",
+    keysToRemove: ["CONFIGURATIONS", "REVIEW", "INSTALL_START_TEST"],
+  },
+  4: {
+    label: translate("addHost.step4.header"),
+    completed: false,
+    Component: <AddHostConfigurations />,
+    canGoBack: true,
+    isNextEnabled: false,
+    nextLabel: String(translate("common.next")).toUpperCase(),
+    name: "CONFIGURATIONS",
+    keysToRemove: ["REVIEW", "INSTALL_START_TEST"],
+  },
+  5: {
+    label: translate("common.review"),
+    completed: false,
+    Component: <ComponentInProgress />,
+    // Component: <Step8 wizardName="addHost" />,
+    canGoBack: true,
+    nextLabel: String(translate("common.deploy")).toUpperCase(),
+    isNextEnabled: false,
+    name: "REVIEW",
+    keysToRemove: ["INSTALL_START_TEST"],
+  },
+  6: {
+    label: translate("installer.step9.header"),
+    completed: false,
+    Component: <ComponentInProgress />,
+    // Component: <Step9 wizardName="addHost" />,
+    canGoBack: false,
+    isNextEnabled: false,
+    name: "INSTALL_START_TEST",
+    keyToRemove: [],
+  },
+  7: {
+    label: translate("installer.step10.header"),
+    completed: false,
+    Component: <ComponentInProgress />,
+    // Component: <Step10 wizardName="addHost" />,
+    canGoBack: false,
+    isNextEnabled: false,
+    keyToRemove: [],
+  },
+};
diff --git 
a/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/context.tsx 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/context.tsx
new file mode 100644
index 0000000000..a61e784f61
--- /dev/null
+++ 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/context.tsx
@@ -0,0 +1,359 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, {
+  createContext,
+  Dispatch,
+  useContext,
+  useEffect,
+  useReducer,
+  useRef,
+  useState,
+} from "react";
+import { State, Action, ActionTypes } from "./types";
+import { reducer, initialState } from "./reducer";
+import ClusterApi from "../../../../api/clusterApi";
+import { AppContext } from "../../../../store/context";
+import { forEach, get, isEmpty } from "lodash";
+import { excludeServicesOnDisplay } from "../../../ClusterWizard/constants";
+import VersionsApi from "../../../../api/VersionsApi";
+import { HostsApi } from "../../../../api/hostsApi";
+import { getAllComponents } from "../../utils";
+import { ClusterProgressStatus } from "../../../../constants";
+
+interface AddHostContextProps {
+  state: State;
+  dispatch: Dispatch<Action>;
+  stepWizardUtilities?: any;
+  flushStateToDb?: any;
+  installedHosts: string[];
+}
+
+export const AddHostContext = createContext<AddHostContextProps>({
+  state: initialState,
+  dispatch: () => undefined,
+  flushStateToDb: () => undefined,
+  installedHosts: [],
+});
+
+export const AddHostProvider: React.FC<{
+  stepWizardUtilities: any;
+  children: React.ReactNode;
+}> = ({ stepWizardUtilities, children }) => {
+  const [state, dispatch] = useReducer(reducer, initialState);
+  const [currStepData, setCurrStepData] = useState({});
+  const [installedHosts, setInstalledHosts] = useState([]);
+  const { clusterName, services, serviceComponentInfo } =
+    useContext(AppContext);
+
+  const isDataPersisted = useRef(false);
+
+  const getInstalledServices = () => {
+    const serviceNames = services.map((service: any) =>
+      get(service, "ServiceInfo.service_name", "")
+    );
+    let installedServicesData: any = {};
+    forEach(serviceComponentInfo.items, (service: any) => {
+      if (serviceNames.includes(get(service, "StackServices.service_name"))) {
+        installedServicesData[service.StackServices.service_name] = {
+          displayName: service.StackServices.display_name,
+          serviceName: service.StackServices.service_name,
+          serviceType: service.StackServices.service_type,
+          version: service.StackServices.service_version,
+          comments: service.StackServices.comments,
+          selected: true,
+          required: service.StackServices.required_services,
+          isIgnored: false,
+          isHiddenOnDisplay: excludeServicesOnDisplay.includes(
+            service.StackServices.service_name
+          ),
+        };
+      }
+    });
+    dispatch({
+      type: ActionTypes.STORE_INFORMATION,
+      payload: {
+        step: "SERVICES",
+        data: { services: installedServicesData },
+      },
+    });
+  };
+
+  const setClusterName = () => {
+    dispatch({
+      type: ActionTypes.STORE_INFORMATION,
+      payload: {
+        step: "NAME",
+        data: { clusterName },
+      },
+    });
+  };
+
+  const setStackAndVersion = async () => {
+    const response = await VersionsApi.getServices(clusterName);
+    const repoVersionId = get(
+      response,
+      "items.[0].ClusterStackVersions.repository_version",
+      ""
+    );
+    const repo = get(response, "items.[0].repository_versions", []).find(
+      (version: any) => get(version, "RepositoryVersions.id") === repoVersionId
+    )?.RepositoryVersions;
+    const repoVersion = get(repo, "repository_version", "");
+    const repoDisplayName = get(repo, "display_name", "");
+    const stackName = get(response, "items.[0].ClusterStackVersions.stack", 
"");
+    const stackVersion = get(
+      response,
+      "items.[0].ClusterStackVersions.version",
+      ""
+    );
+    const repoData = await VersionsApi.getRepoDetails(stackName, repoVersion);
+    const os = get(
+      repoData,
+      "items.[0].repository_versions.[0].operating_systems.[0]",
+      {}
+    );
+    const repos = get(os, "repositories", []).map((repo: any) => {
+      return {
+        id: get(repo, "Repositories.repo_id"),
+        defaultId: get(repo, "Repositories.repo_id"),
+        baseUrl: get(repo, "Repositories.base_url"),
+        name: get(repo, "Repositories.repo_name"),
+        defaultUrl: get(repo, "Repositories.default_base_url"),
+      };
+    });
+    const data = {
+      selectedVersion: {
+        id: repoDisplayName,
+        stack_name: stackName,
+        stack_version: stackVersion,
+      },
+      selectedStack: {
+        id: repoDisplayName,
+        stack_name: stackName,
+        stack_version: stackVersion,
+      },
+      operatingSystems: {
+        [repoDisplayName]: [
+          {
+            os: get(os, "OperatingSystems.os_type", ""),
+            isAdded: true,
+            repos: repos,
+          },
+        ],
+      },
+    };
+    dispatch({
+      type: ActionTypes.STORE_INFORMATION,
+      payload: {
+        step: "VERSION",
+        data: data,
+      },
+    });
+  };
+
+  useEffect(() => {
+    syncUserPersistedData();
+    getHostComponents();
+  }, []);
+
+  useEffect(() => {
+    if (isDataPersisted.current) {
+      flushCurrentData();
+    }
+  }, [state.addHostSteps, currStepData]);
+
+  useEffect(() => {
+    if (!isEmpty(services) && !isEmpty(serviceComponentInfo)) {
+      getInstalledServices();
+    }
+  }, [services, serviceComponentInfo]);
+
+  const getHostComponents = async () => {
+    const response = await HostsApi.getHostComponentsDetails(
+      clusterName,
+      "fields=host_components/HostRoles/state&minimal_response=true"
+    );
+    const hostsList = get(response, "items", []).map((item: any) =>
+      get(item, "Hosts.host_name")
+    );
+    setInstalledHosts(hostsList);
+    let mastersData: any[] = [];
+    const allComponents = getAllComponents(serviceComponentInfo).filter(
+      (c) => get(c, "HostRoles.is_master") === true
+    );
+    forEach(response.items, (host: any, idx: number) => {
+      let masterServicesData: any[] = [];
+      forEach(get(host, "host_components", []), (component) => {
+        const componentName = get(component, "HostRoles.component_name");
+        const componentData = allComponents.find(
+          (c: any) =>
+            get(c, "HostRoles.component_name") ===
+            get(component, "HostRoles.component_name")
+        );
+        if (!isEmpty(componentData)) {
+          masterServicesData.push({
+            display_name: get(componentData, "HostRoles.display_name"),
+            component: componentName,
+            serviceId: get(componentData, "HostRoles.service_name"),
+            isInstalled: true,
+            host_id: idx + 1,
+            hostName: get(host, "Hosts.host_name"),
+          });
+        }
+      });
+      const data = {
+        host_name: get(host, "Hosts.host_name"),
+        masterServices: masterServicesData,
+      };
+      mastersData.push(data);
+    });
+    dispatch({
+      type: ActionTypes.STORE_INFORMATION,
+      payload: {
+        step: "MASTERS",
+        data: { mastersData },
+      },
+    });
+  };
+
+  async function syncUserPersistedData() {
+    try {
+      const persistedData = await ClusterApi.getPersistData("ADD_HOST");
+      if (!isEmpty(get(persistedData, "addHostSteps", {}))) {
+        dispatch({
+          type: ActionTypes.SYNC_STATE,
+          payload: persistedData,
+        });
+      }
+      if (get(persistedData, "activeStep", "")) {
+        try {
+          const activeStepName = get(persistedData, "activeStep");
+          setCurrStepData({
+            progressStatus: ClusterProgressStatus.ADDING_HOST,
+            stepName: activeStepName,
+          });
+          let activeStepNumber = Object.keys(
+            stepWizardUtilities.wizardSteps
+          ).find((stepName) => {
+            return (
+              stepWizardUtilities.wizardSteps?.[stepName]?.name ===
+              activeStepName
+            );
+          });
+          stepWizardUtilities.jumpToStep(Number(activeStepNumber), true);
+        } catch (err) {
+          console.error("Error while jumping to step", err);
+        }
+      } else {
+        getInstalledServices();
+        setClusterName();
+        setStackAndVersion();
+        stepWizardUtilities.jumpToStep(1, true);
+      }
+    } finally {
+      isDataPersisted.current = true;
+    }
+  }
+
+  async function flushCurrentData() {
+    await ClusterApi.postPersistData(
+      JSON.stringify({
+        ADD_HOST: JSON.stringify({
+          ...state,
+          activeStep: get(currStepData, "stepName", ""),
+        }),
+        CLUSTER_STATE: JSON.stringify(currStepData),
+      })
+    );
+  }
+
+  function flushOnCancel() {
+    ClusterApi.postPersistData(
+      JSON.stringify({
+        ADD_HOST: JSON.stringify(initialState),
+        CLUSTER_STATE: JSON.stringify({}),
+      })
+    );
+    window.location.href = "/#/main/hosts";
+  }
+
+  async function flushOnStepChange(nextStep: number) {
+    if (nextStep >= 1) {
+      let nextStepDetails = stepWizardUtilities.wizardSteps?.[nextStep];
+      if (nextStepDetails?.keysToRemove) {
+        nextStepDetails.keysToRemove.forEach((key: string) => {
+          if (state?.addHostSteps?.[key]) {
+            dispatch({
+              type: ActionTypes.REMOVE_KEY,
+              payload: { key },
+            });
+          }
+        });
+      }
+      setCurrStepData({
+        progressStatus: ClusterProgressStatus.ADDING_HOST,
+        stepName: stepWizardUtilities?.wizardSteps?.[nextStep]?.name,
+      });
+    }
+  }
+
+  function flushStateToDb(
+    operation: string = "default",
+    jumpStep: number = -1
+  ) {
+    let activeStep = Object.keys(stepWizardUtilities.wizardSteps).find(
+      (stepName) => {
+        return (
+          stepWizardUtilities.wizardSteps?.[stepName]?.name ===
+          stepWizardUtilities.currentStep.name
+        );
+      }
+    );
+    switch (operation) {
+      case "cancel":
+        flushOnCancel();
+        break;
+      case "back":
+        flushOnStepChange(Number(activeStep) - 1);
+        break;
+      case "next":
+        flushOnStepChange(Number(activeStep) + 1);
+        break;
+      case "jump":
+        flushOnStepChange(jumpStep);
+        break;
+      default:
+        flushCurrentData();
+    }
+  }
+
+  return (
+    <AddHostContext.Provider
+      value={{
+        state,
+        dispatch,
+        stepWizardUtilities,
+        flushStateToDb,
+        installedHosts,
+      }}
+    >
+      {children}
+    </AddHostContext.Provider>
+  );
+};
diff --git 
a/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/reducer.ts 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/reducer.ts
new file mode 100644
index 0000000000..f5acb46222
--- /dev/null
+++ 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/reducer.ts
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { cloneDeep } from "lodash";
+import { Action, ActionTypes, State } from "./types";
+
+export const initialState: State = { addHostSteps: {} };
+
+export const reducer = (state: State, action: Action): State => {
+  switch (action.type) {
+    case ActionTypes.STORE_INFORMATION:
+      const stateCopy = cloneDeep(state);
+      if(!stateCopy.addHostSteps) {
+        stateCopy.addHostSteps = {};
+      }
+      const addHostSteps = cloneDeep(stateCopy.addHostSteps);
+      addHostSteps[action.payload.step] = action.payload;
+      stateCopy.addHostSteps = addHostSteps;
+      return stateCopy;
+    case ActionTypes.SYNC_STATE:
+      return { ...action.payload };
+    case ActionTypes.REMOVE_KEY:
+      const updatedSteps = { ...state.addHostSteps };
+      delete updatedSteps[action.payload.key];
+      return { ...state, addHostSteps: updatedSteps };
+    default:
+      return state;
+  }
+};
diff --git 
a/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/types.ts 
b/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/types.ts
new file mode 100644
index 0000000000..0588835b16
--- /dev/null
+++ b/ambari-web/latest/src/screens/Hosts/AddHostWizard/wizardDataStore/types.ts
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export interface State {
+  addHostSteps: any;
+}
+
+export enum ActionTypes {
+  STORE_INFORMATION = "STORE INFORMATION",
+  SYNC_STATE = "SYNC STATE",
+  REMOVE_KEY = "REMOVE KEY",
+}
+
+export type Action =
+  | { type: ActionTypes.STORE_INFORMATION; payload: any }
+  | { type: ActionTypes.SYNC_STATE; payload: any }
+  | { type: ActionTypes.REMOVE_KEY; payload: any };
diff --git a/ambari-web/latest/src/screens/Hosts/utils.tsx 
b/ambari-web/latest/src/screens/Hosts/utils.tsx
index 3dfca4f1bc..1ff1f4665e 100644
--- a/ambari-web/latest/src/screens/Hosts/utils.tsx
+++ b/ambari-web/latest/src/screens/Hosts/utils.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { get } from "lodash";
+import { get, isEmpty } from "lodash";
 import { ComponentType } from "./enums";
 import { translate, translateWithVariables } from "../../Utils/Utility";
 
@@ -68,3 +68,25 @@ export const validateInteger = (
   }
   return "";
 };
+
+export const getAllComponents = (serviceComponentInfo: any) => {
+  if (!isEmpty(serviceComponentInfo)) {
+    let allComponentsCopy: any[] = [];
+    get(serviceComponentInfo, "items", []).forEach((service: any) => {
+      allComponentsCopy = allComponentsCopy.concat(
+        get(service, "components", []).map((component: any) => {
+          return {
+            HostRoles: {
+              ...get(component, "StackServiceComponents"),
+              dependencies: get(component, "dependencies", []).map(
+                (d: any) => d.Dependencies.component_name
+              ),
+            },
+          };
+        })
+      );
+    });
+    return allComponentsCopy;
+  }
+  return [];
+};


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

Reply via email to