Skip to content

Commit

Permalink
Display node join command during initial install (#4815)
Browse files Browse the repository at this point in the history
  • Loading branch information
miaawong authored Aug 13, 2024
1 parent 982cb68 commit fcb8fda
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 130 deletions.
271 changes: 148 additions & 123 deletions web/src/components/apps/EmbeddedClusterManagement.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { useQuery } from "@tanstack/react-query";
import classNames from "classnames";
import MaterialReactTable, { MRT_ColumnDef } from "material-react-table";
import { ChangeEvent, useEffect, useMemo, useReducer, useState } from "react";
import Modal from "react-modal";
import { useEffect, useMemo, useReducer, useState } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";

import { KotsPageTitle } from "@components/Head";
import { useApps } from "@features/App";
import { rbacRoles } from "../../constants/rbac";
import { Utilities } from "../../utilities/utilities";
import Icon from "../Icon";
import CodeSnippet from "../shared/CodeSnippet";

import "@src/scss/components/apps/EmbeddedClusterManagement.scss";
import { isEqual } from "lodash";
import AddANodeModal from "@components/modals/AddANodeModal";
import Icon from "@components/Icon";

const testData = {
nodes: undefined,
Expand Down Expand Up @@ -241,24 +241,23 @@ const EmbeddedClusterManagement = ({
if (nodeTypes.length === 1) {
// if there's only one node type, select it by default
setSelectedNodeTypes(nodeTypes);
} else {
setSelectedNodeTypes([]);
} else if (nodeTypes.length > 1) {
setSelectedNodeTypes([nodeTypes[0]]);
}
}, [rolesData]);

const determineDisabledState = () => {
return false;
};

const handleSelectNodeType = (e: ChangeEvent<HTMLInputElement>) => {
let nodeType = e.currentTarget.value;
let types = selectedNodeTypes;

if (selectedNodeTypes.includes(nodeType)) {
setSelectedNodeTypes(types.filter((type) => type !== nodeType));
} else {
setSelectedNodeTypes([...types, nodeType]);
}
const handleSelectNodeType = (nodeType) => {
setSelectedNodeTypes((prevSelectedNodeTypes) => {
if (prevSelectedNodeTypes.includes(nodeType)) {
return prevSelectedNodeTypes.filter((type) => type !== nodeType);
} else {
return [...prevSelectedNodeTypes, nodeType];
}
});
};
// #endregion

Expand Down Expand Up @@ -413,6 +412,111 @@ const EmbeddedClusterManagement = ({
}
};

const AddNodeInstructions = () => {
return (
<div className="tw-mb-2 tw-text-base">
<p>
Optionally add nodes to the cluster. Click{" "}
<span className="tw-font-semibold">Continue </span>
to proceed with a single node.
</p>
<p>
{rolesData?.roles &&
rolesData.roles.length > 1 &&
"Select one or more roles to assign to the new node."}{" "}
Copy the join command and run it on the machine you'd like to join to
the cluster.
</p>
</div>
);
};

const AddNodeCommands = () => {
return (
<>
{rolesLoading && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold">
Loading roles...
</p>
)}
{!rolesData && rolesError && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-pink-500 tw-font-semibold">
{rolesError?.message || "Unable to fetch roles"}
</p>
)}

{rolesData?.roles && rolesData.roles.length > 1 && (
<div className="tw-flex tw-gap-2 tw-items-center tw-mt-2">
<p className="tw-text-gray-600 tw-font-semibold">Roles: </p>
{rolesData.roles.map((nodeType) => (
<div
key={nodeType}
className={classNames(
"tw-border-[1px] tw-border-solid tw-border-[#326DE6] tw-rounded tw-px-2 tw-py-2 tw-flex tw-items-center tw-cursor-pointer",
{
"tw-text-white tw-bg-[#326DE6]":
selectedNodeTypes.includes(nodeType),
"is-disabled": determineDisabledState(),
"tw-text-[#326DE6] tw-bg-white tw-hover:tw-bg-[#f8fafe]":
!selectedNodeTypes.includes(nodeType),
}
)}
onClick={() => {
handleSelectNodeType(nodeType);
}}
>
<label
htmlFor={`${nodeType}NodeType`}
className=" u-userSelect--none tw-text-gray-600 u-fontSize--normal u-fontWeight--medium tw-text-center tw-flex tw-items-center"
>
{selectedNodeTypes.includes(nodeType) && (
<Icon icon="check" size={12} className="tw-mr-2" />
)}
<input
id={`${nodeType}NodeType`}
className="u-cursor--pointer tw-mr-2 hidden-input"
type="checkbox"
name={`${nodeType}NodeType`}
value={nodeType}
disabled={determineDisabledState()}
checked={selectedNodeTypes.includes(nodeType)}
/>
</label>
{nodeType}
</div>
))}
</div>
)}
<div className="tw-max-w-[700px]">
{selectedNodeTypes.length > 0 && generateAddNodeCommandLoading && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold">
Generating command...
</p>
)}
{!generateAddNodeCommand && generateAddNodeCommandError && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-pink-500 tw-font-semibold">
{generateAddNodeCommandError?.message}
</p>
)}
{!generateAddNodeCommandLoading && generateAddNodeCommand?.command && (
<>
<CodeSnippet
key={selectedNodeTypes.toString()}
language="bash"
canCopy={true}
onCopyText={
<span className="u-textColor--success">Copied!</span>
}
>
{generateAddNodeCommand?.command}
</CodeSnippet>
</>
)}
</div>
</>
);
};

return (
<div className="EmbeddedClusterManagement--wrapper container u-overflow--auto u-paddingTop--50 tw-font-sans">
<KotsPageTitle pageName="Cluster Management" />
Expand All @@ -421,15 +525,32 @@ const EmbeddedClusterManagement = ({
Nodes
</p>
<div className="tw-flex tw-gap-6 tw-items-center">
{Utilities.sessionRolesHasOneOf([rbacRoles.CLUSTER_ADMIN]) && (
<button
className="btn primary tw-ml-auto tw-w-fit tw-h-fit"
onClick={onAddNodeClick}
>
Add node
</button>
{" "}
{!Utilities.isInitialAppInstall(app) && (
<div className="tw-flex tw-gap-6">
<p>
View the nodes in your cluster, generate commands to add nodes
to the cluster, and view workloads running on each node.
</p>
</div>
)}
{Utilities.sessionRolesHasOneOf([rbacRoles.CLUSTER_ADMIN]) &&
!Utilities.isInitialAppInstall(app) && (
<button
className="btn primary tw-ml-auto tw-w-fit tw-h-fit"
onClick={onAddNodeClick}
>
Add node
</button>
)}
</div>
{Utilities.isInitialAppInstall(app) && (
<div className="tw-mt-4 tw-flex tw-flex-col">
<AddNodeInstructions />
<AddNodeCommands />
</div>
)}

<div className="flex1 u-overflow--auto card-item">
{nodesLoading && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold">
Expand Down Expand Up @@ -498,109 +619,13 @@ const EmbeddedClusterManagement = ({
)}
</div>
{/* MODALS */}
<Modal
isOpen={state.displayAddNode}
onRequestClose={() => setState({ displayAddNode: false })}
contentLabel="Add Node"
className="Modal"
ariaHideApp={false}
<AddANodeModal
displayAddNode={state.displayAddNode}
toggleDisplayAddNode={() => setState({ displayAddNode: false })}
rolesData={rolesData}
>
<div className="Modal-body tw-flex tw-flex-col tw-gap-4 tw-font-sans">
<div className="tw-flex">
<h1 className="u-fontSize--largest u-fontWeight--bold u-textColor--primary u-lineHeight--normal u-marginBottom--more">
Add a Node
</h1>
<Icon
icon="close"
size={14}
className="tw-ml-auto gray-color clickable close-icon"
onClick={() => setState({ displayAddNode: false })}
/>
</div>
<p className="tw-text-base tw-text-gray-600">
{rolesData?.roles &&
rolesData.roles.length > 1 &&
"Select one or more roles to assign to the new node. "}
Copy the join command and run it on the machine you'd like to join
to the cluster.
</p>
{rolesLoading && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold">
Loading roles...
</p>
)}
{!rolesData && rolesError && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-pink-500 tw-font-semibold">
{rolesError?.message || "Unable to fetch roles"}
</p>
)}
{rolesData?.roles && rolesData.roles.length > 1 && (
<div className="tw-grid tw-gap-2 tw-grid-cols-4 tw-auto-rows-auto">
{rolesData.roles.map((nodeType) => (
<div
key={nodeType}
className={classNames("BoxedCheckbox", {
"is-active": selectedNodeTypes.includes(nodeType),
"is-disabled": determineDisabledState(),
})}
>
<input
id={`${nodeType}NodeType`}
className="u-cursor--pointer hidden-input"
type="checkbox"
name={`${nodeType}NodeType`}
value={nodeType}
disabled={determineDisabledState()}
checked={selectedNodeTypes.includes(nodeType)}
onChange={handleSelectNodeType}
/>
<label
htmlFor={`${nodeType}NodeType`}
className="tw-block u-cursor--pointer u-userSelect--none u-textColor--primary u-fontSize--normal u-fontWeight--medium tw-text-center"
>
{nodeType}
</label>
</div>
))}
</div>
)}
<div>
{selectedNodeTypes.length > 0 && generateAddNodeCommandLoading && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-gray-500 tw-font-semibold">
Generating command...
</p>
)}
{!generateAddNodeCommand && generateAddNodeCommandError && (
<p className="tw-text-base tw-w-full tw-text-center tw-py-4 tw-text-pink-500 tw-font-semibold">
{generateAddNodeCommandError?.message}
</p>
)}
{!generateAddNodeCommandLoading && generateAddNodeCommand?.command && (
<>
<CodeSnippet
key={selectedNodeTypes.toString()}
language="bash"
canCopy={true}
onCopyText={
<span className="u-textColor--success">Copied!</span>
}
>
{generateAddNodeCommand?.command}
</CodeSnippet>
</>
)}
</div>
{/* buttons */}
<div className="tw-w-full tw-flex tw-justify-end tw-gap-2">
<button
className="btn secondary large"
onClick={() => setState({ displayAddNode: false })}
>
Close
</button>
</div>
</div>
</Modal>
<AddNodeCommands />
</AddANodeModal>
</div>
);
};
Expand Down
13 changes: 6 additions & 7 deletions web/src/components/apps/EmbeddedClusterViewNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,14 @@ const EmbeddedClusterViewNode = () => {
)}
{!nodeLoading && node && (
<>
{/* Node Info */}
<div className="tw-p-3">
<p className="tw-font-semibold tw-text-xl tw-text-gray-800">
{node?.name}
</p>
</div>
{/* Pods table */}
<div className="card-bg tw-p-3 tw-flex tw-flex-col tw-gap-2">
<p className="tw-font-semibold tw-text-xl tw-text-gray-800">Pods</p>
<div>
<p className="tw-font-semibold tw-text-xl tw-text-gray-800">
Workloads
</p>
<p>View the workloads running on this node.</p>
</div>
<div className="card-item">
<MaterialReactTable
columns={columns}
Expand Down
51 changes: 51 additions & 0 deletions web/src/components/modals/AddANodeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Modal from "react-modal";
import Icon from "../Icon";

const AddANodeModal = ({
displayAddNode,
toggleDisplayAddNode,
rolesData,
children,
}) => {
return (
<Modal
isOpen={displayAddNode}
onRequestClose={() => toggleDisplayAddNode()}
contentLabel="Add Node"
className="Modal"
ariaHideApp={false}
>
<div className="Modal-body tw-flex tw-flex-col tw-gap-4 tw-font-sans">
<div className="tw-flex">
<h1 className="u-fontSize--largest u-fontWeight--bold u-textColor--primary u-lineHeight--normal u-marginBottom--more">
Add a Node
</h1>
<Icon
icon="close"
size={14}
className="tw-ml-auto gray-color clickable close-icon"
onClick={() => toggleDisplayAddNode()}
/>
</div>
<p className="tw-text-base tw-text-gray-600">
{rolesData?.roles &&
rolesData.roles.length > 1 &&
"Select one or more roles to assign to the new node. "}
Copy the join command and run it on the machine you'd like to join to
the cluster.
</p>
{children}
<div className="tw-w-full tw-flex tw-justify-end tw-gap-2">
<button
className="btn secondary large"
onClick={() => toggleDisplayAddNode()}
>
Close
</button>
</div>
</div>
</Modal>
);
};

export default AddANodeModal;
Loading

0 comments on commit fcb8fda

Please sign in to comment.