1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

continue typescript & chakra v1 migrations [skip ci]

This commit is contained in:
checktheroads
2020-12-25 00:45:23 -07:00
parent d263fb9203
commit e407258f72
12 changed files with 167 additions and 142 deletions

View File

@@ -3,7 +3,9 @@ import { Button, Icon, Tooltip } from '@chakra-ui/react';
import type { TPathButton } from './types';
const PathIcon = dynamic<MeronexIcon>(() => import('@meronex/icons/gr').then(i => i.GrNetwork));
const PathIcon = dynamic<MeronexIcon>(() =>
import('@meronex/icons/bi').then(i => i.BisNetworkChart),
);
export const PathButton = (props: TPathButton) => {
const { onOpen } = props;

View File

@@ -1,3 +1,4 @@
import { forwardRef } from 'react';
import {
Modal,
Popover,
@@ -18,24 +19,26 @@ import { useMobile } from '~/context';
import { useLGState } from '~/hooks';
import type { IconButtonProps } from '@chakra-ui/react';
import type { OnChangeArgs } from '~/types';
import type { TSubmitButton, TRSubmitButton } from './types';
const SubmitIcon = (props: Omit<IconButtonProps, 'aria-label'>) => {
const { isLoading } = props;
return (
<IconButton
size="lg"
width={16}
type="submit"
icon={<FiSearch />}
title="Submit Query"
colorScheme="primary"
isLoading={isLoading}
aria-label="Submit Query"
/>
);
};
const SubmitIcon = forwardRef<HTMLButtonElement, Omit<IconButtonProps, 'aria-label'>>(
(props, ref) => {
const { isLoading } = props;
return (
<IconButton
ref={ref}
size="lg"
width={16}
type="submit"
icon={<FiSearch />}
title="Submit Query"
colorScheme="primary"
isLoading={isLoading}
aria-label="Submit Query"
/>
);
},
);
/**
* Mobile Submit Button

View File

@@ -1,57 +0,0 @@
import { useEffect, useMemo } from 'react';
import { Text } from '@chakra-ui/react';
import { components } from 'react-select';
import { Select } from '~/components';
import type { OptionProps } from 'react-select';
import type { TBGPCommunity, TSelectOption } from '~/types';
import type { TCommunitySelect } from './types';
function buildOptions(communities: TBGPCommunity[]): TSelectOption[] {
return communities.map(c => ({
value: c.community,
label: c.display_name,
description: c.description,
}));
}
const Option = (props: OptionProps<Dict, false>) => {
const { label, data } = props;
return (
<components.Option {...props}>
<Text as="span">{label}</Text>
<br />
<Text fontSize="xs" as="span">
{data.description}
</Text>
</components.Option>
);
};
export const CommunitySelect = (props: TCommunitySelect) => {
const { name, communities, onChange, register, unregister } = props;
const options = useMemo(() => buildOptions(communities), [communities.length]);
function handleChange(e: TSelectOption | TSelectOption[]): void {
if (!Array.isArray(e) && e !== null) {
onChange({ field: name, value: e.value });
}
}
useEffect(() => {
register({ name });
return () => unregister(name);
}, [name, register, unregister]);
return (
<Select
size="lg"
name={name}
options={options}
innerRef={register}
onChange={handleChange}
components={{ Option }}
/>
);
};

View File

@@ -1,4 +1,3 @@
export * from './communitySelect';
export * from './field';
export * from './queryLocation';
export * from './queryTarget';

View File

@@ -1,46 +1,95 @@
import { Input } from '@chakra-ui/react';
import { useColorValue } from '~/context';
import { useMemo } from 'react';
import { Input, Text } from '@chakra-ui/react';
import { components } from 'react-select';
import { If, Select } from '~/components';
import { useConfig, useColorValue } from '~/context';
import { useLGState } from '~/hooks';
import type { OptionProps } from 'react-select';
import type { TBGPCommunity, TSelectOption } from '~/types';
import type { TQueryTarget } from './types';
const fqdnPattern = /^(?!:\/\/)([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-][a-zA-Z0-9-]+\.[a-zA-Z-]{2,6}?$/gim;
function buildOptions(communities: TBGPCommunity[]): TSelectOption[] {
return communities.map(c => ({
value: c.community,
label: c.display_name,
description: c.description,
}));
}
const Option = (props: OptionProps<Dict, false>) => {
const { label, data } = props;
return (
<components.Option {...props}>
<Text as="span">{label}</Text>
<br />
<Text fontSize="xs" as="span">
{data.description}
</Text>
</components.Option>
);
};
export const QueryTarget = (props: TQueryTarget) => {
const { name, register, setTarget, placeholder, resolveTarget } = props;
const { name, register, onChange, placeholder, resolveTarget } = props;
const bg = useColorValue('white', 'whiteAlpha.100');
const color = useColorValue('gray.400', 'whiteAlpha.800');
const border = useColorValue('gray.100', 'whiteAlpha.50');
const placeholderColor = useColorValue('gray.600', 'whiteAlpha.700');
const { queryTarget, fqdnTarget, displayTarget } = useLGState();
const { queryType, queryTarget, fqdnTarget, displayTarget } = useLGState();
const { queries } = useConfig();
const options = useMemo(() => buildOptions(queries.bgp_community.communities), []);
function handleChange(e: React.ChangeEvent<HTMLInputElement>): void {
displayTarget.set(e.target.value);
setTarget({ field: name, value: e.target.value });
onChange({ field: name, value: e.target.value });
if (resolveTarget && displayTarget.value && fqdnPattern.test(displayTarget.value)) {
fqdnTarget.set(displayTarget.value);
}
}
function handleSelectChange(e: TSelectOption | TSelectOption[]): void {
if (!Array.isArray(e) && e !== null) {
onChange({ field: name, value: e.value });
displayTarget.set(e.value);
}
}
return (
<>
<input hidden readOnly name={name} ref={register} value={queryTarget.value} />
<Input
bg={bg}
size="lg"
color={color}
borderRadius="md"
value={displayTarget.value}
borderColor={border}
onChange={handleChange}
aria-label={placeholder}
placeholder={placeholder}
name="query_target_display"
_placeholder={{ color: placeholderColor }}
/>
<If c={queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select'}>
<Select
size="lg"
name={name}
options={options}
innerRef={register}
onChange={handleSelectChange}
components={{ Option }}
/>
</If>
<If c={!(queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select')}>
<Input
bg={bg}
size="lg"
color={color}
borderRadius="md"
borderColor={border}
onChange={handleChange}
aria-label={placeholder}
placeholder={placeholder}
value={displayTarget.value}
name="query_target_display"
_placeholder={{ color: placeholderColor }}
/>
</If>
</>
);
};

View File

@@ -29,7 +29,6 @@ export interface TCommunitySelect {
onChange: OnChange;
communities: TBGPCommunity[];
register: Control['register'];
unregister: Control['unregister'];
}
export interface TQueryTarget {
@@ -37,7 +36,7 @@ export interface TQueryTarget {
placeholder: string;
resolveTarget: boolean;
register: Control['register'];
setTarget(e: OnChangeArgs): void;
onChange(e: OnChangeArgs): void;
}
export interface TResolvedTarget {

View File

@@ -31,19 +31,15 @@ export const HelpModal = (props: THelpModal) => {
animate={{ opacity: 1, scale: 1 }}
initial={{ opacity: 0, scale: 0.3 }}>
<IconButton
h={3}
w={3}
mb={1}
ml={1}
maxH={3}
maxW={3}
minH={3}
minW={3}
size="sm"
size="md"
variant="link"
icon={<Info />}
onClick={onOpen}
colorScheme="primary"
colorScheme="blue"
aria-label={`${name}_help`}
/>
</motion.div>

View File

@@ -15,7 +15,6 @@ import {
QueryTarget,
SubmitButton,
QueryLocation,
CommunitySelect,
} from '~/components';
import { useConfig } from '~/context';
import { useStrf, useGreeting, useDevice, useLGState } from '~/hooks';
@@ -37,16 +36,17 @@ export const HyperglassForm = () => {
const formSchema = yup.object().shape({
query_location: yup.array().of(yup.string()).required(noQueryLoc),
query_target: yup.string().required(noQueryTarget),
query_type: yup.string().required(noQueryType),
query_vrf: yup.string(),
query_target: yup.string().required(noQueryTarget),
});
const formInstance = useForm<TFormData>({
resolver: yupResolver(formSchema),
defaultValues: { query_vrf: 'default', query_target: '', query_location: [], query_type: '' },
});
const { handleSubmit, register, unregister, setValue, errors } = formInstance;
const { handleSubmit, register, unregister, setValue, getValues } = formInstance;
const {
queryVrf,
@@ -63,6 +63,8 @@ export const HyperglassForm = () => {
} = useLGState();
function submitHandler(values: TFormData) {
console.dir(values);
console.dir(formData.value);
if (!greetingAck && web.greeting.required) {
window.location.reload(false);
setGreetingAck(false);
@@ -141,6 +143,8 @@ export const HyperglassForm = () => {
} else if (e.field === 'query_target' && isString(e.value)) {
queryTarget.set(e.value);
}
console.log(e.field, e.value);
console.dir(getValues());
}
const vrfContent = useMemo(() => {
@@ -155,9 +159,9 @@ export const HyperglassForm = () => {
useEffect(() => {
register({ name: 'query_location', required: true });
register({ name: 'query_target', required: true });
register({ name: 'query_type', required: true });
register({ name: 'query_vrf' });
register({ name: 'query_target', required: true });
}, [register]);
return (
@@ -192,25 +196,13 @@ export const HyperglassForm = () => {
</FormField>
</If>
<FormField name="query_target" label={web.text.query_target}>
<If c={queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select'}>
<CommunitySelect
name="query_target"
register={register}
unregister={unregister}
onChange={handleChange}
communities={queries.bgp_community.communities}
/>
</If>
<If
c={!(queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select')}>
<QueryTarget
name="query_target"
register={register}
setTarget={handleChange}
resolveTarget={isFqdnQuery}
placeholder={web.text.query_target}
/>
</If>
<QueryTarget
name="query_target"
register={register}
onChange={handleChange}
resolveTarget={isFqdnQuery}
placeholder={web.text.query_target}
/>
</FormField>
</FormRow>
<FormRow mt={0} justifyContent="flex-end">

View File

@@ -1,40 +1,52 @@
import { useMemo } from 'react';
import { Box, Flex, Icon, Badge, VStack } from '@chakra-ui/react';
import { BeatLoader } from 'react-spinners';
import { Box, Flex, SkeletonText, Badge, VStack } from '@chakra-ui/react';
import ReactFlow from 'react-flow-renderer';
import { Background, Controls } from 'react-flow-renderer';
import { Background, ReactFlowProvider } from 'react-flow-renderer';
import { Handle, Position } from 'react-flow-renderer';
import { useConfig, useColorValue, useColorToken } from '~/context';
import { useConfig, useColorValue, useColorToken, useBreakpointValue } from '~/context';
import { useASNDetail } from '~/hooks';
import { Controls } from './controls';
import { buildElements } from './util';
import type { ReactFlowProps } from 'react-flow-renderer';
import type { TChart, TNode, TNodeData } from './types';
export const Chart = (props: TChart) => {
const { data } = props;
const { primary_asn, org_name } = useConfig();
// const elements = useMemo(() => [...buildElements({ asn: primary_asn, name: org_name }, data)], [
// data,
// ]);
const elements = [...buildElements({ asn: primary_asn, name: org_name }, data)];
const dots = useColorToken('blackAlpha.500', 'whiteAlpha.400');
const flowProps = useBreakpointValue<Omit<ReactFlowProps, 'elements'>>({
base: { defaultPosition: [0, 300], defaultZoom: 0 },
lg: { defaultPosition: [500, 450] },
});
const elements = useMemo(() => [...buildElements({ asn: primary_asn, name: org_name }, data)], [
data,
]);
return (
<Box boxSize="100%">
<ReactFlow elements={elements} nodeTypes={{ TestNode }} defaultPosition={[500, 450]}>
<Background color={dots} />
<Controls />
</ReactFlow>
</Box>
<ReactFlowProvider>
<Box boxSize="100%" zIndex={1}>
<ReactFlow elements={elements} nodeTypes={{ TestNode }} {...flowProps}>
<Background color={dots} />
<Controls />
</ReactFlow>
</Box>
</ReactFlowProvider>
);
};
const TestNode = (props: TNode<TNodeData>) => {
const { data } = props;
const { asn, name, hasChildren, hasParents } = data;
const color = useColorValue('black', 'white');
const bg = useColorValue('white', 'blackAlpha.800');
const bg = useColorValue('white', 'whiteAlpha.100');
const { data: asnData, isError, isLoading } = useASNDetail(asn);
return (
<>
{hasChildren && <Handle type="source" position={Position.Top} />}
@@ -42,7 +54,9 @@ const TestNode = (props: TNode<TNodeData>) => {
<VStack spacing={4}>
<Flex fontSize="lg">
{isLoading ? (
<BeatLoader color={color} />
<Box h={2} w={24}>
<SkeletonText noOfLines={1} color={color} />
</Box>
) : !isError && asnData?.data?.description_short ? (
asnData.data.description_short
) : (

View File

@@ -0,0 +1,27 @@
import dynamic from 'next/dynamic';
import { ButtonGroup, IconButton } from '@chakra-ui/react';
import { useZoomPanHelper } from 'react-flow-renderer';
const Plus = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiPlus));
const Minus = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiMinus));
const Square = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiSquare));
export const Controls = () => {
const { fitView, zoomIn, zoomOut } = useZoomPanHelper();
return (
<ButtonGroup
m={4}
size="sm"
right={0}
zIndex={4}
bottom={0}
isAttached
pos="absolute"
variant="solid"
colorScheme="secondary">
<IconButton icon={<Plus />} onClick={() => zoomIn()} aria-label="Zoom In" />
<IconButton icon={<Minus />} onClick={() => zoomOut()} aria-label="Zoom Out" />
<IconButton icon={<Square />} onClick={() => fitView()} aria-label="Fit Nodes" />
</ButtonGroup>
);
};

View File

@@ -22,13 +22,13 @@ export const Path = (props: TPath) => {
const { displayTarget } = useLGState();
const response = getResponse(device);
const output = response?.output as TStructuredResponse;
const bg = useColorValue('white', 'blackFaded.800');
const bg = useColorValue('whiteFaded.50', 'blackFaded.900');
return (
<>
<PathButton onOpen={onOpen} />
<Modal isOpen={isOpen} onClose={onClose} size="full" isCentered>
<ModalOverlay />
<ModalContent bg={bg} maxW="80%" maxH="60%">
<ModalContent bg={bg} maxW={{ base: '100%', lg: '80%' }} maxH={{ base: '80%', lg: '60%' }}>
<ModalHeader>{`Path to ${displayTarget.value}`}</ModalHeader>
<ModalCloseButton />
<ModalBody>

View File

@@ -148,6 +148,7 @@ export const Results = () => {
const device = getDevice(loc.value);
return (
<Result
key={i}
index={i}
device={device}
queryLocation={loc.value}