mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
Update UI dependencies
This commit is contained in:
@@ -13,7 +13,9 @@ export const FormField: React.FC<TField> = (props: TField) => {
|
||||
const errorColor = useColorValue('red.500', 'red.300');
|
||||
const opacity = useBooleanValue(hiddenLabels, 0, undefined);
|
||||
|
||||
const { errors } = useFormContext();
|
||||
const {
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
|
||||
const error = name in errors && (errors[name] as FieldError);
|
||||
|
||||
|
@@ -27,7 +27,9 @@ export const QueryLocation: React.FC<TQuerySelectField> = (props: TQuerySelectFi
|
||||
const { onChange, label } = props;
|
||||
|
||||
const { networks } = useConfig();
|
||||
const { errors } = useFormContext();
|
||||
const {
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const { selections } = useLGState();
|
||||
const { exportState } = useLGMethods();
|
||||
|
||||
|
@@ -58,7 +58,7 @@ export const QueryTarget: React.FC<TQueryTarget> = (props: TQueryTarget) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<input hidden readOnly name={name} ref={register} value={queryTarget.value} />
|
||||
<input {...register} hidden readOnly value={queryTarget.value} />
|
||||
<If c={queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select'}>
|
||||
<Select
|
||||
size="lg"
|
||||
|
@@ -16,7 +16,9 @@ function buildOptions(queryTypes: TQuery[]): TSelectOption[] {
|
||||
export const QueryType: React.FC<TQuerySelectField> = (props: TQuerySelectField) => {
|
||||
const { onChange, label } = props;
|
||||
const { queries } = useConfig();
|
||||
const { errors } = useFormContext();
|
||||
const {
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const { selections } = useLGState();
|
||||
const { exportState } = useLGMethods();
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { FormControlProps } from '@chakra-ui/react';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs } from '~/types';
|
||||
import type { UseFormRegister } from 'react-hook-form';
|
||||
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs, TFormData } from '~/types';
|
||||
|
||||
export interface TField extends FormControlProps {
|
||||
name: string;
|
||||
@@ -25,13 +25,13 @@ export interface TCommunitySelect {
|
||||
name: string;
|
||||
onChange: OnChange;
|
||||
communities: TBGPCommunity[];
|
||||
register: Control['register'];
|
||||
register: UseFormRegister<TFormData>;
|
||||
}
|
||||
|
||||
export interface TQueryTarget {
|
||||
name: string;
|
||||
placeholder: string;
|
||||
register: Control['register'];
|
||||
register: UseFormRegister<TFormData>;
|
||||
onChange(e: OnChangeArgs): void;
|
||||
}
|
||||
|
||||
|
@@ -38,15 +38,15 @@ export const Frame: React.FC<TFrame> = (props: TFrame) => {
|
||||
>
|
||||
<Header resetForm={handleReset} />
|
||||
<Flex
|
||||
px={2}
|
||||
px={4}
|
||||
py={0}
|
||||
w="100%"
|
||||
as="main"
|
||||
align="center"
|
||||
flex="1 1 auto"
|
||||
justify="start"
|
||||
flexDir="column"
|
||||
textAlign="center"
|
||||
w={{ base: '100%', lg: 'calc(100% - 1rem)' }}
|
||||
{...props}
|
||||
/>
|
||||
<Footer />
|
||||
|
@@ -18,7 +18,7 @@ import {
|
||||
} from '~/components';
|
||||
import { useConfig } from '~/context';
|
||||
import { useStrf, useGreeting, useDevice, useLGState, useLGMethods } from '~/hooks';
|
||||
import { isQueryType, isQueryContent, isString } from '~/types';
|
||||
import { isQueryType, isQueryContent, isString, isQueryField } from '~/types';
|
||||
|
||||
import type { TFormData, TDeviceVrf, OnChangeArgs } from '~/types';
|
||||
|
||||
@@ -206,7 +206,11 @@ export const LookingGlass: React.FC = () => {
|
||||
|
||||
function handleChange(e: OnChangeArgs): void {
|
||||
// Signal the field & value to react-hook-form.
|
||||
setValue(e.field, e.value);
|
||||
if (isQueryField(e.field)) {
|
||||
setValue(e.field, e.value);
|
||||
} else {
|
||||
throw new Error(`Field '${e.field}' is not a valid form field.`);
|
||||
}
|
||||
|
||||
if (e.field === 'query_location' && Array.isArray(e.value)) {
|
||||
handleLocChange(e.value);
|
||||
@@ -244,10 +248,10 @@ export const LookingGlass: React.FC = () => {
|
||||
}, [queryVrf.value, queryLocation.value, queryType.value]);
|
||||
|
||||
useEffect(() => {
|
||||
register({ name: 'query_location', required: true });
|
||||
register({ name: 'query_target', required: true });
|
||||
register({ name: 'query_type', required: true });
|
||||
register({ name: 'query_vrf' });
|
||||
register('query_location', { required: true });
|
||||
register('query_target', { required: true });
|
||||
register('query_type', { required: true });
|
||||
register('query_vrf');
|
||||
}, [register]);
|
||||
|
||||
return (
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import type { State } from '@hookstate/core';
|
||||
import type { QueryFunctionContext } from 'react-query';
|
||||
import type * as ReactGA from 'react-ga';
|
||||
import type {
|
||||
TDevice,
|
||||
@@ -10,6 +9,9 @@ import type {
|
||||
TSelectOption,
|
||||
} from '~/types';
|
||||
|
||||
export type LGQueryKey = [string, TFormQuery];
|
||||
export type DNSQueryKey = [string, { target: string | null; family: 4 | 6 }];
|
||||
|
||||
export interface TOpposingOptions {
|
||||
light?: string;
|
||||
dark?: string;
|
||||
@@ -23,26 +25,6 @@ export type TUseGreetingReturn = {
|
||||
greetingReady(): boolean;
|
||||
};
|
||||
|
||||
export interface TUseLGQueryFn {
|
||||
pageParam?: QueryFunctionContext['pageParam'];
|
||||
queryKey: [string, TFormQuery];
|
||||
}
|
||||
|
||||
export interface TUseASNDetailFn {
|
||||
pageParam?: QueryFunctionContext['pageParam'];
|
||||
queryKey: string;
|
||||
}
|
||||
|
||||
interface TUseDNSQueryParams {
|
||||
target: string;
|
||||
family: 4 | 6;
|
||||
}
|
||||
|
||||
export interface TUseDNSQueryFn {
|
||||
pageParam?: QueryFunctionContext['pageParam'];
|
||||
queryKey: [string | null, TUseDNSQueryParams];
|
||||
}
|
||||
|
||||
export type TUseDevice = (
|
||||
/**
|
||||
* Device's ID, e.g. the device.name field.
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import type { QueryObserverResult } from 'react-query';
|
||||
import type { QueryFunctionContext, QueryObserverResult, QueryFunction } from 'react-query';
|
||||
import type { TASNQuery } from '~/types';
|
||||
import type { TUseASNDetailFn } from './types';
|
||||
|
||||
async function query(ctx: TUseASNDetailFn): Promise<TASNQuery> {
|
||||
const [asn] = ctx.queryKey;
|
||||
const query: QueryFunction<TASNQuery, string> = async (ctx: QueryFunctionContext) => {
|
||||
const asn = ctx.queryKey;
|
||||
const res = await fetch('https://api.asrank.caida.org/v2/graphql', {
|
||||
mode: 'cors',
|
||||
method: 'POST',
|
||||
@@ -14,14 +13,16 @@ async function query(ctx: TUseASNDetailFn): Promise<TASNQuery> {
|
||||
body: JSON.stringify({ query: `{ asn(asn:\"${asn}\"){ organization { orgName } } }` }),
|
||||
});
|
||||
return await res.json();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Query the Caida AS Rank API to get an ASN's organization name for the AS Path component.
|
||||
* @see https://api.asrank.caida.org/v2/docs
|
||||
*/
|
||||
export function useASNDetail(asn: string): QueryObserverResult<TASNQuery> {
|
||||
return useQuery(asn, query, {
|
||||
return useQuery<TASNQuery, unknown, TASNQuery, string>({
|
||||
queryKey: asn,
|
||||
queryFn: query,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchInterval: false,
|
||||
refetchOnMount: false,
|
||||
|
@@ -3,14 +3,16 @@ import { useConfig } from '~/context';
|
||||
import { fetchWithTimeout } from '~/util';
|
||||
import { useGoogleAnalytics } from './useGoogleAnalytics';
|
||||
|
||||
import type { QueryObserverResult } from 'react-query';
|
||||
import type { QueryFunction, QueryFunctionContext, QueryObserverResult } from 'react-query';
|
||||
import type { DnsOverHttps } from '~/types';
|
||||
import type { TUseDNSQueryFn } from './types';
|
||||
import type { DNSQueryKey } from './types';
|
||||
|
||||
/**
|
||||
* Perform a DNS over HTTPS query using the application/dns-json MIME type.
|
||||
*/
|
||||
async function dnsQuery(ctx: TUseDNSQueryFn): Promise<DnsOverHttps.Response | undefined> {
|
||||
const query: QueryFunction<DnsOverHttps.Response, DNSQueryKey> = async (
|
||||
ctx: QueryFunctionContext<DNSQueryKey>,
|
||||
) => {
|
||||
const [url, { target, family }] = ctx.queryKey;
|
||||
|
||||
const controller = new AbortController();
|
||||
@@ -33,7 +35,7 @@ async function dnsQuery(ctx: TUseDNSQueryFn): Promise<DnsOverHttps.Response | un
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Query the configured DNS over HTTPS provider for the provided target. If `family` is `4`, only
|
||||
@@ -56,7 +58,9 @@ export function useDNSQuery(
|
||||
trackEvent({ category: 'DNS', action: 'Query', label: target, dimension1: `IPv${family}` });
|
||||
}
|
||||
|
||||
return useQuery([web.dns_provider.url, { target, family }], dnsQuery, {
|
||||
return useQuery<DnsOverHttps.Response, unknown, DnsOverHttps.Response, DNSQueryKey>({
|
||||
queryKey: [web.dns_provider.url, { target, family }],
|
||||
queryFn: query,
|
||||
cacheTime: cache.timeout * 1000,
|
||||
});
|
||||
}
|
||||
|
@@ -4,9 +4,9 @@ import { useConfig } from '~/context';
|
||||
import { useGoogleAnalytics } from './useGoogleAnalytics';
|
||||
import { fetchWithTimeout } from '~/util';
|
||||
|
||||
import type { QueryObserverResult } from 'react-query';
|
||||
import type { QueryFunction, QueryFunctionContext, QueryObserverResult } from 'react-query';
|
||||
import type { TFormQuery } from '~/types';
|
||||
import type { TUseLGQueryFn } from './types';
|
||||
import type { LGQueryKey } from './types';
|
||||
|
||||
/**
|
||||
* Custom hook handle submission of a query to the hyperglass backend.
|
||||
@@ -26,7 +26,9 @@ export function useLGQuery(query: TFormQuery): QueryObserverResult<TQueryRespons
|
||||
dimension4: query.queryVrf,
|
||||
});
|
||||
|
||||
async function runQuery(ctx: TUseLGQueryFn): Promise<TQueryResponse> {
|
||||
const runQuery: QueryFunction<TQueryResponse, LGQueryKey> = async (
|
||||
ctx: QueryFunctionContext<LGQueryKey>,
|
||||
): Promise<TQueryResponse> => {
|
||||
const [url, data] = ctx.queryKey;
|
||||
const { queryLocation, queryTarget, queryType, queryVrf } = data;
|
||||
const res = await fetchWithTimeout(
|
||||
@@ -50,7 +52,7 @@ export function useLGQuery(query: TFormQuery): QueryObserverResult<TQueryRespons
|
||||
} catch (err) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel any still-running queries on unmount.
|
||||
useEffect(
|
||||
@@ -60,18 +62,16 @@ export function useLGQuery(query: TFormQuery): QueryObserverResult<TQueryRespons
|
||||
[],
|
||||
);
|
||||
|
||||
return useQuery<TQueryResponse, Response | TQueryResponse | Error>(
|
||||
['/api/query/', query],
|
||||
runQuery,
|
||||
{
|
||||
// Invalidate react-query's cache just shy of the configured cache timeout.
|
||||
cacheTime: cache.timeout * 1000 * 0.95,
|
||||
// Don't refetch when window refocuses.
|
||||
refetchOnWindowFocus: false,
|
||||
// Don't automatically refetch query data (queries should be on-off).
|
||||
refetchInterval: false,
|
||||
// Don't refetch on component remount.
|
||||
refetchOnMount: false,
|
||||
},
|
||||
);
|
||||
return useQuery<TQueryResponse, Response | TQueryResponse | Error, TQueryResponse, LGQueryKey>({
|
||||
queryKey: ['/api/query/', query],
|
||||
queryFn: runQuery,
|
||||
// Invalidate react-query's cache just shy of the configured cache timeout.
|
||||
cacheTime: cache.timeout * 1000 * 0.95,
|
||||
// Don't refetch when window refocuses.
|
||||
refetchOnWindowFocus: false,
|
||||
// Don't automatically refetch query data (queries should be on-off).
|
||||
refetchInterval: false,
|
||||
// Don't refetch on component remount.
|
||||
refetchOnMount: false,
|
||||
});
|
||||
}
|
||||
|
31
hyperglass/ui/package.json
vendored
31
hyperglass/ui/package.json
vendored
@@ -18,42 +18,43 @@
|
||||
},
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^1.5.2",
|
||||
"@emotion/react": "^11.1.5",
|
||||
"@chakra-ui/react": "^1.6.3",
|
||||
"@emotion/react": "^11.4.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@hookform/resolvers": "1.3.0",
|
||||
"@hookform/devtools": "^3.1.0",
|
||||
"@hookform/resolvers": "^2.5.1",
|
||||
"@hookstate/core": "^3.0.7",
|
||||
"@hookstate/persistence": "^3.0.0",
|
||||
"@meronex/icons": "^4.0.0",
|
||||
"dagre": "^0.8.5",
|
||||
"dayjs": "^1.10.4",
|
||||
"framer-motion": "^4.1.8",
|
||||
"lodash": "^4.17.15",
|
||||
"next": "^10.1.3",
|
||||
"framer-motion": "^4.1.17",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "^10.2.3",
|
||||
"palette-by-numbers": "^0.1.5",
|
||||
"react": "^17.0.2",
|
||||
"react-countdown": "^2.2.1",
|
||||
"react-device-detect": "^1.15.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-fast-compare": "^3.2.0",
|
||||
"react-flow-renderer": "^9.5.2",
|
||||
"react-flow-renderer": "^9.6.0",
|
||||
"react-ga": "^3.3.0",
|
||||
"react-hook-form": "^6.15.4",
|
||||
"react-hook-form": "^7.7.0",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-query": "^3.6.0",
|
||||
"react-select": "^4.1.0",
|
||||
"react-table": "^7.6.2",
|
||||
"react-query": "^3.16.0",
|
||||
"react-select": "^4.3.1",
|
||||
"react-table": "^7.7.0",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"string-format": "^2.0.0",
|
||||
"vest": "^3.1.2"
|
||||
"vest": "^3.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hookstate/devtools": "^3.0.0",
|
||||
"@types/dagre": "^0.7.44",
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-select": "^4.0.13",
|
||||
"@types/react-table": "^7.0.25",
|
||||
"@types/react-select": "^4.0.15",
|
||||
"@types/react-table": "^7.7.1",
|
||||
"@types/string-format": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.11.1",
|
||||
"@typescript-eslint/parser": "^4.11.1",
|
||||
@@ -76,6 +77,6 @@
|
||||
"onchange": "^7.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier-eslint": "^12.0.0",
|
||||
"typescript": "^4.2.4"
|
||||
"typescript": "^4.3.2"
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/* eslint @typescript-eslint/explicit-module-boundary-types: off */
|
||||
/* eslint @typescript-eslint/no-explicit-any: off */
|
||||
import type { State } from '@hookstate/core';
|
||||
import type { TValidQueryTypes, TStringTableData, TQueryResponseString } from './data';
|
||||
import type { TFormData, TValidQueryTypes, TStringTableData, TQueryResponseString } from './data';
|
||||
import type { TSelectOption } from './common';
|
||||
import type { TQueryContent } from './config';
|
||||
|
||||
@@ -57,3 +57,10 @@ export function isState<S>(a: any): a is State<NonNullable<S>> {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a form field name is a valid form key name.
|
||||
*/
|
||||
export function isQueryField(field: string): field is keyof TFormData {
|
||||
return ['query_location', 'query_type', 'query_vrf', 'query_target'].includes(field);
|
||||
}
|
||||
|
1244
hyperglass/ui/yarn.lock
vendored
1244
hyperglass/ui/yarn.lock
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user