diff --git a/components/flow/index.tsx b/components/flow/index.tsx index 3437eb8d..129b5b12 100644 --- a/components/flow/index.tsx +++ b/components/flow/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, type FunctionComponent } from 'react'; +import React, { useCallback, type FunctionComponent, MouseEvent } from 'react'; import ReactFlow, { addEdge, Edge, @@ -6,87 +6,158 @@ import ReactFlow, { NodeTypes, useNodesState, useEdgesState, + Node, } from 'reactflow'; import { GraphNode, type CustomGraphNode } from '../graphNode'; import 'reactflow/dist/style.css'; import { ClusterStatus, ClusterType } from '../../types/provision'; import { InstallationType } from '../../types/redux'; +import { ClusterInfo } from '../../components/clusterTable/clusterTable'; -const initialNodes: CustomGraphNode[] = [ +const clusters: ClusterInfo[] = [ { - id: '1', - type: 'custom', - data: { - clusterName: 'kuberfirst-mgmt2', - type: ClusterType.MANAGEMENT, - cloudProvider: InstallationType.AWS, - cloudRegion: 'ap-southeast-1', - creationDate: '05 Apr 2023, 12:24:56', - gitUser: 'Eleanor Carroll', - status: ClusterStatus.PROVISIONED, - nodes: 2, - }, - position: { x: 5, y: 0 }, - draggable: false, + clusterName: 'kuberfirst-mgmt', + type: ClusterType.MANAGEMENT, + cloudProvider: InstallationType.AWS, + cloudRegion: 'ap-southeast-1', + creationDate: '05 Apr 2023, 12:24:56', + gitUser: 'Eleanor Carroll', + status: ClusterStatus.PROVISIONED, + adminEmail: 'admin@mycompany.com', + gitProvider: 'Github', + domainName: 'yourdomain.com', + nodes: 2, }, { - id: '2', - type: 'custom', - data: { - clusterName: 'kuberfirst-worker-1', - type: ClusterType.WORKLOAD, - cloudProvider: InstallationType.AWS, - cloudRegion: 'ap-southeast-1', - creationDate: '05 Apr 2023, 12:24:56', - gitUser: 'Eleanor Carroll', - status: ClusterStatus.ERROR, - nodes: 2, - }, - position: { x: 500, y: -100 }, - draggable: false, + clusterName: 'kuberfirst-worker-1', + type: ClusterType.WORKLOAD, + cloudProvider: InstallationType.CIVO, + cloudRegion: 'ap-southeast-1', + nodes: 2, + creationDate: '05 Apr 2023, 12:24:56', + gitUser: 'Eleanor Carroll', + status: ClusterStatus.ERROR, + adminEmail: 'admin@mycompany.com', + gitProvider: 'Github', + domainName: 'yourdomain.com', }, { - id: '3', - type: 'custom', - data: { - clusterName: 'kuberfirst-worker-2', - type: ClusterType.WORKLOAD, - cloudProvider: InstallationType.AWS, - cloudRegion: 'ap-southeast-1', - creationDate: '05 Apr 2023, 12:24:56', - gitUser: 'Eleanor Carroll', - status: ClusterStatus.PROVISIONED, - nodes: 2, - }, - position: { x: 500, y: 100 }, + clusterName: 'kuberfirst-worker-2', + type: ClusterType.WORKLOAD, + cloudProvider: InstallationType.DIGITAL_OCEAN, + cloudRegion: 'ap-southeast-1', + nodes: 2, + creationDate: '05 Apr 2023, 12:24:56', + gitUser: 'Eleanor Carroll', + status: ClusterStatus.DELETING, + adminEmail: 'admin@mycompany.com', + gitProvider: 'Github', + domainName: 'yourdomain.com', }, -]; - -const initialEdges: Edge[] = [ { - id: 'edge-1', - source: '1', - target: '2', - animated: false, - type: 'straight', - style: { strokeWidth: 2, stroke: '#CBD5E1' }, + clusterName: 'kuberfirst-worker-3', + type: ClusterType.WORKLOAD, + cloudProvider: InstallationType.DIGITAL_OCEAN, + cloudRegion: 'ap-southeast-1', + nodes: 2, + creationDate: '05 Apr 2023, 12:24:56', + gitUser: 'Eleanor Carroll', + status: ClusterStatus.PROVISIONED, + adminEmail: 'admin@mycompany.com', + gitProvider: 'Github', + domainName: 'yourdomain.com', }, { - id: 'edge-2', - source: '1', - target: '3', + clusterName: 'kuberfirst-worker-4', + type: ClusterType.WORKLOAD, + cloudProvider: InstallationType.VULTR, + cloudRegion: 'ap-southeast-1', + nodes: 2, + creationDate: '05 Apr 2023, 12:24:56', + gitUser: 'Eleanor Carroll', + status: ClusterStatus.PROVISIONED, + adminEmail: 'admin@mycompany.com', + gitProvider: 'Github', + domainName: 'yourdomain.com', + }, + { + clusterName: 'kuberfirst-worker-5', + type: ClusterType.WORKLOAD, + cloudProvider: InstallationType.VULTR, + cloudRegion: 'ap-southeast-1', + nodes: 2, + creationDate: '05 Apr 2023, 12:24:56', + gitUser: 'Eleanor Carroll', + status: ClusterStatus.PROVISIONED, + adminEmail: 'admin@mycompany.com', + gitProvider: 'Github', + domainName: 'yourdomain.com', + }, +]; + +function generateNode( + cluster: ClusterInfo, + id: string, + position: { x: number; y: number }, +): CustomGraphNode { + return { + id, + type: 'custom', + data: cluster, + position, + draggable: false, + }; +} + +function generateEdge(id: string, source: string, target: string): Edge { + return { + id, + source, + target, animated: false, type: 'straight', style: { strokeWidth: 2, stroke: '#CBD5E1' }, - }, -]; + }; +} + +function generateNodesConfig( + managementCluster: ClusterInfo, + workloadClusters: ClusterInfo[], +): [CustomGraphNode[], Edge[]] { + const managementNodeId = '1'; + const nodes: CustomGraphNode[] = [ + generateNode(managementCluster, managementNodeId, { + x: 0, + y: (workloadClusters.length * 200) / 2, + }), + ]; + + const edges: Edge[] = []; + + for (let i = 0; i < workloadClusters.length; i += 1) { + const generatedId = `${i + 2}`; + + nodes.push(generateNode(workloadClusters[i], generatedId, { x: 600, y: i * 200 })); + edges.push(generateEdge(`edge-${i + 1}`, managementNodeId, generatedId)); + } + + return [nodes, edges]; +} + +const [managemenCluster, ...workloadClusters] = clusters; + +const [initialNodes, initialEdges] = generateNodesConfig(managemenCluster, workloadClusters); const nodeTypes: NodeTypes = { custom: GraphNode, }; -export const Flow: FunctionComponent = () => { +interface FlowProps { + onNodeClick: (clusterInfo: ClusterInfo) => void; +} + +export const Flow: FunctionComponent = ({ onNodeClick }) => { const [nodes, , onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = useCallback( @@ -94,12 +165,20 @@ export const Flow: FunctionComponent = () => { [setEdges], ); + const handleNodeClick = useCallback( + (e: MouseEvent, node: Node) => { + onNodeClick(node.data); + }, + [onNodeClick], + ); + return ( = { interface CreateClusterFlowProps { onMenuClose: () => void; + onClusterDelete: () => void; cluster?: ClusterInfo; } export const CreateClusterFlow: FunctionComponent = ({ cluster, onMenuClose, + onClusterDelete, }) => { const { clusterCreationStep } = useAppSelector(({ api }) => api); @@ -48,16 +50,20 @@ export const CreateClusterFlow: FunctionComponent = ({ }, [dispatch, clusterCreationStep, onMenuClose]); const handleClick = useCallback(() => { - if (clusterCreationStep !== ClusterCreationStep.DETAILS && methods.formState.isValid) { + if (clusterCreationStep === ClusterCreationStep.DETAILS) { + onClusterDelete(); + } else if (methods.formState.isValid) { dispatch(setClusterCreationStep(clusterCreationStep + 1)); } - }, [dispatch, clusterCreationStep, methods]); + }, [onClusterDelete, dispatch, clusterCreationStep, methods]); const handleSubmit = useCallback( (config: NewClusterConfig) => { - dispatch(createWorkloadCluster(config)); + if (clusterCreationStep !== ClusterCreationStep.DETAILS) { + dispatch(createWorkloadCluster(config)); + } }, - [dispatch], + [clusterCreationStep, dispatch], ); const showingClusterDetails = clusterCreationStep === ClusterCreationStep.DETAILS; diff --git a/containers/clusterManagement/index.tsx b/containers/clusterManagement/index.tsx index 0394e998..d908d99a 100644 --- a/containers/clusterManagement/index.tsx +++ b/containers/clusterManagement/index.tsx @@ -7,7 +7,7 @@ import Typography from '../../components/typography'; import { useAppDispatch, useAppSelector } from '../../redux/store'; import { deleteCluster, getCluster, getClusters } from '../../redux/thunks/api.thunk'; import { resetInstallState } from '../../redux/slices/installation.slice'; -import { ClusterRequestProps } from '../../types/provision'; +import { ClusterCreationStep, ClusterRequestProps } from '../../types/provision'; import useToggle from '../../hooks/useToggle'; import Drawer from '../../components/drawer'; import useModal from '../../hooks/useModal'; @@ -16,7 +16,7 @@ import TabPanel, { Tab, a11yProps } from '../../components/tab'; import { BISCAY, SALTBOX_BLUE } from '../../constants/colors'; import { Flow } from '../../components/flow'; import { ClusterInfo, ClusterTable } from '../../components/clusterTable/clusterTable'; -import { sortClustersByType } from '../../utils/sortClusterByType'; +import { setClusterCreationStep } from '../../redux/slices/api.slice'; import { CreateClusterFlow } from './createClusterFlow'; import { Container, Content, Header } from './clusterManagement.styled'; @@ -32,10 +32,11 @@ const ClusterManagement: FunctionComponent = () => { const isClusterZero = useAppSelector(({ config }) => config.isClusterZero); const { - isOpen: isDetailsPanelOpen, - open: openDetailsPanel, - close: closeDetailsPanel, + isOpen: createClusterFlowOpen, + open: openCreateClusterFlow, + close: closeCreateClusterFlow, } = useToggle(); + const { isOpen: isDeleteModalOpen, openModal: openDeleteModal, @@ -103,6 +104,15 @@ const ClusterManagement: FunctionComponent = () => { setActiveTab(newValue); }; + const handleNodeClick = useCallback( + (info: ClusterInfo) => { + setSelectedCluster(info); + dispatch(setClusterCreationStep(ClusterCreationStep.DETAILS)); + openCreateClusterFlow(); + }, + [dispatch, openCreateClusterFlow], + ); + return (
@@ -127,7 +137,7 @@ const ClusterManagement: FunctionComponent = () => { color="primary" variant="contained" style={{ marginRight: '24px' }} - onClick={openDetailsPanel} + onClick={openCreateClusterFlow} > Add workload cluster @@ -144,7 +154,7 @@ const ClusterManagement: FunctionComponent = () => { )} - + { message={`Cluster ${selectedCluster?.clusterName} has been deleted`} /> { }, }} > - + {selectedCluster && (