diff --git a/hyperglass/ui/components/form/field.tsx b/hyperglass/ui/components/form/field.tsx index 80f4692..d0d1a47 100644 --- a/hyperglass/ui/components/form/field.tsx +++ b/hyperglass/ui/components/form/field.tsx @@ -37,9 +37,9 @@ export const FormField = (props: TField) => { htmlFor={name} display="flex" opacity={opacity} - color={error !== false ? errorColor : labelColor} alignItems="center" - justifyContent="space-between"> + justifyContent="space-between" + color={error !== false ? errorColor : labelColor}> {label} {labelAddOn} diff --git a/hyperglass/ui/components/index.ts b/hyperglass/ui/components/index.ts index 2221434..d4146af 100644 --- a/hyperglass/ui/components/index.ts +++ b/hyperglass/ui/components/index.ts @@ -19,5 +19,4 @@ export * from './path'; export * from './results'; export * from './select'; export * from './table'; -export * from './title'; export * from './util'; diff --git a/hyperglass/ui/components/lookingGlass.tsx b/hyperglass/ui/components/lookingGlass.tsx index 4f36e5d..9b587a4 100644 --- a/hyperglass/ui/components/lookingGlass.tsx +++ b/hyperglass/ui/components/lookingGlass.tsx @@ -18,7 +18,7 @@ import { } from '~/components'; import { useConfig } from '~/context'; import { useStrf, useGreeting, useDevice, useLGState } from '~/hooks'; -import { isQueryType, isString } from '~/types'; +import { isQueryType, isQueryContent, isString } from '~/types'; import type { TFormData, TDeviceVrf, OnChangeArgs } from '~/types'; @@ -63,8 +63,6 @@ export const HyperglassForm = () => { } = useLGState(); function submitHandler(values: TFormData) { - console.dir(values); - console.dir(formData.value); if (!greetingAck && web.greeting.required) { window.location.reload(false); setGreetingAck(false); @@ -143,15 +141,15 @@ export const HyperglassForm = () => { } else if (e.field === 'query_target' && isString(e.value)) { queryTarget.set(e.value); } - console.log(e.field, e.value); - console.dir(getValues()); } const vrfContent = useMemo(() => { if (Object.keys(content.vrf).includes(queryVrf.value) && queryType.value !== '') { return content.vrf[queryVrf.value][queryType.value]; + } else { + return null; } - }, [queryVrf]); + }, [queryVrf.value, queryLocation.value, queryType.value]); const isFqdnQuery = useMemo(() => { return ['bgp_route', 'ping', 'traceroute'].includes(queryType.value); @@ -185,7 +183,9 @@ export const HyperglassForm = () => { }> + labelAddOn={ + + }> diff --git a/hyperglass/ui/components/path/chart.tsx b/hyperglass/ui/components/path/chart.tsx index 1b13d47..4b84ce8 100644 --- a/hyperglass/ui/components/path/chart.tsx +++ b/hyperglass/ui/components/path/chart.tsx @@ -19,7 +19,7 @@ export const Chart = (props: TChart) => { const flowProps = useBreakpointValue>({ base: { defaultPosition: [0, 300], defaultZoom: 0 }, - lg: { defaultPosition: [500, 450] }, + lg: { defaultPosition: [100, 300], defaultZoom: 0.7 }, }); const elements = useMemo(() => [...buildElements({ asn: primary_asn, name: org_name }, data)], [ @@ -29,7 +29,7 @@ export const Chart = (props: TChart) => { return ( - + @@ -38,7 +38,7 @@ export const Chart = (props: TChart) => { ); }; -const TestNode = (props: TNode) => { +const ASNode = (props: TNode) => { const { data } = props; const { asn, name, hasChildren, hasParents } = data; diff --git a/hyperglass/ui/components/path/path.tsx b/hyperglass/ui/components/path/path.tsx index 8cf53d0..7e4596c 100644 --- a/hyperglass/ui/components/path/path.tsx +++ b/hyperglass/ui/components/path/path.tsx @@ -23,6 +23,7 @@ export const Path = (props: TPath) => { const response = getResponse(device); const output = response?.output as TStructuredResponse; const bg = useColorValue('whiteFaded.50', 'blackFaded.900'); + return ( <> diff --git a/hyperglass/ui/components/path/util.ts b/hyperglass/ui/components/path/util.ts index 34155f8..0f17b4b 100644 --- a/hyperglass/ui/components/path/util.ts +++ b/hyperglass/ui/components/path/util.ts @@ -10,7 +10,7 @@ function treeToElement(part: PathPart, len: number, index: number): FlowElement[ let elements = [ { id: String(part.base), - type: 'TestNode', + type: 'ASNode', position: { x, y }, data: { asn: part.base, @@ -50,7 +50,7 @@ export function* buildElements(base: BasePath, data: TStructuredResponse): Gener // Add the first hop at the base. yield { id: base.asn, - type: 'TestNode', + type: 'ASNode', position: { x: 150, y: numHops * 10 }, data: { asn: base.asn, name: base.name, hasChildren: true, hasParents: false }, }; diff --git a/hyperglass/ui/components/results/group.tsx b/hyperglass/ui/components/results/group.tsx index 791d984..c6439fb 100644 --- a/hyperglass/ui/components/results/group.tsx +++ b/hyperglass/ui/components/results/group.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Accordion, Box, Stack, useToken } from '@chakra-ui/react'; import { motion, AnimatePresence } from 'framer-motion'; import { AnimatedDiv, Label } from '~/components'; @@ -63,7 +63,7 @@ export const Results = () => { xl: { opacity: 0, x: 100 }, }); - const [resultsComplete, setComplete] = useState(null); + const [resultsComplete, setComplete] = useState([]); const matchedVrf = vrfs.filter(v => v.id === queryVrf.value)[0] ?? vrfs.filter(v => v.id === 'default')[0]; @@ -73,6 +73,13 @@ export const Results = () => { queryTypeLabel = queries[queryType.value].display_name; } + // Scroll to the top of the page when results load - primarily for mobile. + useEffect(() => { + if (typeof window !== 'undefined') { + window.scrollTo(0, 0); + } + }, []); + return ( <> { transition={{ duration: 0.3 }} animate={{ opacity: 1, y: 0 }} maxW={{ base: '100%', md: '75%' }}> - + {queryLocation && queryLocation.map((loc, i) => { diff --git a/hyperglass/ui/components/results/individual.tsx b/hyperglass/ui/components/results/individual.tsx index df5843e..a001b0b 100644 --- a/hyperglass/ui/components/results/individual.tsx +++ b/hyperglass/ui/components/results/individual.tsx @@ -16,9 +16,9 @@ import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If, Path } import { useColorValue, useConfig, useMobile } from '~/context'; import { useStrf, useLGQuery, useLGState, useTableToString } from '~/hooks'; import { isStructuredOutput, isStringOutput } from '~/types'; +import { isStackError, isFetchError, isLGError } from './guards'; import { FormattedError } from './error'; import { ResultHeader } from './header'; -import { isStackError, isFetchError, isLGError } from './guards'; import type { TAccordionHeaderWrapper, TResult, TErrorLevels } from './types'; @@ -71,12 +71,15 @@ export const Result = forwardRef((props, ref) => { 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); + // Close if open. + if (resultsComplete.includes(index)) { + setComplete(p => p.filter(i => i !== index)); + } + // Open if closed. + else if (!resultsComplete.includes(index)) { + setComplete(p => [...p, index]); + } }; const errorKeywords = useMemo(() => { @@ -140,17 +143,12 @@ export const Result = forwardRef((props, ref) => { copyValue = errorMsg; } + // If this is the first completed result, open it. useEffect(() => { - if (isLoading && resultsComplete === null) { - setComplete(index); + if (!isLoading && !isError && resultsComplete.length === 0) { + setComplete([index]); } - }, [isLoading, resultsComplete]); - - useEffect(() => { - if (resultsComplete === index && !hasOverride) { - setOpen(true); - } - }, [resultsComplete, index]); + }, [isLoading, isError]); return ( >; } export type TErrorLevels = 'success' | 'warning' | 'error'; diff --git a/hyperglass/ui/components/select/styles.tsx b/hyperglass/ui/components/select/styles.tsx index 2ce0c59..1dc5515 100644 --- a/hyperglass/ui/components/select/styles.tsx +++ b/hyperglass/ui/components/select/styles.tsx @@ -27,7 +27,10 @@ export const useControlStyle = (base: TStyles, state: TControl): TStyles => { ); const focusBorder = useColorValue(useToken('colors', 'blue.500'), useToken('colors', 'blue.300')); const invalidBorder = useColorValue(useToken('colors', 'red.500'), useToken('colors', 'red.300')); - const borderColor = useColorValue('inherit', useToken('colors', 'whiteAlpha.50')); + const borderColor = useColorValue( + useToken('colors', 'gray.100'), + useToken('colors', 'whiteAlpha.50'), + ); const borderRadius = useToken('radii', 'md'); const minHeight = useToken('space', 12); const color = useColorValue(useToken('colors', 'black'), useToken('colors', 'whiteAlpha.800')); @@ -132,11 +135,11 @@ export const useOptionStyle = (base: TStyles, state: TOption): TStyles => { const color = useOpposingColor(backgroundColor); const styles = { - backgroundColor, - color, - fontSize, - '&:focus': { backgroundColor: active, color: activeColor }, + color: backgroundColor === 'transparent' ? 'currentColor' : color, '&:active': { backgroundColor: active, color: activeColor }, + '&:focus': { backgroundColor: active, color: activeColor }, + backgroundColor, + fontSize, }; return useMemo(() => mergeWith({}, base, styles), [