2020-11-29 01:25:48 -07:00
|
|
|
import { forwardRef, useEffect, useMemo, useState } from 'react';
|
|
|
|
import {
|
|
|
|
Box,
|
|
|
|
Flex,
|
|
|
|
Alert,
|
|
|
|
Tooltip,
|
|
|
|
ButtonGroup,
|
|
|
|
AccordionItem,
|
|
|
|
AccordionPanel,
|
|
|
|
AccordionButton,
|
|
|
|
} from '@chakra-ui/react';
|
|
|
|
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';
|
2020-12-14 01:04:15 -07:00
|
|
|
import { useStrf, useLGQuery, useTableToString } from '~/hooks';
|
|
|
|
import { isStructuredOutput, isStringOutput } from '~/types';
|
2020-11-29 01:25:48 -07:00
|
|
|
import { FormattedError } from './error';
|
|
|
|
import { ResultHeader } from './header';
|
2020-12-14 01:04:15 -07:00
|
|
|
import { isStackError, isFetchError, isLGError } from './guards';
|
2020-11-29 01:25:48 -07:00
|
|
|
|
2020-12-14 01:04:15 -07:00
|
|
|
import type { TAccordionHeaderWrapper, TResult, TErrorLevels } from './types';
|
2020-11-29 01:25:48 -07:00
|
|
|
|
|
|
|
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');
|
|
|
|
|
2020-12-14 01:04:15 -07:00
|
|
|
const { data, error, isError, isLoading, refetch } = useLGQuery({
|
|
|
|
queryLocation,
|
|
|
|
queryTarget,
|
|
|
|
queryType,
|
|
|
|
queryVrf,
|
|
|
|
});
|
2020-11-29 01:25:48 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2020-12-14 01:04:15 -07:00
|
|
|
const errorKeywords = useMemo(() => {
|
|
|
|
let kw = [] as string[];
|
|
|
|
if (isLGError(error)) {
|
|
|
|
kw = error.keywords;
|
|
|
|
}
|
|
|
|
return kw;
|
|
|
|
}, [isError]);
|
2020-11-29 01:25:48 -07:00
|
|
|
|
|
|
|
let errorMsg;
|
2020-12-14 01:04:15 -07:00
|
|
|
|
|
|
|
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')) {
|
2020-11-29 01:25:48 -07:00
|
|
|
errorMsg = messages.request_timeout;
|
2020-12-14 01:04:15 -07:00
|
|
|
} else if (isStackError(error)) {
|
2020-11-29 01:25:48 -07:00
|
|
|
errorMsg = startCase(error.message);
|
|
|
|
} else {
|
|
|
|
errorMsg = messages.general;
|
|
|
|
}
|
|
|
|
|
|
|
|
error && console.error(error);
|
|
|
|
|
2020-12-14 01:04:15 -07:00
|
|
|
const errorLevel = useMemo<TErrorLevels>(() => {
|
2020-11-29 01:25:48 -07:00
|
|
|
const statusMap = {
|
|
|
|
success: 'success',
|
|
|
|
warning: 'warning',
|
|
|
|
error: 'warning',
|
|
|
|
danger: 'error',
|
|
|
|
} as { [k in TResponseLevel]: 'success' | 'warning' | 'error' };
|
|
|
|
|
|
|
|
let e: TErrorLevels = 'error';
|
|
|
|
|
2020-12-14 01:04:15 -07:00
|
|
|
if (isLGError(error)) {
|
|
|
|
const idx = error.level as TResponseLevel;
|
2020-11-29 01:25:48 -07:00
|
|
|
e = statusMap[idx];
|
|
|
|
}
|
|
|
|
return e;
|
2020-12-14 01:04:15 -07:00
|
|
|
}, [error]);
|
2020-11-29 01:25:48 -07:00
|
|
|
|
2020-12-14 01:04:15 -07:00
|
|
|
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]);
|
2020-11-29 01:25:48 -07:00
|
|
|
|
2020-12-14 01:04:15 -07:00
|
|
|
let copyValue = data?.output as string;
|
2020-11-29 01:25:48 -07:00
|
|
|
|
2020-12-13 01:49:13 -07:00
|
|
|
const formatData = useTableToString(queryTarget, data, [data?.format]);
|
2020-11-29 01:25:48 -07:00
|
|
|
|
|
|
|
if (data?.format === 'application/json') {
|
|
|
|
copyValue = formatData();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
copyValue = errorMsg;
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
2020-12-14 01:04:15 -07:00
|
|
|
if (isLoading && resultsComplete === null) {
|
|
|
|
setComplete(index);
|
|
|
|
}
|
|
|
|
}, [isLoading, resultsComplete]);
|
2020-11-29 01:25:48 -07:00
|
|
|
|
|
|
|
useEffect(() => {
|
2020-12-14 01:04:15 -07:00
|
|
|
if (resultsComplete === index && !hasOverride) {
|
|
|
|
setOpen(true);
|
|
|
|
}
|
2020-11-29 01:25:48 -07:00
|
|
|
}, [resultsComplete, index]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<AccordionItem
|
|
|
|
ref={ref}
|
|
|
|
isOpen={isOpen}
|
2020-12-14 01:04:15 -07:00
|
|
|
isDisabled={isLoading}
|
2020-11-29 01:25:48 -07:00
|
|
|
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
|
2020-12-14 01:04:15 -07:00
|
|
|
isError={isError}
|
|
|
|
loading={isLoading}
|
2020-11-29 01:25:48 -07:00
|
|
|
errorMsg={errorMsg}
|
|
|
|
errorLevel={errorLevel}
|
2020-12-14 01:04:15 -07:00
|
|
|
runtime={data?.runtime ?? 0}
|
2020-11-29 01:25:48 -07:00
|
|
|
title={device.display_name}
|
|
|
|
/>
|
|
|
|
</AccordionButton>
|
|
|
|
<ButtonGroup px={[1, 1, 3, 3]} py={2}>
|
2020-12-14 01:04:15 -07:00
|
|
|
<CopyButton copyValue={copyValue} isDisabled={isLoading} />
|
|
|
|
<RequeryButton requery={refetch} variant="ghost" isDisabled={isLoading} />
|
2020-11-29 01:25:48 -07:00
|
|
|
</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' },
|
|
|
|
}}>
|
|
|
|
<Flex direction="column" flexWrap="wrap">
|
|
|
|
<Flex direction="column" flex="1 0 auto" maxW={error ? '100%' : undefined}>
|
2020-12-14 01:04:15 -07:00
|
|
|
{!isError && typeof data !== 'undefined' && (
|
|
|
|
<>
|
|
|
|
{isStructuredOutput(data) && tableComponent ? (
|
|
|
|
<BGPTable>{data.output}</BGPTable>
|
|
|
|
) : isStringOutput(data) && !tableComponent ? (
|
|
|
|
<TextOutput>{data.output}</TextOutput>
|
|
|
|
) : null}
|
|
|
|
</>
|
2020-11-29 01:25:48 -07:00
|
|
|
)}
|
2020-12-14 01:04:15 -07:00
|
|
|
{isError && <Alert rounded="lg" my={2} py={4} status={errorLevel}></Alert>}
|
2020-11-29 01:25:48 -07:00
|
|
|
</Flex>
|
|
|
|
</Flex>
|
|
|
|
|
|
|
|
<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 && data && !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>
|
|
|
|
</AccordionItem>
|
|
|
|
);
|
|
|
|
});
|