continue typescript & chakra v1 migrations [skip ci]

This commit is contained in:
checktheroads 2020-12-13 01:49:13 -07:00
parent 59e767e501
commit 8c454cf0ae
20 changed files with 119 additions and 157 deletions

View file

@ -19,7 +19,7 @@ export const ResetButton = forwardRef<HTMLButtonElement, ButtonProps>((props, re
aria-label="Reset Form"
opacity={isSubmitting.value ? 1 : 0}
{...props}>
<Icon as={ChevronLeft} boxSize={24} />
<Icon as={ChevronLeft} boxSize={8} />
</Button>
);
});

View file

@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { Select } from '~/components';
import { useConfig } from '~/context';
import type { TNetwork, TSelectOption } from '~/types';
import type { TNetwork, TSelectOptionMulti } from '~/types';
import type { TQuerySelectField } from './types';
function buildOptions(networks: TNetwork[]) {
@ -23,11 +23,12 @@ export const QueryLocation = (props: TQuerySelectField) => {
const { networks } = useConfig();
const options = useMemo(() => buildOptions(networks), [networks.length]);
function handleChange(e: TSelectOption): void {
if (Array.isArray(e?.value) && e !== null) {
const value = e.value.map(sel => sel);
onChange({ field: 'query_location', value });
function handleChange(e: TSelectOptionMulti): void {
if (e === null) {
e = [];
}
const value = e.map(sel => sel.value);
onChange({ field: 'query_location', value });
}
return (

View file

@ -42,9 +42,9 @@ export const Frame = (props: TFrame) => {
{...props}
/>
<Footer />
{/* <If c={developer_mode}>
<If c={developer_mode}>
<Debugger />
</If> */}
</If>
</Flex>
<If c={web.greeting.enable && !greetingAck}>
<Greeting onClickThrough={setGreetingAck} />

View file

@ -1,9 +1,10 @@
import { useEffect, useMemo, useState } from 'react';
import { Flex } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { uniqWith } from 'lodash';
import { intersectionWith } from 'lodash';
import * as yup from 'yup';
import {
If,
AnimatedForm,
FormRow,
QueryVrf,
@ -17,30 +18,17 @@ import {
CommunitySelect,
} from '~/components';
import { useConfig, useGlobalState } from '~/context';
import { useStrf, useGreeting } from '~/hooks';
import { useStrf, useGreeting, useDevice } from '~/hooks';
import { isQueryType, isString } from '~/types';
import type { Families, TFormData, TDeviceVrf, TQueryTypes, OnChangeArgs } from '~/types';
function isString(a: any): a is string {
return typeof a === 'string';
}
function isQueryType(q: any): q is TQueryTypes {
let result = false;
if (
typeof q === 'string' &&
['bgp_route', 'bgp_community', 'bgp_aspath', 'ping', 'traceroute'].includes(q)
) {
result = true;
}
return result;
}
export const HyperglassForm = () => {
const { web, content, devices, messages, networks, queries } = useConfig();
const { web, content, messages, queries } = useConfig();
const { formData, isSubmitting } = useGlobalState();
const [greetingAck, setGreetingAck] = useGreeting();
const getDevice = useDevice();
const noQueryType = useStrf(messages.no_input, { field: web.text.query_type });
const noQueryLoc = useStrf(messages.no_input, { field: web.text.query_location });
@ -77,105 +65,23 @@ export const HyperglassForm = () => {
}
}
/*
const handleLocChange = locObj => {
setQueryLocation(locObj.value);
const allVrfs = [];
const deviceVrfs = [];
locObj.value.map(loc => {
const locVrfs = [];
config.devices[loc].vrfs.map(vrf => {
locVrfs.push({
label: vrf.display_name,
value: vrf.id,
});
deviceVrfs.push([{ id: vrf.id, ipv4: vrf.ipv4, ipv6: vrf.ipv6 }]);
});
allVrfs.push(locVrfs);
});
function handleLocChange(locations: string[]): void {
const allVrfs = [] as TDeviceVrf[][];
deviceVrfs.length !== 0 &&
intersecting.length !== 0 &&
deviceVrfs
.filter(v => intersecting.every(i => i.id === v.id))
.reduce((a, b) => a.concat(b))
.filter(v => v.id === 'default')
.map(v => {
v.ipv4 === true && ipv4++;
v.ipv6 === true && ipv6++;
});
*/
setQueryLocation(locations);
// function handleLocChange(locObj: TSelectOption) {
// const allVrfs = [] as TDeviceVrf[][];
// const deviceVrfs = [] as TDeviceVrf[][];
// if (Array.isArray(locObj.value)) {
// setQueryLocation(locObj.value);
// for (const loc of locObj.value) {
// const locVrfs = [] as TDeviceVrf[];
// for (const vrf of devices.filter(dev => dev.name === loc)[0].vrfs) {
// locVrfs.push(vrf);
// deviceVrfs.push([vrf]);
// }
// allVrfs.push(locVrfs);
// }
// }
// // Use _.intersectionWith to create an array of VRFs common to all selected locations.
// const intersecting: TDeviceVrf[] = intersectionWith(...allVrfs, isEqual);
// setAvailVrfs(intersecting);
// // If there are no intersecting VRFs, use the default VRF.
// if (intersecting.filter(i => i.id === queryVrf).length === 0 && queryVrf !== 'default') {
// setQueryVrf('default');
// }
// let ipv4 = 0;
// let ipv6 = 0;
// if (deviceVrfs.length !== 0 && intersecting.length !== 0) {
// const matching = deviceVrfs
// // Select intersecting VRFs
// .filter(v => intersecting.every(i => i.id === v.id))
// .reduce((a, b) => a.concat(b))
// .filter(v => v.id === 'default');
// for (const match of matching) {
// if (match.ipv4) {
// ipv4++;
// }
// if (match.ipv6) {
// ipv6++;
// }
// }
// }
// if (ipv4 !== 0 && ipv4 === ipv6) {
// setFamilies([4, 6]);
// } else if (ipv4 > ipv6) {
// setFamilies([4]);
// } else if (ipv4 < ipv6) {
// setFamilies([6]);
// } else {
// setFamilies([]);
// }
// }
function handleLocChange(locations: string | string[]): void {
const allVrfs = [] as TDeviceVrf[];
if (Array.isArray(locations)) {
setQueryLocation(locations);
for (const loc of locations) {
for (const vrf of devices.filter(dev => dev.name === loc)[0].vrfs) {
allVrfs.push(vrf);
}
}
// Create an array of each device's VRFs.
for (const loc of locations) {
const device = getDevice(loc);
allVrfs.push(device.vrfs);
}
// Use _.intersectionWith to create an array of VRFs common to all selected locations.
const intersecting = uniqWith<TDeviceVrf>(allVrfs, (a, b) => a.id === b.id);
const intersecting = intersectionWith(
...allVrfs,
(a: TDeviceVrf, b: TDeviceVrf) => a.id === b.id,
);
setAvailVrfs(intersecting);
// If there are no intersecting VRFs, use the default VRF.
@ -211,7 +117,7 @@ export const HyperglassForm = () => {
function handleChange(e: OnChangeArgs): void {
setValue(e.field, e.value);
if (e.field === 'query_location') {
if (e.field === 'query_location' && Array.isArray(e.value)) {
handleLocChange(e.value);
} else if (e.field === 'query_type' && isQueryType(e.value)) {
setQueryType(e.value);
@ -277,11 +183,11 @@ export const HyperglassForm = () => {
</FormField>
</FormRow>
<FormRow>
{availVrfs.length > 1 && (
<If c={availVrfs.length > 1}>
<FormField label={web.text.query_vrf} name="query_vrf" errors={errors.query_vrf}>
<QueryVrf label={web.text.query_vrf} vrfs={availVrfs} onChange={handleChange} />
</FormField>
)}
</If>
<FormField
name="query_target"
errors={errors.query_target}
@ -298,7 +204,7 @@ export const HyperglassForm = () => {
/>
)
}>
{queryType === 'bgp_community' && queries.bgp_community.mode === 'select' ? (
<If c={queryType === 'bgp_community' && queries.bgp_community.mode === 'select'}>
<CommunitySelect
name="query_target"
register={register}
@ -306,7 +212,8 @@ export const HyperglassForm = () => {
onChange={handleChange}
communities={queries.bgp_community.communities}
/>
) : (
</If>
<If c={!(queryType === 'bgp_community' && queries.bgp_community.mode === 'select')}>
<QueryTarget
name="query_target"
register={register}
@ -319,7 +226,7 @@ export const HyperglassForm = () => {
setDisplayValue={setDisplayTarget}
placeholder={web.text.query_target}
/>
)}
</If>
</FormField>
</FormRow>
<FormRow mt={0} justifyContent="flex-end">

View file

@ -32,7 +32,11 @@ export interface TCommunities {
}
export interface TRPKIState {
state: 0 | 1 | 2 | 3;
state:
| 0 // Invalid
| 1 // Valid
| 2 // Unknown
| 3; // Unverified
active: boolean;
}

View file

@ -3,13 +3,16 @@ import { Accordion, Box, Stack, useToken } from '@chakra-ui/react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimatedDiv, Label } from '~/components';
import { useConfig, useBreakpointValue } from '~/context';
import { useDevice } from '~/hooks';
import { isQueryType } from '~/types';
import { Result } from './individual';
import type { TResults } from './types';
export const Results = (props: TResults) => {
const { queryLocation, queryType, queryVrf, queryTarget, ...rest } = props;
const { request_timeout, devices, queries, vrfs, web } = useConfig();
const { request_timeout, queries, vrfs, web } = useConfig();
const getDevice = useDevice();
const targetBg = useToken('colors', 'teal.600');
const queryBg = useToken('colors', 'cyan.500');
const vrfBg = useToken('colors', 'blue.500');
@ -61,6 +64,11 @@ export const Results = (props: TResults) => {
const matchedVrf =
vrfs.filter(v => v.id === queryVrf)[0] ?? vrfs.filter(v => v.id === 'default')[0];
let queryTypeLabel = '';
if (isQueryType(queryType)) {
queryTypeLabel = queries[queryType].display_name;
}
return (
<>
<Box
@ -84,7 +92,7 @@ export const Results = (props: TResults) => {
bg={queryBg}
label={web.text.query_type}
fontSize={{ base: 'xs', md: 'sm' }}
value={queries[queryType].display_name}
value={queryTypeLabel}
/>
</motion.div>
<motion.div
@ -134,7 +142,7 @@ export const Results = (props: TResults) => {
<AnimatePresence>
{queryLocation &&
queryLocation.map((loc, i) => {
const device = devices.filter(d => d.name === loc)[0];
const device = getDevice(loc);
return (
<motion.div
animate={{ opacity: 1, y: 0 }}

View file

@ -1,14 +1,12 @@
import dynamic from 'next/dynamic';
import { forwardRef, useMemo } from 'react';
import { AccordionIcon, Icon, Spinner, Stack, Text, Tooltip } from '@chakra-ui/react';
import { AccordionIcon, Box, Spinner, Stack, Text, Tooltip } from '@chakra-ui/react';
import { BisError as Warning } from '@meronex/icons/bi';
import { FaCheckCircle as Check } from '@meronex/icons/fa';
import { useConfig, useColorValue } from '~/context';
import { useStrf } from '~/hooks';
import type { TResultHeader } from './types';
const Check = dynamic<MeronexIcon>(() => import('@meronex/icons/fa').then(i => i.FaCheckCircle));
const Warning = dynamic<MeronexIcon>(() => import('@meronex/icons/bi').then(i => i.BisError));
const runtimeText = (runtime: number, text: string): string => {
let unit = 'seconds';
if (runtime === 1) {
@ -34,11 +32,11 @@ export const ResultHeader = forwardRef<HTMLDivElement, TResultHeader>((props, re
<Spinner size="sm" mr={4} color={status} />
) : error ? (
<Tooltip hasArrow label={errorMsg} placement="top">
<Icon as={Warning} color={warning} mr={4} boxSize={6} />
<Box as={Warning} color={warning} mr={4} boxSize={6} />
</Tooltip>
) : (
<Tooltip hasArrow label={label} placement="top">
<Icon as={Check} color={defaultStatus} mr={4} boxSize={6} />
<Box as={Check} color={defaultStatus} mr={4} boxSize={6} />
</Tooltip>
)}
<Text fontSize="lg">{title}</Text>

View file

@ -119,7 +119,7 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
let copyValue = data?.output;
const formatData = useTableToString(queryTarget, data, [data.format]);
const formatData = useTableToString(queryTarget, data, [data?.format]);
if (data?.format === 'application/json') {
copyValue = formatData();

View file

@ -18,14 +18,14 @@ import {
} from './styles';
import type { TSelectOption } from '~/types';
import type { TSelect, TSelectContext, TBoxAsReactSelect } from './types';
import type { TSelectBase, TSelectContext, TBoxAsReactSelect } from './types';
const SelectContext = createContext<TSelectContext>(Object());
export const useSelectContext = () => useContext(SelectContext);
const ReactSelectAsBox = (props: TBoxAsReactSelect) => <Box as={ReactSelect} {...props} />;
export const Select = (props: TSelect) => {
export const Select = (props: TSelectBase) => {
const { ctl, options, multi, onSelect, ...rest } = props;
const { isOpen, onOpen, onClose } = useDisclosure();

View file

@ -26,7 +26,7 @@ export interface TSelectBase extends TBoxAsReactSelect {
multi?: boolean;
options: TOptions;
required?: boolean;
onSelect?: (s: TSelectOption) => void;
onSelect?: (s: TSelectOption[]) => void;
onChange?: (c: TSelectOption) => void;
colorScheme?: ColorNames;
}

View file

@ -1,4 +1,5 @@
export * from './useBooleanValue';
export * from './useDevice';
export * from './useGreeting';
export * from './useOpposingColor';
export * from './useSessionStorage';

View file

@ -0,0 +1,15 @@
import { useCallback, useMemo } from 'react';
import { useConfig } from '~/context';
import { flatten } from '~/util';
import type { TDevice } from '~/types';
export function useDevice(): (i: string) => TDevice {
const { networks } = useConfig();
const devices = useMemo(() => flatten<TDevice>(networks.map(n => n.locations)), []);
function getDevice(id: string): TDevice {
return devices.filter(dev => dev.name === id)[0];
}
return useCallback(getDevice, []);
}

View file

@ -37,7 +37,7 @@
"react-hook-form": "^5.7",
"react-markdown": "^4.3.1",
"react-query": "^2.26.4",
"react-select": "^3.0.8",
"react-select": "^3.1.1",
"react-string-replace": "^0.4.4",
"react-table": "^7.6.2",
"string-format": "^2.0.0",
@ -47,7 +47,7 @@
},
"devDependencies": {
"@types/node": "^14.11.10",
"@types/react-select": "^3.0.22",
"@types/react-select": "^3.0.28",
"@types/react-table": "^7.0.25",
"@types/string-format": "^2.0.0",
"@types/yup": "^0.29.9",

View file

@ -1,8 +1,12 @@
export type TSelectOption = {
export type TSelectOptionBase = {
label: string;
value: string | string[];
value: string;
group?: string;
} | null;
};
export type TSelectOption = TSelectOptionBase | null;
export type TSelectOptionMulti = TSelectOptionBase[] | null;
export type TSelectOptionGroup = {
label: string;

View file

@ -121,12 +121,12 @@ export interface TDevice extends TDeviceBase {
}
export interface TNetworkLocation extends TDeviceBase {
vrfs: TDeviceVrfBase[];
vrfs: TDeviceVrf[];
}
export interface TNetwork {
display_name: string;
locations: TNetworkLocation[];
locations: TDevice[];
}
export type TParsedDataField = [string, keyof TRoute, 'left' | 'right' | 'center' | null];

View file

@ -1,4 +1,5 @@
export type TQueryTypes = '' | 'bgp_route' | 'bgp_community' | 'bgp_aspath' | 'ping' | 'traceroute';
export type TQueryTypes = '' | TValidQueryTypes;
export type TValidQueryTypes = 'bgp_route' | 'bgp_community' | 'bgp_aspath' | 'ping' | 'traceroute';
export interface TFormData {
query_location: string[];

View file

@ -0,0 +1,16 @@
import { TValidQueryTypes } from './data';
export function isQueryType(q: any): q is TValidQueryTypes {
let result = false;
if (
typeof q === 'string' &&
['bgp_route', 'bgp_community', 'bgp_aspath', 'ping', 'traceroute'].includes(q)
) {
result = true;
}
return result;
}
export function isString(a: any): a is string {
return typeof a === 'string';
}

View file

@ -2,5 +2,6 @@ export * from './common';
export * from './config';
export * from './data';
export * from './dns-over-https';
export * from './guards';
export * from './table';
export * from './theme';

View file

@ -6,3 +6,9 @@ export function all(...iter: any[]) {
}
return true;
}
export function flatten<T extends unknown>(arr: any[][]): T[] {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}

View file

@ -1055,10 +1055,10 @@
dependencies:
"@types/react" "*"
"@types/react-select@^3.0.22":
version "3.0.22"
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.22.tgz#b88306365e99fa86809a5c0ce0f1b4e8d0b626bf"
integrity sha512-fqgmC979JPr/6476Pau6QnmI9zVV664R7Q92Ld1rgTn+umtUXT5X3+PO/x6O4imCZnh7XCqZcouabWAlAQJNpQ==
"@types/react-select@^3.0.28":
version "3.0.28"
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.28.tgz#b9adb98421926321b81c4cfe6a40121c96a421f0"
integrity sha512-Gfg3a/EPLyUQkfezcCQkmLW1Vz6+ziclJhn8dpBUEYJF3IUoxS81ToAi3ky2xtnAyk2wJFMXLvE73KiUd56yTA==
dependencies:
"@types/react" "*"
"@types/react-dom" "*"
@ -5549,10 +5549,10 @@ react-remove-scroll@2.4.0:
use-callback-ref "^1.2.3"
use-sidecar "^1.0.1"
react-select@^3.0.8:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"
integrity sha512-wBFVblBH1iuCBprtpyGtd1dGMadsG36W5/t2Aj8OE6WbByDg5jIFyT7X5gT+l0qmT5TqWhxX+VsKJvCEl2uL9g==
react-select@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.1.tgz#156a5b4a6c22b1e3d62a919cb1fd827adb4060bc"
integrity sha512-HjC6jT2BhUxbIbxMZWqVcDibrEpdUJCfGicN0MMV+BQyKtCaPTgFekKWiOizSCy4jdsLMGjLqcFGJMhVGWB0Dg==
dependencies:
"@babel/runtime" "^7.4.4"
"@emotion/cache" "^10.0.9"