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:
@@ -1,3 +1,4 @@
|
|||||||
|
import * as React from 'react';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { Flex, Icon, Text } from '@chakra-ui/react';
|
import { Flex, Icon, Text } from '@chakra-ui/react';
|
||||||
import { usePagination, useSortBy, useTable } from 'react-table';
|
import { usePagination, useSortBy, useTable } from 'react-table';
|
||||||
@@ -68,18 +69,18 @@ export function Table(props: TTable) {
|
|||||||
const instance = useTable<TRoute>(options, ...plugins);
|
const instance = useTable<TRoute>(options, ...plugins);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
|
||||||
headerGroups,
|
|
||||||
prepareRow,
|
|
||||||
page,
|
page,
|
||||||
canPreviousPage,
|
|
||||||
canNextPage,
|
|
||||||
pageOptions,
|
|
||||||
pageCount,
|
|
||||||
gotoPage,
|
gotoPage,
|
||||||
nextPage,
|
nextPage,
|
||||||
previousPage,
|
pageCount,
|
||||||
|
prepareRow,
|
||||||
|
canNextPage,
|
||||||
|
pageOptions,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
|
headerGroups,
|
||||||
|
previousPage,
|
||||||
|
getTableProps,
|
||||||
|
canPreviousPage,
|
||||||
state: { pageIndex, pageSize },
|
state: { pageIndex, pageSize },
|
||||||
} = instance;
|
} = instance;
|
||||||
|
|
||||||
@@ -116,7 +117,6 @@ export function Table(props: TTable) {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{page.map((row, key) => {
|
{page.map((row, key) => {
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
index={key}
|
index={key}
|
||||||
@@ -131,7 +131,8 @@ export function Table(props: TTable) {
|
|||||||
align={cell.column.align}
|
align={cell.column.align}
|
||||||
bordersVertical={[bordersVertical, i]}
|
bordersVertical={[bordersVertical, i]}
|
||||||
{...cell.getCellProps()}>
|
{...cell.getCellProps()}>
|
||||||
{cellRender ?? cell.render('Cell')}
|
{/* {cellRender ?? cell.render('Cell')} */}
|
||||||
|
{React.createElement(cellRender, cell)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@@ -5,6 +5,10 @@ import type { TCell } from './types';
|
|||||||
export const Cell = (props: TCell) => {
|
export const Cell = (props: TCell) => {
|
||||||
const { data, rawData } = props;
|
const { data, rawData } = props;
|
||||||
const cellId = data.column.id as keyof TRoute;
|
const cellId = data.column.id as keyof TRoute;
|
||||||
|
console.group(cellId);
|
||||||
|
console.dir(data);
|
||||||
|
console.dir(rawData);
|
||||||
|
console.groupEnd();
|
||||||
const component = {
|
const component = {
|
||||||
med: <MonoField v={data.value} />,
|
med: <MonoField v={data.value} />,
|
||||||
age: <Age inSeconds={data.value} />,
|
age: <Age inSeconds={data.value} />,
|
||||||
|
@@ -1,14 +1,9 @@
|
|||||||
import dynamic from 'next/dynamic';
|
import { Icon, Text, Box, Tooltip, Menu, MenuButton, MenuList } from '@chakra-ui/react';
|
||||||
import {
|
import { CgMoreO as More } from '@meronex/icons/cg';
|
||||||
Icon,
|
import { BisError as Warning } from '@meronex/icons/bi';
|
||||||
Text,
|
import { MdNotInterested as NotAllowed, MdLastPage } from '@meronex/icons/md';
|
||||||
Popover,
|
import { BsQuestionCircleFill as Question } from '@meronex/icons/bs';
|
||||||
Tooltip,
|
import { FaCheckCircle as Check, FaChevronRight as ChevronRight } from '@meronex/icons/fa';
|
||||||
PopoverArrow,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { MdLastPage } from '@meronex/icons/md';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import relativeTimePlugin from 'dayjs/plugin/relativeTime';
|
import relativeTimePlugin from 'dayjs/plugin/relativeTime';
|
||||||
import utcPlugin from 'dayjs/plugin/utc';
|
import utcPlugin from 'dayjs/plugin/utc';
|
||||||
@@ -28,19 +23,6 @@ import type {
|
|||||||
dayjs.extend(relativeTimePlugin);
|
dayjs.extend(relativeTimePlugin);
|
||||||
dayjs.extend(utcPlugin);
|
dayjs.extend(utcPlugin);
|
||||||
|
|
||||||
const Check = dynamic<MeronexIcon>(() => import('@meronex/icons/fa').then(i => i.FaCheckCircle));
|
|
||||||
const More = dynamic<MeronexIcon>(() => import('@meronex/icons/cg').then(i => i.CgMoreO));
|
|
||||||
const NotAllowed = dynamic<MeronexIcon>(() =>
|
|
||||||
import('@meronex/icons/md').then(i => i.MdNotInterested),
|
|
||||||
);
|
|
||||||
const Question = dynamic<MeronexIcon>(() =>
|
|
||||||
import('@meronex/icons/bs').then(i => i.BsQuestionCircleFill),
|
|
||||||
);
|
|
||||||
const Warning = dynamic<MeronexIcon>(() => import('@meronex/icons/bi').then(i => i.BisError));
|
|
||||||
const ChevronRight = dynamic<MeronexIcon>(() =>
|
|
||||||
import('@meronex/icons/fa').then(i => i.FaChevronRight),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const MonoField = (props: TMonoField) => {
|
export const MonoField = (props: TMonoField) => {
|
||||||
const { v, ...rest } = props;
|
const { v, ...rest } = props;
|
||||||
return (
|
return (
|
||||||
@@ -128,22 +110,21 @@ export const Communities = (props: TCommunities) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</If>
|
</If>
|
||||||
<If c={communities.length !== 0}>
|
<If c={communities.length !== 0}>
|
||||||
<Popover trigger="hover" placement="right">
|
<Menu>
|
||||||
<PopoverTrigger>
|
<MenuButton>
|
||||||
<Icon as={More} />
|
<Icon as={More} />
|
||||||
</PopoverTrigger>
|
</MenuButton>
|
||||||
<PopoverContent
|
<MenuList
|
||||||
p={4}
|
p={3}
|
||||||
width="unset"
|
width="unset"
|
||||||
color={color}
|
color={color}
|
||||||
textAlign="left"
|
textAlign="left"
|
||||||
fontFamily="mono"
|
fontFamily="mono"
|
||||||
fontWeight="normal"
|
fontWeight="normal"
|
||||||
whiteSpace="pre-wrap">
|
whiteSpace="pre-wrap">
|
||||||
<PopoverArrow />
|
|
||||||
{communities.join('\n')}
|
{communities.join('\n')}
|
||||||
</PopoverContent>
|
</MenuList>
|
||||||
</Popover>
|
</Menu>
|
||||||
</If>
|
</If>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -173,7 +154,7 @@ export const RPKIState = (props: TRPKIState) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip hasArrow placement="right" label={text[state] ?? text[3]}>
|
<Tooltip hasArrow placement="right" label={text[state] ?? text[3]}>
|
||||||
<Icon icon={icon[state]} color={color[+active][state]} />
|
<Box as={icon[state]} color={color[+active][state]} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
11
hyperglass/ui/components/results/guards.ts
Normal file
11
hyperglass/ui/components/results/guards.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export function isStackError(error: any): error is Error {
|
||||||
|
return error !== null && 'message' in error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFetchError(error: any): error is Response {
|
||||||
|
return error !== null && 'statusText' in error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLGError(error: any): error is TQueryResponse {
|
||||||
|
return error !== null && 'output' in error;
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import { forwardRef, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { AccordionIcon, Box, Spinner, Stack, Text, Tooltip } from '@chakra-ui/react';
|
import { AccordionIcon, Box, Spinner, HStack, Text, Tooltip } from '@chakra-ui/react';
|
||||||
import { BisError as Warning } from '@meronex/icons/bi';
|
import { BisError as Warning } from '@meronex/icons/bi';
|
||||||
import { FaCheckCircle as Check } from '@meronex/icons/fa';
|
import { FaCheckCircle as Check } from '@meronex/icons/fa';
|
||||||
import { useConfig, useColorValue } from '~/context';
|
import { useConfig, useColorValue } from '~/context';
|
||||||
@@ -15,8 +15,8 @@ const runtimeText = (runtime: number, text: string): string => {
|
|||||||
return `${text} ${unit}`;
|
return `${text} ${unit}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ResultHeader = forwardRef<HTMLDivElement, TResultHeader>((props, ref) => {
|
export const ResultHeader = (props: TResultHeader) => {
|
||||||
const { title, loading, error, errorMsg, errorLevel, runtime } = props;
|
const { title, loading, isError, errorMsg, errorLevel, runtime } = props;
|
||||||
|
|
||||||
const status = useColorValue('primary.500', 'primary.300');
|
const status = useColorValue('primary.500', 'primary.300');
|
||||||
const warning = useColorValue(`${errorLevel}.500`, `${errorLevel}.300`);
|
const warning = useColorValue(`${errorLevel}.500`, `${errorLevel}.300`);
|
||||||
@@ -27,20 +27,27 @@ export const ResultHeader = forwardRef<HTMLDivElement, TResultHeader>((props, re
|
|||||||
const label = useMemo(() => runtimeText(runtime, text), [runtime]);
|
const label = useMemo(() => runtimeText(runtime, text), [runtime]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack ref={ref} isInline alignItems="center" w="100%">
|
<HStack alignItems="center" w="100%">
|
||||||
|
<Tooltip
|
||||||
|
hasArrow
|
||||||
|
placement="top"
|
||||||
|
isDisabled={loading}
|
||||||
|
label={isError ? errorMsg : label}
|
||||||
|
colorScheme={isError ? errorLevel : 'success'}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Spinner size="sm" mr={4} color={status} />
|
<Spinner size="sm" mr={4} color={status} />
|
||||||
) : error ? (
|
|
||||||
<Tooltip hasArrow label={errorMsg} placement="top">
|
|
||||||
<Box as={Warning} color={warning} mr={4} boxSize={6} />
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
) : (
|
||||||
<Tooltip hasArrow label={label} placement="top">
|
<Box
|
||||||
<Box as={Check} color={defaultStatus} mr={4} boxSize={6} />
|
as={isError ? Warning : Check}
|
||||||
</Tooltip>
|
color={isError ? warning : defaultStatus}
|
||||||
|
mr={4}
|
||||||
|
boxSize={6}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<Text fontSize="lg">{title}</Text>
|
<Text fontSize="lg">{title}</Text>
|
||||||
<AccordionIcon ml="auto" />
|
<AccordionIcon ml="auto" />
|
||||||
</Stack>
|
</HStack>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
@@ -10,17 +10,16 @@ import {
|
|||||||
AccordionButton,
|
AccordionButton,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { BsLightningFill } from '@meronex/icons/bs';
|
import { BsLightningFill } from '@meronex/icons/bs';
|
||||||
import useAxios from 'axios-hooks';
|
|
||||||
import { startCase } from 'lodash';
|
import { startCase } from 'lodash';
|
||||||
import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If } from '~/components';
|
import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If } from '~/components';
|
||||||
import { useColorValue, useConfig, useMobile } from '~/context';
|
import { useColorValue, useConfig, useMobile } from '~/context';
|
||||||
import { useStrf, useTableToString } from '~/hooks';
|
import { useStrf, useLGQuery, useTableToString } from '~/hooks';
|
||||||
|
import { isStructuredOutput, isStringOutput } from '~/types';
|
||||||
import { FormattedError } from './error';
|
import { FormattedError } from './error';
|
||||||
import { ResultHeader } from './header';
|
import { ResultHeader } from './header';
|
||||||
|
import { isStackError, isFetchError, isLGError } from './guards';
|
||||||
|
|
||||||
import type { TAccordionHeaderWrapper, TResult } from './types';
|
import type { TAccordionHeaderWrapper, TResult, TErrorLevels } from './types';
|
||||||
|
|
||||||
type TErrorLevels = 'success' | 'warning' | 'error';
|
|
||||||
|
|
||||||
const AccordionHeaderWrapper = (props: TAccordionHeaderWrapper) => {
|
const AccordionHeaderWrapper = (props: TAccordionHeaderWrapper) => {
|
||||||
const { hoverBg, ...rest } = props;
|
const { hoverBg, ...rest } = props;
|
||||||
@@ -38,7 +37,6 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||||||
const {
|
const {
|
||||||
index,
|
index,
|
||||||
device,
|
device,
|
||||||
timeout,
|
|
||||||
queryVrf,
|
queryVrf,
|
||||||
queryType,
|
queryType,
|
||||||
queryTarget,
|
queryTarget,
|
||||||
@@ -54,20 +52,12 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||||||
const scrollbarHover = useColorValue('blackAlpha.400', 'whiteAlpha.400');
|
const scrollbarHover = useColorValue('blackAlpha.400', 'whiteAlpha.400');
|
||||||
const scrollbarBg = useColorValue('blackAlpha.50', 'whiteAlpha.50');
|
const scrollbarBg = useColorValue('blackAlpha.50', 'whiteAlpha.50');
|
||||||
|
|
||||||
let [{ data, loading, error }, refetch] = useAxios(
|
const { data, error, isError, isLoading, refetch } = useLGQuery({
|
||||||
{
|
queryLocation,
|
||||||
url: '/api/query/',
|
queryTarget,
|
||||||
method: 'post',
|
queryType,
|
||||||
data: {
|
queryVrf,
|
||||||
query_vrf: queryVrf,
|
});
|
||||||
query_type: queryType,
|
|
||||||
query_target: queryTarget,
|
|
||||||
query_location: queryLocation,
|
|
||||||
},
|
|
||||||
timeout,
|
|
||||||
},
|
|
||||||
{ useCache: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
const cacheLabel = useStrf(web.text.cache_icon, { time: data?.timestamp }, [data?.timestamp]);
|
const cacheLabel = useStrf(web.text.cache_icon, { time: data?.timestamp }, [data?.timestamp]);
|
||||||
|
|
||||||
@@ -79,16 +69,23 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||||||
setOverride(true);
|
setOverride(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorKw = (error && error.response?.data?.keywords) || [];
|
const errorKeywords = useMemo(() => {
|
||||||
|
let kw = [] as string[];
|
||||||
|
if (isLGError(error)) {
|
||||||
|
kw = error.keywords;
|
||||||
|
}
|
||||||
|
return kw;
|
||||||
|
}, [isError]);
|
||||||
|
|
||||||
let errorMsg;
|
let errorMsg;
|
||||||
if (error && error.response?.data?.output) {
|
|
||||||
errorMsg = error.response.data.output;
|
if (isLGError(error)) {
|
||||||
} else if (error && error.message.startsWith('timeout')) {
|
errorMsg = error.output as string;
|
||||||
|
} else if (isFetchError(error)) {
|
||||||
|
errorMsg = startCase(error.statusText);
|
||||||
|
} else if (isStackError(error) && error.message.toLowerCase().startsWith('timeout')) {
|
||||||
errorMsg = messages.request_timeout;
|
errorMsg = messages.request_timeout;
|
||||||
} else if (error?.response?.statusText) {
|
} else if (isStackError(error)) {
|
||||||
errorMsg = startCase(error.response.statusText);
|
|
||||||
} else if (error && error.message) {
|
|
||||||
errorMsg = startCase(error.message);
|
errorMsg = startCase(error.message);
|
||||||
} else {
|
} else {
|
||||||
errorMsg = messages.general;
|
errorMsg = messages.general;
|
||||||
@@ -96,7 +93,7 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||||||
|
|
||||||
error && console.error(error);
|
error && console.error(error);
|
||||||
|
|
||||||
const getErrorLevel = (): TErrorLevels => {
|
const errorLevel = useMemo<TErrorLevels>(() => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
success: 'success',
|
success: 'success',
|
||||||
warning: 'warning',
|
warning: 'warning',
|
||||||
@@ -106,18 +103,22 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||||||
|
|
||||||
let e: TErrorLevels = 'error';
|
let e: TErrorLevels = 'error';
|
||||||
|
|
||||||
if (error?.response?.data?.level) {
|
if (isLGError(error)) {
|
||||||
const idx = error.response.data.level as TResponseLevel;
|
const idx = error.level as TResponseLevel;
|
||||||
e = statusMap[idx];
|
e = statusMap[idx];
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
};
|
}, [error]);
|
||||||
|
|
||||||
const errorLevel = useMemo(() => getErrorLevel(), [error]);
|
const tableComponent = useMemo<boolean>(() => {
|
||||||
|
let result = false;
|
||||||
|
if (typeof queryType.match(/^bgp_\w+$/) !== null && data?.format === 'application/json') {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [queryType, data?.format]);
|
||||||
|
|
||||||
const tableComponent = useMemo(() => typeof queryType.match(/^bgp_\w+$/) !== null, [queryType]);
|
let copyValue = data?.output as string;
|
||||||
|
|
||||||
let copyValue = data?.output;
|
|
||||||
|
|
||||||
const formatData = useTableToString(queryTarget, data, [data?.format]);
|
const formatData = useTableToString(queryTarget, data, [data?.format]);
|
||||||
|
|
||||||
@@ -130,18 +131,22 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
!loading && resultsComplete === null && setComplete(index);
|
if (isLoading && resultsComplete === null) {
|
||||||
}, [loading, resultsComplete]);
|
setComplete(index);
|
||||||
|
}
|
||||||
|
}, [isLoading, resultsComplete]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resultsComplete === index && !hasOverride && setOpen(true);
|
if (resultsComplete === index && !hasOverride) {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
}, [resultsComplete, index]);
|
}, [resultsComplete, index]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
isDisabled={loading}
|
isDisabled={isLoading}
|
||||||
css={{
|
css={{
|
||||||
'&:last-of-type': { borderBottom: 'none' },
|
'&:last-of-type': { borderBottom: 'none' },
|
||||||
'&:first-of-type': { borderTop: 'none' },
|
'&:first-of-type': { borderTop: 'none' },
|
||||||
@@ -155,17 +160,17 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||||||
flex="1 0 auto"
|
flex="1 0 auto"
|
||||||
onClick={handleToggle}>
|
onClick={handleToggle}>
|
||||||
<ResultHeader
|
<ResultHeader
|
||||||
error={error}
|
isError={isError}
|
||||||
loading={loading}
|
loading={isLoading}
|
||||||
errorMsg={errorMsg}
|
errorMsg={errorMsg}
|
||||||
errorLevel={errorLevel}
|
errorLevel={errorLevel}
|
||||||
runtime={data?.runtime}
|
runtime={data?.runtime ?? 0}
|
||||||
title={device.display_name}
|
title={device.display_name}
|
||||||
/>
|
/>
|
||||||
</AccordionButton>
|
</AccordionButton>
|
||||||
<ButtonGroup px={[1, 1, 3, 3]} py={2}>
|
<ButtonGroup px={[1, 1, 3, 3]} py={2}>
|
||||||
<CopyButton copyValue={copyValue} isDisabled={loading} />
|
<CopyButton copyValue={copyValue} isDisabled={isLoading} />
|
||||||
<RequeryButton requery={refetch} variant="ghost" isDisabled={loading} />
|
<RequeryButton requery={refetch} variant="ghost" isDisabled={isLoading} />
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</AccordionHeaderWrapper>
|
</AccordionHeaderWrapper>
|
||||||
<AccordionPanel
|
<AccordionPanel
|
||||||
@@ -188,20 +193,16 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||||||
}}>
|
}}>
|
||||||
<Flex direction="column" flexWrap="wrap">
|
<Flex direction="column" flexWrap="wrap">
|
||||||
<Flex direction="column" flex="1 0 auto" maxW={error ? '100%' : undefined}>
|
<Flex direction="column" flex="1 0 auto" maxW={error ? '100%' : undefined}>
|
||||||
<If c={!error && data}>
|
{!isError && typeof data !== 'undefined' && (
|
||||||
<If c={tableComponent}>
|
<>
|
||||||
<BGPTable>{data?.output}</BGPTable>
|
{isStructuredOutput(data) && tableComponent ? (
|
||||||
</If>
|
<BGPTable>{data.output}</BGPTable>
|
||||||
<If c={!tableComponent}>
|
) : isStringOutput(data) && !tableComponent ? (
|
||||||
<TextOutput>{data?.output}</TextOutput>
|
<TextOutput>{data.output}</TextOutput>
|
||||||
</If>
|
) : null}
|
||||||
</If>
|
</>
|
||||||
|
|
||||||
{error && (
|
|
||||||
<Alert rounded="lg" my={2} py={4} status={errorLevel}>
|
|
||||||
<FormattedError keywords={errorKw} message={errorMsg} />
|
|
||||||
</Alert>
|
|
||||||
)}
|
)}
|
||||||
|
{isError && <Alert rounded="lg" my={2} py={4} status={errorLevel}></Alert>}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import type { BoxProps, FlexProps } from '@chakra-ui/react';
|
import type { BoxProps, FlexProps } from '@chakra-ui/react';
|
||||||
import type { TDevice, TQueryTypes } from '~/types';
|
import type { TDevice, TQueryTypes, TFormState } from '~/types';
|
||||||
|
|
||||||
export interface TResultHeader {
|
export interface TResultHeader {
|
||||||
title: string;
|
title: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error?: Error;
|
isError?: boolean;
|
||||||
errorMsg: string;
|
errorMsg: string;
|
||||||
errorLevel: 'success' | 'warning' | 'error';
|
errorLevel: 'success' | 'warning' | 'error';
|
||||||
runtime: number;
|
runtime: number;
|
||||||
@@ -22,18 +22,14 @@ export interface TAccordionHeaderWrapper extends FlexProps {
|
|||||||
export interface TResult {
|
export interface TResult {
|
||||||
index: number;
|
index: number;
|
||||||
device: TDevice;
|
device: TDevice;
|
||||||
timeout: number;
|
|
||||||
queryVrf: string;
|
queryVrf: string;
|
||||||
queryType: TQueryTypes;
|
queryType: TQueryTypes;
|
||||||
queryTarget: string;
|
queryTarget: string;
|
||||||
setComplete(v: number | null): void;
|
setComplete(v: number | null): void;
|
||||||
queryLocation: string;
|
queryLocation: string[];
|
||||||
resultsComplete: number | null;
|
resultsComplete: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TResults extends BoxProps {
|
export type TResults = TFormState & BoxProps;
|
||||||
queryType: TQueryTypes;
|
|
||||||
queryLocation: string[];
|
export type TErrorLevels = 'success' | 'warning' | 'error';
|
||||||
queryTarget: string;
|
|
||||||
queryVrf: string;
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
export * from './useBooleanValue';
|
export * from './useBooleanValue';
|
||||||
export * from './useDevice';
|
export * from './useDevice';
|
||||||
export * from './useGreeting';
|
export * from './useGreeting';
|
||||||
|
export * from './useLGQuery';
|
||||||
export * from './useOpposingColor';
|
export * from './useOpposingColor';
|
||||||
export * from './useSessionStorage';
|
export * from './useSessionStorage';
|
||||||
export * from './useStrf';
|
export * from './useStrf';
|
||||||
|
@@ -3,8 +3,4 @@ export interface TOpposingOptions {
|
|||||||
dark?: string;
|
dark?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TStringTableData extends Omit<TQueryResponse, 'output'> {
|
|
||||||
output: TStructuredResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TUseGreetingReturn = [boolean, (v?: boolean) => void];
|
export type TUseGreetingReturn = [boolean, (v?: boolean) => void];
|
||||||
|
62
hyperglass/ui/hooks/useLGQuery.ts
Normal file
62
hyperglass/ui/hooks/useLGQuery.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { useQuery } from 'react-query';
|
||||||
|
import { useConfig } from '~/context';
|
||||||
|
|
||||||
|
import type { TFormState } from '~/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Wrapper that incorporates a timeout via a passed AbortController instance.
|
||||||
|
*
|
||||||
|
* Adapted from: https://lowmess.com/blog/fetch-with-timeout
|
||||||
|
*/
|
||||||
|
export async function fetchWithTimeout(
|
||||||
|
uri: string,
|
||||||
|
options: RequestInit = {},
|
||||||
|
timeout: number,
|
||||||
|
controller: AbortController,
|
||||||
|
): Promise<Response> {
|
||||||
|
/**
|
||||||
|
* Lets set up our `AbortController`, and create a request options object that includes the
|
||||||
|
* controller's `signal` to pass to `fetch`.
|
||||||
|
*/
|
||||||
|
const { signal = new AbortController().signal, ...allOptions } = options;
|
||||||
|
const config = { ...allOptions, signal };
|
||||||
|
/**
|
||||||
|
* Set a timeout limit for the request using `setTimeout`. If the body of this timeout is
|
||||||
|
* reached before the request is completed, it will be cancelled.
|
||||||
|
*/
|
||||||
|
setTimeout(() => {
|
||||||
|
controller.abort();
|
||||||
|
}, timeout);
|
||||||
|
return await fetch(uri, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLGQuery(query: TFormState) {
|
||||||
|
const { request_timeout } = useConfig();
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
async function runQuery(url: string, requestData: TFormState): Promise<TQueryResponse> {
|
||||||
|
const { queryLocation, queryTarget, queryType, queryVrf } = requestData;
|
||||||
|
const res = await fetchWithTimeout(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
query_location: queryLocation,
|
||||||
|
query_target: queryTarget,
|
||||||
|
query_type: queryType,
|
||||||
|
query_vrf: queryVrf,
|
||||||
|
}),
|
||||||
|
mode: 'cors',
|
||||||
|
},
|
||||||
|
request_timeout * 1000,
|
||||||
|
controller,
|
||||||
|
);
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
return useQuery<TQueryResponse, Response | TQueryResponse | Error>(
|
||||||
|
['/api/query/', query],
|
||||||
|
runQuery,
|
||||||
|
{ refetchInterval: false },
|
||||||
|
);
|
||||||
|
}
|
@@ -3,8 +3,7 @@ import dayjs from 'dayjs';
|
|||||||
import relativeTimePlugin from 'dayjs/plugin/relativeTime';
|
import relativeTimePlugin from 'dayjs/plugin/relativeTime';
|
||||||
import utcPlugin from 'dayjs/plugin/utc';
|
import utcPlugin from 'dayjs/plugin/utc';
|
||||||
import { useConfig } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
|
import { isStructuredOutput } from '~/types';
|
||||||
import { TStringTableData } from './types';
|
|
||||||
|
|
||||||
dayjs.extend(relativeTimePlugin);
|
dayjs.extend(relativeTimePlugin);
|
||||||
dayjs.extend(utcPlugin);
|
dayjs.extend(utcPlugin);
|
||||||
@@ -48,10 +47,10 @@ function formatTime(val: number): string {
|
|||||||
|
|
||||||
export function useTableToString(
|
export function useTableToString(
|
||||||
target: string,
|
target: string,
|
||||||
data: TStringTableData,
|
data: TQueryResponse | undefined,
|
||||||
...deps: any
|
...deps: any
|
||||||
): () => string {
|
): () => string {
|
||||||
const { web, parsed_data_fields } = useConfig();
|
const { web, parsed_data_fields, messages } = useConfig();
|
||||||
|
|
||||||
function formatRpkiState(val: number): string {
|
function formatRpkiState(val: number): string {
|
||||||
const rpkiStates = [
|
const rpkiStates = [
|
||||||
@@ -83,12 +82,13 @@ export function useTableToString(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function doFormat(target: string, data: TStringTableData): string {
|
function doFormat(target: string, data: TQueryResponse | undefined): string {
|
||||||
|
let result = messages.no_output;
|
||||||
try {
|
try {
|
||||||
|
if (typeof data !== 'undefined' && isStructuredOutput(data)) {
|
||||||
let tableStringParts = [`Routes For: ${target}`, `Timestamp: ${data.timestamp} UTC`];
|
let tableStringParts = [`Routes For: ${target}`, `Timestamp: ${data.timestamp} UTC`];
|
||||||
|
for (const route of data.output.routes) {
|
||||||
data.output.routes.map(route => {
|
for (const field of parsed_data_fields) {
|
||||||
parsed_data_fields.map(field => {
|
|
||||||
const [header, accessor, align] = field;
|
const [header, accessor, align] = field;
|
||||||
if (align !== null) {
|
if (align !== null) {
|
||||||
let value = route[accessor];
|
let value = route[accessor];
|
||||||
@@ -100,9 +100,11 @@ export function useTableToString(
|
|||||||
tableStringParts.push(` - ${header}: ${value}`);
|
tableStringParts.push(` - ${header}: ${value}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
return tableStringParts.join('\n');
|
result = tableStringParts.join('\n');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return `An error occurred while parsing the output: '${err.message}'`;
|
return `An error occurred while parsing the output: '${err.message}'`;
|
||||||
|
@@ -7,3 +7,18 @@ export interface TFormData {
|
|||||||
query_vrf: string;
|
query_vrf: string;
|
||||||
query_target: string;
|
query_target: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TFormState {
|
||||||
|
queryLocation: string[];
|
||||||
|
queryType: TQueryTypes;
|
||||||
|
queryVrf: string;
|
||||||
|
queryTarget: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TStringTableData extends Omit<TQueryResponse, 'output'> {
|
||||||
|
output: TStructuredResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TQueryResponseString extends Omit<TQueryResponse, 'output'> {
|
||||||
|
output: string;
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { TValidQueryTypes } from './data';
|
import { TValidQueryTypes, TStringTableData, TQueryResponseString } from './data';
|
||||||
|
|
||||||
export function isQueryType(q: any): q is TValidQueryTypes {
|
export function isQueryType(q: any): q is TValidQueryTypes {
|
||||||
let result = false;
|
let result = false;
|
||||||
@@ -14,3 +14,11 @@ export function isQueryType(q: any): q is TValidQueryTypes {
|
|||||||
export function isString(a: any): a is string {
|
export function isString(a: any): a is string {
|
||||||
return typeof a === 'string';
|
return typeof a === 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isStructuredOutput(data: any): data is TStringTableData {
|
||||||
|
return typeof data.output !== 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isStringOutput(data: any): data is TQueryResponseString {
|
||||||
|
return typeof data.output === 'string';
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user