forked from mirrors/thatmattlove-hyperglass
99 lines
2.8 KiB
TypeScript
99 lines
2.8 KiB
TypeScript
import { Badge, Box, Flex, SkeletonText, VStack } from '@chakra-ui/react';
|
|
import { useMemo } from 'react';
|
|
import ReactFlow, {
|
|
Background,
|
|
ReactFlowProvider,
|
|
Handle,
|
|
Position,
|
|
isNode,
|
|
isEdge,
|
|
} from 'reactflow';
|
|
import { useConfig } from '~/context';
|
|
import { useASNDetail, useColorToken, useColorValue } from '~/hooks';
|
|
import { Controls } from './controls';
|
|
import { useElements } from './useElements';
|
|
|
|
import type { NodeProps as ReactFlowNodeProps } from 'reactflow';
|
|
|
|
interface ChartProps {
|
|
data: StructuredResponse;
|
|
}
|
|
|
|
interface NodeProps<D extends unknown> extends Omit<ReactFlowNodeProps, 'data'> {
|
|
data: D;
|
|
}
|
|
|
|
export interface NodeData {
|
|
asn: string;
|
|
name: string;
|
|
hasChildren: boolean;
|
|
hasParents?: boolean;
|
|
}
|
|
|
|
export const Chart = (props: ChartProps): JSX.Element => {
|
|
const { data } = props;
|
|
const { primaryAsn, orgName } = useConfig();
|
|
|
|
const dots = useColorToken('colors', 'blackAlpha.500', 'whiteAlpha.400');
|
|
|
|
const elements = useElements({ asn: primaryAsn, name: orgName }, data);
|
|
|
|
const nodes = useMemo(() => elements.filter(isNode), [elements]);
|
|
const edges = useMemo(() => elements.filter(isEdge), [elements]);
|
|
|
|
return (
|
|
<ReactFlowProvider>
|
|
<Box w="100%" h={{ base: '100vh', lg: '70vh' }} zIndex={1}>
|
|
<ReactFlow
|
|
snapToGrid
|
|
nodes={nodes}
|
|
edges={edges}
|
|
nodeTypes={{ ASNode }}
|
|
edgesUpdatable={false}
|
|
nodesDraggable={false}
|
|
nodesConnectable={false}
|
|
onInit={inst => setTimeout(() => inst.fitView(), 0)}
|
|
proOptions={{ hideAttribution: true }}
|
|
>
|
|
<Background color={dots} />
|
|
<Controls />
|
|
</ReactFlow>
|
|
</Box>
|
|
</ReactFlowProvider>
|
|
);
|
|
};
|
|
|
|
const ASNode = (props: NodeProps<NodeData>): JSX.Element => {
|
|
const { data } = props;
|
|
const { asn, name, hasChildren, hasParents } = data;
|
|
|
|
const color = useColorValue('black', 'white');
|
|
const bg = useColorValue('white', 'whiteAlpha.200');
|
|
|
|
const { data: asnData, isError, isLoading } = useASNDetail(String(asn));
|
|
|
|
return (
|
|
<>
|
|
{hasChildren && <Handle type="source" position={Position.Top} />}
|
|
<Box py={2} px={3} bg={bg} minW={32} minH={8} color={color} boxShadow="md" borderRadius="md">
|
|
<VStack spacing={2}>
|
|
<Flex fontSize="lg">
|
|
{isLoading ? (
|
|
<Box h={2} w={24}>
|
|
<SkeletonText noOfLines={1} color={color} />
|
|
</Box>
|
|
) : !isError && asnData?.data?.asn.organization?.orgName ? (
|
|
asnData.data.asn.organization.orgName
|
|
) : (
|
|
name
|
|
)}
|
|
</Flex>
|
|
<Badge fontFamily="mono" fontWeight="normal" fontSize="sm" colorScheme="primary">
|
|
{asn}
|
|
</Badge>
|
|
</VStack>
|
|
</Box>
|
|
{hasParents && <Handle type="target" position={Position.Bottom} />}
|
|
</>
|
|
);
|
|
};
|