From 633f4e0aa321972972e8cedb22aa1d51c22e6c7c Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:33:25 -0600 Subject: [PATCH 01/17] wip: refactor helm cluster page, add view node page --- web/src/Root.tsx | 4 +- .../apps/HelmVMClusterManagement.jsx | 738 ++++++++++-------- web/src/components/apps/HelmVMNodeRow.jsx | 28 +- web/src/components/apps/HelmVMViewNode.jsx | 158 ++++ web/tailwind.config.js | 65 +- 5 files changed, 634 insertions(+), 359 deletions(-) create mode 100644 web/src/components/apps/HelmVMViewNode.jsx diff --git a/web/src/Root.tsx b/web/src/Root.tsx index 2e7930a438..684fe64170 100644 --- a/web/src/Root.tsx +++ b/web/src/Root.tsx @@ -58,6 +58,7 @@ import SnapshotDetails from "@components/snapshots/SnapshotDetails"; import SnapshotRestore from "@components/snapshots/SnapshotRestore"; import AppSnapshots from "@components/snapshots/AppSnapshots"; import AppSnapshotRestore from "@components/snapshots/AppSnapshotRestore"; +import HelmVMViewNode from "@components/apps/HelmVMViewNode"; // react-query client const queryClient = new QueryClient(); @@ -579,10 +580,11 @@ const Root = () => { state.adminConsoleMetadata?.isKurl ? ( ) : ( - + ) } /> + } /> } diff --git a/web/src/components/apps/HelmVMClusterManagement.jsx b/web/src/components/apps/HelmVMClusterManagement.jsx index 4d1712f258..5d2ef854af 100644 --- a/web/src/components/apps/HelmVMClusterManagement.jsx +++ b/web/src/components/apps/HelmVMClusterManagement.jsx @@ -1,96 +1,167 @@ -import React, { Component, Fragment } from "react"; import classNames from "classnames"; import dayjs from "dayjs"; +import React, { useEffect, useReducer } from "react"; +import Modal from "react-modal"; +import { useQuery } from "react-query"; + import { KotsPageTitle } from "@components/Head"; -import CodeSnippet from "../shared/CodeSnippet"; -import HelmVMNodeRow from "./HelmVMNodeRow"; -import Loader from "../shared/Loader"; import { rbacRoles } from "../../constants/rbac"; -import { Utilities } from "../../utilities/utilities"; import { Repeater } from "../../utilities/repeater"; +import { Utilities } from "../../utilities/utilities"; +import Icon from "../Icon"; import ErrorModal from "../modals/ErrorModal"; -import Modal from "react-modal"; +import CodeSnippet from "../shared/CodeSnippet"; +import Loader from "../shared/Loader"; +import HelmVMNodeRow from "./HelmVMNodeRow"; import "@src/scss/components/apps/HelmVMClusterManagement.scss"; -import Icon from "../Icon"; -export class HelmVMClusterManagement extends Component { - state = { - generating: false, - command: "", - expiry: null, - displayAddNode: false, - selectedNodeType: "primary", - generateCommandErrMsg: "", - helmvm: null, - getNodeStatusJob: new Repeater(), - deletNodeError: "", - confirmDeleteNode: "", - showConfirmDrainModal: false, - nodeNameToDrain: "", - drainingNodeName: null, - drainNodeSuccessful: false, - }; - - componentDidMount() { - this.getNodeStatus(); - this.state.getNodeStatusJob.start(this.getNodeStatus, 1000); - } +const testData = { + isHelmVMEnabled: true, + ha: false, + nodes: [ + { + name: "test-helmvm-node", + isConnected: true, + isReady: true, + isPrimaryNode: true, + canDelete: false, + kubeletVersion: "v1.28.2", + cpu: { + capacity: 8, + available: 7.466876775, + }, + memory: { + capacity: 31.33294677734375, + available: 24.23790740966797, + }, + pods: { + capacity: 110, + available: 77, + }, + labels: [ + "beta.kubernetes.io/arch:amd64", + "beta.kubernetes.io/os:linux", + "node-role.kubernetes.io/master:", + "node.kubernetes.io/exclude-from-external-load-balancers:", + "kubernetes.io/arch:amd64", + "kubernetes.io/hostname:laverya-kurl", + "kubernetes.io/os:linux", + "node-role.kubernetes.io/control-plane:", + ], + conditions: { + memoryPressure: false, + diskPressure: false, + pidPressure: false, + ready: true, + }, + }, + { + name: "test-helmvm-worker", + isConnected: true, + isReady: true, + isPrimaryNode: false, + canDelete: false, + kubeletVersion: "v1.28.2", + cpu: { + capacity: 4, + available: 3.761070507, + }, + memory: { + capacity: 15.50936508178711, + available: 11.742542266845703, + }, + pods: { + capacity: 110, + available: 94, + }, + labels: [ + "beta.kubernetes.io/arch:amd64", + "beta.kubernetes.io/os:linux", + "kubernetes.io/arch:amd64", + "kubernetes.io/os:linux", + "kurl.sh/cluster:true", + ], + conditions: { + memoryPressure: false, + diskPressure: false, + pidPressure: false, + ready: true, + }, + }, + ], +}; - componentWillUnmount() { - this.state.getNodeStatusJob.stop(); - } +const HelmVMClusterManagement = () => { + const [state, setState] = useReducer( + (state, newState) => ({ ...state, ...newState }), + { + generating: false, + command: "", + expiry: null, + displayAddNode: false, + selectedNodeType: "primary", + generateCommandErrMsg: "", + helmvm: null, + deletNodeError: "", + confirmDeleteNode: "", + showConfirmDrainModal: false, + nodeNameToDrain: "", + drainingNodeName: null, + drainNodeSuccessful: false, + } + ); - getNodeStatus = async () => { - try { - const res = await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { - headers: { - Accept: "application/json", - }, - credentials: "include", - method: "GET", - }); - if (!res.ok) { - if (res.status === 401) { - Utilities.logoutUser(); - return; - } - console.log( - "failed to get node status list, unexpected status code", - res.status - ); + const { data: nodes, isLoading: nodesLoading } = useQuery({ + queryKey: "helmVmNodes", + queryFn: async () => { + return ( + await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { + headers: { + Accept: "application/json", + }, + credentials: "include", + method: "GET", + }) + ).json(); + }, + onError: (err) => { + if (err.status === 401) { + Utilities.logoutUser(); return; } - const response = await res.json(); - this.setState({ - helmvm: response, + console.log( + "failed to get node status list, unexpected status code", + err.status + ); + }, + onSuccess: (data) => { + setState({ // if cluster doesn't support ha, then primary will be disabled. Force into secondary - selectedNodeType: !response.ha - ? "secondary" - : this.state.selectedNodeType, + selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, }); - return response; - } catch (err) { - console.log(err); - throw err; - } - }; + }, + config: { + refetchInterval: 1000, + retry: false, + }, + }); - deleteNode = (name) => { - this.setState({ + const deleteNode = (name) => { + setState({ confirmDeleteNode: name, }); }; - cancelDeleteNode = () => { - this.setState({ + const cancelDeleteNode = () => { + setState({ confirmDeleteNode: "", }); }; - reallyDeleteNode = () => { - const name = this.state.confirmDeleteNode; - this.cancelDeleteNode(); + const reallyDeleteNode = () => { + const name = state.confirmDeleteNode; + cancelDeleteNode(); fetch(`${process.env.API_ENDPOINT}/helmvm/nodes/${name}`, { headers: { @@ -106,7 +177,7 @@ export class HelmVMClusterManagement extends Component { Utilities.logoutUser(); return; } - this.setState({ + setState({ deleteNodeError: `Delete failed with status ${res.status}`, }); } @@ -116,8 +187,8 @@ export class HelmVMClusterManagement extends Component { }); }; - generateWorkerAddNodeCommand = async () => { - this.setState({ + const generateWorkerAddNodeCommand = async () => { + setState({ generating: true, command: "", expiry: null, @@ -137,13 +208,13 @@ export class HelmVMClusterManagement extends Component { ) .then(async (res) => { if (!res.ok) { - this.setState({ + setState({ generating: false, generateCommandErrMsg: `Failed to generate command with status ${res.status}`, }); } else { const data = await res.json(); - this.setState({ + setState({ generating: false, command: data.command, expiry: data.expiry, @@ -152,22 +223,22 @@ export class HelmVMClusterManagement extends Component { }) .catch((err) => { console.log(err); - this.setState({ + setState({ generating: false, generateCommandErrMsg: err ? err.message : "Something went wrong", }); }); }; - onDrainNodeClick = (name) => { - this.setState({ + const onDrainNodeClick = (name) => { + setState({ showConfirmDrainModal: true, nodeNameToDrain: name, }); }; - drainNode = async (name) => { - this.setState({ showConfirmDrainModal: false, drainingNodeName: name }); + const drainNode = async (name) => { + setState({ showConfirmDrainModal: false, drainingNodeName: name }); fetch(`${process.env.API_ENDPOINT}/helmvm/nodes/${name}/drain`, { headers: { "Content-Type": "application/json", @@ -177,9 +248,9 @@ export class HelmVMClusterManagement extends Component { method: "POST", }) .then(async (res) => { - this.setState({ drainNodeSuccessful: true }); + setState({ drainNodeSuccessful: true }); setTimeout(() => { - this.setState({ + setState({ drainingNodeName: null, drainNodeSuccessful: false, }); @@ -187,15 +258,15 @@ export class HelmVMClusterManagement extends Component { }) .catch((err) => { console.log(err); - this.setState({ + setState({ drainingNodeName: null, drainNodeSuccessful: false, }); }); }; - generatePrimaryAddNodeCommand = async () => { - this.setState({ + const generatePrimaryAddNodeCommand = async () => { + setState({ generating: true, command: "", expiry: null, @@ -215,13 +286,13 @@ export class HelmVMClusterManagement extends Component { ) .then(async (res) => { if (!res.ok) { - this.setState({ + setState({ generating: false, generateCommandErrMsg: `Failed to generate command with status ${res.status}`, }); } else { const data = await res.json(); - this.setState({ + setState({ generating: false, command: data.command, expiry: data.expiry, @@ -230,257 +301,299 @@ export class HelmVMClusterManagement extends Component { }) .catch((err) => { console.log(err); - this.setState({ + setState({ generating: false, generateCommandErrMsg: err ? err.message : "Something went wrong", }); }); }; - onAddNodeClick = () => { - this.setState( + const onAddNodeClick = () => { + setState( { displayAddNode: true, }, async () => { - await this.generateWorkerAddNodeCommand(); + await generateWorkerAddNodeCommand(); } ); }; - onSelectNodeType = (event) => { + const onSelectNodeType = (event) => { const value = event.currentTarget.value; - this.setState( + setState( { selectedNodeType: value, }, async () => { - if (this.state.selectedNodeType === "secondary") { - await this.generateWorkerAddNodeCommand(); + if (state.selectedNodeType === "secondary") { + await generateWorkerAddNodeCommand(); } else { - await this.generatePrimaryAddNodeCommand(); + await generatePrimaryAddNodeCommand(); } } ); }; - ackDeleteNodeError = () => { - this.setState({ deleteNodeError: "" }); + const ackDeleteNodeError = () => { + setState({ deleteNodeError: "" }); }; - render() { - const { helmvm } = this.state; - const { displayAddNode, generateCommandErrMsg } = this.state; - - if (!helmvm) { - return ( -
- -
- ); - } + const { displayAddNode, generateCommandErrMsg } = state; + if (nodesLoading) { return ( -
- -
-
-
-

- Your nodes -

-
- {helmvm?.nodes && - helmvm?.nodes.map((node, i) => ( - - ))} -
+
+ +
+ ); + } + + return ( +
+ +
+
+
+

+ Cluster Nodes +

+

+ This section lists the nodes that are configured and shows the + status/health of each. To add additional nodes to this cluster, + click the "Add node" button at the bottom of this page. +

+
+ {(nodes?.nodes || testData?.nodes) && + (nodes?.nodes || testData?.nodes).map((node, i) => ( + + ))}
- {helmvm?.isHelmVMEnabled && - Utilities.sessionRolesHasOneOf([rbacRoles.CLUSTER_ADMIN]) ? ( - !displayAddNode ? ( -
-
+ {(nodes?.isHelmVMEnabled || testData.isHelmVMEnabled) && + Utilities.sessionRolesHasOneOf([rbacRoles.CLUSTER_ADMIN]) ? ( + !displayAddNode ? ( +
+ +
+ ) : ( +
+
+

Add a node - +

- ) : ( -
-
-

- Add a Node -

-
-
-
+
+ +
-
+ +
+
+

+ Primary Node +

+

+ Provides high availability +

+
+ +
+
+ +
+
+ +
+
+

+ Secondary Node +

+

+ Optimal for running application workloads +

+
+
- {this.state.generating && ( -
- -
- )} - {!this.state.generating && this.state.command?.length > 0 ? ( - -

- Run this command on the node you wish to join the - cluster -

- - Command has been copied to your clipboard - - } - > - {[this.state.command.join(" \\\n ")]} - - {this.state.expiry && ( - - {`Expires on ${dayjs(this.state.expiry).format( - "MMM Do YYYY, h:mm:ss a z" - )} UTC${(-1 * new Date().getTimezoneOffset()) / 60}`} - - )} -
- ) : ( - - {generateCommandErrMsg && ( -
- - {generateCommandErrMsg} - -
- )} -
- )}
- ) - ) : null} + {state.generating && ( +
+ +
+ )} + {!state.generating && state.command?.length > 0 ? ( + <> +

+ Run this command on the node you wish to join the cluster +

+ + Command has been copied to your clipboard + + } + > + {[state.command.join(" \\\n ")]} + + {state.expiry && ( + + {`Expires on ${dayjs(state.expiry).format( + "MMM Do YYYY, h:mm:ss a z" + )} UTC${(-1 * new Date().getTimezoneOffset()) / 60}`} + + )} + + ) : ( + <> + {generateCommandErrMsg && ( +
+ + {generateCommandErrMsg} + +
+ )} + + )} +
+ ) + ) : null} +
+
+ {state.deleteNodeError && ( + + )} + +
+

+ Deleting this node may cause data loss. Are you sure you want to + proceed? +

+
+ +
- {this.state.deleteNodeError && ( - - )} +
+ {state.showConfirmDrainModal && ( + setState({ + showConfirmDrainModal: false, + nodeNameToDrain: "", + }) + } shouldReturnFocusAfterClose={false} - contentLabel="Confirm Delete Node" + contentLabel="Confirm Drain Node" ariaHideApp={false} - className="Modal" + className="Modal MediumSize" >
+

+ Are you sure you want to drain {state.nodeNameToDrain}? +

- Deleting this node may cause data loss. Are you sure you want to - proceed? + Draining this node may cause data loss. If you want to delete{" "} + {state.nodeNameToDrain} you must disconnect it after it has been + drained.

- {this.state.showConfirmDrainModal && ( - - this.setState({ - showConfirmDrainModal: false, - nodeNameToDrain: "", - }) - } - shouldReturnFocusAfterClose={false} - contentLabel="Confirm Drain Node" - ariaHideApp={false} - className="Modal MediumSize" - > -
-

- Are you sure you want to drain {this.state.nodeNameToDrain}? -

-

- Draining this node may cause data loss. If you want to delete{" "} - {this.state.nodeNameToDrain} you must disconnect it after it has - been drained. -

-
- - -
-
-
- )} -
- ); - } -} + )} +
+ ); +}; export default HelmVMClusterManagement; diff --git a/web/src/components/apps/HelmVMNodeRow.jsx b/web/src/components/apps/HelmVMNodeRow.jsx index 93f2c5489c..e7dac9025d 100644 --- a/web/src/components/apps/HelmVMNodeRow.jsx +++ b/web/src/components/apps/HelmVMNodeRow.jsx @@ -1,15 +1,20 @@ -import React from "react"; import classNames from "classnames"; -import Loader from "../shared/Loader"; +import React from "react"; +import { Link } from "react-router-dom"; + import { rbacRoles } from "../../constants/rbac"; import { getPercentageStatus, Utilities } from "../../utilities/utilities"; import Icon from "../Icon"; +import Loader from "../shared/Loader"; -export default function HelmVMNodeRow(props) { - const { node } = props; - +export default function HelmVMNodeRow({ + node, + drainNode, + drainNodeSuccessful, + drainingNodeName, + deleteNode, +}) { const DrainDeleteNode = () => { - const { drainNode, drainNodeSuccessful, drainingNodeName } = props; if (drainNode && Utilities.sessionRolesHasOneOf(rbacRoles.DRAIN_NODE)) { if ( !drainNodeSuccessful && @@ -44,9 +49,7 @@ export default function HelmVMNodeRow(props) {
+
+
+ ); +}; + +export default HelmVMViewNode; diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 1ea5c980de..d89f3af2a5 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -13,17 +13,60 @@ module.exports = { "teal-muted-dark": "#577981", "teal-medium": "#097992", gray: { - 100: "#dfdfdf", + 100: "#dedede", 200: "#c4c8ca", 300: "#b3b3b3", + 410: "#9b9b9b", 400: "#959595", 500: "#717171", 600: "#585858", 700: "#4f4f4f", - 800: "#323232" + 800: "#323232", + 900: "#2c2c2c", + }, + blue: { + 50: "#ecf4fe", + 75: "#b3d2fc", + 200: "#65a4f8", + 300: "#4591f7", + 400: "#3066ad", + }, + green: { + 50: "#e7f7f3", + 75: "#9cdfcf", + 100: "#73d2bb", + 200: "#37bf9e", + 300: "#0eb28a", + 400: "#0a7d61", + 500: "#096d54", + }, + indigo: { + 100: "#f0f1ff", + 200: "#c2c7fd", + 300: "#a9b0fd", + 400: "#838efc", + 500: "#6a77fb", + 600: "#4a53b0", + 700: "#414999", }, neutral: { - 700: "#4A4A4A" + 700: "#4A4A4A", + }, + teal: { + 300: "#4db9c0", + 400: "#38a3a8", + }, + pink: { + 50: "#fff0f3", + 100: "#ffc1cf", + 200: "#fea7bc", + 300: "#fe819f", + 400: "#fe678b", + 500: "#b24861", + 600: "#9b3f55", + }, + purple: { + 400: "#7242b0", }, error: "#bc4752", "error-xlight": "#fbedeb", @@ -34,26 +77,26 @@ module.exports = { "warning-bright": "#ec8f39", "info-bright": "#76bbca", "disabled-teal": "#76a6cf", - "dark-neon-green": "#38cc97" + "dark-neon-green": "#38cc97", }, extend: { borderRadius: { xs: "0.125rem", sm: "0.187rem", - variants: ["first", "last"] + variants: ["first", "last"], }, fontFamily: { - sans: ["Open Sans", ...defaultTheme.fontFamily.sans] - } - } + sans: ["Open Sans", ...defaultTheme.fontFamily.sans], + }, + }, }, corePlugins: { - preflight: false + preflight: false, }, plugins: [ plugin(function ({ addVariant }) { addVariant("is-enabled", "&:not([disabled])"); addVariant("is-disabled", "&[disabled]"); - }) - ] + }), + ], }; From 3b4d245c09c58fe38d16fe2069c61dbd9aae997c Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:05:54 -0600 Subject: [PATCH 02/17] add new add node modal --- .../apps/HelmVMClusterManagement.jsx | 321 +++++++++--------- web/src/components/apps/HelmVMNodeRow.jsx | 2 +- 2 files changed, 156 insertions(+), 167 deletions(-) diff --git a/web/src/components/apps/HelmVMClusterManagement.jsx b/web/src/components/apps/HelmVMClusterManagement.jsx index 5d2ef854af..28d2c56860 100644 --- a/web/src/components/apps/HelmVMClusterManagement.jsx +++ b/web/src/components/apps/HelmVMClusterManagement.jsx @@ -1,6 +1,6 @@ import classNames from "classnames"; import dayjs from "dayjs"; -import React, { useEffect, useReducer } from "react"; +import React, { useEffect, useReducer, useState } from "react"; import Modal from "react-modal"; import { useQuery } from "react-query"; @@ -111,6 +111,8 @@ const HelmVMClusterManagement = () => { drainNodeSuccessful: false, } ); + const [selectedNodeTypes, setSelectedNodeTypes] = useState([]); + const [useStaticToken, setUseStaticToken] = useState(false); const { data: nodes, isLoading: nodesLoading } = useQuery({ queryKey: "helmVmNodes", @@ -309,37 +311,57 @@ const HelmVMClusterManagement = () => { }; const onAddNodeClick = () => { - setState( - { - displayAddNode: true, - }, - async () => { - await generateWorkerAddNodeCommand(); - } - ); - }; - - const onSelectNodeType = (event) => { - const value = event.currentTarget.value; - setState( - { - selectedNodeType: value, - }, - async () => { - if (state.selectedNodeType === "secondary") { - await generateWorkerAddNodeCommand(); - } else { - await generatePrimaryAddNodeCommand(); - } - } - ); + setState({ + displayAddNode: true, + }); }; const ackDeleteNodeError = () => { setState({ deleteNodeError: "" }); }; - const { displayAddNode, generateCommandErrMsg } = state; + const NODE_TYPES = [ + "controlplane", + "db", + "app", + "search", + "webserver", + "jobs", + ]; + + const determineDisabledState = (nodeType, selectedNodeTypes) => { + if (nodeType === "controlplane") { + const numControlPlanes = testData.nodes.reduce((acc, node) => { + if (node.labels.includes("controlplane")) { + acc++; + } + return acc; + }); + return numControlPlanes === 3; + } + if ( + (nodeType === "db" || nodeType === "search") && + selectedNodeTypes.includes("webserver") + ) { + return true; + } + return false; + }; + + const handleSelectNodeType = (e) => { + const nodeType = e.currentTarget.value; + let types = selectedNodeTypes; + + if (nodeType === "webserver") { + types = types.filter((type) => type !== "db" && type !== "search"); + } + + if (selectedNodeTypes.includes(nodeType)) { + setSelectedNodeTypes(types.filter((type) => type !== nodeType)); + } else { + setSelectedNodeTypes([...types, nodeType]); + } + }; if (nodesLoading) { return ( @@ -350,7 +372,7 @@ const HelmVMClusterManagement = () => { } return ( -
+
@@ -358,7 +380,7 @@ const HelmVMClusterManagement = () => {

Cluster Nodes

-

+

This section lists the nodes that are configured and shows the status/health of each. To add additional nodes to this cluster, click the "Add node" button at the bottom of this page. @@ -377,146 +399,113 @@ const HelmVMClusterManagement = () => { ))}

- {(nodes?.isHelmVMEnabled || testData.isHelmVMEnabled) && - Utilities.sessionRolesHasOneOf([rbacRoles.CLUSTER_ADMIN]) ? ( - !displayAddNode ? ( -
- -
- ) : ( -
-
-

- Add a node -

-
-
-
- - -
-
- - -
-
- {state.generating && ( -
- -
- )} - {!state.generating && state.command?.length > 0 ? ( - <> -

- Run this command on the node you wish to join the cluster -

- - Command has been copied to your clipboard - - } - > - {[state.command.join(" \\\n ")]} - - {state.expiry && ( - - {`Expires on ${dayjs(state.expiry).format( - "MMM Do YYYY, h:mm:ss a z" - )} UTC${(-1 * new Date().getTimezoneOffset()) / 60}`} - - )} - - ) : ( - <> - {generateCommandErrMsg && ( -
- - {generateCommandErrMsg} - -
- )} - - )} -
- ) - ) : null} + {Utilities.sessionRolesHasOneOf([rbacRoles.CLUSTER_ADMIN]) && ( +
+ +
+ )}
+ setState({ displayAddNode: false })} + contentLabel="Add Node" + className="Modal" + ariaHideApp={false} + > +
+
+

+ Add A Node +

+ setState({ displayAddNode: false })} + /> +
+

+ To add a node to this cluster, select the type of node you are + adding, and then select an installation method below. This screen + will automatically show the status when the node successfully joins + the cluster. +

+
+ {NODE_TYPES.map((nodeType) => ( +
+ + +
+ ))} +
+
+ Copied!} + > + {`curl http://node.something/join?token=abc&labels=${selectedNodeTypes.join( + "," + )}`} + +
+
+ setUseStaticToken(e.target.checked)} + /> + +
+ {/* buttons */} +
+ + +
+
+
{state.deleteNodeError && ( {node?.name} From 0e008e8ddfafcb076bef8aebe6a938e2e8486360 Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:10:48 -0600 Subject: [PATCH 03/17] protect routes --- web/src/Root.tsx | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/web/src/Root.tsx b/web/src/Root.tsx index 684fe64170..756c2eea8a 100644 --- a/web/src/Root.tsx +++ b/web/src/Root.tsx @@ -574,17 +574,24 @@ const Root = () => { } /> } /> - - ) : ( - - ) - } - /> - } /> + {state.adminConsoleMetadata?.isKurl || + (state.adminConsoleMetadata?.isHelmVM && ( + + ) : ( + + ) + } + /> + ))} + {state.adminConsoleMetadata?.isHelmVM && ( + } /> + )} } From a1f69cb42afeea18ce85d9d0211acc60bc8376d3 Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:13:02 -0600 Subject: [PATCH 04/17] move parenthesis --- web/src/Root.tsx | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/web/src/Root.tsx b/web/src/Root.tsx index 756c2eea8a..5581d72117 100644 --- a/web/src/Root.tsx +++ b/web/src/Root.tsx @@ -574,21 +574,19 @@ const Root = () => { } /> } /> - {state.adminConsoleMetadata?.isKurl || - (state.adminConsoleMetadata?.isHelmVM && ( - - ) : ( - - ) - } - /> - ))} + {(state.adminConsoleMetadata?.isKurl || + state.adminConsoleMetadata?.isHelmVM) && ( + + ) : ( + + ) + } + /> + )} {state.adminConsoleMetadata?.isHelmVM && ( } /> )} From 513c15f0628d30aa7d0e273f5affbc4b0f5f1c30 Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:44:07 -0600 Subject: [PATCH 05/17] use test data for now --- web/src/Root.tsx | 32 ++-- .../apps/HelmVMClusterManagement.jsx | 71 ++++----- web/src/components/apps/HelmVMViewNode.jsx | 144 +++++++++++++----- 3 files changed, 163 insertions(+), 84 deletions(-) diff --git a/web/src/Root.tsx b/web/src/Root.tsx index 5581d72117..e5566a630d 100644 --- a/web/src/Root.tsx +++ b/web/src/Root.tsx @@ -574,22 +574,22 @@ const Root = () => { } /> } /> - {(state.adminConsoleMetadata?.isKurl || - state.adminConsoleMetadata?.isHelmVM) && ( - - ) : ( - - ) - } - /> - )} - {state.adminConsoleMetadata?.isHelmVM && ( - } /> - )} + {/* {(state.adminConsoleMetadata?.isKurl || + state.adminConsoleMetadata?.isHelmVM) && ( */} + + ) : ( + + ) + } + /> + {/* )} */} + {/* {state.adminConsoleMetadata?.isHelmVM && ( */} + } /> + {/* )} */} } diff --git a/web/src/components/apps/HelmVMClusterManagement.jsx b/web/src/components/apps/HelmVMClusterManagement.jsx index 28d2c56860..8ecbf5b3e8 100644 --- a/web/src/components/apps/HelmVMClusterManagement.jsx +++ b/web/src/components/apps/HelmVMClusterManagement.jsx @@ -114,40 +114,42 @@ const HelmVMClusterManagement = () => { const [selectedNodeTypes, setSelectedNodeTypes] = useState([]); const [useStaticToken, setUseStaticToken] = useState(false); - const { data: nodes, isLoading: nodesLoading } = useQuery({ - queryKey: "helmVmNodes", - queryFn: async () => { - return ( - await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { - headers: { - Accept: "application/json", - }, - credentials: "include", - method: "GET", - }) - ).json(); - }, - onError: (err) => { - if (err.status === 401) { - Utilities.logoutUser(); - return; - } - console.log( - "failed to get node status list, unexpected status code", - err.status - ); - }, - onSuccess: (data) => { - setState({ - // if cluster doesn't support ha, then primary will be disabled. Force into secondary - selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, - }); - }, - config: { - refetchInterval: 1000, - retry: false, - }, - }); + const nodes = testData; + const nodesLoading = false; + // const { data: nodes, isLoading: nodesLoading } = useQuery({ + // queryKey: "helmVmNodes", + // queryFn: async () => { + // return ( + // await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { + // headers: { + // Accept: "application/json", + // }, + // credentials: "include", + // method: "GET", + // }) + // ).json(); + // }, + // onError: (err) => { + // if (err.status === 401) { + // Utilities.logoutUser(); + // return; + // } + // console.log( + // "failed to get node status list, unexpected status code", + // err.status + // ); + // }, + // onSuccess: (data) => { + // setState({ + // // if cluster doesn't support ha, then primary will be disabled. Force into secondary + // selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, + // }); + // }, + // config: { + // refetchInterval: 1000, + // retry: false, + // }, + // }); const deleteNode = (name) => { setState({ @@ -436,6 +438,7 @@ const HelmVMClusterManagement = () => {
{NODE_TYPES.map((nodeType) => (
{ - const { data: nodes } = useQuery({ - queryKey: "helmVmNodes", - queryFn: async () => { - return ( - await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { - headers: { - Accept: "application/json", - }, - credentials: "include", - method: "GET", - }) - ).json(); - }, - onError: (err) => { - if (err.status === 401) { - Utilities.logoutUser(); - return; - } - console.log( - "failed to get node status list, unexpected status code", - err.status - ); - }, - onSuccess: (data) => { - setState({ - // if cluster doesn't support ha, then primary will be disabled. Force into secondary - selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, - }); +const testData = { + isHelmVMEnabled: true, + ha: false, + nodes: [ + { + name: "test-helmvm-node", + isConnected: true, + isReady: true, + isPrimaryNode: true, + canDelete: false, + kubeletVersion: "v1.28.2", + cpu: { + capacity: 8, + available: 7.466876775, + }, + memory: { + capacity: 31.33294677734375, + available: 24.23790740966797, + }, + pods: { + capacity: 110, + available: 77, + }, + labels: [ + "beta.kubernetes.io/arch:amd64", + "beta.kubernetes.io/os:linux", + "node-role.kubernetes.io/master:", + "node.kubernetes.io/exclude-from-external-load-balancers:", + "kubernetes.io/arch:amd64", + "kubernetes.io/hostname:laverya-kurl", + "kubernetes.io/os:linux", + "node-role.kubernetes.io/control-plane:", + ], + conditions: { + memoryPressure: false, + diskPressure: false, + pidPressure: false, + ready: true, + }, }, - config: { - retry: false, + { + name: "test-helmvm-worker", + isConnected: true, + isReady: true, + isPrimaryNode: false, + canDelete: false, + kubeletVersion: "v1.28.2", + cpu: { + capacity: 4, + available: 3.761070507, + }, + memory: { + capacity: 15.50936508178711, + available: 11.742542266845703, + }, + pods: { + capacity: 110, + available: 94, + }, + labels: [ + "beta.kubernetes.io/arch:amd64", + "beta.kubernetes.io/os:linux", + "kubernetes.io/arch:amd64", + "kubernetes.io/os:linux", + "kurl.sh/cluster:true", + ], + conditions: { + memoryPressure: false, + diskPressure: false, + pidPressure: false, + ready: true, + }, }, - }); + ], +}; + +const HelmVMViewNode = () => { + // const { data: nodes } = useQuery({ + // queryKey: "helmVmNodes", + // queryFn: async () => { + // return ( + // await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { + // headers: { + // Accept: "application/json", + // }, + // credentials: "include", + // method: "GET", + // }) + // ).json(); + // }, + // onError: (err) => { + // if (err.status === 401) { + // Utilities.logoutUser(); + // return; + // } + // console.log( + // "failed to get node status list, unexpected status code", + // err.status + // ); + // }, + // onSuccess: (data) => { + // setState({ + // // if cluster doesn't support ha, then primary will be disabled. Force into secondary + // selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, + // }); + // }, + // config: { + // retry: false, + // }, + // }); - const node = nodes.nodes[0]; + const node = testData.nodes[0]; const columns = useMemo( () => [ @@ -113,7 +189,7 @@ const HelmVMViewNode = () => { tw-shadow-md tw-p-3" >

Pods

- +
{columns.map((col) => { From bbb9727322364f5bb77a57480d0367d29d0e5d5e Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Tue, 17 Oct 2023 15:50:55 -0600 Subject: [PATCH 06/17] add material react table --- web/package.json | 5 + web/yarn.lock | 365 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 367 insertions(+), 3 deletions(-) diff --git a/web/package.json b/web/package.json index a623e351ff..2ac897d936 100644 --- a/web/package.json +++ b/web/package.json @@ -121,9 +121,13 @@ "webpack-merge": "5.8.0" }, "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", "@grafana/data": "^8.5.16", "@maji/react-prism": "^1.0.1", "@monaco-editor/react": "^4.4.5", + "@mui/icons-material": "^5.14.14", + "@mui/material": "^5.14.14", "@storybook/addon-storysource": "^6.5.16", "accounting": "^0.4.1", "apexcharts": "^3.24.0", @@ -142,6 +146,7 @@ "js-yaml": "3.14.0", "lodash": "4.17.21", "markdown-it": "^12.3.2", + "material-react-table": "^1.15.1", "monaco-editor": "^0.33.0", "monaco-editor-webpack-plugin": "^7.0.1", "node-polyfill-webpack-plugin": "^1.1.4", diff --git a/web/yarn.lock b/web/yarn.lock index 55cbb3f645..bcabb4abd6 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -350,6 +350,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-module-imports@^7.16.7": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + "@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.20.2", "@babel/helper-module-transforms@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.0.tgz#89a8f86ad748870e3d024e470b2e8405e869db67" @@ -447,11 +454,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + "@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" @@ -1390,6 +1407,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.23.1", "@babel/runtime@^7.8.7": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" + integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.3.1": version "7.20.13" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" @@ -1535,6 +1559,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" @@ -1614,6 +1647,23 @@ resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + "@emotion/babel-utils@^0.6.4": version "0.6.10" resolved "https://registry.npmjs.org/@emotion/babel-utils/-/babel-utils-0.6.10.tgz" @@ -1626,11 +1676,27 @@ find-root "^1.1.0" source-map "^0.7.2" +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + "@emotion/hash@^0.6.2", "@emotion/hash@^0.6.6": version "0.6.6" resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz" integrity sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ== +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + "@emotion/is-prop-valid@^1.1.0": version "1.1.3" resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.3.tgz" @@ -1638,6 +1704,13 @@ dependencies: "@emotion/memoize" "^0.7.4" +"@emotion/is-prop-valid@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" + integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/memoize@^0.6.1", "@emotion/memoize@^0.6.6": version "0.6.6" resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz" @@ -1648,6 +1721,25 @@ resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz" integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/react@^11.11.1": + version "11.11.1" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157" + integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.2" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + hoist-non-react-statics "^3.3.1" + "@emotion/serialize@^0.9.1": version "0.9.1" resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.9.1.tgz" @@ -1658,6 +1750,34 @@ "@emotion/unitless" "^0.6.7" "@emotion/utils" "^0.8.2" +"@emotion/serialize@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" + integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + +"@emotion/styled@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.0.tgz#26b75e1b5a1b7a629d7c0a8b708fbf5a9cdce346" + integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/is-prop-valid" "^1.2.1" + "@emotion/serialize" "^1.1.2" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/stylis@^0.7.0": version "0.7.1" resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.7.1.tgz" @@ -1678,11 +1798,31 @@ resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + "@emotion/utils@^0.8.2": version "0.8.2" resolved "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz" integrity sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw== +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz" @@ -1698,6 +1838,33 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@floating-ui/core@^1.4.2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c" + integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg== + dependencies: + "@floating-ui/utils" "^0.1.3" + +"@floating-ui/dom@^1.5.1": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.3.tgz#54e50efcb432c06c23cd33de2b575102005436fa" + integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA== + dependencies: + "@floating-ui/core" "^1.4.2" + "@floating-ui/utils" "^0.1.3" + +"@floating-ui/react-dom@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.2.tgz#fab244d64db08e6bed7be4b5fcce65315ef44d20" + integrity sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ== + dependencies: + "@floating-ui/dom" "^1.5.1" + +"@floating-ui/utils@^0.1.3": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" + integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -2204,6 +2371,97 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@mui/base@5.0.0-beta.20": + version "5.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.20.tgz#14fcdfe0350f2aad06ab6c37c4c91dacaab8f600" + integrity sha512-CS2pUuqxST7ch9VNDCklRYDbJ3rru20Tx7na92QvVVKfu3RL4z/QLuVIc8jYGsdCnauMaeUSlFNLAJNb0yXe6w== + dependencies: + "@babel/runtime" "^7.23.1" + "@floating-ui/react-dom" "^2.0.2" + "@mui/types" "^7.2.6" + "@mui/utils" "^5.14.13" + "@popperjs/core" "^2.11.8" + clsx "^2.0.0" + prop-types "^15.8.1" + +"@mui/core-downloads-tracker@^5.14.14": + version "5.14.14" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.14.tgz#a54894e9b4dc908ab2d59eac543219d9018448e6" + integrity sha512-Rw/xKiTOUgXD8hdKqj60aC6QcGprMipG7ne2giK6Mz7b4PlhL/xog9xLeclY3BxsRLkZQ05egFnIEY1CSibTbw== + +"@mui/icons-material@^5.14.14": + version "5.14.14" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.14.14.tgz#02d33f51f0b9de238d5c47b0a31ff330144393c4" + integrity sha512-vwuaMsKvI7AWTeYqR8wYbpXijuU8PzMAJWRAq2DDIuOZPxjKyHlr8WQ25+azZYkIXtJ7AqnVb1ZmHdEyB4/kug== + dependencies: + "@babel/runtime" "^7.23.1" + +"@mui/material@^5.14.14": + version "5.14.14" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.14.tgz#e47f3992b609002cd57a71f70e829dc2d286028c" + integrity sha512-cAmCwAHFQXxb44kWbVFkhKATN8tACgMsFwrXo8ro6WzYW73U/qsR5AcCiJIhCyYYg+gcftfkmNcpRaV3JjhHCg== + dependencies: + "@babel/runtime" "^7.23.1" + "@mui/base" "5.0.0-beta.20" + "@mui/core-downloads-tracker" "^5.14.14" + "@mui/system" "^5.14.14" + "@mui/types" "^7.2.6" + "@mui/utils" "^5.14.13" + "@types/react-transition-group" "^4.4.7" + clsx "^2.0.0" + csstype "^3.1.2" + prop-types "^15.8.1" + react-is "^18.2.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^5.14.14": + version "5.14.14" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.14.14.tgz#035dde1eb30c896c69a12b7dee1dce3a323c66e9" + integrity sha512-n77au3CQj9uu16hak2Y+rvbGSBaJKxziG/gEbOLVGrAuqZ+ycVSkorCfN6Y/4XgYOpG/xvmuiY3JwhAEOzY3iA== + dependencies: + "@babel/runtime" "^7.23.1" + "@mui/utils" "^5.14.13" + prop-types "^15.8.1" + +"@mui/styled-engine@^5.14.13": + version "5.14.14" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.14.14.tgz#b0ededf531fff1ef110f7b263c2d3d95a0b8ec9a" + integrity sha512-sF3DS2PVG+cFWvkVHQQaGFpL1h6gSwOW3L91pdxPLQDHDZ5mZ/X0SlXU5XA+WjypoysG4urdAQC7CH/BRvUiqg== + dependencies: + "@babel/runtime" "^7.23.1" + "@emotion/cache" "^11.11.0" + csstype "^3.1.2" + prop-types "^15.8.1" + +"@mui/system@^5.14.14": + version "5.14.14" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.14.tgz#f33327e74230523169107ace960e8bb51cbdbab7" + integrity sha512-y4InFmCgGGWXnz+iK4jRTWVikY0HgYnABjz4wgiUgEa2W1H8M4ow+27BegExUWPkj4TWthQ2qG9FOGSMtI+PKA== + dependencies: + "@babel/runtime" "^7.23.1" + "@mui/private-theming" "^5.14.14" + "@mui/styled-engine" "^5.14.13" + "@mui/types" "^7.2.6" + "@mui/utils" "^5.14.13" + clsx "^2.0.0" + csstype "^3.1.2" + prop-types "^15.8.1" + +"@mui/types@^7.2.6": + version "7.2.6" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.6.tgz#d72b9e9eb0032e107e76033932d65c3f731d2608" + integrity sha512-7sjLQrUmBwufm/M7jw/quNiPK/oor2+pGUQP2CULRcFCArYTq78oJ3D5esTaL0UMkXKJvDqXn6Ike69yAOBQng== + +"@mui/utils@^5.14.13": + version "5.14.14" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.14.tgz#7b2a0bcfb44c3376fc81f85500f9bd01706682ac" + integrity sha512-3AKp8uksje5sRfVrtgG9Q/2TBsHWVBUtA0NaXliZqGcXo8J+A+Agp0qUW2rJ+ivgPWTCCubz9FZVT2IQZ3bGsw== + dependencies: + "@babel/runtime" "^7.23.1" + "@types/prop-types" "^15.7.7" + prop-types "^15.8.1" + react-is "^18.2.0" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" resolved "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz" @@ -2345,6 +2603,11 @@ resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@remix-run/router@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.5.0.tgz#57618e57942a5f0131374a9fdb0167e25a117fdc" @@ -3661,6 +3924,37 @@ regenerator-runtime "^0.13.7" resolve-from "^5.0.0" +"@tanstack/match-sorter-utils@8.8.4": + version "8.8.4" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457" + integrity sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw== + dependencies: + remove-accents "0.4.2" + +"@tanstack/react-table@8.10.7": + version "8.10.7" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.10.7.tgz#733f4bee8cf5aa19582f944dd0fd3224b21e8c94" + integrity sha512-bXhjA7xsTcsW8JPTTYlUg/FuBpn8MNjiEPhkNhIGCUR6iRQM2+WEco4OBpvDeVcR9SE+bmWLzdfiY7bCbCSVuA== + dependencies: + "@tanstack/table-core" "8.10.7" + +"@tanstack/react-virtual@3.0.0-beta.65": + version "3.0.0-beta.65" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.65.tgz#a29a10c761afd00c8000dc38adf60088656e0e62" + integrity sha512-Q21cUoE0C8Oyzy3RAMV+u4BuB+RwIf2/oQRCWksmIBp1PqLEtvXhAldh7v/wUt7WKEkislKDICZAvbYYs7EAyQ== + dependencies: + "@tanstack/virtual-core" "3.0.0-beta.65" + +"@tanstack/table-core@8.10.7": + version "8.10.7" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.10.7.tgz#577e8a635048875de4c9d6d6a3c21d26ff9f9d08" + integrity sha512-KQk5OMg5OH6rmbHZxuNROvdI+hKDIUxANaHlV+dPlNN7ED3qYQ/WkpY2qlXww1SIdeMlkIhpN/2L00rof0fXFw== + +"@tanstack/virtual-core@3.0.0-beta.65": + version "3.0.0-beta.65" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.65.tgz#fac199321db7787db9463082903dca23c0850c5c" + integrity sha512-ObP2pvXBdbivinr7BWDbGqYt4TK8wNzYsOWio+qBkDx5AJFuvqcdJxcCCYnv4dzVTe5ELA1MT4tkt8NB/tnEdA== + "@testing-library/dom@^8.3.0": version "8.19.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f" @@ -4154,6 +4448,11 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/prop-types@^15.7.7": + version "15.7.8" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.8.tgz#805eae6e8f41bd19e88917d2ea200dc992f405d3" + integrity sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ== + "@types/q@1.0.7": version "1.0.7" resolved "https://registry.npmjs.org/@types/q/-/q-1.0.7.tgz" @@ -4223,6 +4522,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.7": + version "4.4.7" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.7.tgz#bf69f269d74aa78b99097673ca6dd6824a68ef1c" + integrity sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg== + dependencies: + "@types/react" "*" + "@types/react@*": version "18.0.20" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.20.tgz#e4c36be3a55eb5b456ecf501bd4a00fd4fd0c9ab" @@ -5628,7 +5934,7 @@ babel-plugin-macros@^2.0.0: cosmiconfig "^6.0.0" resolve "^1.12.0" -babel-plugin-macros@^3.0.1: +babel-plugin-macros@^3.0.1, babel-plugin-macros@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== @@ -6786,6 +7092,11 @@ clsx@^1.0.4: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + co@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" @@ -7339,6 +7650,11 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz" integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== +csstype@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -7794,6 +8110,14 @@ dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" @@ -9746,6 +10070,11 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +highlight-words@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/highlight-words/-/highlight-words-1.2.2.tgz#9875b75d11814d7356b24f23feeb7d77761fa867" + integrity sha512-Mf4xfPXYm8Ay1wTibCrHpNWeR2nUMynMVFkXCi4mbl+TEgmNOe+I4hV7W3OCZcSvzGL6kupaqpfHOemliMTGxQ== + highlight.js@^10.4.1, highlight.js@~10.7.0: version "10.7.3" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" @@ -9777,7 +10106,7 @@ hoek@4.2.1: resolved "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz" integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -11742,6 +12071,16 @@ match-sorter@^6.0.2: "@babel/runtime" "^7.12.5" remove-accents "0.4.2" +material-react-table@^1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/material-react-table/-/material-react-table-1.15.1.tgz#c2bdfdd9c9636acbb2e8ffd5553a82395a2d9f4a" + integrity sha512-TXidRV7lGtCV5G/ON9Y38TztRcmpKFodFmyTCjvlKXCl5/9X+KY4waP8U0l16FFslg1f7HGWhfkqV5OfUfEIoA== + dependencies: + "@tanstack/match-sorter-utils" "8.8.4" + "@tanstack/react-table" "8.10.7" + "@tanstack/react-virtual" "3.0.0-beta.65" + highlight-words "1.2.2" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -13872,7 +14211,7 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== @@ -14009,6 +14348,16 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-vis@^1.11.7: version "1.11.7" resolved "https://registry.npmjs.org/react-vis/-/react-vis-1.11.7.tgz" @@ -14205,6 +14554,11 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2, regenerator-runtime@^ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" @@ -15497,6 +15851,11 @@ stylis-rule-sheet@^0.0.10: resolved "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz" integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + stylis@^3.5.0: version "3.5.4" resolved "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz" From a950cd34d53eef93f71efc096509eb7826cfc5ad Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:19:34 -0600 Subject: [PATCH 07/17] start connecting additional api calls, add test pod data --- web/dist/README.md | 3 - web/src/Root.tsx | 20 +- web/src/components/UploadLicenseFile.tsx | 45 +-- .../apps/HelmVMClusterManagement.jsx | 253 ++++++++-------- web/src/components/apps/HelmVMViewNode.jsx | 277 ++++++++++++++++-- 5 files changed, 433 insertions(+), 165 deletions(-) delete mode 100644 web/dist/README.md diff --git a/web/dist/README.md b/web/dist/README.md deleted file mode 100644 index 7f1a1f8cb0..0000000000 --- a/web/dist/README.md +++ /dev/null @@ -1,3 +0,0 @@ -The dist directory is used in the go build to embed the compiled web resources into the kotsadm binary. Because -web isn't always built (testing, okteto, etc), this README.md will allow compiling of the go binary without first -building web. \ No newline at end of file diff --git a/web/src/Root.tsx b/web/src/Root.tsx index e5566a630d..e9c942f384 100644 --- a/web/src/Root.tsx +++ b/web/src/Root.tsx @@ -101,6 +101,8 @@ type State = { selectedAppName: string | null; snapshotInProgressApps: string[]; themeState: ThemeState; + isKurl: boolean | null; + isHelmVM: boolean | null; }; let interval: ReturnType | undefined; @@ -132,6 +134,8 @@ const Root = () => { navbarLogo: null, }, app: null, + isKurl: null, + isHelmVM: null, } ); @@ -303,6 +307,8 @@ const Root = () => { adminConsoleMetadata: data.adminConsoleMetadata, featureFlags: data.consoleFeatureFlags, fetchingMetadata: false, + isKurl: data.isKurl, + isHelmVM: data.isHelmVM, }); }) .catch((err) => { @@ -532,6 +538,8 @@ const Root = () => { appSlugFromMetadata={state.appSlugFromMetadata || ""} fetchingMetadata={state.fetchingMetadata} onUploadSuccess={getAppsList} + isKurl={state.isKurl} + isHelmVM={state.isHelmVM} /> } /> @@ -574,10 +582,16 @@ const Root = () => { } /> } /> - {/* {(state.adminConsoleMetadata?.isKurl || - state.adminConsoleMetadata?.isHelmVM) && ( */} + {/* {state.adminConsoleMetadata?.isHelmVM && ( */} } + /> + {/* )} */} + {/* {(state.adminConsoleMetadata?.isKurl || + state.adminConsoleMetadata?.isHelmVM) && ( */} + @@ -588,7 +602,7 @@ const Root = () => { /> {/* )} */} {/* {state.adminConsoleMetadata?.isHelmVM && ( */} - } /> + } /> {/* )} */} Promise; - logo: string | null; - snapshot?: { name: string }; -}; - type SelectedAppToInstall = { label: string; value: string; @@ -68,6 +57,20 @@ type UploadLicenseResponse = { slug: string; success?: boolean; }; + +type Props = { + appsListLength: number; + appName: string; + appSlugFromMetadata: string; + fetchingMetadata: boolean; + isBackupRestore?: boolean; + onUploadSuccess: () => Promise; + logo: string | null; + snapshot?: { name: string }; + isHelmVM: boolean; + isKurl: boolean; +}; + const UploadLicenseFile = (props: Props) => { const [state, setState] = useReducer( (currentState: State, newState: Partial) => ({ @@ -264,6 +267,12 @@ const UploadLicenseFile = (props: Props) => { return; } + if (props.isHelmVM && !props.isKurl) { + navigate(`/${data.slug}/cluster/manage`, { replace: true }); + return; + } + // cluster manage -> config -> preflights + if (data.hasPreflight) { navigate(`/${data.slug}/preflight`, { replace: true }); return; diff --git a/web/src/components/apps/HelmVMClusterManagement.jsx b/web/src/components/apps/HelmVMClusterManagement.jsx index 8ecbf5b3e8..189560449d 100644 --- a/web/src/components/apps/HelmVMClusterManagement.jsx +++ b/web/src/components/apps/HelmVMClusterManagement.jsx @@ -1,8 +1,9 @@ import classNames from "classnames"; import dayjs from "dayjs"; -import React, { useEffect, useReducer, useState } from "react"; +import React, { useEffect, useMemo, useReducer, useState } from "react"; import Modal from "react-modal"; -import { useQuery } from "react-query"; +import { useMutation, useQuery } from "react-query"; +import { useNavigate } from "react-router-dom"; import { KotsPageTitle } from "@components/Head"; import { rbacRoles } from "../../constants/rbac"; @@ -92,16 +93,12 @@ const testData = { ], }; -const HelmVMClusterManagement = () => { +const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { const [state, setState] = useReducer( (state, newState) => ({ ...state, ...newState }), { - generating: false, - command: "", - expiry: null, displayAddNode: false, selectedNodeType: "primary", - generateCommandErrMsg: "", helmvm: null, deletNodeError: "", confirmDeleteNode: "", @@ -114,8 +111,13 @@ const HelmVMClusterManagement = () => { const [selectedNodeTypes, setSelectedNodeTypes] = useState([]); const [useStaticToken, setUseStaticToken] = useState(false); + const navigate = useNavigate(); + const nodes = testData; const nodesLoading = false; + + // #region queries + // const { data: nodes, isLoading: nodesLoading } = useQuery({ // queryKey: "helmVmNodes", // queryFn: async () => { @@ -151,6 +153,78 @@ const HelmVMClusterManagement = () => { // }, // }); + const { + data: generateSecondaryAddNodeCommand, + isLoading: generateSecondaryAddNodeCommandLoading, + error: generateSecondaryAddNodeCommandError, + } = useQuery({ + queryKey: "generateSecondaryAddNodeCommand", + queryFn: async () => { + return ( + await fetch( + `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command-secondary`, + { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + method: "POST", + } + ) + ).json(); + }, + }); + + const { + data: generatePrimaryAddNodeCommand, + isLoading: generatePrimaryAddNodeCommandLoading, + error: generatePrimaryAddNodeCommandError, + } = useQuery({ + queryKey: "generatePrimaryAddNodeCommand", + queryFn: async () => { + return ( + await fetch( + `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command-primary`, + { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + method: "POST", + } + ) + ).json(); + }, + }); + + const { + mutate: addNodeType, + isLoading: addNodeTypeLoading, + error: addNodeTypeError, + } = useMutation({ + mutationFn: async () => { + return ( + await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + method: "POST", + }) + ).json(); + }, + onSuccess: () => { + // if (fromLicenseFlow && data.isConfigurable) { + // navigate(`/${data.slug}/config`, { replace: true }); + // return; + // } + }, + }); + // #endregion + const deleteNode = (name) => { setState({ confirmDeleteNode: name, @@ -191,49 +265,6 @@ const HelmVMClusterManagement = () => { }); }; - const generateWorkerAddNodeCommand = async () => { - setState({ - generating: true, - command: "", - expiry: null, - generateCommandErrMsg: "", - }); - - fetch( - `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command-secondary`, - { - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - method: "POST", - } - ) - .then(async (res) => { - if (!res.ok) { - setState({ - generating: false, - generateCommandErrMsg: `Failed to generate command with status ${res.status}`, - }); - } else { - const data = await res.json(); - setState({ - generating: false, - command: data.command, - expiry: data.expiry, - }); - } - }) - .catch((err) => { - console.log(err); - setState({ - generating: false, - generateCommandErrMsg: err ? err.message : "Something went wrong", - }); - }); - }; - const onDrainNodeClick = (name) => { setState({ showConfirmDrainModal: true, @@ -269,49 +300,6 @@ const HelmVMClusterManagement = () => { }); }; - const generatePrimaryAddNodeCommand = async () => { - setState({ - generating: true, - command: "", - expiry: null, - generateCommandErrMsg: "", - }); - - fetch( - `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command-primary`, - { - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - method: "POST", - } - ) - .then(async (res) => { - if (!res.ok) { - setState({ - generating: false, - generateCommandErrMsg: `Failed to generate command with status ${res.status}`, - }); - } else { - const data = await res.json(); - setState({ - generating: false, - command: data.command, - expiry: data.expiry, - }); - } - }) - .catch((err) => { - console.log(err); - setState({ - generating: false, - generateCommandErrMsg: err ? err.message : "Something went wrong", - }); - }); - }; - const onAddNodeClick = () => { setState({ displayAddNode: true, @@ -365,6 +353,13 @@ const HelmVMClusterManagement = () => { } }; + const handleContinue = () => { + if (data.isConfigurable) { + navigate(`/${data.slug}/config`, { replace: true }); + return; + } + }; + if (nodesLoading) { return (
@@ -377,39 +372,48 @@ const HelmVMClusterManagement = () => {
-
-
-

- Cluster Nodes -

-

+

+

+ Cluster Nodes +

+
+

This section lists the nodes that are configured and shows the - status/health of each. To add additional nodes to this cluster, - click the "Add node" button at the bottom of this page. + status/health of each.

-
- {(nodes?.nodes || testData?.nodes) && - (nodes?.nodes || testData?.nodes).map((node, i) => ( - - ))} -
-
- {Utilities.sessionRolesHasOneOf([rbacRoles.CLUSTER_ADMIN]) && ( -
- -
+ )} +
+
+ {(nodes?.nodes || testData?.nodes) && + (nodes?.nodes || testData?.nodes).map((node, i) => ( + + ))} +
+ {fromLicenseFlow && ( + )}
+ {/* MODALS */} setState({ displayAddNode: false })} @@ -420,7 +424,7 @@ const HelmVMClusterManagement = () => {

- Add A Node + Add a Node Type

{ />

- To add a node to this cluster, select the type of node you are + To add a node type to this cluster, select the type of node you are adding, and then select an installation method below. This screen will automatically show the status when the node successfully joins the cluster. @@ -468,11 +472,12 @@ const HelmVMClusterManagement = () => {

Copied!} > - {`curl http://node.something/join?token=abc&labels=${selectedNodeTypes.join( + {`curl ${generatePrimaryAddNodeCommand}?token=abc&labels=${selectedNodeTypes.join( "," )}`} @@ -504,7 +509,7 @@ const HelmVMClusterManagement = () => { disabled={selectedNodeTypes.length === 0} onClick={() => setState({ displayAddNode: false })} > - Add node + Add node type
diff --git a/web/src/components/apps/HelmVMViewNode.jsx b/web/src/components/apps/HelmVMViewNode.jsx index c607a3ed08..f47d18c4f9 100644 --- a/web/src/components/apps/HelmVMViewNode.jsx +++ b/web/src/components/apps/HelmVMViewNode.jsx @@ -1,5 +1,7 @@ +import { MaterialReactTable } from "material-react-table"; import React, { useMemo } from "react"; import { useQuery } from "react-query"; +import { useParams } from "react-router"; import { Link } from "react-router-dom"; const testData = { @@ -41,6 +43,196 @@ const testData = { pidPressure: false, ready: true, }, + podList: [ + { + metadata: { + name: "example-es-85fc9df74-g9jbn", + generateName: "example-es-85fc9df74-", + namespace: "helmvm", + uid: "1caba3fb-bd52-430a-9cff-0eb0939317fa", + resourceVersion: "40284", + creationTimestamp: "2023-10-17T16:22:37Z", + labels: { + app: "example", + component: "es", + "kots.io/app-slug": "laverya-minimal-kots", + "kots.io/backup": "velero", + "pod-template-hash": "85fc9df74", + }, + annotations: { + "cni.projectcalico.org/containerID": + "c3fa12aad2ed6f726ecda31f7f94d1224c9f50a805a9efc67aaf4959e464434c", + "cni.projectcalico.org/podIP": "10.244.45.141/32", + "cni.projectcalico.org/podIPs": "10.244.45.141/32", + "kots.io/app-slug": "laverya-minimal-kots", + }, + ownerReferences: [ + { + apiVersion: "apps/v1", + kind: "ReplicaSet", + name: "example-es-85fc9df74", + uid: "b5008bca-1ad0-4107-8603-397fc3be74f8", + controller: true, + blockOwnerDeletion: true, + }, + ], + }, + spec: { + volumes: [ + { + name: "kube-api-access-fhfc4", + projected: { + sources: [ + { + serviceAccountToken: { + expirationSeconds: 3607, + path: "token", + }, + }, + { + configMap: { + name: "kube-root-ca.crt", + items: [{ key: "ca.crt", path: "ca.crt" }], + }, + }, + { + downwardAPI: { + items: [ + { + path: "namespace", + fieldRef: { + apiVersion: "v1", + fieldPath: "metadata.namespace", + }, + }, + ], + }, + }, + ], + defaultMode: 420, + }, + }, + ], + containers: [ + { + name: "es", + image: + "docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.21", + envFrom: [{ configMapRef: { name: "example-config" } }], + resources: { + limits: { cpu: "500m", memory: "256Mi" }, + requests: { cpu: "50m", memory: "16Mi" }, + }, + volumeMounts: [ + { + name: "kube-api-access-fhfc4", + readOnly: true, + mountPath: "/var/run/secrets/kubernetes.io/serviceaccount", + }, + ], + terminationMessagePath: "/dev/termination-log", + terminationMessagePolicy: "File", + imagePullPolicy: "IfNotPresent", + }, + ], + restartPolicy: "Always", + terminationGracePeriodSeconds: 30, + dnsPolicy: "ClusterFirst", + serviceAccountName: "default", + serviceAccount: "default", + nodeName: "laverya-helmvm", + securityContext: {}, + imagePullSecrets: [{ name: "laverya-minimal-kots-registry" }], + schedulerName: "default-scheduler", + tolerations: [ + { + key: "node.kubernetes.io/not-ready", + operator: "Exists", + effect: "NoExecute", + tolerationSeconds: 300, + }, + { + key: "node.kubernetes.io/unreachable", + operator: "Exists", + effect: "NoExecute", + tolerationSeconds: 300, + }, + ], + priority: 0, + enableServiceLinks: true, + preemptionPolicy: "PreemptLowerPriority", + }, + status: { + phase: "Running", + conditions: [ + { + type: "Initialized", + status: "True", + lastProbeTime: null, + lastTransitionTime: "2023-10-17T16:22:37Z", + }, + { + type: "Ready", + status: "False", + lastProbeTime: null, + lastTransitionTime: "2023-10-17T19:55:16Z", + reason: "ContainersNotReady", + message: "containers with unready status: [es]", + }, + { + type: "ContainersReady", + status: "False", + lastProbeTime: null, + lastTransitionTime: "2023-10-17T19:55:16Z", + reason: "ContainersNotReady", + message: "containers with unready status: [es]", + }, + { + type: "PodScheduled", + status: "True", + lastProbeTime: null, + lastTransitionTime: "2023-10-17T16:22:37Z", + }, + ], + hostIP: "10.128.0.44", + podIP: "10.244.45.141", + podIPs: [{ ip: "10.244.45.141" }], + startTime: "2023-10-17T16:22:37Z", + containerStatuses: [ + { + name: "es", + state: { + waiting: { + reason: "CrashLoopBackOff", + message: + "back-off 5m0s restarting failed container=es pod=example-es-85fc9df74-g9jbn_helmvm(1caba3fb-bd52-430a-9cff-0eb0939317fa)", + }, + }, + lastState: { + terminated: { + exitCode: 137, + reason: "OOMKilled", + startedAt: "2023-10-17T19:55:11Z", + finishedAt: "2023-10-17T19:55:13Z", + containerID: + "containerd://9cce5c792b7ad61d040f7b8aca042d13a714100c75ebc40e71eb5444bbb65e83", + }, + }, + ready: false, + restartCount: 46, + image: + "docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.21", + imageID: + "docker.elastic.co/elasticsearch/elasticsearch-oss@sha256:86e7750c4d896d41bd638b6e510e0610b98fd9fa48f8caeeed8ccd8424b1dc9f", + containerID: + "containerd://9cce5c792b7ad61d040f7b8aca042d13a714100c75ebc40e71eb5444bbb65e83", + started: false, + }, + ], + qosClass: "Burstable", + }, + }, + ], }, { name: "test-helmvm-worker", @@ -79,11 +271,13 @@ const testData = { }; const HelmVMViewNode = () => { - // const { data: nodes } = useQuery({ - // queryKey: "helmVmNodes", - // queryFn: async () => { + // const { nodeName } = useParams(); + // const { data: node } = useQuery({ + // queryKey: ["helmVmNode", nodeName], + // queryFn: async ({ queryKey }) => { + // const [, nodeName] = queryKey; // return ( - // await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { + // await fetch(`${process.env.API_ENDPOINT}/helmvm/node/${nodeName}`, { // headers: { // Accept: "application/json", // }, @@ -125,14 +319,14 @@ const HelmVMViewNode = () => { size: 150, }, { - accessorKey: "isConnected", - header: "Connection", + accessorKey: "status", + header: "Status", size: 150, }, { - accessorKey: "kubeletVersion", - header: "Kubelet Version", - size: 170, + accessorKey: "disk", + header: "Disk", + size: 150, }, { accessorKey: "cpu", @@ -144,20 +338,30 @@ const HelmVMViewNode = () => { header: "Memory", size: 150, }, - { - accessorKey: "pods", - header: "Pods", - size: 150, - }, { accessorKey: "canDelete", - header: "Delete Node", + header: "Delete Pod", size: 150, }, ], [] ); + const mappedPods = useMemo(() => { + return node.podList.map((n) => ({ + name: n.metadata.name, + status: n.status.phase, + disk: null, + cpu: null, + memory: null, + canDelete: ( + <> + + + ), + })); + }, [node.podList]); + return (
{/* Breadcrumbs */} @@ -189,7 +393,7 @@ const HelmVMViewNode = () => { tw-shadow-md tw-p-3" >

Pods

-
+ {/*
{columns.map((col) => { @@ -204,7 +408,46 @@ const HelmVMViewNode = () => { Some pods here -
+ */} +
{/* Troubleshooting */}
Date: Tue, 17 Oct 2023 17:04:33 -0600 Subject: [PATCH 08/17] add material react table to display pods --- web/src/components/apps/HelmVMViewNode.jsx | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/web/src/components/apps/HelmVMViewNode.jsx b/web/src/components/apps/HelmVMViewNode.jsx index f47d18c4f9..7992ca57be 100644 --- a/web/src/components/apps/HelmVMViewNode.jsx +++ b/web/src/components/apps/HelmVMViewNode.jsx @@ -363,7 +363,7 @@ const HelmVMViewNode = () => { }, [node.podList]); return ( -
+
{/* Breadcrumbs */}

{ tw-shadow-md tw-p-3" >

Pods

- {/* - - - {columns.map((col) => { - return ( - - ); - })} - - - Some pods here -
-

- {col.header} -

-
*/} { muiTablePaperProps={{ sx: { width: "100%", - marginTop: "28px", + boxShadow: "none", }, }} initialState={{ density: "compact" }} From 75d35971a20eaa174c240c29b5416587ebd16c00 Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Tue, 17 Oct 2023 17:11:12 -0600 Subject: [PATCH 09/17] revert change --- web/dist/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 web/dist/README.md diff --git a/web/dist/README.md b/web/dist/README.md new file mode 100644 index 0000000000..7f1a1f8cb0 --- /dev/null +++ b/web/dist/README.md @@ -0,0 +1,3 @@ +The dist directory is used in the go build to embed the compiled web resources into the kotsadm binary. Because +web isn't always built (testing, okteto, etc), this README.md will allow compiling of the go binary without first +building web. \ No newline at end of file From a1cae3b6a20744b154c5e033d987488f73c6008d Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Tue, 17 Oct 2023 17:25:05 -0600 Subject: [PATCH 10/17] uncomment real queries --- .../apps/HelmVMClusterManagement.jsx | 77 ++++++++----------- web/src/components/apps/HelmVMViewNode.jsx | 72 ++++++++--------- 2 files changed, 70 insertions(+), 79 deletions(-) diff --git a/web/src/components/apps/HelmVMClusterManagement.jsx b/web/src/components/apps/HelmVMClusterManagement.jsx index 189560449d..4e2ede85d0 100644 --- a/web/src/components/apps/HelmVMClusterManagement.jsx +++ b/web/src/components/apps/HelmVMClusterManagement.jsx @@ -113,45 +113,42 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { const navigate = useNavigate(); - const nodes = testData; - const nodesLoading = false; - // #region queries - // const { data: nodes, isLoading: nodesLoading } = useQuery({ - // queryKey: "helmVmNodes", - // queryFn: async () => { - // return ( - // await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { - // headers: { - // Accept: "application/json", - // }, - // credentials: "include", - // method: "GET", - // }) - // ).json(); - // }, - // onError: (err) => { - // if (err.status === 401) { - // Utilities.logoutUser(); - // return; - // } - // console.log( - // "failed to get node status list, unexpected status code", - // err.status - // ); - // }, - // onSuccess: (data) => { - // setState({ - // // if cluster doesn't support ha, then primary will be disabled. Force into secondary - // selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, - // }); - // }, - // config: { - // refetchInterval: 1000, - // retry: false, - // }, - // }); + const { data: nodes, isLoading: nodesLoading } = useQuery({ + queryKey: "helmVmNodes", + queryFn: async () => { + return ( + await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { + headers: { + Accept: "application/json", + }, + credentials: "include", + method: "GET", + }) + ).json(); + }, + onError: (err) => { + if (err.status === 401) { + Utilities.logoutUser(); + return; + } + console.log( + "failed to get node status list, unexpected status code", + err.status + ); + }, + onSuccess: (data) => { + setState({ + // if cluster doesn't support ha, then primary will be disabled. Force into secondary + selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, + }); + }, + config: { + refetchInterval: 1000, + retry: false, + }, + }); const { data: generateSecondaryAddNodeCommand, @@ -216,12 +213,6 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { }) ).json(); }, - onSuccess: () => { - // if (fromLicenseFlow && data.isConfigurable) { - // navigate(`/${data.slug}/config`, { replace: true }); - // return; - // } - }, }); // #endregion diff --git a/web/src/components/apps/HelmVMViewNode.jsx b/web/src/components/apps/HelmVMViewNode.jsx index 7992ca57be..e22874c3ca 100644 --- a/web/src/components/apps/HelmVMViewNode.jsx +++ b/web/src/components/apps/HelmVMViewNode.jsx @@ -271,43 +271,43 @@ const testData = { }; const HelmVMViewNode = () => { - // const { nodeName } = useParams(); - // const { data: node } = useQuery({ - // queryKey: ["helmVmNode", nodeName], - // queryFn: async ({ queryKey }) => { - // const [, nodeName] = queryKey; - // return ( - // await fetch(`${process.env.API_ENDPOINT}/helmvm/node/${nodeName}`, { - // headers: { - // Accept: "application/json", - // }, - // credentials: "include", - // method: "GET", - // }) - // ).json(); - // }, - // onError: (err) => { - // if (err.status === 401) { - // Utilities.logoutUser(); - // return; - // } - // console.log( - // "failed to get node status list, unexpected status code", - // err.status - // ); - // }, - // onSuccess: (data) => { - // setState({ - // // if cluster doesn't support ha, then primary will be disabled. Force into secondary - // selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, - // }); - // }, - // config: { - // retry: false, - // }, - // }); + const { nodeName } = useParams(); + const { data: nodeData } = useQuery({ + queryKey: ["helmVmNode", nodeName], + queryFn: async ({ queryKey }) => { + const [, nodeName] = queryKey; + return ( + await fetch(`${process.env.API_ENDPOINT}/helmvm/node/${nodeName}`, { + headers: { + Accept: "application/json", + }, + credentials: "include", + method: "GET", + }) + ).json(); + }, + onError: (err) => { + if (err.status === 401) { + Utilities.logoutUser(); + return; + } + console.log( + "failed to get node status list, unexpected status code", + err.status + ); + }, + onSuccess: (data) => { + setState({ + // if cluster doesn't support ha, then primary will be disabled. Force into secondary + selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, + }); + }, + config: { + retry: false, + }, + }); - const node = testData.nodes[0]; + const node = nodeData || testData.nodes[0]; const columns = useMemo( () => [ From 8240eaa8c7542cecf27ec706f2689ddbfb323aad Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:19:08 -0600 Subject: [PATCH 11/17] fix useparams import --- web/src/components/apps/HelmVMViewNode.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/components/apps/HelmVMViewNode.jsx b/web/src/components/apps/HelmVMViewNode.jsx index e22874c3ca..c3337352d0 100644 --- a/web/src/components/apps/HelmVMViewNode.jsx +++ b/web/src/components/apps/HelmVMViewNode.jsx @@ -1,8 +1,7 @@ import { MaterialReactTable } from "material-react-table"; import React, { useMemo } from "react"; import { useQuery } from "react-query"; -import { useParams } from "react-router"; -import { Link } from "react-router-dom"; +import { Link, useParams } from "react-router-dom"; const testData = { isHelmVMEnabled: true, From 300856e4d789c58892ec59789f3aa9d4d116c8b1 Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:51:43 -0600 Subject: [PATCH 12/17] fix params/routing, update api route --- web/src/Root.tsx | 25 +- ...gement.jsx => HelmVMClusterManagement.tsx} | 288 ++++++++++-------- web/src/components/apps/HelmVMNodeRow.jsx | 24 +- web/src/components/apps/HelmVMViewNode.jsx | 4 +- 4 files changed, 188 insertions(+), 153 deletions(-) rename web/src/components/apps/{HelmVMClusterManagement.jsx => HelmVMClusterManagement.tsx} (73%) diff --git a/web/src/Root.tsx b/web/src/Root.tsx index e9c942f384..f04f6951fd 100644 --- a/web/src/Root.tsx +++ b/web/src/Root.tsx @@ -538,8 +538,8 @@ const Root = () => { appSlugFromMetadata={state.appSlugFromMetadata || ""} fetchingMetadata={state.fetchingMetadata} onUploadSuccess={getAppsList} - isKurl={state.isKurl} - isHelmVM={state.isHelmVM} + isKurl={!!state.isKurl} + isHelmVM={!!state.isHelmVM} /> } /> @@ -585,7 +585,12 @@ const Root = () => { {/* {state.adminConsoleMetadata?.isHelmVM && ( */} } + element={ + + } /> {/* )} */} {/* {(state.adminConsoleMetadata?.isKurl || @@ -594,7 +599,7 @@ const Root = () => { path="/:slug/cluster/manage" element={ state.adminConsoleMetadata?.isKurl ? ( - + ) : ( ) @@ -602,7 +607,10 @@ const Root = () => { /> {/* )} */} {/* {state.adminConsoleMetadata?.isHelmVM && ( */} - } /> + } + /> {/* )} */} { } /> - } + element={} /> {/* WHERE IS SELECTEDAPP */} {state.app?.isAppIdentityServiceSupported && ( diff --git a/web/src/components/apps/HelmVMClusterManagement.jsx b/web/src/components/apps/HelmVMClusterManagement.tsx similarity index 73% rename from web/src/components/apps/HelmVMClusterManagement.jsx rename to web/src/components/apps/HelmVMClusterManagement.tsx index 4e2ede85d0..a51ecaddc9 100644 --- a/web/src/components/apps/HelmVMClusterManagement.jsx +++ b/web/src/components/apps/HelmVMClusterManagement.tsx @@ -1,13 +1,12 @@ import classNames from "classnames"; -import dayjs from "dayjs"; -import React, { useEffect, useMemo, useReducer, useState } from "react"; +import React, { ChangeEvent, useReducer, useState } from "react"; import Modal from "react-modal"; -import { useMutation, useQuery } from "react-query"; +import { useQuery } from "react-query"; import { useNavigate } from "react-router-dom"; import { KotsPageTitle } from "@components/Head"; +import { useApps } from "@features/App"; import { rbacRoles } from "../../constants/rbac"; -import { Repeater } from "../../utilities/repeater"; import { Utilities } from "../../utilities/utilities"; import Icon from "../Icon"; import ErrorModal from "../modals/ErrorModal"; @@ -93,50 +92,106 @@ const testData = { ], }; -const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { +type State = { + displayAddNode: boolean; + selectedNodeType: string; + confirmDeleteNode: string; + deleteNodeError: string; + showConfirmDrainModal: boolean; + nodeNameToDrain: string; + drainingNodeName: string | null; + drainNodeSuccessful: boolean; +}; + +const HelmVMClusterManagement = ({ + fromLicenseFlow = false, + appName, +}: { + fromLicenseFlow?: boolean; + appName?: string; +}) => { const [state, setState] = useReducer( - (state, newState) => ({ ...state, ...newState }), + (prevState: State, newState: Partial) => ({ + ...prevState, + ...newState, + }), { displayAddNode: false, selectedNodeType: "primary", - helmvm: null, - deletNodeError: "", confirmDeleteNode: "", + deleteNodeError: "", showConfirmDrainModal: false, nodeNameToDrain: "", drainingNodeName: null, drainNodeSuccessful: false, } ); - const [selectedNodeTypes, setSelectedNodeTypes] = useState([]); + const [selectedNodeTypes, setSelectedNodeTypes] = useState([]); const [useStaticToken, setUseStaticToken] = useState(false); const navigate = useNavigate(); // #region queries - const { data: nodes, isLoading: nodesLoading } = useQuery({ + type NodesResponse = { + ha: boolean; + isHelmVMEnabled: boolean; + nodes: { + name: string; + isConnected: boolean; + isReady: boolean; + isPrimaryNode: boolean; + canDelete: boolean; + kubeletVersion: string; + cpu: { + capacity: number; + available: number; + }; + memory: { + capacity: number; + available: number; + }; + pods: { + capacity: number; + available: number; + }; + labels: string[]; + conditions: { + memoryPressure: boolean; + diskPressure: boolean; + pidPressure: boolean; + ready: boolean; + }; + }[]; + }; + + const { data: nodesData, isLoading: nodesLoading } = useQuery< + NodesResponse, + Error, + NodesResponse, + string + >({ queryKey: "helmVmNodes", queryFn: async () => { - return ( - await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { - headers: { - Accept: "application/json", - }, - credentials: "include", - method: "GET", - }) - ).json(); - }, - onError: (err) => { - if (err.status === 401) { + const res = await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { + headers: { + Accept: "application/json", + }, + credentials: "include", + method: "GET", + }); + if (res.status === 401) { Utilities.logoutUser(); - return; + console.log( + "failed to get node status list, unexpected status code", + res.status + ); + const error = await res.json(); + throw new Error( + error?.error?.message || error?.error || error?.message + ); } - console.log( - "failed to get node status list, unexpected status code", - err.status - ); + return res.json(); }, onSuccess: (data) => { setState({ @@ -144,45 +199,20 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, }); }, - config: { - refetchInterval: 1000, - retry: false, - }, - }); - - const { - data: generateSecondaryAddNodeCommand, - isLoading: generateSecondaryAddNodeCommandLoading, - error: generateSecondaryAddNodeCommandError, - } = useQuery({ - queryKey: "generateSecondaryAddNodeCommand", - queryFn: async () => { - return ( - await fetch( - `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command-secondary`, - { - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - method: "POST", - } - ) - ).json(); - }, + refetchInterval: 1000, + retry: false, }); const { - data: generatePrimaryAddNodeCommand, - isLoading: generatePrimaryAddNodeCommandLoading, - error: generatePrimaryAddNodeCommandError, + data: generateAddNodeCommand, + isLoading: generateAddNodeCommandLoading, + // error: generateAddNodeCommandError, } = useQuery({ - queryKey: "generatePrimaryAddNodeCommand", + queryKey: "generateAddNodeCommand", queryFn: async () => { return ( await fetch( - `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command-primary`, + `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command`, { headers: { "Content-Type": "application/json", @@ -190,33 +220,37 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { }, credentials: "include", method: "POST", + body: JSON.stringify({ + roles: selectedNodeTypes.join(","), + }), } ) ).json(); }, }); - const { - mutate: addNodeType, - isLoading: addNodeTypeLoading, - error: addNodeTypeError, - } = useMutation({ - mutationFn: async () => { - return ( - await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - method: "POST", - }) - ).json(); - }, - }); + // TODO: import useMutation + // const { + // mutate: addNodeType, + // isLoading: addNodeTypeLoading, + // error: addNodeTypeError, + // } = useMutation({ + // mutationFn: async () => { + // return ( + // await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { + // headers: { + // "Content-Type": "application/json", + // Accept: "application/json", + // }, + // credentials: "include", + // method: "POST", + // }) + // ).json(); + // }, + // }); // #endregion - const deleteNode = (name) => { + const deleteNode = (name: string) => { setState({ confirmDeleteNode: name, }); @@ -256,14 +290,14 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { }); }; - const onDrainNodeClick = (name) => { + const onDrainNodeClick = (name: string) => { setState({ showConfirmDrainModal: true, nodeNameToDrain: name, }); }; - const drainNode = async (name) => { + const drainNode = async (name: string) => { setState({ showConfirmDrainModal: false, drainingNodeName: name }); fetch(`${process.env.API_ENDPOINT}/helmvm/nodes/${name}/drain`, { headers: { @@ -273,7 +307,7 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { credentials: "include", method: "POST", }) - .then(async (res) => { + .then(async () => { setState({ drainNodeSuccessful: true }); setTimeout(() => { setState({ @@ -301,42 +335,25 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { setState({ deleteNodeError: "" }); }; - const NODE_TYPES = [ - "controlplane", - "db", - "app", - "search", - "webserver", - "jobs", - ]; + const NODE_TYPES = ["controller"]; - const determineDisabledState = (nodeType, selectedNodeTypes) => { - if (nodeType === "controlplane") { - const numControlPlanes = testData.nodes.reduce((acc, node) => { - if (node.labels.includes("controlplane")) { - acc++; - } - return acc; - }); - return numControlPlanes === 3; - } - if ( - (nodeType === "db" || nodeType === "search") && - selectedNodeTypes.includes("webserver") - ) { - return true; - } + const determineDisabledState = () => { + // if (nodeType === "controller") { + // const numControllers = testData.nodes.reduce((acc, node) => { + // if (node.labels.includes("controller")) { + // acc += 1; + // } + // return acc; + // }, 0); + // return numControllers === 3; + // } return false; }; - const handleSelectNodeType = (e) => { - const nodeType = e.currentTarget.value; + const handleSelectNodeType = (e: ChangeEvent) => { + let nodeType = e.currentTarget.value; let types = selectedNodeTypes; - if (nodeType === "webserver") { - types = types.filter((type) => type !== "db" && type !== "search"); - } - if (selectedNodeTypes.includes(nodeType)) { setSelectedNodeTypes(types.filter((type) => type !== nodeType)); } else { @@ -344,9 +361,12 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { } }; + const { data } = useApps(); + const handleContinue = () => { - if (data.isConfigurable) { - navigate(`/${data.slug}/config`, { replace: true }); + const app = data?.apps?.find((a) => a.name === appName); + if (app?.isConfigurable) { + navigate(`/${app.slug}/config`, { replace: true }); return; } }; @@ -382,15 +402,17 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { )}
- {(nodes?.nodes || testData?.nodes) && - (nodes?.nodes || testData?.nodes).map((node, i) => ( + {(nodesData?.nodes || testData?.nodes) && + (nodesData?.nodes || testData?.nodes).map((node, i) => ( ))}
@@ -436,10 +458,7 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { key={nodeType} className={classNames("BoxedCheckbox", { "is-active": selectedNodeTypes.includes(nodeType), - "is-disabled": determineDisabledState( - nodeType, - selectedNodeTypes - ), + "is-disabled": determineDisabledState(nodeType), })} > { type="checkbox" name={`${nodeType}NodeType`} value={nodeType} - disabled={determineDisabledState(nodeType, selectedNodeTypes)} + disabled={determineDisabledState(nodeType)} checked={selectedNodeTypes.includes(nodeType)} onChange={handleSelectNodeType} /> @@ -456,22 +475,25 @@ const HelmVMClusterManagement = ({ fromLicenseFlow = false }) => { 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} + {nodeType === "controller" ? "controlplane" : nodeType}
))}
- Copied!} - > - {`curl ${generatePrimaryAddNodeCommand}?token=abc&labels=${selectedNodeTypes.join( - "," - )}`} - + {generateAddNodeCommandLoading &&

Generating command...

} + {!generateAddNodeCommandLoading && generateAddNodeCommand?.command && ( + Copied! + } + > + {generateAddNodeCommand?.command || ""} + + )}
{ if (drainNode && Utilities.sessionRolesHasOneOf(rbacRoles.DRAIN_NODE)) { if ( @@ -60,17 +62,25 @@ export default function HelmVMNodeRow({ } } }; + console.log("slug", slug); return (
- - {node?.name} - + {slug && ( + + {node?.name} + + )} + {!slug && ( +

+ {node?.name} +

+ )} {node?.isPrimaryNode && ( Primary node diff --git a/web/src/components/apps/HelmVMViewNode.jsx b/web/src/components/apps/HelmVMViewNode.jsx index c3337352d0..a72f13648f 100644 --- a/web/src/components/apps/HelmVMViewNode.jsx +++ b/web/src/components/apps/HelmVMViewNode.jsx @@ -270,7 +270,7 @@ const testData = { }; const HelmVMViewNode = () => { - const { nodeName } = useParams(); + const { slug, nodeName } = useParams(); const { data: nodeData } = useQuery({ queryKey: ["helmVmNode", nodeName], queryFn: async ({ queryKey }) => { @@ -366,7 +366,7 @@ const HelmVMViewNode = () => { {/* Breadcrumbs */}

Cluster Nodes From 0e6db8526cb206088150b3560bbd4585038243a1 Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:05:43 -0600 Subject: [PATCH 13/17] fix loading/refetch state --- web/src/components/apps/HelmVMClusterManagement.tsx | 2 +- web/src/components/apps/HelmVMNodeRow.jsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/components/apps/HelmVMClusterManagement.tsx b/web/src/components/apps/HelmVMClusterManagement.tsx index a51ecaddc9..e08ee32ddc 100644 --- a/web/src/components/apps/HelmVMClusterManagement.tsx +++ b/web/src/components/apps/HelmVMClusterManagement.tsx @@ -165,7 +165,7 @@ const HelmVMClusterManagement = ({ }[]; }; - const { data: nodesData, isLoading: nodesLoading } = useQuery< + const { data: nodesData, isInitialLoading: nodesLoading } = useQuery< NodesResponse, Error, NodesResponse, diff --git a/web/src/components/apps/HelmVMNodeRow.jsx b/web/src/components/apps/HelmVMNodeRow.jsx index 94c9a42bdb..9386035869 100644 --- a/web/src/components/apps/HelmVMNodeRow.jsx +++ b/web/src/components/apps/HelmVMNodeRow.jsx @@ -62,7 +62,6 @@ export default function HelmVMNodeRow({ } } }; - console.log("slug", slug); return (

From 766e089be0998dfc6976bc469870ed92963069af Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Wed, 18 Oct 2023 13:07:50 -0600 Subject: [PATCH 14/17] update generate add node request --- web/src/components/apps/HelmVMClusterManagement.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/src/components/apps/HelmVMClusterManagement.tsx b/web/src/components/apps/HelmVMClusterManagement.tsx index e08ee32ddc..457f92e93f 100644 --- a/web/src/components/apps/HelmVMClusterManagement.tsx +++ b/web/src/components/apps/HelmVMClusterManagement.tsx @@ -208,8 +208,9 @@ const HelmVMClusterManagement = ({ isLoading: generateAddNodeCommandLoading, // error: generateAddNodeCommandError, } = useQuery({ - queryKey: "generateAddNodeCommand", - queryFn: async () => { + queryKey: ["generateAddNodeCommand", selectedNodeTypes], + queryFn: async ({ queryKey }) => { + const [, selectedNodeTypes] = queryKey; return ( await fetch( `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command`, @@ -221,12 +222,13 @@ const HelmVMClusterManagement = ({ credentials: "include", method: "POST", body: JSON.stringify({ - roles: selectedNodeTypes.join(","), + roles: selectedNodeTypes, }), } ) ).json(); }, + enabled: selectedNodeTypes.length > 0, }); // TODO: import useMutation From 3c12708661a5c838f985ddee39bf3c26977b7ab6 Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:41:25 -0600 Subject: [PATCH 15/17] add error handling, add mui react table to cluster manage page --- web/src/Root.tsx | 4 +- web/src/components/UploadLicenseFile.tsx | 1 - .../apps/HelmVMClusterManagement.tsx | 330 ++++++++++++------ web/src/components/apps/HelmVMNodeRow.jsx | 293 ---------------- web/src/components/apps/HelmVMViewNode.jsx | 10 +- 5 files changed, 229 insertions(+), 409 deletions(-) delete mode 100644 web/src/components/apps/HelmVMNodeRow.jsx diff --git a/web/src/Root.tsx b/web/src/Root.tsx index f04f6951fd..2013369166 100644 --- a/web/src/Root.tsx +++ b/web/src/Root.tsx @@ -584,7 +584,7 @@ const Root = () => { } /> {/* {state.adminConsoleMetadata?.isHelmVM && ( */} { {/* {(state.adminConsoleMetadata?.isKurl || state.adminConsoleMetadata?.isHelmVM) && ( */} diff --git a/web/src/components/UploadLicenseFile.tsx b/web/src/components/UploadLicenseFile.tsx index eabf2636f2..f2b6a9d500 100644 --- a/web/src/components/UploadLicenseFile.tsx +++ b/web/src/components/UploadLicenseFile.tsx @@ -271,7 +271,6 @@ const UploadLicenseFile = (props: Props) => { navigate(`/${data.slug}/cluster/manage`, { replace: true }); return; } - // cluster manage -> config -> preflights if (data.hasPreflight) { navigate(`/${data.slug}/preflight`, { replace: true }); diff --git a/web/src/components/apps/HelmVMClusterManagement.tsx b/web/src/components/apps/HelmVMClusterManagement.tsx index 457f92e93f..3670fd1805 100644 --- a/web/src/components/apps/HelmVMClusterManagement.tsx +++ b/web/src/components/apps/HelmVMClusterManagement.tsx @@ -1,8 +1,9 @@ import classNames from "classnames"; -import React, { ChangeEvent, useReducer, useState } from "react"; +import MaterialReactTable from "material-react-table"; +import React, { ChangeEvent, useMemo, useReducer, useState } from "react"; import Modal from "react-modal"; import { useQuery } from "react-query"; -import { useNavigate } from "react-router-dom"; +import { Link } from "react-router-dom"; import { KotsPageTitle } from "@components/Head"; import { useApps } from "@features/App"; @@ -11,8 +12,6 @@ import { Utilities } from "../../utilities/utilities"; import Icon from "../Icon"; import ErrorModal from "../modals/ErrorModal"; import CodeSnippet from "../shared/CodeSnippet"; -import Loader from "../shared/Loader"; -import HelmVMNodeRow from "./HelmVMNodeRow"; import "@src/scss/components/apps/HelmVMClusterManagement.scss"; @@ -94,7 +93,6 @@ const testData = { type State = { displayAddNode: boolean; - selectedNodeType: string; confirmDeleteNode: string; deleteNodeError: string; showConfirmDrainModal: boolean; @@ -117,7 +115,6 @@ const HelmVMClusterManagement = ({ }), { displayAddNode: false, - selectedNodeType: "primary", confirmDeleteNode: "", deleteNodeError: "", showConfirmDrainModal: false, @@ -127,12 +124,11 @@ const HelmVMClusterManagement = ({ } ); const [selectedNodeTypes, setSelectedNodeTypes] = useState([]); - const [useStaticToken, setUseStaticToken] = useState(false); - const navigate = useNavigate(); + const { data: appsData } = useApps(); + const app = appsData?.apps?.find((a) => a.name === appName); // #region queries - type NodesResponse = { ha: boolean; isHelmVMEnabled: boolean; @@ -165,12 +161,11 @@ const HelmVMClusterManagement = ({ }[]; }; - const { data: nodesData, isInitialLoading: nodesLoading } = useQuery< - NodesResponse, - Error, - NodesResponse, - string - >({ + const { + data: nodesData, + isInitialLoading: nodesLoading, + error: nodesError, + } = useQuery({ queryKey: "helmVmNodes", queryFn: async () => { const res = await fetch(`${process.env.API_ENDPOINT}/helmvm/nodes`, { @@ -180,53 +175,76 @@ const HelmVMClusterManagement = ({ credentials: "include", method: "GET", }); - if (res.status === 401) { - Utilities.logoutUser(); + if (!res.ok) { + if (res.status === 401) { + Utilities.logoutUser(); + } console.log( "failed to get node status list, unexpected status code", res.status ); - const error = await res.json(); - throw new Error( - error?.error?.message || error?.error || error?.message - ); + try { + const error = await res.json(); + throw new Error( + error?.error?.message || error?.error || error?.message + ); + } catch (err) { + throw new Error("Unable to fetch nodes, please try again later."); + } } return res.json(); }, - onSuccess: (data) => { - setState({ - // if cluster doesn't support ha, then primary will be disabled. Force into secondary - selectedNodeType: !data.ha ? "secondary" : state.selectedNodeType, - }); - }, - refetchInterval: 1000, + refetchInterval: (data) => (data ? 1000 : 0), retry: false, }); + type AddNodeCommandResponse = { + command: string; + expiry: string; + }; + const { data: generateAddNodeCommand, isLoading: generateAddNodeCommandLoading, - // error: generateAddNodeCommandError, - } = useQuery({ + error: generateAddNodeCommandError, + } = useQuery({ queryKey: ["generateAddNodeCommand", selectedNodeTypes], queryFn: async ({ queryKey }) => { - const [, selectedNodeTypes] = queryKey; - return ( - await fetch( - `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command`, - { - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - method: "POST", - body: JSON.stringify({ - roles: selectedNodeTypes, - }), - } - ) - ).json(); + const [, nodeTypes] = queryKey; + const res = await fetch( + `${process.env.API_ENDPOINT}/helmvm/generate-node-join-command`, + { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + method: "POST", + body: JSON.stringify({ + roles: nodeTypes, + }), + } + ); + if (!res.ok) { + if (res.status === 401) { + Utilities.logoutUser(); + } + console.log( + "failed to get generate node command, unexpected status code", + res.status + ); + try { + const error = await res.json(); + throw new Error( + error?.error?.message || error?.error || error?.message + ); + } catch (err) { + throw new Error( + "Unable to generate node join command, please try again later." + ); + } + } + return res.json(); }, enabled: selectedNodeTypes.length > 0, }); @@ -337,6 +355,7 @@ const HelmVMClusterManagement = ({ setState({ deleteNodeError: "" }); }; + // #region node type logic const NODE_TYPES = ["controller"]; const determineDisabledState = () => { @@ -362,24 +381,87 @@ const HelmVMClusterManagement = ({ setSelectedNodeTypes([...types, nodeType]); } }; + // #endregion - const { data } = useApps(); - - const handleContinue = () => { - const app = data?.apps?.find((a) => a.name === appName); - if (app?.isConfigurable) { - navigate(`/${app.slug}/config`, { replace: true }); - return; - } - }; + const columns = useMemo( + () => [ + { + accessorKey: "name", + header: "Name", + enableHiding: false, + enableColumnDragging: false, + size: 150, + }, + { + accessorKey: "roles", + header: "Role(s)", + size: 404, + }, + { + accessorKey: "status", + header: "Status", + size: 150, + }, + { + accessorKey: "disk", + header: "Disk", + size: 150, + }, + { + accessorKey: "cpu", + header: "CPU", + size: 150, + }, + { + accessorKey: "memory", + header: "Memory", + size: 150, + }, + { + accessorKey: "pause", + header: "Pause", + size: 100, + }, + { + accessorKey: "delete", + header: "Delete", + size: 100, + }, + ], + [] + ); - if (nodesLoading) { - return ( -
- -
- ); - } + const mappedNodes = useMemo(() => { + return (nodesData?.nodes || testData.nodes).map((n) => ({ + name: n.name, + roles: ( +
+ {n.labels.map((l) => ( + + {l} + + ))} +
+ ), + status: n.isReady ? "Ready" : "Not Ready", + disk: n.conditions.diskPressure ? "Disk Pressure" : "No Disk Pressure", + cpu: n.conditions.pidPressure ? "CPU Pressure" : "No CPU Pressure", + memory: n.conditions.memoryPressure + ? "Memory Pressure" + : "No Memory Pressure", + pause: ( + <> + + + ), + delete: ( + <> + + + ), + })); + }, [nodesData?.nodes?.toString()]); + // #endregion return (
@@ -399,32 +481,74 @@ const HelmVMClusterManagement = ({ className="btn primary tw-ml-auto tw-w-fit tw-h-fit" onClick={onAddNodeClick} > - Add node type + Add node )}
- {(nodesData?.nodes || testData?.nodes) && - (nodesData?.nodes || testData?.nodes).map((node, i) => ( - - ))} + {nodesLoading && ( +

+ Loading nodes... +

+ )} + {!nodesData && nodesError && ( +

+ {nodesError?.message} +

+ )} + {(nodesData?.nodes || testData?.nodes) && ( + + )}
{fromLicenseFlow && ( - + )}
@@ -439,7 +563,7 @@ const HelmVMClusterManagement = ({

- Add a Node Type + Add a Node

- To add a node type to this cluster, select the type of node you are - adding, and then select an installation method below. This screen - will automatically show the status when the node successfully joins - the cluster. + To add a node to this cluster, select the type of node you'd like to + add, and then select an installation method below. When the node + successfully joins the cluster, you will see it appear in the list + of nodes on this page.

{NODE_TYPES.map((nodeType) => ( @@ -460,7 +584,7 @@ const HelmVMClusterManagement = ({ key={nodeType} className={classNames("BoxedCheckbox", { "is-active": selectedNodeTypes.includes(nodeType), - "is-disabled": determineDisabledState(nodeType), + "is-disabled": determineDisabledState(), })} > @@ -483,7 +607,16 @@ const HelmVMClusterManagement = ({ ))}
- {generateAddNodeCommandLoading &&

Generating command...

} + {generateAddNodeCommandLoading && ( +

+ Generating command... +

+ )} + {!generateAddNodeCommand && generateAddNodeCommandError && ( +

+ {generateAddNodeCommandError?.message} +

+ )} {!generateAddNodeCommandLoading && generateAddNodeCommand?.command && ( )}
-
- setUseStaticToken(e.target.checked)} - /> - -
{/* buttons */}
-
diff --git a/web/src/components/apps/HelmVMNodeRow.jsx b/web/src/components/apps/HelmVMNodeRow.jsx deleted file mode 100644 index 9386035869..0000000000 --- a/web/src/components/apps/HelmVMNodeRow.jsx +++ /dev/null @@ -1,293 +0,0 @@ -import classNames from "classnames"; -import React from "react"; -import { Link, useParams } from "react-router-dom"; - -import { rbacRoles } from "../../constants/rbac"; -import { getPercentageStatus, Utilities } from "../../utilities/utilities"; -import Icon from "../Icon"; -import Loader from "../shared/Loader"; - -export default function HelmVMNodeRow({ - node, - drainNode, - drainNodeSuccessful, - drainingNodeName, - deleteNode, -}) { - const { slug } = useParams(); - - const DrainDeleteNode = () => { - if (drainNode && Utilities.sessionRolesHasOneOf(rbacRoles.DRAIN_NODE)) { - if ( - !drainNodeSuccessful && - drainingNodeName && - drainingNodeName === node?.name - ) { - return ( -
- - - - - Draining Node - -
- ); - } else if (drainNodeSuccessful && drainingNodeName === node?.name) { - return ( -
- - - Node successfully drained - -
- ); - } else { - return ( -
- -
- ); - } - } - }; - - return ( -
-
-
- {slug && ( - - {node?.name} - - )} - {!slug && ( -

- {node?.name} -

- )} - {node?.isPrimaryNode && ( - - Primary node - - )} -
-
-
-

- - {node?.isConnected ? "Connected" : "Disconnected"} -

-

-   -

-
-
-

- - {node?.pods?.available === -1 - ? `${node?.pods?.capacity} pods` - : `${ - node?.pods?.available === 0 - ? "0" - : node?.pods?.capacity - node?.pods?.available - } pods used`} -

- {node?.pods?.available !== -1 && ( -

- of {node?.pods?.capacity} pods total -

- )} -
-
-

- - {node?.cpu?.available === -1 - ? `${node?.cpu?.capacity} ${ - node?.cpu?.available === "1" ? "core" : "cores" - }` - : `${ - node?.cpu?.available === 0 - ? "0" - : (node?.cpu?.capacity - node?.cpu?.available).toFixed(1) - } ${ - node?.cpu?.available === "1" ? "core used" : "cores used" - }`} -

- {node?.pods?.available !== -1 && ( -

- of {node?.cpu?.capacity}{" "} - {node?.cpu?.available === "1" ? "core total" : "cores total"} -

- )} -
-
-

- - {node?.memory?.available === -1 - ? `${node?.memory?.capacity?.toFixed(1)} GB` - : `${ - node?.memory?.available === 0 - ? "0" - : ( - node?.memory?.capacity - node?.memory?.available - ).toFixed(1) - } GB used`} -

- {node?.pods?.available !== -1 && ( -

- of {node?.memory?.capacity?.toFixed(1)} GB total -

- )} -
-
-
-
-

- - {node?.kubeletVersion} -

-
-
-

- - {node?.conditions?.diskPressure - ? "No Space on Device" - : "No Disk Pressure"} -

-
-
-

- - {node?.conditions?.pidPressure - ? "Pressure on CPU" - : "No CPU Pressure"} -

-
-
-

- - {node?.conditions?.memoryPressure - ? "No Space on Memory" - : "No Memory Pressure"} -

-
-
- {/* LABELS */} -
- {node?.labels.length > 0 - ? node.labels.sort().map((label, i) => { - let labelToShow = label.replace(":", "="); - return ( -
- {labelToShow} -
- ); - }) - : null} -
-
-

- For more details run{" "} - - kubectl describe node {node?.name} - -

-
-
- -
- ); -} diff --git a/web/src/components/apps/HelmVMViewNode.jsx b/web/src/components/apps/HelmVMViewNode.jsx index a72f13648f..a9b8bae245 100644 --- a/web/src/components/apps/HelmVMViewNode.jsx +++ b/web/src/components/apps/HelmVMViewNode.jsx @@ -308,6 +308,7 @@ const HelmVMViewNode = () => { const node = nodeData || testData.nodes[0]; + // #region table data const columns = useMemo( () => [ { @@ -347,9 +348,9 @@ const HelmVMViewNode = () => { ); const mappedPods = useMemo(() => { - return node.podList.map((n) => ({ - name: n.metadata.name, - status: n.status.phase, + return node?.podList?.map((p) => ({ + name: p.metadata.name, + status: p.status.phase, disk: null, cpu: null, memory: null, @@ -359,7 +360,8 @@ const HelmVMViewNode = () => { ), })); - }, [node.podList]); + }, [node?.podList?.toString()]); + // #endregion return (
From ccfa67fd1a3070298cc09f5107a7d83f144cc49a Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:43:03 -0600 Subject: [PATCH 16/17] move ts-ignore line --- web/src/components/UploadLicenseFile.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/components/UploadLicenseFile.tsx b/web/src/components/UploadLicenseFile.tsx index f2b6a9d500..2fe1ed8a12 100644 --- a/web/src/components/UploadLicenseFile.tsx +++ b/web/src/components/UploadLicenseFile.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useReducer } from "react"; import { Link, useNavigate } from "react-router-dom"; -// TODO: upgrade this dependency -// @ts-ignore import yaml from "js-yaml"; import isEmpty from "lodash/isEmpty"; import keyBy from "lodash/keyBy"; import size from "lodash/size"; +// TODO: upgrade this dependency +// @ts-ignore import Dropzone from "react-dropzone"; import Modal from "react-modal"; import Select from "react-select"; From dd46762876ff48d2e85e6598d45523ac3512139c Mon Sep 17 00:00:00 2001 From: Star Richardson <67430892+alicenstar@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:26:10 -0600 Subject: [PATCH 17/17] remove delete functionality for now --- .../apps/HelmVMClusterManagement.tsx | 193 ++---------------- 1 file changed, 18 insertions(+), 175 deletions(-) diff --git a/web/src/components/apps/HelmVMClusterManagement.tsx b/web/src/components/apps/HelmVMClusterManagement.tsx index 3670fd1805..ed0fcfa3f2 100644 --- a/web/src/components/apps/HelmVMClusterManagement.tsx +++ b/web/src/components/apps/HelmVMClusterManagement.tsx @@ -3,14 +3,13 @@ import MaterialReactTable from "material-react-table"; import React, { ChangeEvent, useMemo, useReducer, useState } from "react"; import Modal from "react-modal"; import { useQuery } from "react-query"; -import { Link } from "react-router-dom"; +import { Link, 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 ErrorModal from "../modals/ErrorModal"; import CodeSnippet from "../shared/CodeSnippet"; import "@src/scss/components/apps/HelmVMClusterManagement.scss"; @@ -127,6 +126,7 @@ const HelmVMClusterManagement = ({ const { data: appsData } = useApps(); const app = appsData?.apps?.find((a) => a.name === appName); + const { slug } = useParams(); // #region queries type NodesResponse = { @@ -270,91 +270,12 @@ const HelmVMClusterManagement = ({ // }); // #endregion - const deleteNode = (name: string) => { - setState({ - confirmDeleteNode: name, - }); - }; - - const cancelDeleteNode = () => { - setState({ - confirmDeleteNode: "", - }); - }; - - const reallyDeleteNode = () => { - const name = state.confirmDeleteNode; - cancelDeleteNode(); - - fetch(`${process.env.API_ENDPOINT}/helmvm/nodes/${name}`, { - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - method: "DELETE", - }) - .then(async (res) => { - if (!res.ok) { - if (res.status === 401) { - Utilities.logoutUser(); - return; - } - setState({ - deleteNodeError: `Delete failed with status ${res.status}`, - }); - } - }) - .catch((err) => { - console.log(err); - }); - }; - - const onDrainNodeClick = (name: string) => { - setState({ - showConfirmDrainModal: true, - nodeNameToDrain: name, - }); - }; - - const drainNode = async (name: string) => { - setState({ showConfirmDrainModal: false, drainingNodeName: name }); - fetch(`${process.env.API_ENDPOINT}/helmvm/nodes/${name}/drain`, { - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - method: "POST", - }) - .then(async () => { - setState({ drainNodeSuccessful: true }); - setTimeout(() => { - setState({ - drainingNodeName: null, - drainNodeSuccessful: false, - }); - }, 3000); - }) - .catch((err) => { - console.log(err); - setState({ - drainingNodeName: null, - drainNodeSuccessful: false, - }); - }); - }; - const onAddNodeClick = () => { setState({ displayAddNode: true, }); }; - const ackDeleteNodeError = () => { - setState({ deleteNodeError: "" }); - }; - // #region node type logic const NODE_TYPES = ["controller"]; @@ -433,7 +354,14 @@ const HelmVMClusterManagement = ({ const mappedNodes = useMemo(() => { return (nodesData?.nodes || testData.nodes).map((n) => ({ - name: n.name, + name: slug ? ( + + ) : ( + n.name + ), roles: (
{n.labels.map((l) => ( @@ -472,8 +400,8 @@ const HelmVMClusterManagement = ({ Cluster Nodes

-

- This section lists the nodes that are configured and shows the +

+ This page lists the nodes that are configured and shows the status/health of each.

{Utilities.sessionRolesHasOneOf([rbacRoles.CLUSTER_ADMIN]) && ( @@ -560,7 +488,7 @@ const HelmVMClusterManagement = ({ className="Modal" ariaHideApp={false} > -
+

Add a Node @@ -572,11 +500,12 @@ const HelmVMClusterManagement = ({ onClick={() => setState({ displayAddNode: false })} />

-

+

To add a node to this cluster, select the type of node you'd like to - add, and then select an installation method below. When the node - successfully joins the cluster, you will see it appear in the list - of nodes on this page. + add. Once you've selected a node type, we will generate a node join + command for you to use in the CLI. When the node successfully joins + the cluster, you will see it appear in the list of nodes on this + page.

{NODE_TYPES.map((nodeType) => ( @@ -641,92 +570,6 @@ const HelmVMClusterManagement = ({
- {state.deleteNodeError && ( - - )} - -
-

- Deleting this node may cause data loss. Are you sure you want to - proceed? -

-
- - -
-
-
- {state.showConfirmDrainModal && ( - - setState({ - showConfirmDrainModal: false, - nodeNameToDrain: "", - }) - } - shouldReturnFocusAfterClose={false} - contentLabel="Confirm Drain Node" - ariaHideApp={false} - className="Modal MediumSize" - > -
-

- Are you sure you want to drain {state.nodeNameToDrain}? -

-

- Draining this node may cause data loss. If you want to delete{" "} - {state.nodeNameToDrain} you must disconnect it after it has been - drained. -

-
- - -
-
-
- )}
); };