136 lines
3.2 KiB
TypeScript
136 lines
3.2 KiB
TypeScript
|
|
'use client'
|
||
|
|
|
||
|
|
import { useCallback, useMemo } from 'react'
|
||
|
|
import ReactFlow, {
|
||
|
|
Node,
|
||
|
|
Edge,
|
||
|
|
addEdge,
|
||
|
|
Connection,
|
||
|
|
useNodesState,
|
||
|
|
useEdgesState,
|
||
|
|
Controls,
|
||
|
|
MiniMap,
|
||
|
|
Background,
|
||
|
|
MarkerType,
|
||
|
|
} from 'reactflow'
|
||
|
|
import { useQuery } from '@tanstack/react-query'
|
||
|
|
import { gql } from 'graphql-tag'
|
||
|
|
import { apolloClient } from '@/lib/graphql/client'
|
||
|
|
import 'reactflow/dist/style.css'
|
||
|
|
|
||
|
|
const GET_RESOURCE_GRAPH = gql`
|
||
|
|
query GetResourceGraph($query: ResourceGraphQuery) {
|
||
|
|
resourceGraph(query: $query) {
|
||
|
|
nodes {
|
||
|
|
id
|
||
|
|
resourceType
|
||
|
|
provider
|
||
|
|
name
|
||
|
|
region
|
||
|
|
}
|
||
|
|
edges {
|
||
|
|
id
|
||
|
|
source
|
||
|
|
target
|
||
|
|
relationshipType
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
`
|
||
|
|
|
||
|
|
export function ResourceGraphEditor({ query }: { query?: any }) {
|
||
|
|
const { data } = useQuery({
|
||
|
|
queryKey: ['resourceGraph', query],
|
||
|
|
queryFn: async () => {
|
||
|
|
const result = await apolloClient.query({
|
||
|
|
query: GET_RESOURCE_GRAPH,
|
||
|
|
variables: { query },
|
||
|
|
})
|
||
|
|
return result.data.resourceGraph
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
const initialNodes = useMemo<Node[]>(() => {
|
||
|
|
if (!data?.nodes) return []
|
||
|
|
|
||
|
|
return data.nodes.map((node: any, index: number) => ({
|
||
|
|
id: node.id,
|
||
|
|
type: 'default',
|
||
|
|
data: { label: node.name },
|
||
|
|
position: {
|
||
|
|
x: (index % 10) * 150 + 50,
|
||
|
|
y: Math.floor(index / 10) * 150 + 50,
|
||
|
|
},
|
||
|
|
style: {
|
||
|
|
background: getProviderColor(node.provider),
|
||
|
|
color: '#FFFFFF',
|
||
|
|
border: '2px solid #FF4500',
|
||
|
|
borderRadius: '8px',
|
||
|
|
padding: '10px',
|
||
|
|
},
|
||
|
|
}))
|
||
|
|
}, [data?.nodes])
|
||
|
|
|
||
|
|
const initialEdges = useMemo<Edge[]>(() => {
|
||
|
|
if (!data?.edges) return []
|
||
|
|
|
||
|
|
return data.edges.map((edge: any) => ({
|
||
|
|
id: edge.id,
|
||
|
|
source: edge.source,
|
||
|
|
target: edge.target,
|
||
|
|
label: edge.relationshipType,
|
||
|
|
markerEnd: {
|
||
|
|
type: MarkerType.ArrowClosed,
|
||
|
|
color: '#FF4500',
|
||
|
|
},
|
||
|
|
style: {
|
||
|
|
stroke: '#FF4500',
|
||
|
|
strokeWidth: 2,
|
||
|
|
},
|
||
|
|
}))
|
||
|
|
}, [data?.edges])
|
||
|
|
|
||
|
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
|
||
|
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
|
||
|
|
|
||
|
|
const onConnect = useCallback(
|
||
|
|
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
|
||
|
|
[setEdges]
|
||
|
|
)
|
||
|
|
|
||
|
|
function getProviderColor(provider: string): string {
|
||
|
|
const colors: Record<string, string> = {
|
||
|
|
PROXMOX: '#FF4500',
|
||
|
|
KUBERNETES: '#326CE5',
|
||
|
|
CLOUDFLARE: '#F38020',
|
||
|
|
CEPH: '#FF6B35',
|
||
|
|
MINIO: '#FFD700',
|
||
|
|
}
|
||
|
|
return colors[provider] || '#2A2A2A'
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="h-[800px] w-full rounded-lg border border-studio-medium bg-studio-black">
|
||
|
|
<ReactFlow
|
||
|
|
nodes={nodes}
|
||
|
|
edges={edges}
|
||
|
|
onNodesChange={onNodesChange}
|
||
|
|
onEdgesChange={onEdgesChange}
|
||
|
|
onConnect={onConnect}
|
||
|
|
fitView
|
||
|
|
className="bg-studio-black"
|
||
|
|
>
|
||
|
|
<Controls className="bg-studio-dark border-studio-medium" />
|
||
|
|
<MiniMap
|
||
|
|
className="bg-studio-dark border-studio-medium"
|
||
|
|
nodeColor={(node) => {
|
||
|
|
return node.style?.background as string || '#2A2A2A'
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<Background color="#2A2A2A" gap={16} />
|
||
|
|
</ReactFlow>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|