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

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


The following commit(s) were added to refs/heads/frontend-refactor by this push:
     new f73aebda0b AMBARI-26362 : Ambari Web React: Cluster Installation 
Wizard - Step3 (#4070)
f73aebda0b is described below

commit f73aebda0b7a46ce1b0e6d8d611a4130c67c1685
Author: Himanshu Maurya <[email protected]>
AuthorDate: Thu Nov 27 06:43:46 2025 +0530

    AMBARI-26362 : Ambari Web React: Cluster Installation Wizard - Step3 (#4070)
---
 ambari-web/latest/src/api/wizardApi.ts             |  70 ++
 .../latest/src/screens/ClusterWizard/Step3.tsx     | 878 +++++++++++++++++++++
 .../src/screens/ClusterWizard/wizardSteps.tsx      | 190 +++++
 3 files changed, 1138 insertions(+)

diff --git a/ambari-web/latest/src/api/wizardApi.ts 
b/ambari-web/latest/src/api/wizardApi.ts
new file mode 100644
index 0000000000..c737250aea
--- /dev/null
+++ b/ambari-web/latest/src/api/wizardApi.ts
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ambariApi } from "./config/axiosConfig";
+
+const WizardApi = {
+  isHostsRegistered: async () => {
+    const url = `/hosts?fields=Hosts/host_status`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getStackConfigurations: async (
+    stackName: string,
+    stackVersion: string,
+    services: string,
+    fields: string
+  ) => {
+    const url = 
`/stacks/${stackName}/versions/${stackVersion}/services?StackServices/service_name.in(${services})&fields=${fields}`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getStackThemes: async (
+    stackName: string,
+    stackVersion: string,
+    services: string,
+    fields: string
+  ) => {
+    const url = 
`/stacks/${stackName}/versions/${stackVersion}/services?StackServices/service_name.in(${services})&themes/ThemeInfo/default=true&fields=${fields}`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data;
+  },
+  getStackLevelConfigurations: async (
+    stackName: string,
+    stackVersion: string,
+    fields: string
+  ) => {
+    const url = 
`/stacks/${stackName}/versions/${stackVersion}?fields=${fields}`;
+    const response = await ambariApi.request({
+      url,
+      method: "GET",
+    });
+    return response.data;
+  },
+};
+
+export default WizardApi;
diff --git a/ambari-web/latest/src/screens/ClusterWizard/Step3.tsx 
b/ambari-web/latest/src/screens/ClusterWizard/Step3.tsx
new file mode 100644
index 0000000000..2b3d893115
--- /dev/null
+++ b/ambari-web/latest/src/screens/ClusterWizard/Step3.tsx
@@ -0,0 +1,878 @@
+/**
+ * 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 { faRotateRight, faTrashCan } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useContext, useEffect, useRef, useState } from "react";
+import {
+  Alert,
+  Button,
+  ButtonGroup,
+  Card,
+  Form,
+  ProgressBar,
+} from "react-bootstrap";
+import Table from "../../components/Table";
+import ClusterApi from "../../api/clusterApi";
+import { get, isEmpty, startCase } from "lodash";
+import WizardApi from "../../api/wizardApi";
+import Modal from "../../components/Modal";
+import Spinner from "../../components/Spinner";
+import Paginator from "../../components/Paginator";
+import usePagination from "../../hooks/usePagination";
+import WizardFooter from "../../components/StepWizard/WizardFooter";
+import { ActionTypes } from "./clusterStore/types";
+import wizardSteps from "./wizardSteps";
+import HostChecks, { getHostWithIssues } from "../Hosts/HostChecks";
+import { useHostChecks } from "../../hooks/useHostChecks";
+import { ContextWrapper } from ".";
+import modalManager from "../../store/ModalManager";
+import { translate, translateWithVariables } from "../../Utils/Utility";
+
+interface Host {
+  name: string;
+  bootStatus: string;
+  isChecked: boolean;
+  bootLog: string;
+}
+
+export enum BootStatus {
+  PENDING = "PENDING",
+  DONE = "DONE",
+  REGISTERING = "REGISTERING",
+  REGISTERED = "REGISTERED",
+  FAILED = "FAILED",
+  RUNNING = "RUNNING",
+}
+
+enum ShowOptions {
+  ALL = "All",
+  INSTALLING = "Installing",
+  REGISTERING = "Registering",
+  SUCCESS = "Success",
+  FAIL = "Fail",
+}
+
+type Step3Props = {
+  wizardName?: string;
+};
+
+export default function Step3({ wizardName = "clusterCreation" }: Step3Props) {
+  const [selectedShowOption, setSelectedShowOption] = 
useState(ShowOptions.ALL);
+  const [loading, setLoading] = useState(false);
+  const [hosts, setHosts] = useState<Host[]>([]);
+  const [otherRegHosts, setOtherRegHosts] = useState<string[]>([]);
+  const [hostsToBeRemoved, setHostsToBeRemoved] = useState<string[]>([]);
+  const [hostsToBeDisplayed, setHostsToBeDisplayed] = useState<Host[]>([]);
+  const [_ambariProperties, setAmbariProperties] = useState<any>({});
+  const [showRegLogModal, setShowRegLogModal] = useState(false);
+  const [showSelectedHostsModal, setShowSelectedHostsModal] = useState(false);
+  const [showOtherRegHostsModal, setShowOtherRegHostsModal] = useState(false);
+  const [showRemoveHostsModal, setShowRemoveHostsModal] = useState(false);
+  const [regLogInfo, setRegLogInfo] = useState<Host>({
+    name: "",
+    bootStatus: "",
+    isChecked: false,
+    bootLog: "",
+  });
+  const [showOptions, setShowOptions] = useState([
+    { name: ShowOptions.ALL, count: 0 },
+    { name: ShowOptions.INSTALLING, count: 0 },
+    { name: ShowOptions.REGISTERING, count: 0 },
+    { name: ShowOptions.SUCCESS, count: 0 },
+    { name: ShowOptions.FAIL, count: 0 },
+  ]);
+  const [showHostCheck, setShowHostCheck] = useState(false);
+
+  const { startHostCheck, isHostCheckRunning, hostCheckResult } = 
useHostChecks(
+    false,
+    true
+  );
+  const { Context } = useContext(ContextWrapper);
+  const {
+    state,
+    dispatch,
+    flushStateToDb,
+    stepWizardUtilities: {
+      currentStep,
+      handleNextImperitive,
+      handleBackImperitive,
+    },
+  }: any = useContext(Context);
+  const [nextEnabled, setNextEnabled] = useState(false);
+  const enableNext = () => {
+    setNextEnabled(true);
+  };
+  const disableNext = () => {
+    setNextEnabled(false);
+  };
+
+  const registrationStartedAt = useRef<any>(null);
+  //TODO: Remove this hack
+  const serviceCheckStarted = useRef(false);
+  const hostsRef = useRef<Host[]>([]);
+
+  const {
+    currentItems,
+    changePage,
+    currentPage,
+    maxPage,
+    itemsPerPage,
+    setItemsPerPage,
+  } = usePagination(hostsToBeDisplayed);
+
+  const registrationTimeoutSecs = 15;
+  const bootStatusFilterMapping = {
+    [ShowOptions.ALL]: [
+      BootStatus.PENDING,
+      BootStatus.DONE,
+      BootStatus.REGISTERING,
+      BootStatus.REGISTERED,
+      BootStatus.FAILED,
+      BootStatus.RUNNING,
+    ],
+    [ShowOptions.INSTALLING]: [BootStatus.PENDING, BootStatus.DONE],
+    [ShowOptions.REGISTERING]: [BootStatus.REGISTERING],
+    [ShowOptions.SUCCESS]: [BootStatus.REGISTERED],
+    [ShowOptions.FAIL]: [BootStatus.FAILED],
+  };
+
+  useEffect(() => {
+    getAmbariProperties();
+  }, []);
+
+  useEffect(() => {
+    if (
+      !isEmpty(get(state, `${wizardName}Steps.${wizardSteps[2].name}.data`, 
{}))
+    ) {
+      loadHosts();
+    }
+  }, [state]);
+
+  useEffect(() => {
+    hostsRef.current = hosts;
+    if (hostsRef.current.length) {
+      if (!registrationStartedAt.current) {
+        startRegistration();
+      }
+
+      if (isRegistrationInProgress()) {
+        if (
+          hostsRef.current.some(
+            (_host) => _host.bootStatus === BootStatus.RUNNING
+          ) ||
+          Date.now() - registrationStartedAt.current <
+          registrationTimeoutSecs * 1000
+        ) {
+          setTimeout(isHostsRegistered, 3000);
+        } else {
+          let updatedHosts = hostsRef.current.map((_host) => {
+            if (_host.bootStatus === BootStatus.REGISTERING) {
+              _host.bootStatus = BootStatus.FAILED;
+              _host.bootLog =
+                (_host.bootLog || "") +
+                "Registration with the server failed.\n\n";
+            }
+            return _host;
+          });
+          setHosts(updatedHosts);
+        }
+      }
+    }
+    let showOptionsTemp = [...showOptions];
+    showOptionsTemp.forEach((option) => {
+      option.count = hostsRef.current.filter((host) =>
+        bootStatusFilterMapping[option.name].includes(
+          host.bootStatus as BootStatus
+        )
+      ).length;
+    });
+    setShowOptions(showOptionsTemp);
+    setHostsToBeDisplayed(hosts);
+    applyFilters();
+  }, [hosts]);
+
+  useEffect(() => {
+    applyFilters();
+  }, [selectedShowOption]);
+
+  useEffect(() => {
+    if (!serviceCheckStarted.current) {
+      beginHostsCheck();
+    }
+  }, [JSON.stringify(hosts.map((host) => host.bootStatus))]);
+
+  const applyFilters = () => {
+    changePage(1);
+    let filteredHosts = hostsRef.current.filter((host) =>
+      bootStatusFilterMapping[selectedShowOption].includes(
+        host.bootStatus as BootStatus
+      )
+    );
+    setHostsToBeDisplayed(filteredHosts);
+  };
+
+  const isRegistrationInProgress = () => {
+    return !!hostsRef.current.find(
+      (host) =>
+        ![BootStatus.REGISTERED, BootStatus.FAILED].includes(
+          host.bootStatus as BootStatus
+        )
+    );
+  };
+
+  const beginHostsCheck = () => {
+    if (!isRegistrationInProgress() && hostsRef.current.length > 0) {
+      startHostCheckLocal(hostsRef.current);
+      serviceCheckStarted.current = true;
+    }
+  };
+
+  const startRegistration = () => {
+    if (registrationStartedAt.current === null) {
+      registrationStartedAt.current = Date.now();
+      isHostsRegistered();
+    }
+  };
+
+  const isHostsRegistered = async () => {
+    const response = await WizardApi.isHostsRegistered();
+    if (response) {
+      isHostsRegisteredSuccessCallback(response);
+    }
+  };
+
+  const isHostsRegisteredSuccessCallback = (data: any) => {
+    if (isEmpty(otherRegHosts)) {
+      const inputtedHosts = hostsRef.current.map((host) => host.name);
+      const registeredHosts = data.items.map((item: any) =>
+        get(item, "Hosts.host_name")
+      );
+      const installedHosts = get(
+        state,
+        `${wizardName}Steps.${wizardSteps[2].name}.data.installedHosts`,
+        []
+      );
+      setOtherRegHosts(
+        registeredHosts.filter(
+          (host: string) =>
+            !inputtedHosts.includes(host) && !installedHosts.includes(host)
+        )
+      );
+    }
+    let updatedHosts = hostsRef.current.map((_host) => {
+      let updatedHost = { ..._host };
+      if (updatedHost.bootStatus === BootStatus.DONE) {
+        updatedHost.bootStatus = BootStatus.REGISTERING;
+        updatedHost.bootLog =
+          (updatedHost.bootLog || "") + "Registering with the server...\n\n";
+        registrationStartedAt.current = Date.now();
+      } else if (
+        updatedHost.bootStatus === BootStatus.REGISTERING &&
+        data.items.find(
+          (item: any) =>
+            get(item, "Hosts.host_name") === updatedHost.name &&
+            get(item, "Hosts.host_status") !== "UNKNOWN"
+        )
+      ) {
+        updatedHost.bootStatus = BootStatus.REGISTERED;
+        updatedHost.bootLog =
+          (updatedHost.bootLog || "") + "Registered with the server.\n\n";
+      }
+      return updatedHost;
+    });
+
+    setHosts(updatedHosts);
+  };
+
+  const loadHosts = () => {
+    const hostInfo = get(
+      state,
+      `${wizardName}Steps.${wizardSteps[2].name}.data.targetHosts`,
+      []
+    );
+    let allHosts: any = [];
+    const bootStatus = get(
+      state,
+      `${wizardName}Steps.${wizardSteps[2].name}.data.isSshRegistration`,
+      "true"
+    )
+      ? "PENDING"
+      : "DONE";
+    hostInfo.forEach((host: string[]) => {
+      allHosts.push({
+        name: host,
+        bootStatus: bootStatus,
+        isChecked: false,
+        bootLog: "",
+      });
+    });
+    setHosts(allHosts);
+  };
+
+  const retryHostRegistration = () => {
+    let updatedHosts = hostsRef.current.map((_host) => {
+      let updatedHost = { ..._host };
+      if (updatedHost.bootStatus === BootStatus.FAILED) {
+        updatedHost.bootStatus = BootStatus.DONE;
+        updatedHost.bootLog = "Retrying ...\n\n";
+      }
+      return updatedHost;
+    });
+    setHosts(updatedHosts);
+    registrationStartedAt.current = null;
+  };
+
+  const startHostCheckLocal = (hostsList: Host[]) => {
+    if (
+      hostsList.length > 0 &&
+      !hostsList.every((host) => host.bootStatus === BootStatus.FAILED)
+    ) {
+      disableNext();
+      startHostCheck(getRegisteredHosts(hostsList));
+    }
+  };
+
+  const removeHosts = () => {
+    let tempOtherRegHosts = [...otherRegHosts];
+    const updatedHosts = hostsRef.current.filter((host) => {
+      if (hostsToBeRemoved.includes(host.name)) {
+        if (host.bootStatus === BootStatus.REGISTERED) {
+          tempOtherRegHosts.push(host.name);
+        }
+        return false;
+      }
+      return true;
+    });
+    setOtherRegHosts(tempOtherRegHosts);
+    setHosts(updatedHosts);
+    setHostsToBeRemoved([]);
+  };
+
+  const getAmbariProperties = async () => {
+    setLoading(true);
+    const response = await ClusterApi.loadAmbariProperties();
+    setAmbariProperties(response);
+    setLoading(false);
+  };
+
+  const handleClearSelection = () => {
+    const updatedHosts = hostsRef.current.map((host) => {
+      return {
+        ...host,
+        isChecked: false,
+      };
+    });
+    setHosts(updatedHosts);
+  };
+
+  const isAllSelected = () => {
+    if (!hostsToBeDisplayed.length) return false;
+    return hostsToBeDisplayed.every((host) => host.isChecked);
+  };
+
+  const handleSelectAll = () => {
+    const newSelectValue = !isAllSelected();
+    const updatedHosts = hostsRef.current.map((host) => {
+      if (hostsToBeDisplayed.includes(host)) {
+        return {
+          ...host,
+          isChecked: newSelectValue,
+        };
+      }
+      return host;
+    });
+    setHosts(updatedHosts);
+  };
+
+  const handleSelect = (host: Host) => {
+    const updatedHosts = hostsRef.current.map((_host) => {
+      if (_host.name === host.name) {
+        return {
+          ..._host,
+          isChecked: !_host.isChecked,
+        };
+      }
+      return _host;
+    });
+    setHosts(updatedHosts);
+  };
+
+  const isAnyFailed = () => {
+    return (
+      hostsRef.current.find((host) => host.bootStatus === BootStatus.FAILED) 
!==
+      undefined
+    );
+  };
+
+  const getNumberOfSelectedHosts = () => {
+    return hostsRef.current.filter((host) => host.isChecked).length;
+  };
+
+  const getModalProps = () => {
+    if (showRemoveHostsModal) {
+      return {
+        modalTitle: "Confirmation",
+        modalBody: "Are you sure you want to remove the selected host(s)?",
+        onClose: () => setShowRemoveHostsModal(false),
+        isOpen: showRemoveHostsModal,
+        successCallback: () => {
+          removeHosts();
+          setShowRemoveHostsModal(false);
+        },
+        options: {
+          cancelableViaBtn: true,
+          cancelableViaIcon: true,
+        },
+      };
+    } else if (showSelectedHostsModal) {
+      return {
+        modalTitle: "Selected Hosts",
+        modalBody: hosts
+          .filter((host) => host.isChecked)
+          .map((host) => host.name)
+          .join("\n\n"),
+        onClose: () => setShowSelectedHostsModal(false),
+        isOpen: showSelectedHostsModal,
+        successCallback: () => setShowSelectedHostsModal(false),
+        options: {
+          cancelableViaBtn: false,
+          cancelableViaIcon: true,
+        },
+      };
+    } else if (showOtherRegHostsModal) {
+      return {
+        modalTitle: `${otherRegHosts.length} Other Registered Hosts`,
+        modalBody:
+          `These are the hosts that have registered with the server, but do 
not appear in the list of hosts that you are adding.\n\n` +
+          otherRegHosts.join("\n\n"),
+        onClose: () => setShowOtherRegHostsModal(false),
+        isOpen: showOtherRegHostsModal,
+        successCallback: () => setShowOtherRegHostsModal(false),
+        options: {
+          cancelableViaBtn: false,
+          cancelableViaIcon: true,
+        },
+      };
+    } else {
+      return {
+        modalTitle: `Registration log for ${regLogInfo?.name}`,
+        modalBody: regLogInfo?.bootLog,
+        onClose: () => setShowRegLogModal(false),
+        isOpen: showRegLogModal,
+        successCallback: () => setShowRegLogModal(false),
+        options: {
+          cancelableViaBtn: false,
+          cancelableViaIcon: true,
+        },
+      };
+    }
+  };
+
+  const columnsInHostsList = [
+    {
+      header: (
+        <Form.Check
+          type="checkbox"
+          id="select-all-hosts-step3"
+          checked={isAllSelected()}
+          onChange={handleSelectAll}
+          className="custom-checkbox"
+        />
+      ),
+      id: "selectAll",
+      width: "1%",
+      cell: (info: any) => {
+        const hostName = get(info, "row.original.name");
+        const checkboxId = `host-step3-checkbox-${hostName}`;
+        return (
+          <Form.Check
+            type="checkbox"
+            id={checkboxId}
+            checked={get(info, "row.original.isChecked")}
+            onChange={() => handleSelect(get(info, "row.original"))}
+            className="custom-checkbox"
+          />
+        );
+      },
+    },
+    {
+      header: "Host",
+      accessorKey: "name",
+      id: "host",
+    },
+    {
+      header: "Progress",
+      width: "15%",
+      cell: (info: any) => {
+        let currentStatus = get(info, "row.original.bootStatus", "");
+        if (currentStatus === BootStatus.REGISTERED) {
+          return <ProgressBar variant="success" now={100} />;
+        } else if (currentStatus === BootStatus.FAILED) {
+          return <ProgressBar variant="danger" now={100} />;
+        }
+        return <ProgressBar striped variant="info" animated now={100} />;
+      },
+    },
+    {
+      header: "Status",
+      cell: (info: any) => {
+        let currentStatus = get(info, "row.original.bootStatus", "");
+        let statusText =
+          currentStatus === BootStatus.REGISTERED
+            ? "Success"
+            : startCase(currentStatus.toLowerCase());
+        return (
+          <span
+            onClick={() => {
+              setRegLogInfo(info.row.original);
+              setShowRegLogModal(true);
+            }}
+            className={
+              currentStatus === BootStatus.REGISTERED
+                ? "text-success make-link"
+                : currentStatus === BootStatus.FAILED
+                  ? "text-danger make-link"
+                  : "text-info make-link"
+            }
+          >
+            {statusText}
+          </span>
+        );
+      },
+    },
+    {
+      header: "Action",
+      cell: (info: any) => {
+        return (
+          <div>
+            <Button
+              className={
+                isRegistrationInProgress() || isHostCheckRunning
+                  ? "btn-wrapping-icon text-muted disabled-btn"
+                  : "btn-wrapping-icon text-muted"
+              }
+              onClick={() => {
+                if (!isRegistrationInProgress()) {
+                  setHostsToBeRemoved([get(info, "row.original.name")]);
+                  setShowRemoveHostsModal(true);
+                }
+              }}
+              disabled={isRegistrationInProgress() || isHostCheckRunning}
+            >
+              <FontAwesomeIcon icon={faTrashCan} />
+            </Button>
+          </div>
+        );
+      },
+      width: "5%",
+    },
+  ];
+
+  const getUnregisteredHosts = (hostsList: Host[]) => {
+    return hostsList.length - getRegisteredHosts(hostsList).length;
+  };
+
+  const getRegisteredHosts = (hostsList: Host[]) => {
+    const hostNames = hostsList
+      .filter((host: Host) => {
+        return host.bootStatus === BootStatus.REGISTERED;
+      })
+      .map((host: Host) => {
+        return {
+          name: host.name,
+        };
+      });
+    return hostNames;
+  };
+
+  const getHostCheckResultAlert = () => {
+    if (!serviceCheckStarted.current) {
+      return null;
+    }
+
+    if (isHostCheckRunning) {
+      return (
+        <Alert variant="info" className="mt-2">
+          Please wait while the hosts are being checked for potential
+          problems...
+          <Spinner />
+        </Alert>
+      );
+    }
+    const hostWithIssues = getHostWithIssues(hostCheckResult).length;
+    const registeredHosts = getRegisteredHosts(hostsRef.current).length;
+    const unregisteredHosts = getUnregisteredHosts(hostsRef.current);
+
+    if (!registeredHosts) {
+      return (
+        <Alert variant="warning" className="mt-2">
+          {translateWithVariables("installer.step3.warnings.allFailed", {
+            "0": String(unregisteredHosts),
+          })}
+        </Alert>
+      );
+    }
+
+    if (!nextEnabled) {
+      enableNext();
+    }
+
+    if (hostWithIssues) {
+      return (
+        <Alert variant="warning" className="mt-2">
+          <div>
+            {translateWithVariables("installer.step3.warnings.fails", {
+              "0": String(registeredHosts),
+            })}
+            <span
+              onClick={() => {
+                setShowHostCheck(true);
+              }}
+              className="ps-2 custom-link"
+            >
+              {translate("installer.step3.warnings.linkText")}
+            </span>
+          </div>
+        </Alert>
+      );
+    } else if (unregisteredHosts) {
+      return (
+        <Alert variant="warning" className="mt-2">
+          <div>
+            {translateWithVariables("installer.step3.warnings.someWarnings", {
+              "0": String(registeredHosts),
+              "1": String(unregisteredHosts),
+            })}
+            <span
+              onClick={() => {
+                setShowHostCheck(true);
+              }}
+              className="ps-2 custom-link"
+            >
+              {translate("installer.step3.warnings.linkText")}
+            </span>
+          </div>
+        </Alert>
+      );
+    } else {
+      return (
+        <Alert variant="success" className="mt-2">
+          {translateWithVariables("installer.step3.warnings.noWarnings", {
+            "0": String(registeredHosts),
+          })}
+          <span
+            onClick={() => {
+              setShowHostCheck(true);
+            }}
+            className="ps-2 custom-link"
+          >
+            {translate("installer.step3.noWarnings.linkText")}
+          </span>
+        </Alert>
+      );
+    }
+  };
+
+  const moveToNextStep = () => {
+    dispatch({
+      type: ActionTypes.STORE_INFORMATION,
+      payload: {
+        step: currentStep.name,
+        data: { hosts, otherRegHosts, hostsStatus: hostsRef.current },
+      },
+    });
+    flushStateToDb("next");
+    handleNextImperitive();
+  };
+
+  if (loading) {
+    return <Spinner />;
+  }
+
+  return (
+    <>
+      <div>
+        {showHostCheck ? (
+          <HostChecks
+            isOpen={showHostCheck}
+            onClose={() => {
+              setShowHostCheck(false);
+            }}
+            successCallback={() => {
+              startHostCheck(getRegisteredHosts(hostsRef.current));
+            }}
+            loading={isHostCheckRunning}
+            hostCheckResult={hostCheckResult}
+          />
+        ) : null}
+        {showRemoveHostsModal ||
+          showOtherRegHostsModal ||
+          showRegLogModal ||
+          showSelectedHostsModal ? (
+          <Modal {...getModalProps()} />
+        ) : null}
+        <div>
+          <h2 className="step-title">Confirm Hosts</h2>
+          <p className="text-muted mb-1 step-description">
+            Registering your hosts.
+          </p>
+          <p className="text-muted step-description ">
+            Please confirm the host list and remove any hosts that you do not
+            want to include in the cluster.
+          </p>
+          <Card className="p-4">
+            <Card>
+              <Card.Title className="d-flex justify-content-between 
align-items-center p-3">
+                <div>
+                  {getNumberOfSelectedHosts() > 0 ? (
+                    <Button
+                      className="text-white me-2"
+                      onClick={() => {
+                        setHostsToBeRemoved(
+                          hosts
+                            .filter((host) => host.isChecked)
+                            .map((host) => host.name)
+                        );
+                        setShowRemoveHostsModal(true);
+                      }}
+                    >
+                      <FontAwesomeIcon icon={faTrashCan} className="me-2" />
+                      REMOVE SELECTED
+                    </Button>
+                  ) : null}
+                  {isAnyFailed() ? (
+                    <Button
+                      variant="primary"
+                      className="text-white"
+                      onClick={() => retryHostRegistration()}
+                    >
+                      <FontAwesomeIcon icon={faRotateRight} className="me-2" />
+                      RETRY FAILED
+                    </Button>
+                  ) : null}
+                </div>
+                <div>
+                  <span className="me-2">Show:</span>
+                  <ButtonGroup>
+                    {showOptions.map((radio) => (
+                      <div key={radio.name}>
+                        <span className="me-1">|</span>
+                        <Form.Label
+                          className={`me-1 p-2 rounded-2 border-0 
custom-radio-label ${selectedShowOption === radio.name ? "active" : ""
+                            }`}
+                          onClick={() =>
+                            setSelectedShowOption(radio.name as ShowOptions)
+                          }
+                        >
+                          {radio.name}({radio.count})
+                        </Form.Label>
+                      </div>
+                    ))}
+                  </ButtonGroup>
+                </div>
+              </Card.Title>
+              <Card.Body>
+                <div className="scrollable-table">
+                  <Table
+                    data={currentItems}
+                    columns={columnsInHostsList as any}
+                  />
+                  {!hostsToBeDisplayed.length ? (
+                    <p>No hosts to display</p>
+                  ) : null}
+                </div>
+                <Paginator
+                  currentPage={currentPage}
+                  maxPage={maxPage}
+                  changePage={changePage}
+                  itemsPerPage={itemsPerPage}
+                  setItemsPerPage={setItemsPerPage}
+                  totalItems={hostsToBeDisplayed.length}
+                />
+                {getNumberOfSelectedHosts() > 0 ? (
+                  <div className="p-2 mt-2">
+                    <span
+                      className="custom-link me-2"
+                      onClick={() => setShowSelectedHostsModal(true)}
+                    >
+                      {getNumberOfSelectedHosts()} host selected
+                    </span>
+                    <span className="me-2">|</span>
+                    <span
+                      className="custom-link"
+                      onClick={() => handleClearSelection()}
+                    >
+                      clear selection
+                    </span>
+                  </div>
+                ) : null}
+                {!isEmpty(otherRegHosts) ? (
+                  <Alert
+                    variant="warning"
+                    className="custom-link mt-2"
+                    onClick={() => setShowOtherRegHostsModal(true)}
+                  >{`${otherRegHosts.length} Other Registered Hosts`}</Alert>
+                ) : null}
+                {getHostCheckResultAlert()}
+              </Card.Body>
+            </Card>
+          </Card>
+        </div>
+      </div>
+      <WizardFooter
+        step={currentStep}
+        isNextEnabled={nextEnabled}
+        onNext={() => {
+          if (getHostWithIssues(hostCheckResult).length) {
+            const modalProps = {
+              modalTitle: translate(
+                "installer.step3.hostWarningsPopup.hostHasWarnings.header"
+              ) as string,
+              modalBody: translate(
+                "installer.step3.hostWarningsPopup.hostHasWarnings"
+              ) as string,
+              onClose: () => { },
+              successCallback: () => {
+                moveToNextStep();
+                modalManager.hide();
+              },
+              options: {
+                buttonSize: "sm" as "sm" | "lg" | undefined,
+                cancelableViaIcon: true,
+                cancelableViaBtn: true,
+                okButtonVariant: "warning",
+              },
+            };
+            modalManager.show(modalProps);
+          } else {
+            moveToNextStep();
+          }
+        }}
+        onCancel={() => {
+          flushStateToDb("cancel");
+        }}
+        isBackEnabled={serviceCheckStarted.current && !isHostCheckRunning}
+        onBack={() => {
+          flushStateToDb("back")
+          handleBackImperitive();
+        }}
+      />
+    </>
+  );
+}
diff --git a/ambari-web/latest/src/screens/ClusterWizard/wizardSteps.tsx 
b/ambari-web/latest/src/screens/ClusterWizard/wizardSteps.tsx
new file mode 100644
index 0000000000..6611860284
--- /dev/null
+++ b/ambari-web/latest/src/screens/ClusterWizard/wizardSteps.tsx
@@ -0,0 +1,190 @@
+/**
+ * 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 Step0 from "./Step0";
+// import Step1 from "./Step1";
+import Step2 from "./Step2";
+import Step3 from "./Step3";
+// import Step4 from "./Step4";
+// import Step5 from "./Step5";
+// import Step6 from "./Step6";
+// import Step7 from "./Step7";
+// import Step8 from "./Step8"
+// import Step9 from "./Step9";
+// import Step10 from "./Step10";
+
+function ComponentInProgress() {
+  return <h1>Component In Progress</h1>;
+}
+
+export default {
+  0: {
+    label: "Get Started",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: false,
+    isNextEnabled: false,
+    name: "NAME",
+    keysToRemove: [
+      "VERSION",
+      "HOSTS",
+      "HOST_STATUS",
+      "SERVICES",
+      "MASTERS",
+      "SLAVES_AND_CLIENTS",
+      "CONFIGURATION",
+      "REVIEW",
+      "INSTALL_START_TEST",
+    ],
+  },
+  1: {
+    label: "Select Version",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: true,
+    isNextEnabled: false,
+    name: "VERSION",
+    keysToRemove: [
+      "HOSTS",
+      "HOST_STATUS",
+      "SERVICES",
+      "MASTERS",
+      "SLAVES_AND_CLIENTS",
+      "CONFIGURATION",
+      "REVIEW",
+      "INSTALL_START_TEST",
+    ],
+  },
+  2: {
+    label: "Install Options",
+    completed: false,
+    Component: <Step2 />,
+    canGoBack: true,
+    isNextEnabled: false,
+    nextLabel: "REGISTER AND CONFIRM",
+    name: "HOSTS",
+    keysToRemove: [
+      "HOST_STATUS",
+      "SERVICES",
+      "MASTERS",
+      "SLAVES_AND_CLIENTS",
+      "CONFIGURATION",
+      "REVIEW",
+      "INSTALL_START_TEST",
+    ],
+  },
+  3: {
+    label: "Confirm Hosts",
+    completed: false,
+    Component: <Step3 />,
+    canGoBack: true,
+    isNextEnabled: true,
+    onNext: () => {
+      return new Promise((resolve) => resolve("Something"));
+    },
+    name: "HOST_STATUS",
+    keysToRemove: [
+      "SERVICES",
+      "MASTERS",
+      "SLAVES_AND_CLIENTS",
+      "CONFIGURATION",
+      "REVIEW",
+      "INSTALL_START_TEST",
+    ],
+  },
+  4: {
+    label: "Choose Services",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: true,
+    isNextEnabled: true,
+    onNext: () => {
+      return new Promise((resolve) => resolve("Something"));
+    },
+    name: "SERVICES",
+    keysToRemove: [
+      "MASTERS",
+      "SLAVES_AND_CLIENTS",
+      "CONFIGURATION",
+      "REVIEW",
+      "INSTALL_START_TEST",
+    ],
+  },
+  5: {
+    label: "Assign Masters",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: true,
+    isNextEnabled: true,
+    onNext: () => {
+      return new Promise((resolve) => resolve("Something"));
+    },
+    name: "MASTERS",
+    keysToRemove: [
+      "SLAVES_AND_CLIENTS",
+      "CONFIGURATION",
+      "REVIEW",
+      "INSTALL_START_TEST",
+    ],
+  },
+  6: {
+    label: "Assign Slaves and Clients",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: true,
+    isNextEnabled: false,
+    name: "SLAVES_AND_CLIENTS",
+    keysToRemove: ["CONFIGURATION", "REVIEW", "INSTALL_START_TEST"],
+  },
+  7: {
+    label: "Customize Services",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: true,
+    isNextEnabled: false,
+    name: "CONFIGURATION",
+    keysToRemove: ["REVIEW", "INSTALL_START_TEST"],
+  },
+  8: {
+    label: "Review",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: true,
+    nextLabel: "DEPLOY",
+    isNextEnabled: false,
+    name: "REVIEW",
+    keysToRemove: ["INSTALL_START_TEST"],
+  },
+  9: {
+    label: "Install, Start and Test",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: false,
+    isNextEnabled: false,
+    name: "INSTALL_START_TEST",
+    keysToRemove: [],
+  },
+  10: {
+    label: "Summary",
+    completed: false,
+    Component: <ComponentInProgress />,
+    canGoBack: false,
+    isNextEnabled: false,
+    keyToRemove: [],
+  },
+};


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


Reply via email to