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:
		
							
								
								
									
										21
									
								
								hyperglass/ui/components/results/error.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								hyperglass/ui/components/results/error.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import { Text } from '@chakra-ui/react';
 | 
			
		||||
import strReplace from 'react-string-replace';
 | 
			
		||||
 | 
			
		||||
import type { TFormattedError } from './types';
 | 
			
		||||
 | 
			
		||||
export const FormattedError = (props: TFormattedError) => {
 | 
			
		||||
  const { keywords, message } = props;
 | 
			
		||||
  const patternStr = keywords.map(kw => `(${kw})`).join('|');
 | 
			
		||||
  const pattern = new RegExp(patternStr, 'gi');
 | 
			
		||||
  let errorFmt;
 | 
			
		||||
  try {
 | 
			
		||||
    errorFmt = strReplace(message, pattern, match => (
 | 
			
		||||
      <Text key={match} as="strong">
 | 
			
		||||
        {match}
 | 
			
		||||
      </Text>
 | 
			
		||||
    ));
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    errorFmt = <Text as="span">{message}</Text>;
 | 
			
		||||
  }
 | 
			
		||||
  return <Text as="span">{keywords.length !== 0 ? errorFmt : message}</Text>;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										164
									
								
								hyperglass/ui/components/results/group.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								hyperglass/ui/components/results/group.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
			
		||||
import { useState } from 'react';
 | 
			
		||||
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 { Result } from './individual';
 | 
			
		||||
 | 
			
		||||
import type { TResults } from './types';
 | 
			
		||||
 | 
			
		||||
export const Results = (props: TResults) => {
 | 
			
		||||
  const { queryLocation, queryType, queryVrf, queryTarget, setSubmitting, ...rest } = props;
 | 
			
		||||
  const { request_timeout, devices, queries, vrfs, web } = useConfig();
 | 
			
		||||
  const targetBg = useToken('colors', 'teal.600');
 | 
			
		||||
  const queryBg = useToken('colors', 'cyan.500');
 | 
			
		||||
  const vrfBg = useToken('colors', 'blue.500');
 | 
			
		||||
 | 
			
		||||
  const animateLeft = useBreakpointValue({
 | 
			
		||||
    base: { opacity: 1, x: 0 },
 | 
			
		||||
    md: { opacity: 1, x: 0 },
 | 
			
		||||
    lg: { opacity: 1, x: 0 },
 | 
			
		||||
    xl: { opacity: 1, x: 0 },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const animateCenter = useBreakpointValue({
 | 
			
		||||
    base: { opacity: 1 },
 | 
			
		||||
    md: { opacity: 1 },
 | 
			
		||||
    lg: { opacity: 1 },
 | 
			
		||||
    xl: { opacity: 1 },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const animateRight = useBreakpointValue({
 | 
			
		||||
    base: { opacity: 1, x: 0 },
 | 
			
		||||
    md: { opacity: 1, x: 0 },
 | 
			
		||||
    lg: { opacity: 1, x: 0 },
 | 
			
		||||
    xl: { opacity: 1, x: 0 },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const initialLeft = useBreakpointValue({
 | 
			
		||||
    base: { opacity: 0, x: -100 },
 | 
			
		||||
    md: { opacity: 0, x: -100 },
 | 
			
		||||
    lg: { opacity: 0, x: -100 },
 | 
			
		||||
    xl: { opacity: 0, x: -100 },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const initialCenter = useBreakpointValue({
 | 
			
		||||
    base: { opacity: 0 },
 | 
			
		||||
    md: { opacity: 0 },
 | 
			
		||||
    lg: { opacity: 0 },
 | 
			
		||||
    xl: { opacity: 0 },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const initialRight = useBreakpointValue({
 | 
			
		||||
    base: { opacity: 0, x: 100 },
 | 
			
		||||
    md: { opacity: 0, x: 100 },
 | 
			
		||||
    lg: { opacity: 0, x: 100 },
 | 
			
		||||
    xl: { opacity: 0, x: 100 },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const [resultsComplete, setComplete] = useState<number | null>(null);
 | 
			
		||||
 | 
			
		||||
  const matchedVrf =
 | 
			
		||||
    vrfs.filter(v => v.id === queryVrf)[0] ?? vrfs.filter(v => v.id === 'default')[0];
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Box
 | 
			
		||||
        p={0}
 | 
			
		||||
        my={4}
 | 
			
		||||
        w="100%"
 | 
			
		||||
        mx="auto"
 | 
			
		||||
        textAlign="left"
 | 
			
		||||
        maxW={{ base: '100%', lg: '75%', xl: '50%' }}
 | 
			
		||||
        {...rest}>
 | 
			
		||||
        <Stack isInline align="center" justify="center" mt={4} flexWrap="wrap">
 | 
			
		||||
          <AnimatePresence>
 | 
			
		||||
            {queryLocation && (
 | 
			
		||||
              <>
 | 
			
		||||
                <motion.div
 | 
			
		||||
                  initial={initialLeft}
 | 
			
		||||
                  animate={animateLeft}
 | 
			
		||||
                  exit={{ opacity: 0, x: -100 }}
 | 
			
		||||
                  transition={{ duration: 0.3, delay: 0.3 }}>
 | 
			
		||||
                  <Label
 | 
			
		||||
                    bg={queryBg}
 | 
			
		||||
                    label={web.text.query_type}
 | 
			
		||||
                    fontSize={{ base: 'xs', md: 'sm' }}
 | 
			
		||||
                    value={queries[queryType].display_name}
 | 
			
		||||
                  />
 | 
			
		||||
                </motion.div>
 | 
			
		||||
                <motion.div
 | 
			
		||||
                  initial={initialCenter}
 | 
			
		||||
                  animate={animateCenter}
 | 
			
		||||
                  exit={{ opacity: 0, scale: 0.5 }}
 | 
			
		||||
                  transition={{ duration: 0.3, delay: 0.3 }}>
 | 
			
		||||
                  <Label
 | 
			
		||||
                    bg={targetBg}
 | 
			
		||||
                    value={queryTarget}
 | 
			
		||||
                    label={web.text.query_target}
 | 
			
		||||
                    fontSize={{ base: 'xs', md: 'sm' }}
 | 
			
		||||
                  />
 | 
			
		||||
                </motion.div>
 | 
			
		||||
                <motion.div
 | 
			
		||||
                  initial={initialRight}
 | 
			
		||||
                  animate={animateRight}
 | 
			
		||||
                  exit={{ opacity: 0, x: 100 }}
 | 
			
		||||
                  transition={{ duration: 0.3, delay: 0.3 }}>
 | 
			
		||||
                  <Label
 | 
			
		||||
                    bg={vrfBg}
 | 
			
		||||
                    label={web.text.query_vrf}
 | 
			
		||||
                    value={matchedVrf.display_name}
 | 
			
		||||
                    fontSize={{ base: 'xs', md: 'sm' }}
 | 
			
		||||
                  />
 | 
			
		||||
                </motion.div>
 | 
			
		||||
              </>
 | 
			
		||||
            )}
 | 
			
		||||
          </AnimatePresence>
 | 
			
		||||
        </Stack>
 | 
			
		||||
      </Box>
 | 
			
		||||
      <AnimatedDiv
 | 
			
		||||
        p={0}
 | 
			
		||||
        my={4}
 | 
			
		||||
        w="100%"
 | 
			
		||||
        mx="auto"
 | 
			
		||||
        rounded="lg"
 | 
			
		||||
        textAlign="left"
 | 
			
		||||
        borderWidth="1px"
 | 
			
		||||
        overflow="hidden"
 | 
			
		||||
        initial={{ opacity: 1 }}
 | 
			
		||||
        exit={{ opacity: 0, y: 300 }}
 | 
			
		||||
        transition={{ duration: 0.3 }}
 | 
			
		||||
        animate={{ opacity: 1, y: 0 }}
 | 
			
		||||
        maxW={{ base: '100%', md: '75%' }}>
 | 
			
		||||
        <Accordion allowMultiple>
 | 
			
		||||
          <AnimatePresence>
 | 
			
		||||
            {queryLocation &&
 | 
			
		||||
              queryLocation.map((loc, i) => {
 | 
			
		||||
                const device = devices.filter(d => d.name === loc)[0];
 | 
			
		||||
                return (
 | 
			
		||||
                  <motion.div
 | 
			
		||||
                    animate={{ opacity: 1, y: 0 }}
 | 
			
		||||
                    initial={{ opacity: 0, y: 300 }}
 | 
			
		||||
                    transition={{ duration: 0.3, delay: i * 0.3 }}
 | 
			
		||||
                    exit={{ opacity: 0, y: 300 }}>
 | 
			
		||||
                    <Result
 | 
			
		||||
                      key={loc}
 | 
			
		||||
                      index={i}
 | 
			
		||||
                      device={device}
 | 
			
		||||
                      queryLocation={loc}
 | 
			
		||||
                      queryVrf={queryVrf}
 | 
			
		||||
                      queryType={queryType}
 | 
			
		||||
                      queryTarget={queryTarget}
 | 
			
		||||
                      setComplete={setComplete}
 | 
			
		||||
                      timeout={request_timeout * 1000}
 | 
			
		||||
                      resultsComplete={resultsComplete}
 | 
			
		||||
                    />
 | 
			
		||||
                  </motion.div>
 | 
			
		||||
                );
 | 
			
		||||
              })}
 | 
			
		||||
          </AnimatePresence>
 | 
			
		||||
        </Accordion>
 | 
			
		||||
      </AnimatedDiv>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										48
									
								
								hyperglass/ui/components/results/header.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								hyperglass/ui/components/results/header.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
import dynamic from 'next/dynamic';
 | 
			
		||||
import { forwardRef, useMemo } from 'react';
 | 
			
		||||
import { AccordionIcon, Icon, Spinner, Stack, Text, Tooltip, useColorMode } from '@chakra-ui/react';
 | 
			
		||||
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) {
 | 
			
		||||
    unit = 'second';
 | 
			
		||||
  }
 | 
			
		||||
  return `${text} ${unit}`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ResultHeader = forwardRef<HTMLDivElement, TResultHeader>((props, ref) => {
 | 
			
		||||
  const { title, loading, error, errorMsg, errorLevel, runtime } = props;
 | 
			
		||||
 | 
			
		||||
  const status = useColorValue('primary.500', 'primary.300');
 | 
			
		||||
  const warning = useColorValue(`${errorLevel}.500`, `${errorLevel}.300`);
 | 
			
		||||
  const defaultStatus = useColorValue('success.500', 'success.300');
 | 
			
		||||
 | 
			
		||||
  const { web } = useConfig();
 | 
			
		||||
  const text = useStrf(web.text.complete_time, { seconds: runtime }, [runtime]);
 | 
			
		||||
  const label = useMemo(() => runtimeText(runtime, text), [runtime]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Stack ref={ref} isInline alignItems="center" w="100%">
 | 
			
		||||
      {loading ? (
 | 
			
		||||
        <Spinner size="sm" mr={4} color={status} />
 | 
			
		||||
      ) : error ? (
 | 
			
		||||
        <Tooltip hasArrow label={errorMsg} placement="top">
 | 
			
		||||
          <Icon as={Warning} color={warning} mr={4} boxSize={6} />
 | 
			
		||||
        </Tooltip>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <Tooltip hasArrow label={label} placement="top">
 | 
			
		||||
          <Icon as={Check} color={defaultStatus} mr={4} boxSize={6} />
 | 
			
		||||
        </Tooltip>
 | 
			
		||||
      )}
 | 
			
		||||
      <Text fontSize="lg">{title}</Text>
 | 
			
		||||
      <AccordionIcon ml="auto" />
 | 
			
		||||
    </Stack>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										1
									
								
								hyperglass/ui/components/results/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hyperglass/ui/components/results/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export * from './group';
 | 
			
		||||
							
								
								
									
										236
									
								
								hyperglass/ui/components/results/individual.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								hyperglass/ui/components/results/individual.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,236 @@
 | 
			
		||||
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 useAxios from 'axios-hooks';
 | 
			
		||||
import { startCase } from 'lodash';
 | 
			
		||||
import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If } from '~/components';
 | 
			
		||||
import { useColorValue, useConfig, useMobile } from '~/context';
 | 
			
		||||
import { useStrf, useTableToString } from '~/hooks';
 | 
			
		||||
import { FormattedError } from './error';
 | 
			
		||||
import { ResultHeader } from './header';
 | 
			
		||||
 | 
			
		||||
import type { TAccordionHeaderWrapper, TResult } from './types';
 | 
			
		||||
 | 
			
		||||
type TErrorLevels = 'success' | 'warning' | 'error';
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
    timeout,
 | 
			
		||||
    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');
 | 
			
		||||
 | 
			
		||||
  let [{ data, loading, error }, refetch] = useAxios(
 | 
			
		||||
    {
 | 
			
		||||
      url: '/api/query/',
 | 
			
		||||
      method: 'post',
 | 
			
		||||
      data: {
 | 
			
		||||
        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 [isOpen, setOpen] = useState(false);
 | 
			
		||||
  const [hasOverride, setOverride] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const handleToggle = () => {
 | 
			
		||||
    setOpen(!isOpen);
 | 
			
		||||
    setOverride(true);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const errorKw = (error && error.response?.data?.keywords) || [];
 | 
			
		||||
 | 
			
		||||
  let errorMsg;
 | 
			
		||||
  if (error && error.response?.data?.output) {
 | 
			
		||||
    errorMsg = error.response.data.output;
 | 
			
		||||
  } else if (error && error.message.startsWith('timeout')) {
 | 
			
		||||
    errorMsg = messages.request_timeout;
 | 
			
		||||
  } else if (error?.response?.statusText) {
 | 
			
		||||
    errorMsg = startCase(error.response.statusText);
 | 
			
		||||
  } else if (error && error.message) {
 | 
			
		||||
    errorMsg = startCase(error.message);
 | 
			
		||||
  } else {
 | 
			
		||||
    errorMsg = messages.general;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  error && console.error(error);
 | 
			
		||||
 | 
			
		||||
  const getErrorLevel = (): TErrorLevels => {
 | 
			
		||||
    const statusMap = {
 | 
			
		||||
      success: 'success',
 | 
			
		||||
      warning: 'warning',
 | 
			
		||||
      error: 'warning',
 | 
			
		||||
      danger: 'error',
 | 
			
		||||
    } as { [k in TResponseLevel]: 'success' | 'warning' | 'error' };
 | 
			
		||||
 | 
			
		||||
    let e: TErrorLevels = 'error';
 | 
			
		||||
 | 
			
		||||
    if (error?.response?.data?.level) {
 | 
			
		||||
      const idx = error.response.data.level as TResponseLevel;
 | 
			
		||||
      e = statusMap[idx];
 | 
			
		||||
    }
 | 
			
		||||
    return e;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const errorLevel = useMemo(() => getErrorLevel(), [error]);
 | 
			
		||||
 | 
			
		||||
  const tableComponent = useMemo(() => typeof queryType.match(/^bgp_\w+$/) !== null, [queryType]);
 | 
			
		||||
 | 
			
		||||
  let copyValue = data?.output;
 | 
			
		||||
 | 
			
		||||
  const formatData = useTableToString(queryTarget, data, [data.format]);
 | 
			
		||||
 | 
			
		||||
  if (data?.format === 'application/json') {
 | 
			
		||||
    copyValue = formatData();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (error) {
 | 
			
		||||
    copyValue = errorMsg;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    !loading && resultsComplete === null && setComplete(index);
 | 
			
		||||
  }, [loading, resultsComplete]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    resultsComplete === index && !hasOverride && setOpen(true);
 | 
			
		||||
  }, [resultsComplete, index]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AccordionItem
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      isOpen={isOpen}
 | 
			
		||||
      isDisabled={loading}
 | 
			
		||||
      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
 | 
			
		||||
            error={error}
 | 
			
		||||
            loading={loading}
 | 
			
		||||
            errorMsg={errorMsg}
 | 
			
		||||
            errorLevel={errorLevel}
 | 
			
		||||
            runtime={data?.runtime}
 | 
			
		||||
            title={device.display_name}
 | 
			
		||||
          />
 | 
			
		||||
        </AccordionButton>
 | 
			
		||||
        <ButtonGroup px={[1, 1, 3, 3]} py={2}>
 | 
			
		||||
          <CopyButton copyValue={copyValue} isDisabled={loading} />
 | 
			
		||||
          <RequeryButton requery={refetch} variant="ghost" isDisabled={loading} />
 | 
			
		||||
        </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}>
 | 
			
		||||
            <If c={!error && data}>
 | 
			
		||||
              <If c={tableComponent}>
 | 
			
		||||
                <BGPTable>{data?.output}</BGPTable>
 | 
			
		||||
              </If>
 | 
			
		||||
              <If c={!tableComponent}>
 | 
			
		||||
                <TextOutput>{data?.output}</TextOutput>
 | 
			
		||||
              </If>
 | 
			
		||||
            </If>
 | 
			
		||||
 | 
			
		||||
            {error && (
 | 
			
		||||
              <Alert rounded="lg" my={2} py={4} status={errorLevel}>
 | 
			
		||||
                <FormattedError keywords={errorKw} message={errorMsg} />
 | 
			
		||||
              </Alert>
 | 
			
		||||
            )}
 | 
			
		||||
          </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>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										40
									
								
								hyperglass/ui/components/results/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								hyperglass/ui/components/results/types.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import type { BoxProps, FlexProps } from '@chakra-ui/react';
 | 
			
		||||
import type { TDevice, TQueryTypes } from '~/types';
 | 
			
		||||
 | 
			
		||||
export interface TResultHeader {
 | 
			
		||||
  title: string;
 | 
			
		||||
  loading: boolean;
 | 
			
		||||
  error?: Error;
 | 
			
		||||
  errorMsg: string;
 | 
			
		||||
  errorLevel: 'success' | 'warning' | 'error';
 | 
			
		||||
  runtime: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TFormattedError {
 | 
			
		||||
  keywords: string[];
 | 
			
		||||
  message: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TAccordionHeaderWrapper extends FlexProps {
 | 
			
		||||
  hoverBg: FlexProps['bg'];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TResult {
 | 
			
		||||
  index: number;
 | 
			
		||||
  device: TDevice;
 | 
			
		||||
  timeout: number;
 | 
			
		||||
  queryVrf: string;
 | 
			
		||||
  queryType: TQueryTypes;
 | 
			
		||||
  queryTarget: string;
 | 
			
		||||
  setComplete(v: number | null): void;
 | 
			
		||||
  queryLocation: string;
 | 
			
		||||
  resultsComplete: number | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TResults extends BoxProps {
 | 
			
		||||
  setSubmitting(v: boolean): boolean;
 | 
			
		||||
  queryType: TQueryTypes;
 | 
			
		||||
  queryLocation: string[];
 | 
			
		||||
  queryTarget: string;
 | 
			
		||||
  queryVrf: string;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user