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]