mirror of
https://github.com/thatmattlove/hyperglass.git
synced 2026-01-17 08:48:05 +00:00
continue typescript & chakra v1 migrations [skip ci]
This commit is contained in:
parent
0bb19082a0
commit
1fb1dfe182
34 changed files with 363 additions and 361 deletions
|
|
@ -125,6 +125,7 @@ class Text(HyperglassModel):
|
|||
query_target: StrictStr = "Target"
|
||||
query_vrf: StrictStr = "Routing Table"
|
||||
fqdn_tooltip: StrictStr = "Use {protocol}" # Formatted by Javascript
|
||||
fqdn_message: StrictStr = "Your browser has resolved {fqdn} to" # Formatted by Javascript
|
||||
cache_prefix: StrictStr = "Results cached for "
|
||||
cache_icon: StrictStr = "Cached from {time} UTC" # Formatted by Javascript
|
||||
complete_time: StrictStr = "Completed in {seconds}" # Formatted by Javascript
|
||||
|
|
|
|||
|
|
@ -82,6 +82,6 @@ export const TableData = (props: TTableData) => {
|
|||
};
|
||||
|
||||
export const Paragraph = (props: TextProps) => <ChakraText {...props} />;
|
||||
export const InlineCode = (props: CodeProps) => <ChakraCode {...props} />;
|
||||
export const InlineCode = (props: CodeProps) => <ChakraCode children={props.children} />;
|
||||
export const Divider = (props: DividerProps) => <ChakraDivider {...props} />;
|
||||
export const Table = (props: BoxProps) => <ChakraTable {...props} />;
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ export interface TCheckbox extends CheckboxProps {
|
|||
checked: boolean;
|
||||
}
|
||||
|
||||
export interface TListItem extends ListItemProps {
|
||||
export interface TListItem {
|
||||
checked: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface TList extends ListProps {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import * as React from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Flex, Icon, Text } from '@chakra-ui/react';
|
||||
import { usePagination, useSortBy, useTable } from 'react-table';
|
||||
|
|
@ -12,7 +11,8 @@ import { TableBody } from './body';
|
|||
import { TableIconButton } from './button';
|
||||
import { TableSelectShow } from './pageSelect';
|
||||
|
||||
import type { TableOptions, PluginHook, Row } from 'react-table';
|
||||
import type { TableOptions, PluginHook } from 'react-table';
|
||||
import type { TCellRender } from '~/types';
|
||||
import type { TTable } from './types';
|
||||
|
||||
const ChevronRight = dynamic<MeronexIcon>(() =>
|
||||
|
|
@ -39,7 +39,7 @@ export function Table(props: TTable) {
|
|||
data,
|
||||
columns,
|
||||
heading,
|
||||
cellRender,
|
||||
Cell,
|
||||
rowHighlightBg,
|
||||
striped = false,
|
||||
rowHighlightProp,
|
||||
|
|
@ -132,13 +132,17 @@ export function Table(props: TTable) {
|
|||
highlight={row.values[rowHighlightProp ?? ''] ?? false}
|
||||
{...row.getRowProps()}>
|
||||
{row.cells.map((cell, i) => {
|
||||
const { column, row, value } = cell as TCellRender;
|
||||
return (
|
||||
<TableCell
|
||||
align={cell.column.align}
|
||||
bordersVertical={[bordersVertical, i]}
|
||||
{...cell.getCellProps()}>
|
||||
{/* {cellRender ?? cell.render('Cell')} */}
|
||||
{React.createElement(cellRender, cell)}
|
||||
{typeof Cell !== 'undefined' ? (
|
||||
<Cell column={column} row={row} value={value} />
|
||||
) : (
|
||||
cell.render('Cell')
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { BoxProps, IconButtonProps } from '@chakra-ui/react';
|
||||
|
||||
import type { Colors, TColumn } from '~/types';
|
||||
import type { Colors, TColumn, TCellRender } from '~/types';
|
||||
|
||||
export interface TTable {
|
||||
columns: TColumn[];
|
||||
|
|
@ -9,7 +9,7 @@ export interface TTable {
|
|||
striped?: boolean;
|
||||
bordersVertical?: boolean;
|
||||
bordersHorizontal?: boolean;
|
||||
cellRender?: React.ReactNode;
|
||||
Cell?: React.FC<TCellRender>;
|
||||
rowHighlightProp?: keyof IRoute;
|
||||
rowHighlightBg?: keyof Colors;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import dynamic from 'next/dynamic';
|
||||
import { Button, Icon, Tooltip, useClipboard } from '@chakra-ui/react';
|
||||
import { If } from '~/components';
|
||||
|
||||
const Copy = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiCopy));
|
||||
const Check = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiCheck));
|
||||
|
|
@ -18,15 +17,9 @@ export const CopyButton = (props: TCopyButton) => {
|
|||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={onCopy}
|
||||
zIndex="dropdown"
|
||||
colorScheme="secondary"
|
||||
colorScheme="primary"
|
||||
{...rest}>
|
||||
<If c={hasCopied}>
|
||||
<Icon as={Check} boxSize="16px" />
|
||||
</If>
|
||||
<If c={!hasCopied}>
|
||||
<Icon as={Copy} boxSize="16px" />
|
||||
</If>
|
||||
<Icon as={hasCopied ? Check : Copy} boxSize="16px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { forwardRef } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Button, Icon, Tooltip } from '@chakra-ui/react';
|
||||
|
||||
|
|
@ -5,13 +6,23 @@ import type { TRequeryButton } from './types';
|
|||
|
||||
const Repeat = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiRepeat));
|
||||
|
||||
export const RequeryButton = (props: TRequeryButton) => {
|
||||
const { requery, bg = 'secondary', ...rest } = props;
|
||||
export const RequeryButton = forwardRef<HTMLButtonElement, TRequeryButton>((props, ref) => {
|
||||
const { requery, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Tooltip hasArrow label="Reload Query" placement="top">
|
||||
<Button mx={1} as="a" size="sm" zIndex="1" variantColor={bg} onClick={requery} {...rest}>
|
||||
<Button
|
||||
mx={1}
|
||||
as="a"
|
||||
ref={ref}
|
||||
size="sm"
|
||||
zIndex="1"
|
||||
variant="ghost"
|
||||
onClick={requery}
|
||||
colorScheme="secondary"
|
||||
{...rest}>
|
||||
<Icon as={Repeat} boxSize="16px" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,107 +1,51 @@
|
|||
import { forwardRef } from 'react';
|
||||
import { Box, Spinner } from '@chakra-ui/react';
|
||||
import {
|
||||
IconButton,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverArrow,
|
||||
PopoverCloseButton,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
} from '@chakra-ui/react';
|
||||
import { FiSearch } from '@meronex/icons/fi';
|
||||
import { useColorValue } from '~/context';
|
||||
import { useOpposingColor } from '~/hooks';
|
||||
import { ResolvedTarget } from '~/components';
|
||||
import { useLGState } from '~/hooks';
|
||||
|
||||
import type { TSubmitButton, TButtonSizeMap } from './types';
|
||||
import type { TSubmitButton } from './types';
|
||||
|
||||
const btnSizeMap = {
|
||||
lg: {
|
||||
height: 12,
|
||||
minWidth: 12,
|
||||
fontSize: 'lg',
|
||||
px: 6,
|
||||
},
|
||||
md: {
|
||||
height: 10,
|
||||
minWidth: 10,
|
||||
fontSize: 'md',
|
||||
px: 4,
|
||||
},
|
||||
sm: {
|
||||
height: 8,
|
||||
minWidth: 8,
|
||||
fontSize: 'sm',
|
||||
px: 3,
|
||||
},
|
||||
xs: {
|
||||
height: 6,
|
||||
minWidth: 6,
|
||||
fontSize: 'xs',
|
||||
px: 2,
|
||||
},
|
||||
} as TButtonSizeMap;
|
||||
export const SubmitButton = (props: TSubmitButton) => {
|
||||
const { children, handleChange, ...rest } = props;
|
||||
const { btnLoading, resolvedIsOpen, resolvedClose } = useLGState();
|
||||
|
||||
export const SubmitButton = forwardRef<HTMLDivElement, TSubmitButton>((props, ref) => {
|
||||
const {
|
||||
isLoading = false,
|
||||
isDisabled = false,
|
||||
isActive = false,
|
||||
isFullWidth = false,
|
||||
size = 'lg',
|
||||
loadingText,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
const _isDisabled = isDisabled || isLoading;
|
||||
|
||||
const bg = useColorValue('primary.400', 'primary.500');
|
||||
const bgActive = useColorValue('primary.500', 'primary.600');
|
||||
const bgHover = useColorValue('primary.300', 'primary.400');
|
||||
const color = useOpposingColor(bg);
|
||||
const colorActive = useOpposingColor(bgActive);
|
||||
const colorHover = useOpposingColor(bgHover);
|
||||
|
||||
const btnSize = btnSizeMap[size];
|
||||
function handleClose(): void {
|
||||
btnLoading.set(false);
|
||||
resolvedClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
bg={bg}
|
||||
ref={ref}
|
||||
as="button"
|
||||
color={color}
|
||||
type="submit"
|
||||
outline="none"
|
||||
lineHeight="1.2"
|
||||
appearance="none"
|
||||
userSelect="none"
|
||||
borderRadius="md"
|
||||
alignItems="center"
|
||||
position="relative"
|
||||
whiteSpace="nowrap"
|
||||
display="inline-flex"
|
||||
fontWeight="semibold"
|
||||
disabled={_isDisabled}
|
||||
transition="all 250ms"
|
||||
verticalAlign="middle"
|
||||
justifyContent="center"
|
||||
aria-label="Submit Query"
|
||||
aria-disabled={_isDisabled}
|
||||
_focus={{ boxShadow: 'outline' }}
|
||||
width={isFullWidth ? 'full' : undefined}
|
||||
data-active={isActive ? 'true' : undefined}
|
||||
_hover={{ bg: bgHover, color: colorHover }}
|
||||
_active={{ bg: bgActive, color: colorActive }}
|
||||
{...btnSize}
|
||||
{...rest}>
|
||||
{isLoading ? (
|
||||
<Spinner
|
||||
position={loadingText ? 'relative' : 'absolute'}
|
||||
mr={loadingText ? 2 : 0}
|
||||
color="currentColor"
|
||||
size="1em"
|
||||
/>
|
||||
) : (
|
||||
<FiSearch color={color} />
|
||||
)}
|
||||
{isLoading
|
||||
? loadingText || (
|
||||
<Box as="span" opacity="0">
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
: children}
|
||||
</Box>
|
||||
<>
|
||||
<Popover isOpen={resolvedIsOpen.value} onClose={handleClose} closeOnBlur={false}>
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
size="lg"
|
||||
width={16}
|
||||
type="submit"
|
||||
icon={<FiSearch />}
|
||||
title="Submit Query"
|
||||
aria-label="Submit Query"
|
||||
colorScheme="primary"
|
||||
isLoading={btnLoading.value}
|
||||
{...rest}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverCloseButton />
|
||||
<PopoverBody p={6}>
|
||||
{resolvedIsOpen.value && <ResolvedTarget setTarget={handleChange} />}
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { BoxProps, ButtonProps } from '@chakra-ui/react';
|
||||
import type { IconButtonProps, ButtonProps } from '@chakra-ui/react';
|
||||
import type { OnChangeArgs } from '~/types';
|
||||
|
||||
export interface TCopyButton extends ButtonProps {
|
||||
copyValue: string;
|
||||
|
|
@ -7,20 +8,9 @@ export interface TCopyButton extends ButtonProps {
|
|||
export interface TColorModeToggle extends ButtonProps {
|
||||
size?: string;
|
||||
}
|
||||
export type TButtonSizeMap = {
|
||||
xs: BoxProps;
|
||||
sm: BoxProps;
|
||||
md: BoxProps;
|
||||
lg: BoxProps;
|
||||
};
|
||||
|
||||
export interface TSubmitButton extends BoxProps {
|
||||
isLoading?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isActive?: boolean;
|
||||
isFullWidth?: boolean;
|
||||
size?: keyof TButtonSizeMap;
|
||||
loadingText?: string;
|
||||
export interface TSubmitButton extends Omit<IconButtonProps, 'aria-label'> {
|
||||
handleChange(e: OnChangeArgs): void;
|
||||
}
|
||||
|
||||
export interface TRequeryButton extends ButtonProps {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ function buildOptions(communities: TBGPCommunity[]): TSelectOption[] {
|
|||
}));
|
||||
}
|
||||
|
||||
const Option = (props: OptionProps<Dict>) => {
|
||||
const Option = (props: OptionProps<Dict, false>) => {
|
||||
const { label, data } = props;
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|||
import { Select } from '~/components';
|
||||
import { useConfig } from '~/context';
|
||||
|
||||
import type { TNetwork, TSelectOptionMulti } from '~/types';
|
||||
import type { TNetwork, TSelectOption } from '~/types';
|
||||
import type { TQuerySelectField } from './types';
|
||||
|
||||
function buildOptions(networks: TNetwork[]) {
|
||||
|
|
@ -23,12 +23,16 @@ export const QueryLocation = (props: TQuerySelectField) => {
|
|||
const { networks } = useConfig();
|
||||
const options = useMemo(() => buildOptions(networks), [networks.length]);
|
||||
|
||||
function handleChange(e: TSelectOptionMulti): void {
|
||||
function handleChange(e: TSelectOption | TSelectOption[]): void {
|
||||
if (e === null) {
|
||||
e = [];
|
||||
} else if (typeof e === 'string') {
|
||||
e = [e];
|
||||
}
|
||||
if (Array.isArray(e)) {
|
||||
const value = e.map(sel => sel!.value);
|
||||
onChange({ field: 'query_location', value });
|
||||
}
|
||||
const value = e.map(sel => sel.value);
|
||||
onChange({ field: 'query_location', value });
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export const QueryType = (props: TQuerySelectField) => {
|
|||
|
||||
const options = useMemo(() => buildOptions(queries.list), [queries.list.length]);
|
||||
|
||||
function handleChange(e: TSelectOption): void {
|
||||
if (e !== null) {
|
||||
function handleChange(e: TSelectOption | TSelectOption[]): void {
|
||||
if (!Array.isArray(e) && e !== null) {
|
||||
onChange({ field: 'query_type', value: e.value });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ export const QueryVrf = (props: TQueryVrf) => {
|
|||
|
||||
const options = useMemo(() => buildOptions(vrfs), [vrfs.length]);
|
||||
|
||||
function handleChange(e: TSelectOption): void {
|
||||
if (e !== null) {
|
||||
function handleChange(e: TSelectOption | TSelectOption[]): void {
|
||||
if (!Array.isArray(e) && e !== null) {
|
||||
onChange({ field: 'query_vrf', value: e.value });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { useEffect } from 'react';
|
||||
import { Button, Icon, Spinner, Stack, Tag, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Button, Stack, Text, VStack } from '@chakra-ui/react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useConfig } from '~/context';
|
||||
import { useStrf } from '~/hooks';
|
||||
import { FiArrowRightCircle as RightArrow } from '@meronex/icons/fi';
|
||||
import { useConfig, useColorValue, useGlobalState } from '~/context';
|
||||
import { useStrf, useLGState } from '~/hooks';
|
||||
|
||||
import type { DnsOverHttps, ColorNames } from '~/types';
|
||||
import type { DnsOverHttps } from '~/types';
|
||||
import type { TResolvedTarget } from './types';
|
||||
|
||||
function findAnswer(data: DnsOverHttps.Response | undefined): string {
|
||||
|
|
@ -17,26 +18,32 @@ function findAnswer(data: DnsOverHttps.Response | undefined): string {
|
|||
}
|
||||
|
||||
export const ResolvedTarget = (props: TResolvedTarget) => {
|
||||
const { fqdnTarget, setTarget, queryTarget, families, availVrfs } = props;
|
||||
const { setTarget } = props;
|
||||
const { web } = useConfig();
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const { fqdnTarget, queryTarget, families, formData } = useLGState();
|
||||
|
||||
const color = useColorValue('secondary.500', 'secondary.300');
|
||||
|
||||
const dnsUrl = web.dns_provider.url;
|
||||
const query4 = Array.from(families).includes(4);
|
||||
const query6 = Array.from(families).includes(6);
|
||||
const query4 = Array.from(families.value).includes(4);
|
||||
const query6 = Array.from(families.value).includes(6);
|
||||
|
||||
const tooltip4 = useStrf(web.text.fqdn_tooltip, { protocol: 'IPv4' });
|
||||
const tooltip6 = useStrf(web.text.fqdn_tooltip, { protocol: 'IPv6' });
|
||||
const [messageStart, messageEnd] = useMemo(() => web.text.fqdn_message.split('{fqdn}'), [
|
||||
web.text.fqdn_message,
|
||||
]);
|
||||
|
||||
const { data: data4, isLoading: isLoading4, isError: isError4 } = useQuery(
|
||||
[fqdnTarget, 4],
|
||||
[fqdnTarget.value, 4],
|
||||
dnsQuery,
|
||||
);
|
||||
|
||||
const { data: data6, isLoading: isLoading6, isError: isError6 } = useQuery(
|
||||
[fqdnTarget, 6],
|
||||
[fqdnTarget.value, 6],
|
||||
dnsQuery,
|
||||
);
|
||||
|
||||
async function dnsQuery(
|
||||
target: string,
|
||||
family: 4 | 6,
|
||||
|
|
@ -58,13 +65,9 @@ export const ResolvedTarget = (props: TResolvedTarget) => {
|
|||
function handleOverride(value: string): void {
|
||||
setTarget({ field: 'query_target', value });
|
||||
}
|
||||
|
||||
function isSelected(value: string): ColorNames {
|
||||
if (value === queryTarget) {
|
||||
return 'success';
|
||||
} else {
|
||||
return 'secondary';
|
||||
}
|
||||
function selectTarget(value: string): void {
|
||||
formData.set(p => ({ ...p, query_target: value }));
|
||||
isSubmitting.set(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -78,69 +81,42 @@ export const ResolvedTarget = (props: TResolvedTarget) => {
|
|||
}, [data4, data6]);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
isInline
|
||||
w="100%"
|
||||
justifyContent={
|
||||
query4 && data4?.Answer && query6 && data6?.Answer && availVrfs.length > 1
|
||||
? 'space-between'
|
||||
: 'flex-end'
|
||||
}
|
||||
flexWrap="wrap">
|
||||
{isLoading4 ||
|
||||
isError4 ||
|
||||
(query4 && findAnswer(data4) && (
|
||||
<Tag my={2}>
|
||||
<Tooltip hasArrow label={tooltip4} placement="bottom">
|
||||
<Button
|
||||
px={2}
|
||||
mr={2}
|
||||
py="0.1rem"
|
||||
minW="unset"
|
||||
fontSize="xs"
|
||||
height="unset"
|
||||
borderRadius="md"
|
||||
colorScheme={isSelected(findAnswer(data4))}
|
||||
onClick={() => handleOverride(findAnswer(data4))}>
|
||||
IPv4
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{isLoading4 && <Spinner />}
|
||||
{isError4 && <Icon name="warning" />}
|
||||
{findAnswer(data4) && (
|
||||
<Text fontSize="xs" fontFamily="mono" as="span" fontWeight={400}>
|
||||
{findAnswer(data4)}
|
||||
</Text>
|
||||
)}
|
||||
</Tag>
|
||||
))}
|
||||
{isLoading6 ||
|
||||
isError6 ||
|
||||
(query6 && findAnswer(data6) && (
|
||||
<Tag my={2}>
|
||||
<Tooltip hasArrow label={tooltip6} placement="bottom">
|
||||
<Button
|
||||
px={2}
|
||||
mr={2}
|
||||
py="0.1rem"
|
||||
minW="unset"
|
||||
fontSize="xs"
|
||||
height="unset"
|
||||
borderRadius="md"
|
||||
colorScheme={isSelected(findAnswer(data6))}
|
||||
onClick={() => handleOverride(findAnswer(data6))}>
|
||||
IPv6
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{isLoading6 && <Spinner />}
|
||||
{isError6 && <Icon name="warning" />}
|
||||
{findAnswer(data6) && (
|
||||
<Text fontSize="xs" fontFamily="mono" as="span" fontWeight={400}>
|
||||
{findAnswer(data6)}
|
||||
</Text>
|
||||
)}
|
||||
</Tag>
|
||||
))}
|
||||
</Stack>
|
||||
<VStack w="100%" spacing={4} justify="center">
|
||||
<Text fontSize="sm" textAlign="center">
|
||||
{messageStart}
|
||||
<Text as="span" fontSize="sm" fontWeight="bold" color={color}>
|
||||
{fqdnTarget.value}
|
||||
</Text>
|
||||
{messageEnd}
|
||||
</Text>
|
||||
<Stack spacing={2}>
|
||||
{!isLoading4 && !isError4 && query4 && findAnswer(data4) && (
|
||||
<Button
|
||||
size="sm"
|
||||
fontSize="xs"
|
||||
colorScheme="primary"
|
||||
justifyContent="space-between"
|
||||
rightIcon={<RightArrow size="18px" />}
|
||||
title={tooltip4}
|
||||
fontFamily="mono"
|
||||
onClick={() => selectTarget(findAnswer(data4))}>
|
||||
{findAnswer(data4)}
|
||||
</Button>
|
||||
)}
|
||||
{!isLoading6 && !isError6 && query6 && findAnswer(data6) && (
|
||||
<Button
|
||||
size="sm"
|
||||
fontSize="xs"
|
||||
colorScheme="secondary"
|
||||
justifyContent="space-between"
|
||||
rightIcon={<RightArrow size="18px" />}
|
||||
title={tooltip6}
|
||||
fontFamily="mono"
|
||||
onClick={() => selectTarget(findAnswer(data6))}>
|
||||
{findAnswer(data6)}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { FormControlProps } from '@chakra-ui/react';
|
||||
import type { FieldError, Control } from 'react-hook-form';
|
||||
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs, Families, TFormData } from '~/types';
|
||||
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs, TFormData } from '~/types';
|
||||
|
||||
export interface TField extends FormControlProps {
|
||||
name: string;
|
||||
|
|
@ -59,9 +59,5 @@ export interface TQueryTarget {
|
|||
}
|
||||
|
||||
export interface TResolvedTarget {
|
||||
families: Families;
|
||||
queryTarget: string;
|
||||
availVrfs: TDeviceVrf[];
|
||||
fqdnTarget: string | null;
|
||||
setTarget(e: OnChangeArgs): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export const Header = (props: THeader) => {
|
|||
const resetButton = (
|
||||
<AnimatePresence key="resetButton">
|
||||
<AnimatedDiv
|
||||
layoutTransition={headerTransition}
|
||||
transition={headerTransition}
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
animate={{ opacity: 1, x: 0, width: 'unset' }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
|
|
@ -94,7 +94,7 @@ export const Header = (props: THeader) => {
|
|||
key="title"
|
||||
px={1}
|
||||
alignItems={isSubmitting ? 'center' : ['center', 'center', 'flex-end', 'flex-end']}
|
||||
positionTransition={headerTransition}
|
||||
transition={headerTransition}
|
||||
initial={{ scale: 0.5 }}
|
||||
animate={
|
||||
isSubmitting && web.text.title_mode === 'text_only'
|
||||
|
|
@ -114,7 +114,7 @@ export const Header = (props: THeader) => {
|
|||
);
|
||||
const colorModeToggle = (
|
||||
<AnimatedDiv
|
||||
layoutTransition={headerTransition}
|
||||
transition={headerTransition}
|
||||
key="colorModeToggle"
|
||||
alignItems="center"
|
||||
initial={{ opacity: 0 }}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { AnimatePresence } from 'framer-motion';
|
||||
import { If, HyperglassForm, Results } from '~/components';
|
||||
import { useGlobalState } from '~/context';
|
||||
import { useLGState } from '~/hooks';
|
||||
import { all } from '~/util';
|
||||
import { Frame } from './frame';
|
||||
|
||||
export const Layout: React.FC = () => {
|
||||
const { isSubmitting, formData } = useGlobalState();
|
||||
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const { formData } = useLGState();
|
||||
return (
|
||||
<Frame>
|
||||
<If
|
||||
|
|
@ -19,12 +20,7 @@ export const Layout: React.FC = () => {
|
|||
formData.query_vrf.value,
|
||||
)
|
||||
}>
|
||||
<Results
|
||||
queryLocation={formData.query_location.value}
|
||||
queryType={formData.query_type.value}
|
||||
queryVrf={formData.query_vrf.value}
|
||||
queryTarget={formData.query_target.value}
|
||||
/>
|
||||
<Results />
|
||||
</If>
|
||||
<AnimatePresence>
|
||||
<If c={!isSubmitting.value}>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { intersectionWith } from 'lodash';
|
||||
|
|
@ -14,19 +14,20 @@ import {
|
|||
QueryTarget,
|
||||
SubmitButton,
|
||||
QueryLocation,
|
||||
ResolvedTarget,
|
||||
CommunitySelect,
|
||||
} from '~/components';
|
||||
import { useConfig, useGlobalState } from '~/context';
|
||||
import { useStrf, useGreeting, useDevice } from '~/hooks';
|
||||
import { useStrf, useGreeting, useDevice, useLGState } from '~/hooks';
|
||||
import { isQueryType, isString } from '~/types';
|
||||
|
||||
import type { Families, TFormData, TDeviceVrf, TQueryTypes, OnChangeArgs } from '~/types';
|
||||
import type { TFormData, TDeviceVrf, OnChangeArgs } from '~/types';
|
||||
|
||||
const fqdnPattern = /^(?!:\/\/)([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-][a-zA-Z0-9-]+\.[a-zA-Z-]{2,6}?$/gim;
|
||||
|
||||
export const HyperglassForm = () => {
|
||||
const { web, content, messages, queries } = useConfig();
|
||||
|
||||
const { formData, isSubmitting } = useGlobalState();
|
||||
const { isSubmitting } = useGlobalState();
|
||||
const [greetingAck, setGreetingAck] = useGreeting();
|
||||
const getDevice = useDevice();
|
||||
|
||||
|
|
@ -41,24 +42,34 @@ export const HyperglassForm = () => {
|
|||
query_target: yup.string().required(noQueryTarget),
|
||||
});
|
||||
|
||||
const { handleSubmit, register, unregister, setValue, errors } = useForm<TFormData>({
|
||||
const { handleSubmit, register, unregister, setValue, errors, reset } = useForm<TFormData>({
|
||||
validationSchema: formSchema,
|
||||
defaultValues: { query_vrf: 'default', query_target: '' },
|
||||
defaultValues: { query_vrf: 'default', query_target: '', query_location: [], query_type: '' },
|
||||
});
|
||||
|
||||
const [queryLocation, setQueryLocation] = useState<string[]>([]);
|
||||
const [queryType, setQueryType] = useState<TQueryTypes>('');
|
||||
const [queryVrf, setQueryVrf] = useState<string>('');
|
||||
const [queryTarget, setQueryTarget] = useState<string>('');
|
||||
const [availVrfs, setAvailVrfs] = useState<TDeviceVrf[]>([]);
|
||||
const [fqdnTarget, setFqdnTarget] = useState<string | null>('');
|
||||
const [displayTarget, setDisplayTarget] = useState<string>('');
|
||||
const [families, setFamilies] = useState<Families>([]);
|
||||
const {
|
||||
queryVrf,
|
||||
families,
|
||||
queryType,
|
||||
availVrfs,
|
||||
fqdnTarget,
|
||||
btnLoading,
|
||||
queryTarget,
|
||||
resolvedOpen,
|
||||
queryLocation,
|
||||
displayTarget,
|
||||
formData,
|
||||
} = useLGState();
|
||||
|
||||
function onSubmit(values: TFormData): void {
|
||||
function submitHandler(values: TFormData) {
|
||||
if (!greetingAck && web.greeting.required) {
|
||||
window.location.reload(false);
|
||||
setGreetingAck(false);
|
||||
} else if (fqdnPattern.test(values.query_target)) {
|
||||
btnLoading.set(true);
|
||||
fqdnTarget.set(values.query_target);
|
||||
formData.set(values);
|
||||
resolvedOpen();
|
||||
} else {
|
||||
formData.set(values);
|
||||
isSubmitting.set(true);
|
||||
|
|
@ -68,7 +79,7 @@ export const HyperglassForm = () => {
|
|||
function handleLocChange(locations: string[]): void {
|
||||
const allVrfs = [] as TDeviceVrf[][];
|
||||
|
||||
setQueryLocation(locations);
|
||||
queryLocation.set(locations);
|
||||
|
||||
// Create an array of each device's VRFs.
|
||||
for (const loc of locations) {
|
||||
|
|
@ -82,11 +93,14 @@ export const HyperglassForm = () => {
|
|||
(a: TDeviceVrf, b: TDeviceVrf) => a.id === b.id,
|
||||
);
|
||||
|
||||
setAvailVrfs(intersecting);
|
||||
availVrfs.set(intersecting);
|
||||
|
||||
// If there are no intersecting VRFs, use the default VRF.
|
||||
if (intersecting.filter(i => i.id === queryVrf).length === 0 && queryVrf !== 'default') {
|
||||
setQueryVrf('default');
|
||||
if (
|
||||
intersecting.filter(i => i.id === queryVrf.value).length === 0 &&
|
||||
queryVrf.value !== 'default'
|
||||
) {
|
||||
queryVrf.set('default');
|
||||
}
|
||||
|
||||
let ipv4 = 0;
|
||||
|
|
@ -104,13 +118,13 @@ export const HyperglassForm = () => {
|
|||
}
|
||||
|
||||
if (ipv4 !== 0 && ipv4 === ipv6) {
|
||||
setFamilies([4, 6]);
|
||||
families.set([4, 6]);
|
||||
} else if (ipv4 > ipv6) {
|
||||
setFamilies([4]);
|
||||
families.set([4]);
|
||||
} else if (ipv4 < ipv6) {
|
||||
setFamilies([6]);
|
||||
families.set([6]);
|
||||
} else {
|
||||
setFamilies([]);
|
||||
families.set([]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,35 +134,35 @@ export const HyperglassForm = () => {
|
|||
if (e.field === 'query_location' && Array.isArray(e.value)) {
|
||||
handleLocChange(e.value);
|
||||
} else if (e.field === 'query_type' && isQueryType(e.value)) {
|
||||
setQueryType(e.value);
|
||||
queryType.set(e.value);
|
||||
} else if (e.field === 'query_vrf' && isString(e.value)) {
|
||||
setQueryVrf(e.value);
|
||||
queryVrf.set(e.value);
|
||||
} else if (e.field === 'query_target' && isString(e.value)) {
|
||||
setQueryTarget(e.value);
|
||||
queryTarget.set(e.value);
|
||||
}
|
||||
}
|
||||
|
||||
const vrfContent = useMemo(() => {
|
||||
if (Object.keys(content.vrf).includes(queryVrf) && queryType !== '') {
|
||||
return content.vrf[queryVrf][queryType];
|
||||
if (Object.keys(content.vrf).includes(queryVrf.value) && queryType.value !== '') {
|
||||
return content.vrf[queryVrf.value][queryType.value];
|
||||
}
|
||||
}, [queryVrf]);
|
||||
|
||||
const isFqdnQuery = useMemo(() => {
|
||||
return ['bgp_route', 'ping', 'traceroute'].includes(queryType);
|
||||
return ['bgp_route', 'ping', 'traceroute'].includes(queryType.value);
|
||||
}, [queryType]);
|
||||
|
||||
const fqdnQuery = useMemo(() => {
|
||||
let result = null;
|
||||
if (fqdnTarget && queryVrf === 'default' && fqdnTarget) {
|
||||
if (fqdnTarget && queryVrf.value === 'default' && fqdnTarget) {
|
||||
result = fqdnTarget;
|
||||
}
|
||||
return result;
|
||||
}, [queryVrf, queryType]);
|
||||
|
||||
useEffect(() => {
|
||||
register({ name: 'query_location' });
|
||||
register({ name: 'query_type' });
|
||||
register({ name: 'query_location', required: true });
|
||||
register({ name: 'query_type', required: true });
|
||||
register({ name: 'query_vrf' });
|
||||
}, [register]);
|
||||
|
||||
|
|
@ -165,7 +179,7 @@ export const HyperglassForm = () => {
|
|||
transition={{ duration: 0.3 }}
|
||||
exit={{ opacity: 0, x: -300 }}
|
||||
initial={{ opacity: 0, y: 300 }}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
onSubmit={handleSubmit(submitHandler)}
|
||||
maxW={{ base: '100%', lg: '75%' }}>
|
||||
<FormRow>
|
||||
<FormField
|
||||
|
|
@ -185,26 +199,11 @@ export const HyperglassForm = () => {
|
|||
<FormRow>
|
||||
<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} />
|
||||
<QueryVrf label={web.text.query_vrf} vrfs={availVrfs.value} onChange={handleChange} />
|
||||
</FormField>
|
||||
</If>
|
||||
<FormField
|
||||
name="query_target"
|
||||
errors={errors.query_target}
|
||||
label={web.text.query_target}
|
||||
fieldAddOn={
|
||||
queryLocation.length !== 0 &&
|
||||
fqdnQuery !== null && (
|
||||
<ResolvedTarget
|
||||
families={families}
|
||||
availVrfs={availVrfs}
|
||||
fqdnTarget={fqdnQuery}
|
||||
setTarget={handleChange}
|
||||
queryTarget={queryTarget}
|
||||
/>
|
||||
)
|
||||
}>
|
||||
<If c={queryType === 'bgp_community' && queries.bgp_community.mode === 'select'}>
|
||||
<FormField name="query_target" errors={errors.query_target} label={web.text.query_target}>
|
||||
<If c={queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select'}>
|
||||
<CommunitySelect
|
||||
name="query_target"
|
||||
register={register}
|
||||
|
|
@ -213,17 +212,17 @@ export const HyperglassForm = () => {
|
|||
communities={queries.bgp_community.communities}
|
||||
/>
|
||||
</If>
|
||||
<If c={!(queryType === 'bgp_community' && queries.bgp_community.mode === 'select')}>
|
||||
<If c={!(queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select')}>
|
||||
<QueryTarget
|
||||
name="query_target"
|
||||
register={register}
|
||||
value={queryTarget}
|
||||
value={queryTarget.value}
|
||||
unregister={unregister}
|
||||
setFqdn={setFqdnTarget}
|
||||
setFqdn={fqdnTarget.set}
|
||||
setTarget={handleChange}
|
||||
resolveTarget={isFqdnQuery}
|
||||
displayValue={displayTarget}
|
||||
setDisplayValue={setDisplayTarget}
|
||||
displayValue={displayTarget.value}
|
||||
setDisplayValue={displayTarget.set}
|
||||
placeholder={web.text.query_target}
|
||||
/>
|
||||
</If>
|
||||
|
|
@ -238,7 +237,7 @@ export const HyperglassForm = () => {
|
|||
flex="0 0 0"
|
||||
flexDir="column"
|
||||
mr={{ base: 0, lg: 2 }}>
|
||||
<SubmitButton isLoading={isSubmitting.value} />
|
||||
<SubmitButton handleChange={handleChange} />
|
||||
</Flex>
|
||||
</FormRow>
|
||||
</AnimatedForm>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ import { forwardRef } from 'react';
|
|||
import { Icon, Text, Box, Tooltip, Menu, MenuButton, MenuList } from '@chakra-ui/react';
|
||||
import { CgMoreO as More } from '@meronex/icons/cg';
|
||||
import { BisError as Warning } from '@meronex/icons/bi';
|
||||
import { MdNotInterested as NotAllowed, MdLastPage } from '@meronex/icons/md';
|
||||
import { MdNotInterested as NotAllowed } from '@meronex/icons/md';
|
||||
import { RiHome2Fill as End } from '@meronex/icons/ri';
|
||||
import { BsQuestionCircleFill as Question } from '@meronex/icons/bs';
|
||||
import { FaCheckCircle as Check, FaChevronRight as ChevronRight } from '@meronex/icons/fa';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTimePlugin from 'dayjs/plugin/relativeTime';
|
||||
import utcPlugin from 'dayjs/plugin/utc';
|
||||
import { useConfig, useColorValue } from '~/context';
|
||||
import { If } from '~/components';
|
||||
import { useConfig, useColorValue } from '~/context';
|
||||
import { useOpposingColor } from '~/hooks';
|
||||
|
||||
import type {
|
||||
|
|
@ -85,7 +86,7 @@ export const ASPath = (props: TASPath) => {
|
|||
);
|
||||
|
||||
if (path.length === 0) {
|
||||
return <Icon as={MdLastPage} />;
|
||||
return <Icon as={End} />;
|
||||
}
|
||||
|
||||
let paths = [] as JSX.Element[];
|
||||
|
|
@ -108,7 +109,8 @@ export const ASPath = (props: TASPath) => {
|
|||
|
||||
export const Communities = (props: TCommunities) => {
|
||||
const { communities } = props;
|
||||
const color = useColorValue('black', 'white');
|
||||
const bg = useColorValue('white', 'gray.900');
|
||||
const color = useOpposingColor(bg);
|
||||
return (
|
||||
<>
|
||||
<If c={communities.length === 0}>
|
||||
|
|
@ -123,6 +125,7 @@ export const Communities = (props: TCommunities) => {
|
|||
</MenuButton>
|
||||
<MenuList
|
||||
p={3}
|
||||
bg={bg}
|
||||
width="unset"
|
||||
color={color}
|
||||
textAlign="left"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ import { useConfig } from '~/context';
|
|||
import { Table } from '~/components';
|
||||
import { Cell } from './cell';
|
||||
|
||||
import type { CellProps } from 'react-table';
|
||||
import type { TColumn, TParsedDataField } from '~/types';
|
||||
import type { TColumn, TParsedDataField, TCellRender } from '~/types';
|
||||
import type { TBGPTable } from './types';
|
||||
|
||||
function makeColumns(fields: TParsedDataField[]): TColumn[] {
|
||||
|
|
@ -37,7 +36,7 @@ export const BGPTable = (props: TBGPTable) => {
|
|||
columns={columns}
|
||||
data={data.routes}
|
||||
rowHighlightProp="active"
|
||||
cellRender={(d: CellProps<TRouteField>) => <Cell data={d} rawData={data} />}
|
||||
Cell={(d: TCellRender) => <Cell data={d} rawData={data} />}
|
||||
bordersHorizontal
|
||||
rowHighlightBg="green"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { BoxProps, FlexProps, TextProps } from '@chakra-ui/react';
|
||||
import type { CellProps } from 'react-table';
|
||||
import type { TCellRender } from '~/types';
|
||||
|
||||
export interface TTextOutput extends Omit<BoxProps, 'children'> {
|
||||
children: string;
|
||||
|
|
@ -41,7 +41,7 @@ export interface TRPKIState {
|
|||
}
|
||||
|
||||
export interface TCell {
|
||||
data: CellProps<TRouteField>;
|
||||
data: TCellRender;
|
||||
rawData: TStructuredResponse;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,19 @@ 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 { useDevice, useLGState } 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, queries, vrfs, web } = useConfig();
|
||||
export const Results = () => {
|
||||
const { queries, vrfs, web } = useConfig();
|
||||
const { formData } = useLGState();
|
||||
const {
|
||||
query_location: queryLocation,
|
||||
query_target: queryTarget,
|
||||
query_type: queryType,
|
||||
query_vrf: queryVrf,
|
||||
} = formData;
|
||||
const getDevice = useDevice();
|
||||
const targetBg = useToken('colors', 'teal.600');
|
||||
const queryBg = useToken('colors', 'cyan.500');
|
||||
|
|
@ -62,11 +66,11 @@ export const Results = (props: TResults) => {
|
|||
const [resultsComplete, setComplete] = useState<number | null>(null);
|
||||
|
||||
const matchedVrf =
|
||||
vrfs.filter(v => v.id === queryVrf)[0] ?? vrfs.filter(v => v.id === 'default')[0];
|
||||
vrfs.filter(v => v.id === queryVrf.value)[0] ?? vrfs.filter(v => v.id === 'default')[0];
|
||||
|
||||
let queryTypeLabel = '';
|
||||
if (isQueryType(queryType)) {
|
||||
queryTypeLabel = queries[queryType].display_name;
|
||||
if (isQueryType(queryType.value)) {
|
||||
queryTypeLabel = queries[queryType.value].display_name;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -77,8 +81,7 @@ export const Results = (props: TResults) => {
|
|||
w="100%"
|
||||
mx="auto"
|
||||
textAlign="left"
|
||||
maxW={{ base: '100%', lg: '75%', xl: '50%' }}
|
||||
{...rest}>
|
||||
maxW={{ base: '100%', lg: '75%', xl: '50%' }}>
|
||||
<Stack isInline align="center" justify="center" mt={4} flexWrap="wrap">
|
||||
<AnimatePresence>
|
||||
{queryLocation && (
|
||||
|
|
@ -102,7 +105,7 @@ export const Results = (props: TResults) => {
|
|||
transition={{ duration: 0.3, delay: 0.3 }}>
|
||||
<Label
|
||||
bg={targetBg}
|
||||
value={queryTarget}
|
||||
value={queryTarget.value}
|
||||
label={web.text.query_target}
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
/>
|
||||
|
|
@ -138,27 +141,26 @@ export const Results = (props: TResults) => {
|
|||
transition={{ duration: 0.3 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
maxW={{ base: '100%', md: '75%' }}>
|
||||
<Accordion allowMultiple>
|
||||
<Accordion allowMultiple allowToggle>
|
||||
<AnimatePresence>
|
||||
{queryLocation &&
|
||||
queryLocation.map((loc, i) => {
|
||||
const device = getDevice(loc);
|
||||
const device = getDevice(loc.value);
|
||||
return (
|
||||
<motion.div
|
||||
key={loc.value}
|
||||
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}
|
||||
queryLocation={loc.value}
|
||||
queryVrf={queryVrf.value}
|
||||
setComplete={setComplete}
|
||||
timeout={request_timeout * 1000}
|
||||
queryType={queryType.value}
|
||||
queryTarget={queryTarget.value}
|
||||
resultsComplete={resultsComplete}
|
||||
/>
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -145,7 +145,6 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||
return (
|
||||
<AccordionItem
|
||||
ref={ref}
|
||||
isOpen={isOpen}
|
||||
isDisabled={isLoading}
|
||||
css={{
|
||||
'&:last-of-type': { borderBottom: 'none' },
|
||||
|
|
@ -170,7 +169,7 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||
</AccordionButton>
|
||||
<ButtonGroup px={[1, 1, 3, 3]} py={2}>
|
||||
<CopyButton copyValue={copyValue} isDisabled={isLoading} />
|
||||
<RequeryButton requery={refetch} variant="ghost" isDisabled={isLoading} />
|
||||
<RequeryButton requery={refetch} isDisabled={isLoading} />
|
||||
</ButtonGroup>
|
||||
</AccordionHeaderWrapper>
|
||||
<AccordionPanel
|
||||
|
|
@ -200,11 +199,12 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||
) : isStringOutput(data) && data.level === 'success' && !tableComponent ? (
|
||||
<TextOutput>{data.output}</TextOutput>
|
||||
) : isStringOutput(data) && data.level !== 'success' ? (
|
||||
<FormattedError message={data.output} keywords={errorKeywords} />
|
||||
<Alert rounded="lg" my={2} py={4} status={errorLevel}>
|
||||
<FormattedError message={data.output} keywords={errorKeywords} />
|
||||
</Alert>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
{isError && <Alert rounded="lg" my={2} py={4} status={errorLevel}></Alert>}
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
|
|
@ -214,7 +214,7 @@ export const Result = forwardRef<HTMLDivElement, TResult>((props, ref) => {
|
|||
mt={2}
|
||||
justifyContent={['flex-start', 'flex-start', 'flex-end', 'flex-end']}
|
||||
flex="1 0 auto">
|
||||
<If c={cache.show_text && data && !error}>
|
||||
<If c={cache.show_text && typeof data !== 'undefined' && !error}>
|
||||
<If c={!isMobile}>
|
||||
<Countdown timeout={cache.timeout} text={web.text.cache_prefix} />
|
||||
</If>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { BoxProps, FlexProps } from '@chakra-ui/react';
|
||||
import type { TDevice, TQueryTypes, TFormState } from '~/types';
|
||||
import type { FlexProps } from '@chakra-ui/react';
|
||||
import type { TDevice, TQueryTypes } from '~/types';
|
||||
|
||||
export interface TResultHeader {
|
||||
title: string;
|
||||
|
|
@ -26,10 +26,8 @@ export interface TResult {
|
|||
queryType: TQueryTypes;
|
||||
queryTarget: string;
|
||||
setComplete(v: number | null): void;
|
||||
queryLocation: string[];
|
||||
queryLocation: string;
|
||||
resultsComplete: number | null;
|
||||
}
|
||||
|
||||
export type TResults = TFormState & BoxProps;
|
||||
|
||||
export type TErrorLevels = 'success' | 'warning' | 'error';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type {
|
|||
PlaceholderProps,
|
||||
} from 'react-select';
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import type { ColorNames, TSelectOption, TSelectOptionGroup } from '~/types';
|
||||
import type { ColorNames, TSelectOption, TSelectOptionMulti, TSelectOptionGroup } from '~/types';
|
||||
|
||||
export interface TSelectState {
|
||||
[k: string]: string[];
|
||||
|
|
@ -27,7 +27,7 @@ export interface TSelectBase extends TBoxAsReactSelect {
|
|||
options: TOptions;
|
||||
required?: boolean;
|
||||
onSelect?: (s: TSelectOption[]) => void;
|
||||
onChange?: (c: TSelectOption) => void;
|
||||
onChange?: (c: TSelectOption | TSelectOptionMulti) => void;
|
||||
colorScheme?: ColorNames;
|
||||
}
|
||||
|
||||
|
|
@ -52,19 +52,19 @@ export interface TRSTheme extends Omit<Theme, 'borderRadius'> {
|
|||
borderRadius: string | number;
|
||||
}
|
||||
|
||||
export type TControl = ControlProps<TOptions>;
|
||||
export type TControl = ControlProps<TOptions, false>;
|
||||
|
||||
export type TMenu = MenuProps<TOptions>;
|
||||
export type TMenu = MenuProps<TOptions, false>;
|
||||
|
||||
export type TMenuList = MenuListComponentProps<TOptions>;
|
||||
export type TMenuList = MenuListComponentProps<TOptions, false>;
|
||||
|
||||
export type TOption = OptionProps<TOptions>;
|
||||
export type TOption = OptionProps<TOptions, false>;
|
||||
|
||||
export type TMultiValueState = MultiValueProps<TOptions>;
|
||||
|
||||
export type TIndicator = IndicatorProps<TOptions>;
|
||||
export type TIndicator = IndicatorProps<TOptions, false>;
|
||||
|
||||
export type TPlaceholder = PlaceholderProps<TOptions>;
|
||||
export type TPlaceholder = PlaceholderProps<TOptions, false>;
|
||||
|
||||
export type TMultiValue = Pick<TSelectContext, 'colorMode'>;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ import type { TTitle, TTextOnly } from './types';
|
|||
|
||||
const TextOnly = (props: TTextOnly) => {
|
||||
const { showSubtitle, ...rest } = props;
|
||||
const textAlign = useBooleanValue(
|
||||
showSubtitle,
|
||||
{ base: 'right', md: 'center' },
|
||||
{ base: 'left', md: 'center' },
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack spacing={2} maxW="100%" textAlign={textAlign} {...rest}>
|
||||
<Stack
|
||||
spacing={2}
|
||||
maxW="100%"
|
||||
textAlign={showSubtitle ? ['right', 'center'] : ['left', 'center']}
|
||||
{...rest}>
|
||||
<TitleOnly showSubtitle={showSubtitle} />
|
||||
<If c={showSubtitle}>
|
||||
<SubtitleOnly />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createState, useState } from '@hookstate/core';
|
||||
import type { TGlobalState } from './types';
|
||||
import type { TGlobalState, TUseGlobalState } from './types';
|
||||
|
||||
// const StateContext = createContext(null);
|
||||
|
||||
|
|
@ -26,8 +26,23 @@ import type { TGlobalState } from './types';
|
|||
|
||||
// export const useHyperglassState = () => useContext(StateContext);
|
||||
|
||||
export const globalState = createState<TGlobalState>({
|
||||
const defaultFormData = {
|
||||
query_location: [],
|
||||
query_target: '',
|
||||
query_type: '',
|
||||
query_vrf: '',
|
||||
} as TGlobalState['formData'];
|
||||
|
||||
const globalState = createState<TGlobalState>({
|
||||
isSubmitting: false,
|
||||
formData: { query_location: [], query_target: '', query_type: '', query_vrf: '' },
|
||||
formData: defaultFormData,
|
||||
});
|
||||
export const useGlobalState = () => useState(globalState);
|
||||
|
||||
export function useGlobalState(): TUseGlobalState {
|
||||
const state = useState<TGlobalState>(globalState);
|
||||
function resetForm(): void {
|
||||
state.formData.set(defaultFormData);
|
||||
state.isSubmitting.set(false);
|
||||
}
|
||||
return { resetForm, ...state };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { State } from '@hookstate/core';
|
||||
import type { IConfig, TFormData } from '~/types';
|
||||
|
||||
export interface THyperglassProvider {
|
||||
|
|
@ -9,3 +10,9 @@ export interface TGlobalState {
|
|||
isSubmitting: boolean;
|
||||
formData: TFormData;
|
||||
}
|
||||
|
||||
interface TGlobalStateFunctions {
|
||||
resetForm(): void;
|
||||
}
|
||||
|
||||
export type TUseGlobalState = State<TGlobalState> & TGlobalStateFunctions;
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ export * from './useOpposingColor';
|
|||
export * from './useSessionStorage';
|
||||
export * from './useStrf';
|
||||
export * from './useTableToString';
|
||||
export * from './useLGState';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useQuery } from 'react-query';
|
||||
import { useConfig } from '~/context';
|
||||
|
||||
import type { TFormState } from '~/types';
|
||||
import type { TFormQuery } from '~/types';
|
||||
|
||||
/**
|
||||
* Fetch Wrapper that incorporates a timeout via a passed AbortController instance.
|
||||
|
|
@ -30,11 +30,11 @@ export async function fetchWithTimeout(
|
|||
return await fetch(uri, config);
|
||||
}
|
||||
|
||||
export function useLGQuery(query: TFormState) {
|
||||
export function useLGQuery(query: TFormQuery) {
|
||||
const { request_timeout } = useConfig();
|
||||
const controller = new AbortController();
|
||||
|
||||
async function runQuery(url: string, requestData: TFormState): Promise<TQueryResponse> {
|
||||
async function runQuery(url: string, requestData: TFormQuery): Promise<TQueryResponse> {
|
||||
const { queryLocation, queryTarget, queryType, queryVrf } = requestData;
|
||||
const res = await fetchWithTimeout(
|
||||
url,
|
||||
|
|
|
|||
49
hyperglass/ui/hooks/useLGState.ts
Normal file
49
hyperglass/ui/hooks/useLGState.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { useState, createState } from '@hookstate/core';
|
||||
|
||||
import type { State } from '@hookstate/core';
|
||||
import type { Families, TDeviceVrf, TQueryTypes, TFormData } from '~/types';
|
||||
|
||||
type TLGState = {
|
||||
queryVrf: string;
|
||||
families: Families;
|
||||
queryTarget: string;
|
||||
btnLoading: boolean;
|
||||
displayTarget: string;
|
||||
queryType: TQueryTypes;
|
||||
queryLocation: string[];
|
||||
availVrfs: TDeviceVrf[];
|
||||
resolvedIsOpen: boolean;
|
||||
fqdnTarget: string | null;
|
||||
formData: TFormData;
|
||||
};
|
||||
|
||||
type TLGStateHandlers = {
|
||||
resolvedOpen(): void;
|
||||
resolvedClose(): void;
|
||||
};
|
||||
|
||||
const LGState = createState<TLGState>({
|
||||
resolvedIsOpen: false,
|
||||
displayTarget: '',
|
||||
queryLocation: [],
|
||||
btnLoading: false,
|
||||
fqdnTarget: null,
|
||||
queryTarget: '',
|
||||
queryType: '',
|
||||
availVrfs: [],
|
||||
queryVrf: '',
|
||||
families: [],
|
||||
formData: { query_location: [], query_target: '', query_type: '', query_vrf: '' },
|
||||
});
|
||||
|
||||
export function useLGState(): State<TLGState> & TLGStateHandlers {
|
||||
const state = useState<TLGState>(LGState);
|
||||
function resolvedOpen() {
|
||||
state.resolvedIsOpen.set(true);
|
||||
}
|
||||
function resolvedClose() {
|
||||
state.resolvedIsOpen.set(false);
|
||||
}
|
||||
|
||||
return { resolvedOpen, resolvedClose, ...state };
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ export interface IConfigWebText {
|
|||
query_target: string;
|
||||
query_vrf: string;
|
||||
fqdn_tooltip: string;
|
||||
fqdn_message: string;
|
||||
cache_prefix: string;
|
||||
cache_icon: string;
|
||||
complete_time: string;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ export interface TFormState {
|
|||
queryTarget: string;
|
||||
}
|
||||
|
||||
export interface TFormQuery extends Omit<TFormState, 'queryLocation'> {
|
||||
queryLocation: string;
|
||||
}
|
||||
|
||||
export interface TStringTableData extends Omit<TQueryResponse, 'output'> {
|
||||
output: TStructuredResponse;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import type { CellProps } from 'react-table';
|
||||
|
||||
export interface TColumn {
|
||||
Header: string;
|
||||
accessor: keyof TRoute;
|
||||
align: string;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
export type TCellRender = {
|
||||
column: CellProps<TRouteField>['column'];
|
||||
row: CellProps<TRouteField>['row'];
|
||||
value: CellProps<TRouteField>['value'];
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue