From 33f73953646ab9315d5c0a450d618fba6713d7c3 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] 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 */}