forked from mirrors/thatmattlove-hyperglass
continue typescript & chakra v1 migrations [skip ci]
This commit is contained in:
parent
781a2608a0
commit
be601e4aef
43 changed files with 2042 additions and 2083 deletions
|
|
@ -1,41 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Text } from '@chakra-ui/core';
|
||||
import { components } from 'react-select';
|
||||
import { ChakraSelect } from 'app/components';
|
||||
|
||||
export const CommunitySelect = ({ name, communities, onChange, register, unregister }) => {
|
||||
const communitySelections = communities.map(c => {
|
||||
return {
|
||||
value: c.community,
|
||||
label: c.display_name,
|
||||
description: c.description,
|
||||
};
|
||||
});
|
||||
const Option = ({ label, data, ...props }) => {
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<Text>{label}</Text>
|
||||
<Text fontSize="xs" as="span">
|
||||
{data.description}
|
||||
</Text>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
useEffect(() => {
|
||||
register({ name });
|
||||
return () => unregister(name);
|
||||
}, [name, register, unregister]);
|
||||
return (
|
||||
<ChakraSelect
|
||||
innerRef={register}
|
||||
size="lg"
|
||||
name={name}
|
||||
onChange={e => {
|
||||
onChange({ field: name, value: e.value || '' });
|
||||
}}
|
||||
options={communitySelections}
|
||||
components={{ Option }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Input, useColorMode } from '@chakra-ui/core';
|
||||
|
||||
const fqdnPattern = /^(?!:\/\/)([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-][a-zA-Z0-9-]+\.[a-zA-Z-]{2,6}?$/gim;
|
||||
|
||||
const bg = { dark: 'whiteAlpha.100', light: 'white' };
|
||||
const color = { dark: 'whiteAlpha.800', light: 'gray.400' };
|
||||
const border = { dark: 'whiteAlpha.50', light: 'gray.100' };
|
||||
const placeholderColor = { dark: 'whiteAlpha.700', light: 'gray.600' };
|
||||
|
||||
export const QueryTarget = ({
|
||||
placeholder,
|
||||
register,
|
||||
unregister,
|
||||
setFqdn,
|
||||
name,
|
||||
value,
|
||||
setTarget,
|
||||
resolveTarget,
|
||||
displayValue,
|
||||
setDisplayValue,
|
||||
}) => {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const handleBlur = () => {
|
||||
if (resolveTarget && displayValue && fqdnPattern.test(displayValue)) {
|
||||
setFqdn(displayValue);
|
||||
} else if (resolveTarget && !displayValue) {
|
||||
setFqdn(false);
|
||||
}
|
||||
};
|
||||
const handleChange = e => {
|
||||
setDisplayValue(e.target.value);
|
||||
setTarget({ field: name, value: e.target.value });
|
||||
};
|
||||
const handleKeyDown = e => {
|
||||
if ([9, 13].includes(e.keyCode)) {
|
||||
handleBlur();
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
register({ name });
|
||||
return () => unregister(name);
|
||||
}, [register, unregister, name]);
|
||||
return (
|
||||
<>
|
||||
<input hidden readOnly name={name} ref={register} value={value} />
|
||||
<Input
|
||||
size="lg"
|
||||
aria-label={placeholder}
|
||||
name="query_target_display"
|
||||
bg={bg[colorMode]}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
value={displayValue}
|
||||
borderRadius="0.25rem"
|
||||
onChange={handleChange}
|
||||
color={color[colorMode]}
|
||||
placeholder={placeholder}
|
||||
borderColor={border[colorMode]}
|
||||
_placeholder={{
|
||||
color: placeholderColor[colorMode],
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import { ChakraSelect } from 'app/components';
|
||||
|
||||
export const QueryType = ({ queryTypes, onChange, label }) => {
|
||||
const queries = queryTypes
|
||||
.filter(q => q.enable === true)
|
||||
.map(q => {
|
||||
return { value: q.name, label: q.display_name };
|
||||
});
|
||||
return (
|
||||
<ChakraSelect
|
||||
size="lg"
|
||||
name="query_type"
|
||||
onChange={e => onChange({ field: 'query_type', value: e.value })}
|
||||
options={queries}
|
||||
aria-label={label}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import { ChakraSelect } from 'app/components';
|
||||
|
||||
export const QueryVrf = ({ vrfs, onChange, label }) => (
|
||||
<ChakraSelect
|
||||
size="lg"
|
||||
options={vrfs}
|
||||
name="query_vrf"
|
||||
aria-label={label}
|
||||
onChange={e => onChange({ field: 'query_vrf', value: e.value })}
|
||||
/>
|
||||
);
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import { forwardRef, useEffect } from 'react';
|
||||
import { Button, Icon, Spinner, Stack, Tag, Text, Tooltip, useColorMode } from '@chakra-ui/core';
|
||||
import useAxios from 'axios-hooks';
|
||||
import format from 'string-format';
|
||||
import { useConfig } from 'app/context';
|
||||
|
||||
format.extend(String.prototype, {});
|
||||
|
||||
const labelBg = { dark: 'secondary', light: 'secondary' };
|
||||
const labelBgSuccess = { dark: 'success', light: 'success' };
|
||||
|
||||
export const ResolvedTarget = forwardRef(
|
||||
({ fqdnTarget, setTarget, queryTarget, families, availVrfs }, ref) => {
|
||||
const { colorMode } = useColorMode();
|
||||
const config = useConfig();
|
||||
const labelBgStatus = {
|
||||
true: labelBgSuccess[colorMode],
|
||||
false: labelBg[colorMode],
|
||||
};
|
||||
const dnsUrl = config.web.dns_provider.url;
|
||||
const query4 = families.includes(4);
|
||||
const query6 = families.includes(6);
|
||||
const params = {
|
||||
4: {
|
||||
url: dnsUrl,
|
||||
params: { name: fqdnTarget, type: 'A' },
|
||||
headers: { accept: 'application/dns-json' },
|
||||
crossdomain: true,
|
||||
timeout: 1000,
|
||||
},
|
||||
6: {
|
||||
url: dnsUrl,
|
||||
params: { name: fqdnTarget, type: 'AAAA' },
|
||||
headers: { accept: 'application/dns-json' },
|
||||
crossdomain: true,
|
||||
timeout: 1000,
|
||||
},
|
||||
};
|
||||
|
||||
const [{ data: data4, loading: loading4, error: error4 }] = useAxios(params[4]);
|
||||
|
||||
const [{ data: data6, loading: loading6, error: error6 }] = useAxios(params[6]);
|
||||
|
||||
const handleOverride = overridden => {
|
||||
setTarget({ field: 'query_target', value: overridden });
|
||||
};
|
||||
|
||||
const isSelected = value => {
|
||||
return labelBgStatus[value === queryTarget];
|
||||
};
|
||||
|
||||
const findAnswer = data => {
|
||||
return data?.Answer?.filter(answerData => answerData.type === data?.Question[0]?.type)[0]
|
||||
?.data;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (query6 && data6?.Answer) {
|
||||
handleOverride(findAnswer(data6));
|
||||
} else if (query4 && data4?.Answer && !query6 && !data6?.Answer) {
|
||||
handleOverride(findAnswer(data4));
|
||||
} else if (query4 && data4?.Answer) {
|
||||
handleOverride(findAnswer(data4));
|
||||
}
|
||||
}, [data4, data6]);
|
||||
return (
|
||||
<Stack
|
||||
ref={ref}
|
||||
isInline
|
||||
w="100%"
|
||||
justifyContent={
|
||||
query4 && data4?.Answer && query6 && data6?.Answer && availVrfs.length > 1
|
||||
? 'space-between'
|
||||
: 'flex-end'
|
||||
}
|
||||
flexWrap="wrap">
|
||||
{loading4 ||
|
||||
error4 ||
|
||||
(query4 && findAnswer(data4) && (
|
||||
<Tag my={2}>
|
||||
<Tooltip
|
||||
hasArrow
|
||||
label={config.web.text.fqdn_tooltip.format({
|
||||
protocol: 'IPv4',
|
||||
})}
|
||||
placement="bottom">
|
||||
<Button
|
||||
height="unset"
|
||||
minW="unset"
|
||||
fontSize="xs"
|
||||
py="0.1rem"
|
||||
px={2}
|
||||
mr={2}
|
||||
variantColor={labelBgStatus[findAnswer(data4) === queryTarget]}
|
||||
borderRadius="md"
|
||||
onClick={() => handleOverride(findAnswer(data4))}>
|
||||
IPv4
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{loading4 && <Spinner />}
|
||||
{error4 && <Icon name="warning" />}
|
||||
{findAnswer(data4) && (
|
||||
<Text fontSize="xs" fontFamily="mono" as="span" fontWeight={400}>
|
||||
{findAnswer(data4)}
|
||||
</Text>
|
||||
)}
|
||||
</Tag>
|
||||
))}
|
||||
{loading6 ||
|
||||
error6 ||
|
||||
(query6 && findAnswer(data6) && (
|
||||
<Tag my={2}>
|
||||
<Tooltip
|
||||
hasArrow
|
||||
label={config.web.text.fqdn_tooltip.format({
|
||||
protocol: 'IPv6',
|
||||
})}
|
||||
placement="bottom">
|
||||
<Button
|
||||
height="unset"
|
||||
minW="unset"
|
||||
fontSize="xs"
|
||||
py="0.1rem"
|
||||
px={2}
|
||||
mr={2}
|
||||
variantColor={isSelected(findAnswer(data6))}
|
||||
borderRadius="md"
|
||||
onClick={() => handleOverride(findAnswer(data6))}>
|
||||
IPv6
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{loading6 && <Spinner />}
|
||||
{error6 && <Icon name="warning" />}
|
||||
{findAnswer(data6) && (
|
||||
<Text fontSize="xs" fontFamily="mono" as="span" fontWeight={400}>
|
||||
{findAnswer(data6)}
|
||||
</Text>
|
||||
)}
|
||||
</Tag>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -15,12 +15,12 @@ export type TButtonSizeMap = {
|
|||
};
|
||||
|
||||
export interface TSubmitButton extends BoxProps {
|
||||
isLoading: boolean;
|
||||
isDisabled: boolean;
|
||||
isActive: boolean;
|
||||
isFullWidth: boolean;
|
||||
size: keyof TButtonSizeMap;
|
||||
loadingText: string;
|
||||
isLoading?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isActive?: boolean;
|
||||
isFullWidth?: boolean;
|
||||
size?: keyof TButtonSizeMap;
|
||||
loadingText?: string;
|
||||
}
|
||||
|
||||
export interface TRequeryButton extends ButtonProps {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Text } from '@chakra-ui/core';
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import ReactCountdown, { zeroPad } from 'react-countdown';
|
||||
import { If } from '~/components';
|
||||
import { useColorValue } from '~/context';
|
||||
|
|
@ -13,10 +13,10 @@ const Renderer = (props: IRenderer) => {
|
|||
const bg = useColorValue('black', 'white');
|
||||
return (
|
||||
<>
|
||||
<If condition={completed}>
|
||||
<If c={completed}>
|
||||
<Text fontSize="xs" />
|
||||
</If>
|
||||
<If condition={!completed}>
|
||||
<If c={!completed}>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
{text}
|
||||
<Text as="span" fontSize="xs" color={bg}>
|
||||
|
|
|
|||
56
hyperglass/ui/components/form/communitySelect.tsx
Normal file
56
hyperglass/ui/components/form/communitySelect.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import { components } from 'react-select';
|
||||
import { Select } from '~/components';
|
||||
|
||||
import type { OptionProps } from 'react-select';
|
||||
import type { TBGPCommunity, TSelectOption } from '~/types';
|
||||
import type { TCommunitySelect } from './types';
|
||||
|
||||
function buildOptions(communities: TBGPCommunity[]): TSelectOption[] {
|
||||
return communities.map(c => ({
|
||||
value: c.community,
|
||||
label: c.display_name,
|
||||
description: c.description,
|
||||
}));
|
||||
}
|
||||
|
||||
const Option = (props: OptionProps<Dict>) => {
|
||||
const { label, data } = props;
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<Text as="span">{label}</Text>
|
||||
<Text fontSize="xs" as="span">
|
||||
{data.description}
|
||||
</Text>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
export const CommunitySelect = (props: TCommunitySelect) => {
|
||||
const { name, communities, onChange, register, unregister } = props;
|
||||
|
||||
const options = useMemo(() => buildOptions(communities), [communities.length]);
|
||||
|
||||
function handleChange(e: TSelectOption | TSelectOption[]): void {
|
||||
if (!Array.isArray(e)) {
|
||||
onChange({ field: name, value: e.value });
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
register({ name });
|
||||
return () => unregister(name);
|
||||
}, [name, register, unregister]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
size="lg"
|
||||
name={name}
|
||||
options={options}
|
||||
innerRef={register}
|
||||
onChange={handleChange}
|
||||
components={{ Option }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Flex, FormControl, FormLabel, FormErrorMessage } from '@chakra-ui/react';
|
||||
import { If } from '~/components';
|
||||
import { useColorValue } from '~/context';
|
||||
|
|
@ -6,9 +7,29 @@ import { useBooleanValue } from '~/hooks';
|
|||
import { TField } from './types';
|
||||
|
||||
export const FormField = (props: TField) => {
|
||||
const { label, name, error, hiddenLabels, labelAddOn, fieldAddOn, children, ...rest } = props;
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
errors,
|
||||
children,
|
||||
labelAddOn,
|
||||
fieldAddOn,
|
||||
hiddenLabels = false,
|
||||
...rest
|
||||
} = props;
|
||||
const labelColor = useColorValue('blackAlpha.700', 'whiteAlpha.700');
|
||||
const opacity = useBooleanValue(hiddenLabels, 0, undefined);
|
||||
|
||||
const error = useMemo<string | undefined>(() => {
|
||||
let result;
|
||||
if (Array.isArray(errors)) {
|
||||
result = errors.join(', ');
|
||||
} else if (typeof errors === 'string') {
|
||||
result = errors;
|
||||
}
|
||||
return result;
|
||||
}, [errors]);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
mx={2}
|
||||
|
|
@ -38,7 +59,7 @@ export const FormField = (props: TField) => {
|
|||
{fieldAddOn}
|
||||
</Flex>
|
||||
</If>
|
||||
<FormErrorMessage opacity={opacity}>{error && error.message}</FormErrorMessage>
|
||||
<FormErrorMessage opacity={opacity}>{error}</FormErrorMessage>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1,8 @@
|
|||
export * from './communitySelect';
|
||||
export * from './field';
|
||||
export * from './queryLocation';
|
||||
export * from './queryTarget';
|
||||
export * from './queryType';
|
||||
export * from './queryVrf';
|
||||
export * from './resolvedTarget';
|
||||
export * from './row';
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Select } from '~/components';
|
||||
import { useConfig } from '~/context';
|
||||
|
||||
import type { TNetwork } from '~/types';
|
||||
import type { TQueryLocation, OnChangeArgs } from './types';
|
||||
|
||||
function isOnChangeArgsArray(e: OnChangeArgs | OnChangeArgs[]): e is OnChangeArgs[] {
|
||||
return Array.isArray(e);
|
||||
}
|
||||
import type { TNetwork, TSelectOption } from '~/types';
|
||||
import type { TQuerySelectField } from './types';
|
||||
|
||||
function buildOptions(networks: TNetwork[]) {
|
||||
return networks.map(net => {
|
||||
|
|
@ -20,15 +17,16 @@ function buildOptions(networks: TNetwork[]) {
|
|||
});
|
||||
}
|
||||
|
||||
export const QueryLocation = (props: TQueryLocation) => {
|
||||
const { locations, onChange, label } = props;
|
||||
export const QueryLocation = (props: TQuerySelectField) => {
|
||||
const { onChange, label } = props;
|
||||
|
||||
const options = useMemo(() => buildOptions(locations), [locations.length]);
|
||||
const { networks } = useConfig();
|
||||
const options = useMemo(() => buildOptions(networks), [networks.length]);
|
||||
|
||||
function handleChange(e: OnChangeArgs | OnChangeArgs[]): void {
|
||||
if (isOnChangeArgsArray(e)) {
|
||||
const value = e.map(sel => sel.value as string);
|
||||
onChange({ label: 'query_location', value });
|
||||
function handleChange(e: TSelectOption): void {
|
||||
if (Array.isArray(e.value)) {
|
||||
const value = e.value.map(sel => sel);
|
||||
onChange({ field: 'query_location', value });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
73
hyperglass/ui/components/form/queryTarget.tsx
Normal file
73
hyperglass/ui/components/form/queryTarget.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { useEffect } from 'react';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { useColorValue } from '~/context';
|
||||
|
||||
import type { TQueryTarget } from './types';
|
||||
|
||||
const fqdnPattern = /^(?!:\/\/)([a-zA-Z0-9-]+\.)?[a-zA-Z0-9-][a-zA-Z0-9-]+\.[a-zA-Z-]{2,6}?$/gim;
|
||||
|
||||
export const QueryTarget = (props: TQueryTarget) => {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
setFqdn,
|
||||
register,
|
||||
setTarget,
|
||||
unregister,
|
||||
placeholder,
|
||||
displayValue,
|
||||
resolveTarget,
|
||||
setDisplayValue,
|
||||
} = props;
|
||||
|
||||
const bg = useColorValue('white', 'whiteAlpha.100');
|
||||
const color = useColorValue('gray.400', 'whiteAlpha.800');
|
||||
const border = useColorValue('gray.100', 'whiteAlpha.50');
|
||||
const placeholderColor = useColorValue('gray.600', 'whiteAlpha.700');
|
||||
|
||||
function handleBlur(): void {
|
||||
if (resolveTarget && displayValue && fqdnPattern.test(displayValue)) {
|
||||
setFqdn(displayValue);
|
||||
} else if (resolveTarget && !displayValue) {
|
||||
setFqdn(null);
|
||||
}
|
||||
}
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setDisplayValue(e.target.value);
|
||||
setTarget({ field: name, value: e.target.value });
|
||||
}
|
||||
|
||||
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (['Tab', 'NumpadEnter'].includes(e.key)) {
|
||||
handleBlur();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
register({ name });
|
||||
return () => unregister(name);
|
||||
}, [register, unregister, name]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<input hidden readOnly name={name} ref={register} value={value} />
|
||||
<Input
|
||||
bg={bg}
|
||||
size="lg"
|
||||
color={color}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleBlur}
|
||||
value={displayValue}
|
||||
borderColor={border}
|
||||
borderRadius="0.25rem"
|
||||
onChange={handleChange}
|
||||
aria-label={placeholder}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
name="query_target_display"
|
||||
_placeholder={{ color: placeholderColor }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
33
hyperglass/ui/components/form/queryType.tsx
Normal file
33
hyperglass/ui/components/form/queryType.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Select } from '~/components';
|
||||
import { useConfig } from '~/context';
|
||||
|
||||
import type { TQuery, TSelectOption } from '~/types';
|
||||
import type { TQuerySelectField } from './types';
|
||||
|
||||
function buildOptions(queryTypes: TQuery[]): TSelectOption[] {
|
||||
return queryTypes
|
||||
.filter(q => q.enable === true)
|
||||
.map(q => ({ value: q.name, label: q.display_name }));
|
||||
}
|
||||
|
||||
export const QueryType = (props: TQuerySelectField) => {
|
||||
const { onChange, label } = props;
|
||||
const { queries } = useConfig();
|
||||
|
||||
const options = useMemo(() => buildOptions(queries.list), [queries.list.length]);
|
||||
|
||||
function handleChange(e: TSelectOption): void {
|
||||
onChange({ field: 'query_type', value: e.value });
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
size="lg"
|
||||
name="query_type"
|
||||
options={options}
|
||||
aria-label={label}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
29
hyperglass/ui/components/form/queryVrf.tsx
Normal file
29
hyperglass/ui/components/form/queryVrf.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Select } from '~/components';
|
||||
|
||||
import { TDeviceVrf, TSelectOption } from '~/types';
|
||||
import type { TQueryVrf } from './types';
|
||||
|
||||
function buildOptions(queryVrfs: TDeviceVrf[]): TSelectOption[] {
|
||||
return queryVrfs.map(q => ({ value: q.id, label: q.display_name }));
|
||||
}
|
||||
|
||||
export const QueryVrf = (props: TQueryVrf) => {
|
||||
const { vrfs, onChange, label } = props;
|
||||
|
||||
const options = useMemo(() => buildOptions(vrfs), [vrfs.length]);
|
||||
|
||||
function handleChange(e: TSelectOption): void {
|
||||
onChange({ field: 'query_vrf', value: e.value });
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
size="lg"
|
||||
name="query_vrf"
|
||||
options={options}
|
||||
aria-label={label}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
146
hyperglass/ui/components/form/resolvedTarget.tsx
Normal file
146
hyperglass/ui/components/form/resolvedTarget.tsx
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import { useEffect } from 'react';
|
||||
import { Button, Icon, Spinner, Stack, Tag, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useConfig } from '~/context';
|
||||
import { useStrf } from '~/hooks';
|
||||
|
||||
import type { DnsOverHttps, ColorNames } from '~/types';
|
||||
import type { TResolvedTarget } from './types';
|
||||
|
||||
function findAnswer(data: DnsOverHttps.Response | undefined): string {
|
||||
let answer = '';
|
||||
if (typeof data !== 'undefined') {
|
||||
answer = data?.Answer?.filter(answerData => answerData.type === data?.Question[0]?.type)[0]
|
||||
?.data;
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
export const ResolvedTarget = (props: TResolvedTarget) => {
|
||||
const { fqdnTarget, setTarget, queryTarget, families, availVrfs } = props;
|
||||
const { web } = useConfig();
|
||||
|
||||
const dnsUrl = web.dns_provider.url;
|
||||
const query4 = Array.from(families).includes(4);
|
||||
const query6 = Array.from(families).includes(6);
|
||||
|
||||
const tooltip4 = useStrf(web.text.fqdn_tooltip, { protocol: 'IPv4' });
|
||||
const tooltip6 = useStrf(web.text.fqdn_tooltip, { protocol: 'IPv6' });
|
||||
|
||||
const { data: data4, isLoading: isLoading4, isError: isError4 } = useQuery(
|
||||
[fqdnTarget, 4],
|
||||
dnsQuery,
|
||||
);
|
||||
|
||||
const { data: data6, isLoading: isLoading6, isError: isError6 } = useQuery(
|
||||
[fqdnTarget, 6],
|
||||
dnsQuery,
|
||||
);
|
||||
|
||||
async function dnsQuery(
|
||||
target: string,
|
||||
family: 4 | 6,
|
||||
): Promise<DnsOverHttps.Response | undefined> {
|
||||
let json;
|
||||
const type = family === 4 ? 'A' : family === 6 ? 'AAAA' : '';
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 1000);
|
||||
const res = await fetch(`${dnsUrl}?name=${target}&type=${type}`, {
|
||||
headers: { accept: 'application/dns-json' },
|
||||
signal: controller.signal,
|
||||
mode: 'cors',
|
||||
});
|
||||
json = await res.json();
|
||||
clearTimeout(timeout);
|
||||
return json;
|
||||
}
|
||||
|
||||
function handleOverride(value: string): void {
|
||||
setTarget({ field: 'query_target', value });
|
||||
}
|
||||
|
||||
function isSelected(value: string): ColorNames {
|
||||
if (value === queryTarget) {
|
||||
return 'success';
|
||||
} else {
|
||||
return 'secondary';
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (query6 && data6?.Answer) {
|
||||
handleOverride(findAnswer(data6));
|
||||
} else if (query4 && data4?.Answer && !query6 && !data6?.Answer) {
|
||||
handleOverride(findAnswer(data4));
|
||||
} else if (query4 && data4?.Answer) {
|
||||
handleOverride(findAnswer(data4));
|
||||
}
|
||||
}, [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>
|
||||
);
|
||||
};
|
||||
15
hyperglass/ui/components/form/row.tsx
Normal file
15
hyperglass/ui/components/form/row.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Flex } from '@chakra-ui/react';
|
||||
|
||||
import { FlexProps } from '@chakra-ui/react';
|
||||
|
||||
export const FormRow = (props: FlexProps) => {
|
||||
return (
|
||||
<Flex
|
||||
w="100%"
|
||||
flexDir="row"
|
||||
flexWrap="wrap"
|
||||
justifyContent={{ base: 'center', lg: 'space-between' }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,20 +1,67 @@
|
|||
import type { FormControlProps } from '@chakra-ui/react';
|
||||
import type { FieldError } from 'react-hook-form';
|
||||
import type { TNetwork } from '~/types';
|
||||
import type { FieldError, Control } from 'react-hook-form';
|
||||
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs, Families, TFormData } from '~/types';
|
||||
|
||||
export interface TField extends FormControlProps {
|
||||
name: string;
|
||||
label: string;
|
||||
error?: FieldError;
|
||||
hiddenLabels: boolean;
|
||||
errors?: FieldError | FieldError[];
|
||||
hiddenLabels?: boolean;
|
||||
labelAddOn?: React.ReactNode;
|
||||
fieldAddOn?: React.ReactNode;
|
||||
}
|
||||
|
||||
export type OnChangeArgs = { label: string; value: string | string[] };
|
||||
export type OnChange = (f: OnChangeArgs) => void;
|
||||
|
||||
export interface TQueryLocation {
|
||||
locations: TNetwork[];
|
||||
onChange(f: OnChangeArgs | OnChangeArgs[]): void;
|
||||
export interface TQuerySelectField {
|
||||
onChange: OnChange;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface TQueryVrf extends TQuerySelectField {
|
||||
vrfs: TDeviceVrf[];
|
||||
}
|
||||
|
||||
export interface TCommunitySelect {
|
||||
name: string;
|
||||
onChange: OnChange;
|
||||
communities: TBGPCommunity[];
|
||||
register: Control['register'];
|
||||
unregister: Control['unregister'];
|
||||
}
|
||||
|
||||
/**
|
||||
* placeholder,
|
||||
register,
|
||||
unregister,
|
||||
setFqdn,
|
||||
|
||||
name,
|
||||
value,
|
||||
|
||||
setTarget,
|
||||
resolveTarget,
|
||||
|
||||
displayValue,
|
||||
setDisplayValue,
|
||||
*/
|
||||
export interface TQueryTarget {
|
||||
name: string;
|
||||
placeholder: string;
|
||||
displayValue: string;
|
||||
resolveTarget: boolean;
|
||||
setFqdn(f: string | null): void;
|
||||
setTarget(e: OnChangeArgs): void;
|
||||
register: Control['register'];
|
||||
value: TFormData['query_target'];
|
||||
setDisplayValue(d: string): void;
|
||||
unregister: Control['unregister'];
|
||||
}
|
||||
|
||||
export interface TResolvedTarget {
|
||||
families: Families;
|
||||
queryTarget: string;
|
||||
availVrfs: TDeviceVrf[];
|
||||
fqdnTarget: string | null;
|
||||
setTarget(e: OnChangeArgs): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { Flex } from '@chakra-ui/react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useColorValue, useConfig, useGlobalState, useBreakpointValue } from '~/context';
|
||||
import { AnimatedDiv, Title, ResetButton, ColorModeToggle } from '~/components';
|
||||
import { useColorValue, useConfig, useGlobalState, useBreakpointValue } from '~/context';
|
||||
import { useBooleanValue } from '~/hooks';
|
||||
|
||||
import type { ResponsiveValue } from '@chakra-ui/react';
|
||||
import type { THeader, TTitleMode } from './types';
|
||||
import type { THeader, TTitleMode, THeaderLayout } from './types';
|
||||
|
||||
const headerTransition = {
|
||||
type: 'spring',
|
||||
|
|
@ -139,7 +139,10 @@ export const Header = (props: THeader) => {
|
|||
lg: [resetButton, title, colorModeToggle],
|
||||
xl: [resetButton, title, colorModeToggle],
|
||||
},
|
||||
);
|
||||
) as THeaderLayout;
|
||||
|
||||
const layoutBp: keyof THeaderLayout =
|
||||
useBreakpointValue({ base: 'sm', md: 'md', lg: 'lg', xl: 'xl' }) ?? 'sm';
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
|
@ -158,7 +161,7 @@ export const Header = (props: THeader) => {
|
|||
justify="space-between"
|
||||
flex="1 0 auto"
|
||||
alignItems={isSubmitting ? 'center' : 'flex-start'}>
|
||||
{layout}
|
||||
{layout[layoutBp]}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,3 +7,10 @@ export interface THeader extends FlexProps {
|
|||
}
|
||||
|
||||
export type TTitleMode = IConfig['web']['text']['title_mode'];
|
||||
|
||||
export type THeaderLayout = {
|
||||
sm: [JSX.Element, JSX.Element, JSX.Element];
|
||||
md: [JSX.Element, JSX.Element, JSX.Element];
|
||||
lg: [JSX.Element, JSX.Element, JSX.Element];
|
||||
xl: [JSX.Element, JSX.Element, JSX.Element];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export * from './buttons';
|
||||
export * from './card';
|
||||
export * from './codeBlock';
|
||||
export * from './CommunitySelect';
|
||||
export * from './countdown';
|
||||
export * from './debugger';
|
||||
export * from './footer';
|
||||
|
|
@ -9,17 +8,13 @@ export * from './form';
|
|||
export * from './greeting';
|
||||
export * from './header';
|
||||
export * from './help';
|
||||
export * from './HyperglassForm';
|
||||
export * from './label';
|
||||
export * from './layout';
|
||||
export * from './loading';
|
||||
export * from './lookingGlass';
|
||||
export * from './markdown';
|
||||
export * from './meta';
|
||||
export * from './output';
|
||||
export * from './QueryTarget';
|
||||
export * from './QueryType';
|
||||
export * from './QueryVrf';
|
||||
export * from './ResolvedTarget';
|
||||
export * from './results';
|
||||
export * from './select';
|
||||
export * from './table';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Flex, Spinner } from '@chakra-ui/react';
|
||||
import { useColorValue } from '~/context';
|
||||
|
||||
export const Loading: React.FC = () => {
|
||||
import type { LoadableBaseOptions } from 'next/dynamic';
|
||||
|
||||
export const Loading: LoadableBaseOptions['loading'] = () => {
|
||||
const bg = useColorValue('white', 'black');
|
||||
const color = useColorValue('black', 'white');
|
||||
return (
|
||||
|
|
|
|||
335
hyperglass/ui/components/lookingGlass.tsx
Normal file
335
hyperglass/ui/components/lookingGlass.tsx
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { uniqWith } from 'lodash';
|
||||
import * as yup from 'yup';
|
||||
import {
|
||||
FormRow,
|
||||
QueryVrf,
|
||||
FormField,
|
||||
HelpModal,
|
||||
QueryType,
|
||||
QueryTarget,
|
||||
SubmitButton,
|
||||
QueryLocation,
|
||||
ResolvedTarget,
|
||||
CommunitySelect,
|
||||
} from '~/components';
|
||||
import { useConfig, useGlobalState } from '~/context';
|
||||
import { useStrf, useGreeting } from '~/hooks';
|
||||
|
||||
import type { Families, TFormData, TDeviceVrf, TQueryTypes, OnChangeArgs } from '~/types';
|
||||
|
||||
function isString(a: any): a is string {
|
||||
return typeof a === 'string';
|
||||
}
|
||||
|
||||
function isQueryType(q: any): q is TQueryTypes {
|
||||
let result = false;
|
||||
if (
|
||||
typeof q === 'string' &&
|
||||
['bgp_route', 'bgp_community', 'bgp_aspath', 'ping', 'traceroute'].includes(q)
|
||||
) {
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const HyperglassForm = () => {
|
||||
const { web, content, devices, messages, networks, queries } = useConfig();
|
||||
|
||||
const { formData, isSubmitting } = useGlobalState();
|
||||
const [greetingAck, setGreetingAck] = useGreeting();
|
||||
|
||||
const noQueryType = useStrf(messages.no_input, { field: web.text.query_type });
|
||||
const noQueryLoc = useStrf(messages.no_input, { field: web.text.query_location });
|
||||
const noQueryTarget = useStrf(messages.no_input, { field: web.text.query_target });
|
||||
|
||||
const formSchema = yup.object().shape({
|
||||
query_location: yup.array().of(yup.string()).required(noQueryLoc),
|
||||
query_type: yup.string().required(noQueryType),
|
||||
query_vrf: yup.string(),
|
||||
query_target: yup.string().required(noQueryTarget),
|
||||
});
|
||||
|
||||
const { handleSubmit, register, unregister, setValue, errors } = useForm<TFormData>({
|
||||
validationSchema: formSchema,
|
||||
defaultValues: { query_vrf: 'default', query_target: '' },
|
||||
});
|
||||
|
||||
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>([]);
|
||||
|
||||
function onSubmit(values: TFormData): void {
|
||||
if (!greetingAck && web.greeting.required) {
|
||||
window.location.reload(false);
|
||||
setGreetingAck(false);
|
||||
} else {
|
||||
formData.set(values);
|
||||
isSubmitting.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
const handleLocChange = locObj => {
|
||||
setQueryLocation(locObj.value);
|
||||
const allVrfs = [];
|
||||
const deviceVrfs = [];
|
||||
locObj.value.map(loc => {
|
||||
const locVrfs = [];
|
||||
config.devices[loc].vrfs.map(vrf => {
|
||||
locVrfs.push({
|
||||
label: vrf.display_name,
|
||||
value: vrf.id,
|
||||
});
|
||||
deviceVrfs.push([{ id: vrf.id, ipv4: vrf.ipv4, ipv6: vrf.ipv6 }]);
|
||||
});
|
||||
allVrfs.push(locVrfs);
|
||||
});
|
||||
|
||||
deviceVrfs.length !== 0 &&
|
||||
intersecting.length !== 0 &&
|
||||
deviceVrfs
|
||||
.filter(v => intersecting.every(i => i.id === v.id))
|
||||
.reduce((a, b) => a.concat(b))
|
||||
.filter(v => v.id === 'default')
|
||||
.map(v => {
|
||||
v.ipv4 === true && ipv4++;
|
||||
v.ipv6 === true && ipv6++;
|
||||
});
|
||||
*/
|
||||
|
||||
// function handleLocChange(locObj: TSelectOption) {
|
||||
// const allVrfs = [] as TDeviceVrf[][];
|
||||
// const deviceVrfs = [] as TDeviceVrf[][];
|
||||
|
||||
// if (Array.isArray(locObj.value)) {
|
||||
// setQueryLocation(locObj.value);
|
||||
// for (const loc of locObj.value) {
|
||||
// const locVrfs = [] as TDeviceVrf[];
|
||||
// for (const vrf of devices.filter(dev => dev.name === loc)[0].vrfs) {
|
||||
// locVrfs.push(vrf);
|
||||
// deviceVrfs.push([vrf]);
|
||||
// }
|
||||
// allVrfs.push(locVrfs);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Use _.intersectionWith to create an array of VRFs common to all selected locations.
|
||||
// const intersecting: TDeviceVrf[] = intersectionWith(...allVrfs, isEqual);
|
||||
// setAvailVrfs(intersecting);
|
||||
|
||||
// // If there are no intersecting VRFs, use the default VRF.
|
||||
// if (intersecting.filter(i => i.id === queryVrf).length === 0 && queryVrf !== 'default') {
|
||||
// setQueryVrf('default');
|
||||
// }
|
||||
|
||||
// let ipv4 = 0;
|
||||
// let ipv6 = 0;
|
||||
|
||||
// if (deviceVrfs.length !== 0 && intersecting.length !== 0) {
|
||||
// const matching = deviceVrfs
|
||||
// // Select intersecting VRFs
|
||||
// .filter(v => intersecting.every(i => i.id === v.id))
|
||||
// .reduce((a, b) => a.concat(b))
|
||||
// .filter(v => v.id === 'default');
|
||||
|
||||
// for (const match of matching) {
|
||||
// if (match.ipv4) {
|
||||
// ipv4++;
|
||||
// }
|
||||
// if (match.ipv6) {
|
||||
// ipv6++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (ipv4 !== 0 && ipv4 === ipv6) {
|
||||
// setFamilies([4, 6]);
|
||||
// } else if (ipv4 > ipv6) {
|
||||
// setFamilies([4]);
|
||||
// } else if (ipv4 < ipv6) {
|
||||
// setFamilies([6]);
|
||||
// } else {
|
||||
// setFamilies([]);
|
||||
// }
|
||||
// }
|
||||
|
||||
function handleLocChange(locations: string | string[]): void {
|
||||
const allVrfs = [] as TDeviceVrf[];
|
||||
|
||||
if (Array.isArray(locations)) {
|
||||
setQueryLocation(locations);
|
||||
for (const loc of locations) {
|
||||
for (const vrf of devices.filter(dev => dev.name === loc)[0].vrfs) {
|
||||
allVrfs.push(vrf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use _.intersectionWith to create an array of VRFs common to all selected locations.
|
||||
const intersecting = uniqWith<TDeviceVrf>(allVrfs, (a, b) => a.id === b.id);
|
||||
setAvailVrfs(intersecting);
|
||||
|
||||
// If there are no intersecting VRFs, use the default VRF.
|
||||
if (intersecting.filter(i => i.id === queryVrf).length === 0 && queryVrf !== 'default') {
|
||||
setQueryVrf('default');
|
||||
}
|
||||
|
||||
let ipv4 = 0;
|
||||
let ipv6 = 0;
|
||||
|
||||
if (intersecting.length !== 0) {
|
||||
for (const intersection of intersecting) {
|
||||
if (intersection.ipv4) {
|
||||
ipv4++;
|
||||
}
|
||||
if (intersection.ipv6) {
|
||||
ipv6++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ipv4 !== 0 && ipv4 === ipv6) {
|
||||
setFamilies([4, 6]);
|
||||
} else if (ipv4 > ipv6) {
|
||||
setFamilies([4]);
|
||||
} else if (ipv4 < ipv6) {
|
||||
setFamilies([6]);
|
||||
} else {
|
||||
setFamilies([]);
|
||||
}
|
||||
}
|
||||
|
||||
function handleChange(e: OnChangeArgs): void {
|
||||
setValue(e.field, e.value);
|
||||
|
||||
if (e.field === 'query_location') {
|
||||
handleLocChange(e.value);
|
||||
} else if (e.field === 'query_type' && isQueryType(e.value)) {
|
||||
setQueryType(e.value);
|
||||
} else if (e.field === 'query_vrf' && isString(e.value)) {
|
||||
setQueryVrf(e.value);
|
||||
} else if (e.field === 'query_target' && isString(e.value)) {
|
||||
setQueryTarget(e.value);
|
||||
}
|
||||
}
|
||||
|
||||
const vrfContent = useMemo(() => {
|
||||
if (Object.keys(content.vrf).includes(queryVrf) && queryType !== '') {
|
||||
return content.vrf[queryVrf][queryType];
|
||||
}
|
||||
}, [queryVrf]);
|
||||
|
||||
const isFqdnQuery = useMemo(() => {
|
||||
return ['bgp_route', 'ping', 'traceroute'].includes(queryType);
|
||||
}, [queryType]);
|
||||
|
||||
const fqdnQuery = useMemo(() => {
|
||||
let result = null;
|
||||
if (fqdnTarget && queryVrf === 'default' && fqdnTarget) {
|
||||
result = fqdnTarget;
|
||||
}
|
||||
return result;
|
||||
}, [queryVrf, queryType]);
|
||||
|
||||
useEffect(() => {
|
||||
register({ name: 'query_location' });
|
||||
register({ name: 'query_type' });
|
||||
register({ name: 'query_vrf' });
|
||||
}, [register]);
|
||||
|
||||
Object.keys(errors).length >= 1 && console.error(errors);
|
||||
|
||||
return (
|
||||
<Box
|
||||
p={0}
|
||||
my={4}
|
||||
w="100%"
|
||||
as="form"
|
||||
mx="auto"
|
||||
textAlign="left"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
maxW={{ base: '100%', lg: '75%' }}>
|
||||
<FormRow>
|
||||
<FormField
|
||||
name="query_location"
|
||||
errors={errors.query_location}
|
||||
label={web.text.query_location}>
|
||||
<QueryLocation onChange={handleChange} label={web.text.query_location} />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="query_type"
|
||||
errors={errors.query_type}
|
||||
label={web.text.query_type}
|
||||
labelAddOn={vrfContent && <HelpModal item={vrfContent} name="query_type" />}>
|
||||
<QueryType onChange={handleChange} label={web.text.query_type} />
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
{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} />
|
||||
</FormField>
|
||||
)}
|
||||
<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}
|
||||
/>
|
||||
)
|
||||
}>
|
||||
{queryType === 'bgp_community' && queries.bgp_community.mode === 'select' ? (
|
||||
<CommunitySelect
|
||||
name="query_target"
|
||||
register={register}
|
||||
unregister={unregister}
|
||||
onChange={handleChange}
|
||||
communities={queries.bgp_community.communities}
|
||||
/>
|
||||
) : (
|
||||
<QueryTarget
|
||||
name="query_target"
|
||||
register={register}
|
||||
value={queryTarget}
|
||||
unregister={unregister}
|
||||
setFqdn={setFqdnTarget}
|
||||
setTarget={handleChange}
|
||||
resolveTarget={isFqdnQuery}
|
||||
displayValue={displayTarget}
|
||||
setDisplayValue={setDisplayTarget}
|
||||
placeholder={web.text.query_target}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow mt={0} justifyContent="flex-end">
|
||||
<Flex
|
||||
my={2}
|
||||
w="100%"
|
||||
ml="auto"
|
||||
maxW="100%"
|
||||
flex="0 0 0"
|
||||
flexDir="column"
|
||||
mr={{ base: 0, lg: 2 }}>
|
||||
<SubmitButton isLoading={isSubmitting.value} />
|
||||
</Flex>
|
||||
</FormRow>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { createContext, useContext, useMemo } from 'react';
|
||||
import ReactSelect from 'react-select';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { Box, useDisclosure } from '@chakra-ui/react';
|
||||
import { useColorMode } from '~/context';
|
||||
import {
|
||||
useRSTheme,
|
||||
|
|
@ -17,7 +17,8 @@ import {
|
|||
useIndicatorSeparatorStyle,
|
||||
} from './styles';
|
||||
|
||||
import type { TSelect, TSelectOption, TSelectContext, TBoxAsReactSelect } from './types';
|
||||
import type { TSelectOption } from '~/types';
|
||||
import type { TSelect, TSelectContext, TBoxAsReactSelect } from './types';
|
||||
|
||||
const SelectContext = createContext<TSelectContext>(Object());
|
||||
export const useSelectContext = () => useContext(SelectContext);
|
||||
|
|
@ -26,7 +27,8 @@ const ReactSelectAsBox = (props: TBoxAsReactSelect) => <Box as={ReactSelect} {..
|
|||
|
||||
export const Select = (props: TSelect) => {
|
||||
const { ctl, options, multi, onSelect, ...rest } = props;
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const selectContext = useMemo<TSelectContext>(() => ({ colorMode, isOpen }), [colorMode, isOpen]);
|
||||
|
|
@ -49,18 +51,14 @@ export const Select = (props: TSelect) => {
|
|||
return (
|
||||
<SelectContext.Provider value={selectContext}>
|
||||
<ReactSelectAsBox
|
||||
as={ReactSelect}
|
||||
options={options}
|
||||
isMulti={multi}
|
||||
onChange={handleChange}
|
||||
ref={ctl}
|
||||
onMenuClose={() => {
|
||||
isOpen && setIsOpen(false);
|
||||
}}
|
||||
onMenuOpen={() => {
|
||||
!isOpen && setIsOpen(true);
|
||||
}}
|
||||
onMenuClose={onClose}
|
||||
onMenuOpen={onOpen}
|
||||
options={options}
|
||||
as={ReactSelect}
|
||||
isMulti={multi}
|
||||
theme={rsTheme}
|
||||
ref={ctl}
|
||||
styles={{
|
||||
menuPortal,
|
||||
multiValue,
|
||||
|
|
|
|||
|
|
@ -10,34 +10,24 @@ import type {
|
|||
PlaceholderProps,
|
||||
} from 'react-select';
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import type { ColorNames } from '~/types';
|
||||
import type { ColorNames, TSelectOption, TSelectOptionGroup } from '~/types';
|
||||
|
||||
export interface TSelectState {
|
||||
[k: string]: string[];
|
||||
}
|
||||
|
||||
export type TSelectOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type TSelectOptionGroup = {
|
||||
label: string;
|
||||
options: TSelectOption[];
|
||||
};
|
||||
|
||||
export type TOptions = Array<TSelectOptionGroup | TSelectOption>;
|
||||
|
||||
export type TBoxAsReactSelect = Omit<IReactSelect, 'isMulti' | 'onSelect' | 'onChange'> &
|
||||
Omit<BoxProps, 'onChange' | 'onSelect'>;
|
||||
|
||||
export interface TSelect extends TBoxAsReactSelect {
|
||||
options: TOptions;
|
||||
export interface TSelectBase extends TBoxAsReactSelect {
|
||||
name: string;
|
||||
required?: boolean;
|
||||
multi?: boolean;
|
||||
onSelect?: (v: TSelectOption[]) => void;
|
||||
onChange?: (c: TSelectOption | TSelectOption[]) => void;
|
||||
options: TOptions;
|
||||
required?: boolean;
|
||||
onSelect?: (s: TSelectOption) => void;
|
||||
onChange?: (c: TSelectOption) => void;
|
||||
colorScheme?: ColorNames;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
export * from './HyperglassProvider';
|
||||
export * from './MediaProvider';
|
||||
export * from './GlobalState';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { IConfig, IFormData } from '~/types';
|
||||
import type { IConfig, TFormData } from '~/types';
|
||||
|
||||
export interface THyperglassProvider {
|
||||
config: IConfig;
|
||||
|
|
@ -7,5 +7,5 @@ export interface THyperglassProvider {
|
|||
|
||||
export interface TGlobalState {
|
||||
isSubmitting: boolean;
|
||||
formData: IFormData;
|
||||
formData: TFormData;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ export interface TStringTableData extends Omit<TQueryResponse, 'output'> {
|
|||
output: TStructuredResponse;
|
||||
}
|
||||
|
||||
export type TUseGreetingReturn = [boolean, () => void];
|
||||
export type TUseGreetingReturn = [boolean, (v?: boolean) => void];
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import type { TUseGreetingReturn } from './types';
|
|||
|
||||
export function useGreeting(): TUseGreetingReturn {
|
||||
const state = useState<boolean>(false);
|
||||
state.attach(Persistence('plugin-persisted-data-key'));
|
||||
if (typeof window !== 'undefined') {
|
||||
state.attach(Persistence('hyperglass-greeting'));
|
||||
}
|
||||
|
||||
function setAck(): void {
|
||||
function setAck(v: boolean = true): void {
|
||||
if (!state.get()) {
|
||||
state.set(true);
|
||||
state.set(v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
16
hyperglass/ui/package.json
vendored
16
hyperglass/ui/package.json
vendored
|
|
@ -10,12 +10,13 @@
|
|||
"build": "next build && next export -o ../hyperglass/static/ui",
|
||||
"start": "next start",
|
||||
"clean": "rimraf --no-glob ./.next ./out",
|
||||
"typecheck": "tsc",
|
||||
"check:es:build": "es-check es5 './.next/static/**/*.js' -v",
|
||||
"check:es:export": "es-check es5 './out/**/*.js' -v"
|
||||
},
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^1.0.1",
|
||||
"@chakra-ui/react": "^1.0.3",
|
||||
"@emotion/react": "^11.1.1",
|
||||
"@emotion/styled": "^11.0.0",
|
||||
"@hookstate/core": "^3.0.1",
|
||||
|
|
@ -26,27 +27,30 @@
|
|||
"axios-hooks": "^1.9.0",
|
||||
"chroma-js": "^2.1.0",
|
||||
"dayjs": "^1.8.25",
|
||||
"framer-motion": "^2.9.4",
|
||||
"framer-motion": "^2.9.5",
|
||||
"lodash": "^4.17.15",
|
||||
"next": "^9.5.4",
|
||||
"react": "16.14.0",
|
||||
"next": "^10.0.3",
|
||||
"react": "^17.0.1",
|
||||
"react-countdown": "^2.2.1",
|
||||
"react-dom": "16.14.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-fast-compare": "^3.2.0",
|
||||
"react-hook-form": "^5.7",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-query": "^2.26.4",
|
||||
"react-select": "^3.0.8",
|
||||
"react-string-replace": "^0.4.4",
|
||||
"react-table": "^7.6.2",
|
||||
"string-format": "^2.0.0",
|
||||
"tempy": "^0.5.0",
|
||||
"yarn": "^1.22.10",
|
||||
"yup": "^0.28.3"
|
||||
"yup": "^0.32.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.11.10",
|
||||
"@types/react-select": "^3.0.22",
|
||||
"@types/react-table": "^7.0.25",
|
||||
"@types/string-format": "^2.0.0",
|
||||
"@types/yup": "^0.29.9",
|
||||
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
||||
"@typescript-eslint/parser": "^2.24.0",
|
||||
"@upstatement/eslint-config": "^0.4.3",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
import * as React from 'react';
|
||||
import Head from 'next/head';
|
||||
import { HyperglassProvider } from '~/context';
|
||||
import { IConfig } from '~/types';
|
||||
// import { useRouter } from "next/router";
|
||||
import { HyperglassProvider } from 'app/context';
|
||||
// import Error from "./_error";
|
||||
|
||||
const config = process.env._HYPERGLASS_CONFIG_;
|
||||
import type { AppProps, AppInitialProps } from 'next/app';
|
||||
|
||||
const Hyperglass = ({ Component, pageProps }) => {
|
||||
type TAppProps = AppProps & AppInitialProps;
|
||||
|
||||
interface TApp extends TAppProps {
|
||||
appProps: { config: IConfig };
|
||||
}
|
||||
|
||||
type TAppInitial = Pick<TApp, 'appProps'>;
|
||||
|
||||
const App = (props: TApp) => {
|
||||
const { Component, pageProps, appProps } = props;
|
||||
const { config } = appProps;
|
||||
// const { asPath } = useRouter();
|
||||
// if (asPath === "/structured") {
|
||||
// return <Error msg="/structured" statusCode={404} />;
|
||||
|
|
@ -29,4 +39,9 @@ const Hyperglass = ({ Component, pageProps }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Hyperglass;
|
||||
App.getInitialProps = async (): Promise<TAppInitial> => {
|
||||
const config = (process.env._HYPERGLASS_CONFIG_ as unknown) as IConfig;
|
||||
return { appProps: { config } };
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
||||
import type { DocumentContext } from 'next/document';
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx) {
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return { ...initialProps };
|
||||
}
|
||||
|
|
@ -11,10 +11,10 @@ class MyDocument extends Document {
|
|||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||
<link rel="dns-prefetch" href="//fonts.gstatic.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
|
||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://www.google-analytics.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
|
||||
</Head>
|
||||
<body>
|
||||
<script src="noflash.js" />
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import React from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
Button,
|
||||
CSSReset,
|
||||
Flex,
|
||||
Heading,
|
||||
Text,
|
||||
ThemeProvider,
|
||||
useColorMode,
|
||||
theme as defaultTheme,
|
||||
} from '@chakra-ui/core';
|
||||
import { inRange } from 'lodash';
|
||||
|
||||
const ColorModeProvider = dynamic(
|
||||
() => import('@chakra-ui/core').then(mod => mod.ColorModeProvider),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
const ErrorContent = ({ msg, statusCode }) => {
|
||||
const { colorMode } = useColorMode();
|
||||
const bg = { light: 'white', dark: 'black' };
|
||||
const baseCode = inRange(statusCode, 400, 500) ? 400 : inRange(statusCode, 500, 600) ? 500 : 400;
|
||||
const errorColor = {
|
||||
400: { light: 'error.500', dark: 'error.300' },
|
||||
500: { light: 'danger.500', dark: 'danger.300' },
|
||||
};
|
||||
const variantColor = {
|
||||
400: 'error',
|
||||
500: 'danger',
|
||||
};
|
||||
const color = { light: 'black', dark: 'white' };
|
||||
const { push } = useRouter();
|
||||
const handleClick = () => push('/');
|
||||
return (
|
||||
<Flex
|
||||
w="100%"
|
||||
minHeight="100vh"
|
||||
bg={bg[colorMode]}
|
||||
flexDirection="column"
|
||||
color={color[colorMode]}>
|
||||
<Flex
|
||||
px={2}
|
||||
py={0}
|
||||
w="100%"
|
||||
as="main"
|
||||
flexGrow={1}
|
||||
flexShrink={1}
|
||||
flexBasis="auto"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
justifyContent="start"
|
||||
mt={['50%', '50%', '50%', '25%']}>
|
||||
<Heading mb={4} as="h1" fontSize="2xl">
|
||||
<Text as="span" color={errorColor[baseCode][colorMode]}>
|
||||
{msg}
|
||||
</Text>
|
||||
{statusCode === 404 && <Text as="span"> isn't a thing...</Text>}
|
||||
</Heading>
|
||||
|
||||
<Button variant="outline" onClick={handleClick} variantColor={variantColor[baseCode]}>
|
||||
Home
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorPage = ({ msg, statusCode }) => {
|
||||
return (
|
||||
<ThemeProvider theme={defaultTheme}>
|
||||
<ColorModeProvider>
|
||||
<CSSReset />
|
||||
<ErrorContent msg={msg} statusCode={statusCode} />
|
||||
</ColorModeProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorPage.getInitialProps = ({ res, err }) => {
|
||||
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
|
||||
const msg = err ? err.message : res.req?.url || 'Error';
|
||||
return { msg, statusCode };
|
||||
};
|
||||
|
||||
export default ErrorPage;
|
||||
88
hyperglass/ui/pages/_error.tsx
Normal file
88
hyperglass/ui/pages/_error.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
Flex,
|
||||
Text,
|
||||
theme,
|
||||
Button,
|
||||
Heading,
|
||||
ThemeProvider,
|
||||
ChakraProvider,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import { inRange } from 'lodash';
|
||||
|
||||
import type { NextPageContext } from 'next';
|
||||
|
||||
interface TError {
|
||||
status: string;
|
||||
code: number;
|
||||
}
|
||||
|
||||
const ErrorContent = (props: TError) => {
|
||||
const { status, code } = props;
|
||||
const router = useRouter();
|
||||
|
||||
const bg = useColorModeValue('white', 'black');
|
||||
const color = useColorModeValue('black', 'white');
|
||||
const error400 = useColorModeValue('error.500', 'error.300');
|
||||
const error500 = useColorModeValue('danger.500', 'danger.300');
|
||||
const errorColor = { 400: error400, 500: error500 };
|
||||
const colorScheme = { 400: 'error', 500: 'danger' };
|
||||
|
||||
const baseCode = useMemo(() => {
|
||||
return inRange(code, 400, 500) ? 400 : inRange(code, 500, 600) ? 500 : 400;
|
||||
}, [code]);
|
||||
|
||||
function handleClick(): void {
|
||||
router.push('/');
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex w="100%" minHeight="100vh" bg={bg} flexDirection="column" color={color}>
|
||||
<Flex
|
||||
px={2}
|
||||
py={0}
|
||||
w="100%"
|
||||
as="main"
|
||||
flexGrow={1}
|
||||
flexShrink={1}
|
||||
flexBasis="auto"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
justifyContent="start"
|
||||
mt={{ base: '50%', xl: '25%' }}>
|
||||
<Heading mb={4} as="h1" fontSize="2xl">
|
||||
<Text as="span" color={errorColor[baseCode]}>
|
||||
{status}
|
||||
</Text>
|
||||
{code === 404 && <Text as="span"> isn't a thing...</Text>}
|
||||
</Heading>
|
||||
<Button variant="outline" onClick={handleClick} colorScheme={colorScheme[baseCode]}>
|
||||
Home
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorPage = (props: TError) => {
|
||||
const { status, code } = props;
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<ChakraProvider>
|
||||
<ErrorContent status={status} code={code} />
|
||||
</ChakraProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorPage.getInitialProps = (ctx: NextPageContext): TError => {
|
||||
const { res, err } = ctx;
|
||||
const code = res ? res.statusCode : err ? err.statusCode ?? 500 : 404;
|
||||
const status = err ? err.message : 'Error';
|
||||
return { status, code };
|
||||
};
|
||||
|
||||
export default ErrorPage;
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import Head from 'next/head';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Meta, Loading } from 'app/components';
|
||||
|
||||
const Layout = dynamic(() => import('~/components').then(i => i.Layout), {
|
||||
loading: Loading,
|
||||
});
|
||||
|
||||
const Index = ({ faviconComponents }) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
{faviconComponents.map(({ rel, href, type }, i) => (
|
||||
<link rel={rel} href={href} type={type} key={i} />
|
||||
))}
|
||||
</Head>
|
||||
<Meta />
|
||||
<Layout />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getStaticProps(context) {
|
||||
const components = process.env._HYPERGLASS_FAVICONS_.map(icon => {
|
||||
const { image_format, dimensions, prefix, rel } = icon;
|
||||
const src = `/images/favicons/${prefix}-${dimensions[0]}x${dimensions[1]}.${image_format}`;
|
||||
return { rel, href: src, type: `image/${image_format}` };
|
||||
});
|
||||
return {
|
||||
props: {
|
||||
faviconComponents: components,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default Index;
|
||||
47
hyperglass/ui/pages/index.tsx
Normal file
47
hyperglass/ui/pages/index.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import Head from 'next/head';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Meta, Loading } from '~/components';
|
||||
|
||||
import type { GetStaticProps } from 'next';
|
||||
import type { Favicon, FaviconComponent } from '~/types';
|
||||
|
||||
const Layout = dynamic<Dict>(() => import('~/components').then(i => i.Layout), {
|
||||
loading: Loading,
|
||||
});
|
||||
|
||||
interface TIndex {
|
||||
favicons: FaviconComponent[];
|
||||
}
|
||||
|
||||
const Index = (props: TIndex) => {
|
||||
const { favicons } = props;
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
{favicons.map((icon, idx) => {
|
||||
const { rel, href, type } = icon;
|
||||
return <link rel={rel} href={href} type={type} key={idx} />;
|
||||
})}
|
||||
</Head>
|
||||
<Meta />
|
||||
<Layout />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<TIndex> = async () => {
|
||||
const faviconConfig = (process.env._HYPERGLASS_FAVICONS_ as unknown) as Favicon[];
|
||||
const favicons = faviconConfig.map(icon => {
|
||||
let { image_format, dimensions, prefix, rel } = icon;
|
||||
if (rel === null) {
|
||||
rel = '';
|
||||
}
|
||||
const src = `/images/favicons/${prefix}-${dimensions[0]}x${dimensions[1]}.${image_format}`;
|
||||
return { rel, href: src, type: `image/${image_format}` };
|
||||
});
|
||||
return {
|
||||
props: { favicons },
|
||||
};
|
||||
};
|
||||
|
||||
export default Index;
|
||||
13
hyperglass/ui/types/common.ts
Normal file
13
hyperglass/ui/types/common.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export type TSelectOption = {
|
||||
label: string;
|
||||
value: string | string[];
|
||||
};
|
||||
|
||||
export type TSelectOptionGroup = {
|
||||
label: string;
|
||||
options: TSelectOption[];
|
||||
};
|
||||
|
||||
export type OnChangeArgs = { field: string; value: string | string[] };
|
||||
|
||||
export type Families = [4] | [6] | [4, 6] | [];
|
||||
|
|
@ -70,42 +70,42 @@ export interface IConfigWeb {
|
|||
theme: IConfigTheme;
|
||||
}
|
||||
|
||||
export interface IQuery {
|
||||
export interface TQuery {
|
||||
name: string;
|
||||
enable: boolean;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export interface IBGPCommunity {
|
||||
export interface TBGPCommunity {
|
||||
community: string;
|
||||
display_name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface IQueryBGPRoute extends IQuery {}
|
||||
export interface IQueryBGPASPath extends IQuery {}
|
||||
export interface IQueryPing extends IQuery {}
|
||||
export interface IQueryTraceroute extends IQuery {}
|
||||
export interface IQueryBGPCommunity extends IQuery {
|
||||
export interface IQueryBGPRoute extends TQuery {}
|
||||
export interface IQueryBGPASPath extends TQuery {}
|
||||
export interface IQueryPing extends TQuery {}
|
||||
export interface IQueryTraceroute extends TQuery {}
|
||||
export interface IQueryBGPCommunity extends TQuery {
|
||||
mode: 'input' | 'select';
|
||||
communities: IBGPCommunity[];
|
||||
communities: TBGPCommunity[];
|
||||
}
|
||||
|
||||
export interface IConfigQueries {
|
||||
export interface TConfigQueries {
|
||||
bgp_route: IQueryBGPRoute;
|
||||
bgp_community: IQueryBGPCommunity;
|
||||
bgp_aspath: IQueryBGPASPath;
|
||||
ping: IQueryPing;
|
||||
traceroute: IQueryTraceroute;
|
||||
list: IQuery[];
|
||||
list: TQuery[];
|
||||
}
|
||||
|
||||
interface IDeviceVrfBase {
|
||||
interface TDeviceVrfBase {
|
||||
id: string;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export interface IDeviceVrf extends IDeviceVrfBase {
|
||||
export interface TDeviceVrf extends TDeviceVrfBase {
|
||||
ipv4: boolean;
|
||||
ipv6: boolean;
|
||||
}
|
||||
|
|
@ -117,11 +117,11 @@ interface TDeviceBase {
|
|||
}
|
||||
|
||||
export interface TDevice extends TDeviceBase {
|
||||
vrfs: IDeviceVrf[];
|
||||
vrfs: TDeviceVrf[];
|
||||
}
|
||||
|
||||
export interface TNetworkLocation extends TDeviceBase {
|
||||
vrfs: IDeviceVrfBase[];
|
||||
vrfs: TDeviceVrfBase[];
|
||||
}
|
||||
|
||||
export interface TNetwork {
|
||||
|
|
@ -173,10 +173,23 @@ export interface IConfig {
|
|||
web: IConfigWeb;
|
||||
messages: IConfigMessages;
|
||||
hyperglass_version: string;
|
||||
queries: IConfigQueries;
|
||||
queries: TConfigQueries;
|
||||
devices: TDevice[];
|
||||
networks: TNetwork[];
|
||||
vrfs: IDeviceVrfBase[];
|
||||
vrfs: TDeviceVrfBase[];
|
||||
parsed_data_fields: TParsedDataField[];
|
||||
content: IConfigContent;
|
||||
}
|
||||
|
||||
export interface Favicon {
|
||||
rel: string | null;
|
||||
dimensions: [number, number];
|
||||
image_format: string;
|
||||
prefix: string;
|
||||
}
|
||||
|
||||
export interface FaviconComponent {
|
||||
rel: string;
|
||||
href: string;
|
||||
type: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export type TQueryTypes = '' | 'bgp_route' | 'bgp_community' | 'bgp_aspath' | 'ping' | 'traceroute';
|
||||
|
||||
export interface IFormData {
|
||||
export interface TFormData {
|
||||
query_location: string[];
|
||||
query_type: TQueryTypes | '';
|
||||
query_type: TQueryTypes;
|
||||
query_vrf: string;
|
||||
query_target: string;
|
||||
}
|
||||
|
|
|
|||
204
hyperglass/ui/types/dns-over-https.ts
Normal file
204
hyperglass/ui/types/dns-over-https.ts
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* DNS Over HTTPS Types, primarily adapted from:
|
||||
*
|
||||
* @see https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
|
||||
* @see https://developers.cloudflare.com/1.1.1.1/dns-over-https/json-format
|
||||
* @see https://developers.google.com/speed/public-dns/docs/doh/json
|
||||
*/
|
||||
export namespace DnsOverHttps {
|
||||
/**
|
||||
* DNS RCODEs
|
||||
* @see https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
|
||||
*/
|
||||
export enum Status {
|
||||
/**
|
||||
* No Error
|
||||
*/
|
||||
NO_ERROR = 0,
|
||||
/**
|
||||
* Format Error
|
||||
*/
|
||||
FORM_ERR = 1,
|
||||
/**
|
||||
* Server Failure
|
||||
*/
|
||||
SERV_FAIL = 2,
|
||||
/**
|
||||
* Non-Existent Domain
|
||||
*/
|
||||
NX_DOMAIN = 3,
|
||||
/**
|
||||
* Not Implemented
|
||||
*/
|
||||
NOT_IMP = 4,
|
||||
/**
|
||||
* Query Refused
|
||||
*/
|
||||
REFUSED = 5,
|
||||
/**
|
||||
* Name Exists when it should not
|
||||
*/
|
||||
YX_DOMAIN = 6,
|
||||
/**
|
||||
* RR Set Exists when it should not
|
||||
*/
|
||||
YXRR_SET = 7,
|
||||
/**
|
||||
* RR Set that should exist does not
|
||||
*/
|
||||
NXRR_SET = 8,
|
||||
/**
|
||||
* Server Not Authoritative for zone
|
||||
*/
|
||||
NOT_AUTH = 9,
|
||||
/**
|
||||
* Name not contained in zone
|
||||
*/
|
||||
NOT_ZONE = 10,
|
||||
/**
|
||||
* DSO-TYPE Not Implemented
|
||||
*/
|
||||
DSO_TYPE_NI = 11,
|
||||
/**
|
||||
* TSIG Signature Failure
|
||||
*/
|
||||
BADSIG = 16,
|
||||
/**
|
||||
* Key not recognized
|
||||
*/
|
||||
BADKEY = 17,
|
||||
/**
|
||||
* Signature out of time window
|
||||
*/
|
||||
BADTIME = 18,
|
||||
/**
|
||||
* Bad TKEY Mode
|
||||
*/
|
||||
BADMODE = 19,
|
||||
/**
|
||||
* Duplicate key name
|
||||
*/
|
||||
BADNAME = 20,
|
||||
/**
|
||||
* Algorithm not supported
|
||||
*/
|
||||
BADALG = 21,
|
||||
/**
|
||||
* Bad Truncation
|
||||
*/
|
||||
BADTRUNC = 22,
|
||||
/**
|
||||
* Bad/missing Server Cookie
|
||||
*/
|
||||
BADCOOKIE = 23,
|
||||
}
|
||||
/**
|
||||
* Resource Record (RR) Types
|
||||
* @see https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
|
||||
*/
|
||||
export enum Type {
|
||||
/**
|
||||
* IPv4 Host Address Record.
|
||||
*/
|
||||
A = 1,
|
||||
/**
|
||||
* Name Server Record.
|
||||
*/
|
||||
NS = 2,
|
||||
/**
|
||||
* Canonical Alias Name Record.
|
||||
*/
|
||||
CNAME = 5,
|
||||
/**
|
||||
* Start of Zone Authority Record.
|
||||
*/
|
||||
SOA = 6,
|
||||
/**
|
||||
* Well Know Service Description Record.
|
||||
*/
|
||||
WKS = 11,
|
||||
/**
|
||||
* Domain Name Pointer Record.
|
||||
*/
|
||||
PTR = 12,
|
||||
/**
|
||||
* Mail Exchange Record.
|
||||
*/
|
||||
MX = 15,
|
||||
/**
|
||||
* IPv6 Host Address Record.
|
||||
*/
|
||||
AAAA = 28,
|
||||
/**
|
||||
* Server Selection Record.
|
||||
*/
|
||||
SRV = 33,
|
||||
/**
|
||||
* DNAME Record.
|
||||
*/
|
||||
DNAME = 39,
|
||||
/**
|
||||
* DNSKEY Record.
|
||||
*/
|
||||
DNSKEY = 48,
|
||||
}
|
||||
export interface Question {
|
||||
/**
|
||||
* FQDN with trailing dot.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* DNS RR Type.
|
||||
*/
|
||||
type: Type;
|
||||
}
|
||||
export interface Answer {
|
||||
/**
|
||||
* FQDN with trailing dot.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* DNS RR Type.
|
||||
*/
|
||||
type: Type;
|
||||
/**
|
||||
* Time to live in seconds.
|
||||
*/
|
||||
TTL: number;
|
||||
/**
|
||||
* Response data.
|
||||
*/
|
||||
data: string;
|
||||
}
|
||||
export interface Response {
|
||||
Status: Status;
|
||||
/**
|
||||
* Truncated bit was set.
|
||||
*/
|
||||
TC: boolean;
|
||||
/**
|
||||
* Recursive Desired bit was set.
|
||||
*/
|
||||
RD: boolean;
|
||||
/**
|
||||
* Recursion Available bit was set.
|
||||
*/
|
||||
RA: boolean;
|
||||
/**
|
||||
* If true, it means that every record in the answer was verified with DNSSEC.
|
||||
*/
|
||||
AD: boolean;
|
||||
/**
|
||||
* If true, the client asked to disable DNSSEC validation.
|
||||
*/
|
||||
CD: boolean;
|
||||
/**
|
||||
* Queried Resources.
|
||||
*/
|
||||
Question: Question[];
|
||||
/**
|
||||
* Response Data.
|
||||
*/
|
||||
Answer: Answer[];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
export * from './common';
|
||||
export * from './config';
|
||||
export * from './data';
|
||||
export * from './dns-over-https';
|
||||
export * from './table';
|
||||
export * from './theme';
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ interface CustomColors {
|
|||
tertiary: ColorHues;
|
||||
dark: ColorHues;
|
||||
light: ColorHues;
|
||||
success: ColorHues;
|
||||
}
|
||||
|
||||
type AllColors = CustomColors & ChakraColors;
|
||||
|
|
|
|||
|
|
@ -1,37 +1,17 @@
|
|||
import { theme as chakraTheme } from '@chakra-ui/core';
|
||||
import { theme as chakraTheme } from '@chakra-ui/react';
|
||||
import chroma from 'chroma-js';
|
||||
|
||||
const alphaColors = color => ({
|
||||
900: chroma(color)
|
||||
.alpha(0.92)
|
||||
.css(),
|
||||
800: chroma(color)
|
||||
.alpha(0.8)
|
||||
.css(),
|
||||
700: chroma(color)
|
||||
.alpha(0.6)
|
||||
.css(),
|
||||
600: chroma(color)
|
||||
.alpha(0.48)
|
||||
.css(),
|
||||
500: chroma(color)
|
||||
.alpha(0.38)
|
||||
.css(),
|
||||
400: chroma(color)
|
||||
.alpha(0.24)
|
||||
.css(),
|
||||
300: chroma(color)
|
||||
.alpha(0.16)
|
||||
.css(),
|
||||
200: chroma(color)
|
||||
.alpha(0.12)
|
||||
.css(),
|
||||
100: chroma(color)
|
||||
.alpha(0.08)
|
||||
.css(),
|
||||
50: chroma(color)
|
||||
.alpha(0.04)
|
||||
.css(),
|
||||
900: chroma(color).alpha(0.92).css(),
|
||||
800: chroma(color).alpha(0.8).css(),
|
||||
700: chroma(color).alpha(0.6).css(),
|
||||
600: chroma(color).alpha(0.48).css(),
|
||||
500: chroma(color).alpha(0.38).css(),
|
||||
400: chroma(color).alpha(0.24).css(),
|
||||
300: chroma(color).alpha(0.16).css(),
|
||||
200: chroma(color).alpha(0.12).css(),
|
||||
100: chroma(color).alpha(0.08).css(),
|
||||
50: chroma(color).alpha(0.04).css(),
|
||||
});
|
||||
|
||||
const generateColors = colorInput => {
|
||||
|
|
@ -166,13 +146,10 @@ export const opposingColor = (theme, color) => {
|
|||
|
||||
export const googleFontUrl = (fontFamily, weights = [300, 400, 700]) => {
|
||||
const urlWeights = weights.join(',');
|
||||
const fontName = fontFamily
|
||||
.split(/, /)[0]
|
||||
.trim()
|
||||
.replace(/'|"/g, '');
|
||||
const fontName = fontFamily.split(/, /)[0].trim().replace(/'|"/g, '');
|
||||
const urlFont = fontName.split(/ /).join('+');
|
||||
const urlBase = `https://fonts.googleapis.com/css?family=${urlFont}:${urlWeights}&display=swap`;
|
||||
return urlBase;
|
||||
};
|
||||
|
||||
export { theme as defaultTheme } from '@chakra-ui/core';
|
||||
export { theme as defaultTheme } from '@chakra-ui/react';
|
||||
|
|
|
|||
2296
hyperglass/ui/yarn.lock
vendored
2296
hyperglass/ui/yarn.lock
vendored
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue