1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00
2020-12-20 01:21:24 -07:00

247 lines
7.3 KiB
TypeScript

import { forwardRef, useEffect, useMemo, useState } from 'react';
import {
Box,
Flex,
Alert,
Tooltip,
ButtonGroup,
AccordionItem,
AccordionPanel,
AccordionButton,
} from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { BsLightningFill } from '@meronex/icons/bs';
import { startCase } from 'lodash';
import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If } from '~/components';
import { useColorValue, useConfig, useMobile } from '~/context';
import { useStrf, useLGQuery, useTableToString } from '~/hooks';
import { isStructuredOutput, isStringOutput } from '~/types';
import { FormattedError } from './error';
import { ResultHeader } from './header';
import { isStackError, isFetchError, isLGError } from './guards';
import type { TAccordionHeaderWrapper, TResult, TErrorLevels } from './types';
const AnimatedAccordionItem = motion.custom(AccordionItem);
const AccordionHeaderWrapper = (props: TAccordionHeaderWrapper) => {
const { hoverBg, ...rest } = props;
return (
<Flex
justify="space-between"
_hover={{ bg: hoverBg }}
_focus={{ boxShadow: 'outline' }}
{...rest}
/>
);
};
export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
const {
index,
device,
queryVrf,
queryType,
queryTarget,
setComplete,
queryLocation,
resultsComplete,
} = props;
const { web, cache, messages } = useConfig();
const isMobile = useMobile();
const color = useColorValue('black', 'white');
const scrollbar = useColorValue('blackAlpha.300', 'whiteAlpha.300');
const scrollbarHover = useColorValue('blackAlpha.400', 'whiteAlpha.400');
const scrollbarBg = useColorValue('blackAlpha.50', 'whiteAlpha.50');
const { data, error, isError, isLoading, refetch } = useLGQuery({
queryLocation,
queryTarget,
queryType,
queryVrf,
});
const cacheLabel = useStrf(web.text.cache_icon, { time: data?.timestamp }, [data?.timestamp]);
const [isOpen, setOpen] = useState(false);
const [hasOverride, setOverride] = useState(false);
const handleToggle = () => {
setOpen(!isOpen);
setOverride(true);
};
const errorKeywords = useMemo(() => {
let kw = [] as string[];
if (isLGError(error)) {
kw = error.keywords;
}
return kw;
}, [isError]);
let errorMsg;
if (isLGError(error)) {
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;
} else if (isStackError(error)) {
errorMsg = startCase(error.message);
} else {
errorMsg = messages.general;
}
error && console.error(error);
const errorLevel = useMemo<TErrorLevels>(() => {
const statusMap = {
success: 'success',
warning: 'warning',
error: 'warning',
danger: 'error',
} as { [k in TResponseLevel]: 'success' | 'warning' | 'error' };
let e: TErrorLevels = 'error';
if (isLGError(error)) {
const idx = error.level as TResponseLevel;
e = statusMap[idx];
}
return e;
}, [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]);
let copyValue = data?.output as string;
const formatData = useTableToString(queryTarget, data, [data?.format]);
if (data?.format === 'application/json') {
copyValue = formatData();
}
if (error) {
copyValue = errorMsg;
}
useEffect(() => {
if (isLoading && resultsComplete === null) {
setComplete(index);
}
}, [isLoading, resultsComplete]);
useEffect(() => {
if (resultsComplete === index && !hasOverride) {
setOpen(true);
}
}, [resultsComplete, index]);
return (
<AnimatedAccordionItem
ref={ref}
isDisabled={isLoading}
exit={{ opacity: 0, y: 300 }}
animate={{ opacity: 1, y: 0 }}
initial={{ opacity: 0, y: 300 }}
transition={{ duration: 0.3, delay: index * 0.3 }}
css={{
'&:last-of-type': { borderBottom: 'none' },
'&:first-of-type': { borderTop: 'none' },
}}>
<AccordionHeaderWrapper hoverBg="blackAlpha.50">
<AccordionButton
py={2}
w="unset"
_hover={{}}
_focus={{}}
flex="1 0 auto"
onClick={handleToggle}>
<ResultHeader
isError={isError}
loading={isLoading}
errorMsg={errorMsg}
errorLevel={errorLevel}
runtime={data?.runtime ?? 0}
title={device.display_name}
/>
</AccordionButton>
<ButtonGroup px={[1, 1, 3, 3]} py={2}>
<CopyButton copyValue={copyValue} isDisabled={isLoading} />
<RequeryButton requery={refetch} isDisabled={isLoading} />
</ButtonGroup>
</AccordionHeaderWrapper>
<AccordionPanel
pb={4}
overflowX="auto"
css={{
WebkitOverflowScrolling: 'touch',
'&::-webkit-scrollbar': { height: '5px' },
'&::-webkit-scrollbar-track': {
backgroundColor: scrollbarBg,
},
'&::-webkit-scrollbar-thumb': {
backgroundColor: scrollbar,
},
'&::-webkit-scrollbar-thumb:hover': {
backgroundColor: scrollbarHover,
},
'-ms-overflow-style': { display: 'none' },
}}>
<Box>
<Flex direction="column" flex="1 0 auto" maxW={error ? '100%' : undefined}>
{!isError && typeof data !== 'undefined' && (
<>
{isStructuredOutput(data) && data.level === 'success' && tableComponent ? (
<BGPTable>{data.output}</BGPTable>
) : isStringOutput(data) && data.level === 'success' && !tableComponent ? (
<TextOutput>{data.output}</TextOutput>
) : isStringOutput(data) && data.level !== 'success' ? (
<Alert rounded="lg" my={2} py={4} status={errorLevel}>
<FormattedError message={data.output} keywords={errorKeywords} />
</Alert>
) : null}
</>
)}
</Flex>
</Box>
<Flex direction="row" flexWrap="wrap">
<Flex
px={3}
mt={2}
justifyContent={['flex-start', 'flex-start', 'flex-end', 'flex-end']}
flex="1 0 auto">
<If c={cache.show_text && typeof data !== 'undefined' && !error}>
<If c={!isMobile}>
<Countdown timeout={cache.timeout} text={web.text.cache_prefix} />
</If>
<Tooltip
display={!data?.cached ? 'none' : undefined}
hasArrow
label={cacheLabel}
placement="top">
<Box ml={1} display={data?.cached ? 'block' : 'none'}>
<BsLightningFill color={color} />
</Box>
</Tooltip>
<If c={isMobile}>
<Countdown timeout={cache.timeout} text={web.text.cache_prefix} />
</If>
</If>
</Flex>
</Flex>
</AccordionPanel>
</AnimatedAccordionItem>
);
});