diff --git a/hyperglass/models/config/web.py b/hyperglass/models/config/web.py index 33cad58..1a91a2d 100644 --- a/hyperglass/models/config/web.py +++ b/hyperglass/models/config/web.py @@ -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 diff --git a/hyperglass/ui/components/Markdown/elements.tsx b/hyperglass/ui/components/Markdown/elements.tsx index 32b118d..37f7e57 100644 --- a/hyperglass/ui/components/Markdown/elements.tsx +++ b/hyperglass/ui/components/Markdown/elements.tsx @@ -82,6 +82,6 @@ export const TableData = (props: TTableData) => { }; export const Paragraph = (props: TextProps) => ; -export const InlineCode = (props: CodeProps) => ; +export const InlineCode = (props: CodeProps) => ; export const Divider = (props: DividerProps) => ; export const Table = (props: BoxProps) => ; diff --git a/hyperglass/ui/components/Markdown/types.ts b/hyperglass/ui/components/Markdown/types.ts index 08f9da9..f0b5b71 100644 --- a/hyperglass/ui/components/Markdown/types.ts +++ b/hyperglass/ui/components/Markdown/types.ts @@ -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 { diff --git a/hyperglass/ui/components/Table/main.tsx b/hyperglass/ui/components/Table/main.tsx index fb48453..d94a4b8 100644 --- a/hyperglass/ui/components/Table/main.tsx +++ b/hyperglass/ui/components/Table/main.tsx @@ -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(() => @@ -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 ( - {/* {cellRender ?? cell.render('Cell')} */} - {React.createElement(cellRender, cell)} + {typeof Cell !== 'undefined' ? ( + + ) : ( + cell.render('Cell') + )} ); })} diff --git a/hyperglass/ui/components/Table/types.ts b/hyperglass/ui/components/Table/types.ts index e98d60d..e0ced51 100644 --- a/hyperglass/ui/components/Table/types.ts +++ b/hyperglass/ui/components/Table/types.ts @@ -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; rowHighlightProp?: keyof IRoute; rowHighlightBg?: keyof Colors; } diff --git a/hyperglass/ui/components/buttons/copy.tsx b/hyperglass/ui/components/buttons/copy.tsx index aab49ff..6afb4e2 100644 --- a/hyperglass/ui/components/buttons/copy.tsx +++ b/hyperglass/ui/components/buttons/copy.tsx @@ -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(() => import('@meronex/icons/fi').then(i => i.FiCopy)); const Check = dynamic(() => 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}> - - - - - - + ); diff --git a/hyperglass/ui/components/buttons/requery.tsx b/hyperglass/ui/components/buttons/requery.tsx index ba373f5..88bed26 100644 --- a/hyperglass/ui/components/buttons/requery.tsx +++ b/hyperglass/ui/components/buttons/requery.tsx @@ -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(() => import('@meronex/icons/fi').then(i => i.FiRepeat)); -export const RequeryButton = (props: TRequeryButton) => { - const { requery, bg = 'secondary', ...rest } = props; +export const RequeryButton = forwardRef((props, ref) => { + const { requery, ...rest } = props; + return ( - ); -}; +}); diff --git a/hyperglass/ui/components/buttons/submit.tsx b/hyperglass/ui/components/buttons/submit.tsx index 56ee636..c34b180 100644 --- a/hyperglass/ui/components/buttons/submit.tsx +++ b/hyperglass/ui/components/buttons/submit.tsx @@ -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((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 ( - - {isLoading ? ( - - ) : ( - - )} - {isLoading - ? loadingText || ( - - {children} - - ) - : children} - + <> + + + } + title="Submit Query" + aria-label="Submit Query" + colorScheme="primary" + isLoading={btnLoading.value} + {...rest} + /> + + + + + + {resolvedIsOpen.value && } + + + + ); -}); +}; diff --git a/hyperglass/ui/components/buttons/types.ts b/hyperglass/ui/components/buttons/types.ts index 7660523..fe80fd3 100644 --- a/hyperglass/ui/components/buttons/types.ts +++ b/hyperglass/ui/components/buttons/types.ts @@ -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 { + handleChange(e: OnChangeArgs): void; } export interface TRequeryButton extends ButtonProps { diff --git a/hyperglass/ui/components/form/communitySelect.tsx b/hyperglass/ui/components/form/communitySelect.tsx index c1cfe37..714f219 100644 --- a/hyperglass/ui/components/form/communitySelect.tsx +++ b/hyperglass/ui/components/form/communitySelect.tsx @@ -15,7 +15,7 @@ function buildOptions(communities: TBGPCommunity[]): TSelectOption[] { })); } -const Option = (props: OptionProps) => { +const Option = (props: OptionProps) => { const { label, data } = props; return ( diff --git a/hyperglass/ui/components/form/queryLocation.tsx b/hyperglass/ui/components/form/queryLocation.tsx index cfd60df..98b9cc3 100644 --- a/hyperglass/ui/components/form/queryLocation.tsx +++ b/hyperglass/ui/components/form/queryLocation.tsx @@ -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 ( diff --git a/hyperglass/ui/components/form/queryType.tsx b/hyperglass/ui/components/form/queryType.tsx index 42418f3..94c0ee9 100644 --- a/hyperglass/ui/components/form/queryType.tsx +++ b/hyperglass/ui/components/form/queryType.tsx @@ -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 }); } } diff --git a/hyperglass/ui/components/form/queryVrf.tsx b/hyperglass/ui/components/form/queryVrf.tsx index c82ddab..4c6354e 100644 --- a/hyperglass/ui/components/form/queryVrf.tsx +++ b/hyperglass/ui/components/form/queryVrf.tsx @@ -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 }); } } diff --git a/hyperglass/ui/components/form/resolvedTarget.tsx b/hyperglass/ui/components/form/resolvedTarget.tsx index 7880c94..f9d0f51 100644 --- a/hyperglass/ui/components/form/resolvedTarget.tsx +++ b/hyperglass/ui/components/form/resolvedTarget.tsx @@ -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 ( - 1 - ? 'space-between' - : 'flex-end' - } - flexWrap="wrap"> - {isLoading4 || - isError4 || - (query4 && findAnswer(data4) && ( - - - - - {isLoading4 && } - {isError4 && } - {findAnswer(data4) && ( - - {findAnswer(data4)} - - )} - - ))} - {isLoading6 || - isError6 || - (query6 && findAnswer(data6) && ( - - - - - {isLoading6 && } - {isError6 && } - {findAnswer(data6) && ( - - {findAnswer(data6)} - - )} - - ))} - + + + {messageStart} + + {fqdnTarget.value} + + {messageEnd} + + + {!isLoading4 && !isError4 && query4 && findAnswer(data4) && ( + + )} + {!isLoading6 && !isError6 && query6 && findAnswer(data6) && ( + + )} + + ); }; diff --git a/hyperglass/ui/components/form/types.ts b/hyperglass/ui/components/form/types.ts index 740c546..63acab3 100644 --- a/hyperglass/ui/components/form/types.ts +++ b/hyperglass/ui/components/form/types.ts @@ -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; } diff --git a/hyperglass/ui/components/header/header.tsx b/hyperglass/ui/components/header/header.tsx index 0488d72..59ce102 100644 --- a/hyperglass/ui/components/header/header.tsx +++ b/hyperglass/ui/components/header/header.tsx @@ -75,7 +75,7 @@ export const Header = (props: THeader) => { const resetButton = ( { 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 = ( { - const { isSubmitting, formData } = useGlobalState(); - + const { isSubmitting } = useGlobalState(); + const { formData } = useLGState(); return ( { formData.query_vrf.value, ) }> - + diff --git a/hyperglass/ui/components/lookingGlass.tsx b/hyperglass/ui/components/lookingGlass.tsx index 9275b27..490762a 100644 --- a/hyperglass/ui/components/lookingGlass.tsx +++ b/hyperglass/ui/components/lookingGlass.tsx @@ -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({ + const { handleSubmit, register, unregister, setValue, errors, reset } = useForm({ validationSchema: formSchema, - defaultValues: { query_vrf: 'default', query_target: '' }, + defaultValues: { query_vrf: 'default', query_target: '', query_location: [], query_type: '' }, }); - const [queryLocation, setQueryLocation] = useState([]); - const [queryType, setQueryType] = useState(''); - const [queryVrf, setQueryVrf] = useState(''); - const [queryTarget, setQueryTarget] = useState(''); - const [availVrfs, setAvailVrfs] = useState([]); - const [fqdnTarget, setFqdnTarget] = useState(''); - const [displayTarget, setDisplayTarget] = useState(''); - const [families, setFamilies] = useState([]); + 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%' }}> { 1}> - + - - ) - }> - + + { communities={queries.bgp_community.communities} /> - + @@ -238,7 +237,7 @@ export const HyperglassForm = () => { flex="0 0 0" flexDir="column" mr={{ base: 0, lg: 2 }}> - + diff --git a/hyperglass/ui/components/output/fields.tsx b/hyperglass/ui/components/output/fields.tsx index b93498d..d156a99 100644 --- a/hyperglass/ui/components/output/fields.tsx +++ b/hyperglass/ui/components/output/fields.tsx @@ -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 ; + return ; } 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 ( <> @@ -123,6 +125,7 @@ export const Communities = (props: TCommunities) => { { columns={columns} data={data.routes} rowHighlightProp="active" - cellRender={(d: CellProps) => } + Cell={(d: TCellRender) => } bordersHorizontal rowHighlightBg="green" /> diff --git a/hyperglass/ui/components/output/types.ts b/hyperglass/ui/components/output/types.ts index 116941a..a492625 100644 --- a/hyperglass/ui/components/output/types.ts +++ b/hyperglass/ui/components/output/types.ts @@ -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 { children: string; @@ -41,7 +41,7 @@ export interface TRPKIState { } export interface TCell { - data: CellProps; + data: TCellRender; rawData: TStructuredResponse; } diff --git a/hyperglass/ui/components/results/group.tsx b/hyperglass/ui/components/results/group.tsx index 399b9c7..15b5a4e 100644 --- a/hyperglass/ui/components/results/group.tsx +++ b/hyperglass/ui/components/results/group.tsx @@ -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(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%' }}> {queryLocation && ( @@ -102,7 +105,7 @@ export const Results = (props: TResults) => { transition={{ duration: 0.3, delay: 0.3 }}>