start: add DNS resolution

This commit is contained in:
checktheroads 2020-01-27 21:28:27 -07:00
parent bc91fe6f29
commit 03c58ceb76
5 changed files with 258 additions and 28 deletions

View file

@ -1,8 +1,19 @@
import React from "react";
import { Flex, FormControl, FormLabel, FormErrorMessage, useColorMode } from "@chakra-ui/core";
import HelpModal from "~/components/HelpModal";
export default ({ label, name, error, hiddenLabels, helpIcon, children, ...props }) => {
export default ({
label,
name,
error,
hiddenLabels,
helpIcon,
targetInfo,
setTarget,
labelAddOn,
fieldAddOn,
children,
...props
}) => {
const { colorMode } = useColorMode();
const labelColor = { dark: "whiteAlpha.600", light: "blackAlpha.600" };
return (
@ -22,11 +33,20 @@ export default ({ label, name, error, hiddenLabels, helpIcon, children, ...props
color={labelColor[colorMode]}
pl={1}
opacity={hiddenLabels ? 0 : null}
display="flex"
alignItems="center"
justifyContent="space-between"
pr={0}
>
{label}
{helpIcon?.enable && <HelpModal item={helpIcon} name={name} />}
{labelAddOn || null}
</FormLabel>
{children}
{fieldAddOn && (
<Flex justifyContent="flex-end" pt={3}>
{fieldAddOn}
</Flex>
)}
<FormErrorMessage opacity={hiddenLabels ? 0 : null}>
{error && error.message}
</FormErrorMessage>

View file

@ -5,10 +5,12 @@ import lodash from "lodash";
import * as yup from "yup";
import format from "string-format";
import FormField from "~/components/FormField";
import HelpModal from "~/components/HelpModal";
import QueryLocation from "~/components/QueryLocation";
import QueryType from "~/components/QueryType";
import QueryTarget from "~/components/QueryTarget";
import QueryVrf from "~/components/QueryVrf";
import ResolvedTarget from "~/components/ResolvedTarget";
import SubmitButton from "~/components/SubmitButton";
import useConfig from "~/components/HyperglassProvider";
@ -49,11 +51,16 @@ const HyperglassForm = React.forwardRef(
const { handleSubmit, register, setValue, errors } = useForm({
validationSchema: formSchema(config)
});
const [queryLocation, setQueryLocation] = useState([]);
const [queryType, setQueryType] = useState("");
const [queryVrf, setQueryVrf] = useState("");
const [queryTarget, setQueryTarget] = useState("");
const [availVrfs, setAvailVrfs] = useState([]);
const [fqdnTarget, setFqdnTarget] = useState("");
const [displayTarget, setDisplayTarget] = useState("");
const onSubmit = values => {
console.log(values);
setFormData(values);
setSubmitting(true);
};
@ -81,17 +88,28 @@ const HyperglassForm = React.forwardRef(
? setQueryType(e.value)
: e.field === "query_vrf"
? setQueryVrf(e.value)
: e.field === "query_target"
? setQueryTarget(e.value)
: null;
};
const vrfContent = config.content.vrf[queryVrf]?.[queryType];
const validFqdnQueryType =
["ping", "traceroute", "bgp_route"].includes(queryType) &&
fqdnTarget &&
queryVrf === "default"
? fqdnTarget
: null;
useEffect(() => {
register({ name: "query_location" });
register({ name: "query_target" });
register({ name: "query_type" });
register({ name: "query_vrf" });
});
return (
<Box
maxW={["100%", "100%", "75%", "50%"]}
maxW={["100%", "100%", "75%", "75%"]}
w="100%"
p={0}
mx="auto"
@ -113,7 +131,9 @@ const HyperglassForm = React.forwardRef(
label={config.branding.text.query_type}
name="query_type"
error={errors.query_type}
helpIcon={config.content.vrf[queryVrf]?.[queryType] ?? null}
labelAddOn={
vrfContent && <HelpModal item={vrfContent} name="query_type" />
}
>
<QueryType onChange={handleChange} queryTypes={config.queries} />
</FormField>
@ -136,10 +156,28 @@ const HyperglassForm = React.forwardRef(
label={config.branding.text.query_target}
name="query_target"
error={errors.query_target}
fieldAddOn={
validFqdnQueryType && (
<ResolvedTarget
formQueryTarget={queryTarget}
target={validFqdnQueryType}
setTarget={handleChange}
/>
)
}
>
<QueryTarget
name="query_target"
placeholder={config.branding.text.query_target}
register={register}
resolveTarget={["ping", "traceroute", "bgp_route"].includes(
queryType
)}
value={queryTarget}
setFqdn={setFqdnTarget}
setValue={handleChange}
displayValue={displayTarget}
setDisplayValue={setDisplayTarget}
/>
</FormField>
</FormRow>

View file

@ -1,6 +1,6 @@
import React from "react";
import React, { useState } from "react";
import styled from "@emotion/styled";
import { Input, useColorMode, useTheme } from "@chakra-ui/core";
import { Input, useColorMode } from "@chakra-ui/core";
const StyledInput = styled(Input)`
&::placeholder {
@ -8,26 +8,63 @@ const StyledInput = styled(Input)`
}
`;
export default ({ placeholder, register }) => {
const theme = useTheme();
const fqdnPattern = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63}$)/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.400", light: "gray.400" };
const QueryTarget = ({
placeholder,
register,
setFqdn,
name,
value,
setValue,
resolveTarget,
displayValue,
setDisplayValue
}) => {
const { colorMode } = useColorMode();
const bg = colorMode === "dark" ? theme.colors.whiteAlpha[100] : theme.colors.white;
const color = colorMode === "dark" ? theme.colors.whiteAlpha[800] : theme.colors.gray[400];
const border = colorMode === "dark" ? theme.colors.whiteAlpha[50] : theme.colors.gray[100];
const borderRadius = theme.space[1];
const placeholderColor =
colorMode === "dark" ? theme.colors.whiteAlpha[400] : theme.colors.gray[400];
const handleBlur = () => {
if (resolveTarget && displayValue && fqdnPattern.test(displayValue)) {
setFqdn(displayValue);
} else if (resolveTarget && !displayValue) {
setFqdn(false);
}
};
const handleChange = e => {
setDisplayValue(e.target.value);
setValue({ field: name, value: e.target.value });
};
const handleKeyDown = e => {
if ([9, 13].includes(e.keyCode)) {
handleBlur();
}
};
return (
<StyledInput
name="query_target"
ref={register}
placeholder={placeholder}
placeholderColor={placeholderColor}
size="lg"
bg={bg}
color={color}
borderColor={border}
borderRadius={borderRadius}
/>
<>
<input hidden readOnly name={name} ref={register} value={value} />
<StyledInput
size="lg"
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]}
placeholderColor={placeholderColor[colorMode]}
/>
</>
);
};
QueryTarget.displayName = "QueryTarget";
export default QueryTarget;

View file

@ -0,0 +1,132 @@
import React, { useEffect, useState } 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 "~/components/HyperglassProvider";
format.extend(String.prototype, {});
const labelBg = { dark: "secondary", light: "secondary" };
const labelBgSuccess = { dark: "success", light: "success" };
const ResolvedTarget = React.forwardRef(({ target, setTarget, formQueryTarget }, ref) => {
const { colorMode } = useColorMode();
const config = useConfig();
const labelBgStatus = { true: labelBgSuccess[colorMode], false: labelBg[colorMode] };
const params4 = {
url: "https://cloudflare-dns.com/dns-query",
params: { name: target, type: "A" },
headers: { accept: "application/dns-json" },
timeout: 1000
};
const params6 = {
url: "https://cloudflare-dns.com/dns-query",
params: { name: target, type: "AAAA" },
headers: { accept: "application/dns-json" },
timeout: 1000
};
const [{ data: data4, loading: loading4, error: error4 }] = useAxios(params4);
const [{ data: data6, loading: loading6, error: error6 }] = useAxios(params6);
const [data, setData] = useState("");
data && setTarget({ field: "query_target", value: data });
const handleOverride = overridden => {
setData(overridden);
setTarget({ field: "query_target", value: overridden });
};
const isSelected = value => {
console.log("value: ", value, "formQuerytarget: ", formQueryTarget, "target: ", target);
return labelBgStatus[value === formQueryTarget];
};
useEffect(() => {
if (data6 && data6.Answer && data6.Answer[0].type === 28 && data === "") {
handleOverride(data6.Answer[0].data);
}
}, [data6, data]);
useEffect(() => {
if (data4 && data4.Answer && data4.Answer[0].type === 28 && data === "") {
handleOverride(data4.Answer[0].data);
}
}, [data4, data]);
return (
<Stack
ref={ref}
isInline
w="100%"
justifyContent={data4?.Answer && data6?.Answer ? "space-between" : "flex-end"}
>
{loading4 ||
error4 ||
(data4?.Answer?.[0] && (
<Tag>
<Tooltip
hasArrow
label={config.branding.text.fqdn_tooltip.format({ protocol: "IPv4" })}
placement="bottom"
>
<Button
height="unset"
minW="unset"
fontSize="xs"
py="0.1rem"
px={2}
mr={2}
variantColor={isSelected(data4.Answer[0].data)}
borderRadius="md"
onClick={() => handleOverride(data4.Answer[0].data)}
>
IPv4
</Button>
</Tooltip>
{loading4 && <Spinner />}
{error4 && <Icon name="warning" />}
{data4?.Answer?.[0] && (
<Text fontSize="xs" fontFamily="mono" as="span" fontWeight={400}>
{data4.Answer[0].data}
</Text>
)}
</Tag>
))}
{loading6 ||
error6 ||
(data6?.Answer?.[0] && (
<Tag>
<Tooltip
hasArrow
label={config.branding.text.fqdn_tooltip.format({ protocol: "IPv6" })}
placement="bottom"
>
<Button
height="unset"
minW="unset"
fontSize="xs"
py="0.1rem"
px={2}
mr={2}
variantColor={isSelected(data6.Answer[0].data)}
borderRadius="md"
onClick={() => handleOverride(data6.Answer[0].data)}
>
IPv6
</Button>
</Tooltip>
{loading6 && <Spinner />}
{error6 && <Icon name="warning" />}
{data6?.Answer?.[0] && (
<Text fontSize="xs" fontFamily="mono" as="span" fontWeight={400}>
{data6.Answer[0].data}
</Text>
)}
</Tag>
))}
</Stack>
);
});
ResolvedTarget.displayName = "ResolvedTarget";
export default ResolvedTarget;

View file

@ -4,7 +4,6 @@ import {
AccordionHeader,
AccordionPanel,
Alert,
AlertDescription,
Box,
ButtonGroup,
css,
@ -30,7 +29,7 @@ const FormattedError = ({ keywords, message }) => {
{match}
</Text>
));
return <Text>{errorFmt}</Text>;
return <Text>{keywords.length !== 0 ? errorFmt : message}</Text>;
};
const AccordionHeaderWrapper = styled(Flex)`
@ -79,12 +78,16 @@ const Result = React.forwardRef(
errorMsg = error.response.data.output;
} else if (error && error.message.startsWith("timeout")) {
errorMsg = config.messages.request_timeout;
} else if (error?.response?.statusText) {
errorMsg = startCase(error.response.statusText);
} else if (error && error.message) {
errorMsg = startCase(error.message);
} else {
errorMsg = config.messages.general;
}
error && console.dir(error);
const errorLevel =
(error?.response?.data?.level && statusMap[error.response?.data?.level]) ?? "error";