From 03c58ceb76660f8ba58266d3698e96fff9b46088 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Mon, 27 Jan 2020 21:28:27 -0700 Subject: [PATCH] start: add DNS resolution --- ui/components/FormField.js | 26 ++++++- ui/components/HyperglassForm.js | 42 +++++++++- ui/components/QueryTarget.js | 79 ++++++++++++++----- ui/components/ResolvedTarget.js | 132 ++++++++++++++++++++++++++++++++ ui/components/Result.js | 7 +- 5 files changed, 258 insertions(+), 28 deletions(-) create mode 100644 ui/components/ResolvedTarget.js diff --git a/ui/components/FormField.js b/ui/components/FormField.js index 7abb83d..e86c309 100644 --- a/ui/components/FormField.js +++ b/ui/components/FormField.js @@ -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 && } + {labelAddOn || null} {children} + {fieldAddOn && ( + + {fieldAddOn} + + )} {error && error.message} diff --git a/ui/components/HyperglassForm.js b/ui/components/HyperglassForm.js index 60496d2..fe562cf 100644 --- a/ui/components/HyperglassForm.js +++ b/ui/components/HyperglassForm.js @@ -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 ( + } > @@ -136,10 +156,28 @@ const HyperglassForm = React.forwardRef( label={config.branding.text.query_target} name="query_target" error={errors.query_target} + fieldAddOn={ + validFqdnQueryType && ( + + ) + } > diff --git a/ui/components/QueryTarget.js b/ui/components/QueryTarget.js index 07f7cdb..01e9b63 100644 --- a/ui/components/QueryTarget.js +++ b/ui/components/QueryTarget.js @@ -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}(? { 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 ( - + <> + + + ); }; + +QueryTarget.displayName = "QueryTarget"; +export default QueryTarget; diff --git a/ui/components/ResolvedTarget.js b/ui/components/ResolvedTarget.js new file mode 100644 index 0000000..9a48ab3 --- /dev/null +++ b/ui/components/ResolvedTarget.js @@ -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 ( + + {loading4 || + error4 || + (data4?.Answer?.[0] && ( + + + + + {loading4 && } + {error4 && } + {data4?.Answer?.[0] && ( + + {data4.Answer[0].data} + + )} + + ))} + {loading6 || + error6 || + (data6?.Answer?.[0] && ( + + + + + {loading6 && } + {error6 && } + {data6?.Answer?.[0] && ( + + {data6.Answer[0].data} + + )} + + ))} + + ); +}); + +ResolvedTarget.displayName = "ResolvedTarget"; +export default ResolvedTarget; diff --git a/ui/components/Result.js b/ui/components/Result.js index aec4936..99b76d3 100644 --- a/ui/components/Result.js +++ b/ui/components/Result.js @@ -4,7 +4,6 @@ import { AccordionHeader, AccordionPanel, Alert, - AlertDescription, Box, ButtonGroup, css, @@ -30,7 +29,7 @@ const FormattedError = ({ keywords, message }) => { {match} )); - return {errorFmt}; + return {keywords.length !== 0 ? errorFmt : message}; }; 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";