forked from mirrors/thatmattlove-hyperglass
accessibility improvements
This commit is contained in:
parent
51d3804d07
commit
07508b4b02
16 changed files with 2482 additions and 2485 deletions
|
|
@ -58,8 +58,8 @@ const ChakraSelect = React.forwardRef(
|
|||
);
|
||||
const selectedDisabled = theme.colors.whiteAlpha[400];
|
||||
const placeholderColor = {
|
||||
dark: theme.colors.whiteAlpha[400],
|
||||
light: theme.colors.gray[400]
|
||||
dark: theme.colors.whiteAlpha[700],
|
||||
light: theme.colors.gray[600]
|
||||
};
|
||||
const menuBg = { dark: theme.colors.black, light: theme.colors.white };
|
||||
const menuColor = {
|
||||
|
|
|
|||
|
|
@ -10,119 +10,135 @@ import FooterContent from "~/components/FooterContent";
|
|||
format.extend(String.prototype, {});
|
||||
|
||||
const Footer = () => {
|
||||
const theme = useTheme();
|
||||
const config = useConfig();
|
||||
const { colorMode } = useColorMode();
|
||||
const footerBg = { light: theme.colors.blackAlpha[50], dark: theme.colors.whiteAlpha[100] };
|
||||
const footerColor = { light: theme.colors.black, dark: theme.colors.white };
|
||||
const contentBorder = {
|
||||
light: theme.colors.blackAlpha[100],
|
||||
dark: theme.colors.whiteAlpha[200]
|
||||
};
|
||||
const [helpVisible, showHelp] = useState(false);
|
||||
const [termsVisible, showTerms] = useState(false);
|
||||
const [creditVisible, showCredit] = useState(false);
|
||||
const extUrl = config.web.external_link.url.includes("{primary_asn}")
|
||||
? config.web.external_link.url.format({ primary_asn: config.primary_asn })
|
||||
: config.web.external_link.url || "/";
|
||||
const handleCollapse = i => {
|
||||
if (i === "help") {
|
||||
showTerms(false);
|
||||
showCredit(false);
|
||||
showHelp(!helpVisible);
|
||||
} else if (i === "credit") {
|
||||
showTerms(false);
|
||||
showHelp(false);
|
||||
showCredit(!creditVisible);
|
||||
} else if (i === "terms") {
|
||||
showHelp(false);
|
||||
showCredit(false);
|
||||
showTerms(!termsVisible);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{config.web.help_menu.enable && (
|
||||
<FooterContent
|
||||
isOpen={helpVisible}
|
||||
content={config.content.help_menu}
|
||||
title={config.web.help_menu.title}
|
||||
bg={footerBg[colorMode]}
|
||||
borderColor={contentBorder[colorMode]}
|
||||
side="left"
|
||||
/>
|
||||
)}
|
||||
{config.web.terms.enable && (
|
||||
<FooterContent
|
||||
isOpen={termsVisible}
|
||||
content={config.content.terms}
|
||||
title={config.web.terms.title}
|
||||
bg={footerBg[colorMode]}
|
||||
borderColor={contentBorder[colorMode]}
|
||||
side="left"
|
||||
/>
|
||||
)}
|
||||
{config.web.credit.enable && (
|
||||
<FooterContent
|
||||
isOpen={creditVisible}
|
||||
content={config.content.credit}
|
||||
title={config.web.credit.title}
|
||||
bg={footerBg[colorMode]}
|
||||
borderColor={contentBorder[colorMode]}
|
||||
side="right"
|
||||
/>
|
||||
)}
|
||||
<Flex
|
||||
py={[4, 4, 2, 2]}
|
||||
px={6}
|
||||
w="100%"
|
||||
as="footer"
|
||||
flexWrap="wrap"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
bg={footerBg[colorMode]}
|
||||
color={footerColor[colorMode]}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{config.web.terms.enable && (
|
||||
<FooterButton side="left" onClick={() => handleCollapse("terms")}>
|
||||
{config.web.terms.title}
|
||||
</FooterButton>
|
||||
)}
|
||||
{config.web.help_menu.enable && (
|
||||
<FooterButton side="left" onClick={() => handleCollapse("help")}>
|
||||
{config.web.help_menu.title}
|
||||
</FooterButton>
|
||||
)}
|
||||
<Flex
|
||||
flexBasis="auto"
|
||||
flexGrow={0}
|
||||
flexShrink={0}
|
||||
maxWidth="100%"
|
||||
marginRight="auto"
|
||||
p={0}
|
||||
/>
|
||||
{config.web.credit.enable && (
|
||||
<FooterButton side="right" onClick={() => handleCollapse("credit")}>
|
||||
<FiCode />
|
||||
</FooterButton>
|
||||
)}
|
||||
{config.web.external_link.enable && (
|
||||
<FooterButton
|
||||
as="a"
|
||||
href={extUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="ghost"
|
||||
rightIcon={GoLinkExternal}
|
||||
size="xs"
|
||||
>
|
||||
{config.web.external_link.title}
|
||||
</FooterButton>
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
const theme = useTheme();
|
||||
const config = useConfig();
|
||||
const { colorMode } = useColorMode();
|
||||
const footerBg = {
|
||||
light: theme.colors.blackAlpha[50],
|
||||
dark: theme.colors.whiteAlpha[100]
|
||||
};
|
||||
const footerColor = { light: theme.colors.black, dark: theme.colors.white };
|
||||
const contentBorder = {
|
||||
light: theme.colors.blackAlpha[100],
|
||||
dark: theme.colors.whiteAlpha[200]
|
||||
};
|
||||
const [helpVisible, showHelp] = useState(false);
|
||||
const [termsVisible, showTerms] = useState(false);
|
||||
const [creditVisible, showCredit] = useState(false);
|
||||
const extUrl = config.web.external_link.url.includes("{primary_asn}")
|
||||
? config.web.external_link.url.format({ primary_asn: config.primary_asn })
|
||||
: config.web.external_link.url || "/";
|
||||
const handleCollapse = i => {
|
||||
if (i === "help") {
|
||||
showTerms(false);
|
||||
showCredit(false);
|
||||
showHelp(!helpVisible);
|
||||
} else if (i === "credit") {
|
||||
showTerms(false);
|
||||
showHelp(false);
|
||||
showCredit(!creditVisible);
|
||||
} else if (i === "terms") {
|
||||
showHelp(false);
|
||||
showCredit(false);
|
||||
showTerms(!termsVisible);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{config.web.help_menu.enable && (
|
||||
<FooterContent
|
||||
isOpen={helpVisible}
|
||||
content={config.content.help_menu}
|
||||
title={config.web.help_menu.title}
|
||||
bg={footerBg[colorMode]}
|
||||
borderColor={contentBorder[colorMode]}
|
||||
side="left"
|
||||
/>
|
||||
)}
|
||||
{config.web.terms.enable && (
|
||||
<FooterContent
|
||||
isOpen={termsVisible}
|
||||
content={config.content.terms}
|
||||
title={config.web.terms.title}
|
||||
bg={footerBg[colorMode]}
|
||||
borderColor={contentBorder[colorMode]}
|
||||
side="left"
|
||||
/>
|
||||
)}
|
||||
{config.web.credit.enable && (
|
||||
<FooterContent
|
||||
isOpen={creditVisible}
|
||||
content={config.content.credit}
|
||||
title={config.web.credit.title}
|
||||
bg={footerBg[colorMode]}
|
||||
borderColor={contentBorder[colorMode]}
|
||||
side="right"
|
||||
/>
|
||||
)}
|
||||
<Flex
|
||||
py={[4, 4, 2, 2]}
|
||||
px={6}
|
||||
w="100%"
|
||||
as="footer"
|
||||
flexWrap="wrap"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
bg={footerBg[colorMode]}
|
||||
color={footerColor[colorMode]}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{config.web.terms.enable && (
|
||||
<FooterButton
|
||||
side="left"
|
||||
onClick={() => handleCollapse("terms")}
|
||||
aria-label={config.web.terms.title}
|
||||
>
|
||||
{config.web.terms.title}
|
||||
</FooterButton>
|
||||
)}
|
||||
{config.web.help_menu.enable && (
|
||||
<FooterButton
|
||||
side="left"
|
||||
onClick={() => handleCollapse("help")}
|
||||
aria-label={config.web.help_menu.title}
|
||||
>
|
||||
{config.web.help_menu.title}
|
||||
</FooterButton>
|
||||
)}
|
||||
<Flex
|
||||
flexBasis="auto"
|
||||
flexGrow={0}
|
||||
flexShrink={0}
|
||||
maxWidth="100%"
|
||||
marginRight="auto"
|
||||
p={0}
|
||||
/>
|
||||
{config.web.credit.enable && (
|
||||
<FooterButton
|
||||
side="right"
|
||||
onClick={() => handleCollapse("credit")}
|
||||
aria-label="Powered by hyperglass"
|
||||
>
|
||||
<FiCode />
|
||||
</FooterButton>
|
||||
)}
|
||||
{config.web.external_link.enable && (
|
||||
<FooterButton
|
||||
as="a"
|
||||
href={extUrl}
|
||||
aria-label={config.web.external_link.title}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="ghost"
|
||||
rightIcon={GoLinkExternal}
|
||||
size="xs"
|
||||
>
|
||||
{config.web.external_link.title}
|
||||
</FooterButton>
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Footer.displayName = "Footer";
|
||||
|
|
|
|||
|
|
@ -4,24 +4,29 @@ import { motion } from "framer-motion";
|
|||
|
||||
const AnimatedFlex = motion.custom(Flex);
|
||||
|
||||
export default React.forwardRef(({ onClick, side, children, ...props }, ref) => {
|
||||
const FooterButton = React.forwardRef(
|
||||
({ onClick, side, children, ...props }, ref) => {
|
||||
return (
|
||||
<AnimatedFlex
|
||||
p={0}
|
||||
w="auto"
|
||||
ref={ref}
|
||||
flexGrow={0}
|
||||
float={side}
|
||||
flexShrink={0}
|
||||
maxWidth="100%"
|
||||
flexBasis="auto"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<Button size="xs" variant="ghost" onClick={onClick} {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
</AnimatedFlex>
|
||||
<AnimatedFlex
|
||||
p={0}
|
||||
w="auto"
|
||||
ref={ref}
|
||||
flexGrow={0}
|
||||
float={side}
|
||||
flexShrink={0}
|
||||
maxWidth="100%"
|
||||
flexBasis="auto"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<Button size="xs" variant="ghost" onClick={onClick} {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
</AnimatedFlex>
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
FooterButton.displayName = "FooterButton";
|
||||
export default FooterButton;
|
||||
|
|
|
|||
|
|
@ -1,55 +1,61 @@
|
|||
import React from "react";
|
||||
import { Flex, FormControl, FormLabel, FormErrorMessage, useColorMode } from "@chakra-ui/core";
|
||||
import {
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormErrorMessage,
|
||||
useColorMode
|
||||
} from "@chakra-ui/core";
|
||||
|
||||
export default ({
|
||||
label,
|
||||
name,
|
||||
error,
|
||||
hiddenLabels,
|
||||
helpIcon,
|
||||
targetInfo,
|
||||
setTarget,
|
||||
labelAddOn,
|
||||
fieldAddOn,
|
||||
children,
|
||||
...props
|
||||
label,
|
||||
name,
|
||||
error,
|
||||
hiddenLabels,
|
||||
helpIcon,
|
||||
targetInfo,
|
||||
setTarget,
|
||||
labelAddOn,
|
||||
fieldAddOn,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const { colorMode } = useColorMode();
|
||||
const labelColor = { dark: "whiteAlpha.600", light: "blackAlpha.600" };
|
||||
return (
|
||||
<FormControl
|
||||
as={Flex}
|
||||
flexDirection="column"
|
||||
flex={["1 0 100%", "1 0 100%", "1 0 33.33%", "1 0 33.33%"]}
|
||||
w="100%"
|
||||
maxW="100%"
|
||||
mx={2}
|
||||
my={[2, 2, 4, 4]}
|
||||
isInvalid={error && error.message}
|
||||
{...props}
|
||||
>
|
||||
<FormLabel
|
||||
htmlFor={name}
|
||||
color={labelColor[colorMode]}
|
||||
pl={1}
|
||||
opacity={hiddenLabels ? 0 : null}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
pr={0}
|
||||
>
|
||||
{label}
|
||||
{labelAddOn || null}
|
||||
</FormLabel>
|
||||
{children}
|
||||
{fieldAddOn && (
|
||||
<Flex justifyContent="flex-end" pt={3}>
|
||||
{fieldAddOn}
|
||||
</Flex>
|
||||
)}
|
||||
<FormErrorMessage opacity={hiddenLabels ? 0 : null}>
|
||||
{error && error.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
);
|
||||
const { colorMode } = useColorMode();
|
||||
const labelColor = { dark: "whiteAlpha.700", light: "blackAlpha.700" };
|
||||
return (
|
||||
<FormControl
|
||||
as={Flex}
|
||||
flexDirection="column"
|
||||
flex={["1 0 100%", "1 0 100%", "1 0 33.33%", "1 0 33.33%"]}
|
||||
w="100%"
|
||||
maxW="100%"
|
||||
mx={2}
|
||||
my={[2, 2, 4, 4]}
|
||||
isInvalid={error && error.message}
|
||||
{...props}
|
||||
>
|
||||
<FormLabel
|
||||
htmlFor={name}
|
||||
color={labelColor[colorMode]}
|
||||
pl={1}
|
||||
opacity={hiddenLabels ? 0 : null}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
pr={0}
|
||||
>
|
||||
{label}
|
||||
{labelAddOn || null}
|
||||
</FormLabel>
|
||||
{children}
|
||||
{fieldAddOn && (
|
||||
<Flex justifyContent="flex-end" pt={3}>
|
||||
{fieldAddOn}
|
||||
</Flex>
|
||||
)}
|
||||
<FormErrorMessage opacity={hiddenLabels ? 0 : null}>
|
||||
{error && error.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,236 +18,265 @@ import useConfig from "~/components/HyperglassProvider";
|
|||
|
||||
format.extend(String.prototype, {});
|
||||
|
||||
const formSchema = (config) =>
|
||||
yup.object().shape({
|
||||
query_location: yup
|
||||
.array()
|
||||
.of(yup.string())
|
||||
.required(config.messages.no_input.format({ field: config.web.text.query_location })),
|
||||
query_type: yup
|
||||
.string()
|
||||
.required(config.messages.no_input.format({ field: config.web.text.query_type })),
|
||||
query_vrf: yup.string(),
|
||||
query_target: yup
|
||||
.string()
|
||||
.required(config.messages.no_input.format({ field: config.web.text.query_target })),
|
||||
});
|
||||
const formSchema = config =>
|
||||
yup.object().shape({
|
||||
query_location: yup
|
||||
.array()
|
||||
.of(yup.string())
|
||||
.required(
|
||||
config.messages.no_input.format({
|
||||
field: config.web.text.query_location
|
||||
})
|
||||
),
|
||||
query_type: yup
|
||||
.string()
|
||||
.required(
|
||||
config.messages.no_input.format({ field: config.web.text.query_type })
|
||||
),
|
||||
query_vrf: yup.string(),
|
||||
query_target: yup
|
||||
.string()
|
||||
.required(
|
||||
config.messages.no_input.format({ field: config.web.text.query_target })
|
||||
)
|
||||
});
|
||||
|
||||
const FormRow = ({ children, ...props }) => (
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
flexWrap="wrap"
|
||||
w="100%"
|
||||
justifyContent={["center", "center", "space-between", "space-between"]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
flexWrap="wrap"
|
||||
w="100%"
|
||||
justifyContent={["center", "center", "space-between", "space-between"]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const HyperglassForm = React.forwardRef(
|
||||
({ isSubmitting, setSubmitting, setFormData, greetingAck, setGreetingAck, ...props }, ref) => {
|
||||
const config = useConfig();
|
||||
const { handleSubmit, register, unregister, setValue, errors } = useForm({
|
||||
validationSchema: formSchema(config),
|
||||
defaultValues: { query_vrf: "default", query_target: "" },
|
||||
(
|
||||
{
|
||||
isSubmitting,
|
||||
setSubmitting,
|
||||
setFormData,
|
||||
greetingAck,
|
||||
setGreetingAck,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const config = useConfig();
|
||||
const { handleSubmit, register, unregister, setValue, errors } = useForm({
|
||||
validationSchema: formSchema(config),
|
||||
defaultValues: { query_vrf: "default", query_target: "" }
|
||||
});
|
||||
|
||||
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 [families, setFamilies] = useState([]);
|
||||
const onSubmit = values => {
|
||||
if (!greetingAck && config.web.greeting.required) {
|
||||
window.location.reload(false);
|
||||
setGreetingAck(false);
|
||||
} else {
|
||||
setFormData(values);
|
||||
setSubmitting(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);
|
||||
});
|
||||
|
||||
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 [families, setFamilies] = useState([]);
|
||||
const onSubmit = (values) => {
|
||||
if (!greetingAck && config.web.greeting.required) {
|
||||
window.location.reload(false);
|
||||
setGreetingAck(false);
|
||||
} else {
|
||||
setFormData(values);
|
||||
setSubmitting(true);
|
||||
}
|
||||
};
|
||||
const intersecting = lodash.intersectionWith(...allVrfs, lodash.isEqual);
|
||||
setAvailVrfs(intersecting);
|
||||
!intersecting.includes(queryVrf) &&
|
||||
queryVrf !== "default" &&
|
||||
setQueryVrf("default");
|
||||
|
||||
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);
|
||||
});
|
||||
let ipv4 = 0;
|
||||
let ipv6 = 0;
|
||||
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++;
|
||||
});
|
||||
if (ipv4 !== 0 && ipv4 === ipv6) {
|
||||
setFamilies([4, 6]);
|
||||
} else if (ipv4 > ipv6) {
|
||||
setFamilies([4]);
|
||||
} else if (ipv4 < ipv6) {
|
||||
setFamilies([6]);
|
||||
} else {
|
||||
setFamilies([]);
|
||||
}
|
||||
};
|
||||
|
||||
const intersecting = lodash.intersectionWith(...allVrfs, lodash.isEqual);
|
||||
setAvailVrfs(intersecting);
|
||||
!intersecting.includes(queryVrf) && queryVrf !== "default" && setQueryVrf("default");
|
||||
const handleChange = e => {
|
||||
setValue(e.field, e.value);
|
||||
e.field === "query_location"
|
||||
? handleLocChange(e)
|
||||
: e.field === "query_type"
|
||||
? setQueryType(e.value)
|
||||
: e.field === "query_vrf"
|
||||
? setQueryVrf(e.value)
|
||||
: e.field === "query_target"
|
||||
? setQueryTarget(e.value)
|
||||
: null;
|
||||
};
|
||||
|
||||
let ipv4 = 0;
|
||||
let ipv6 = 0;
|
||||
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++;
|
||||
});
|
||||
if (ipv4 !== 0 && ipv4 === ipv6) {
|
||||
setFamilies([4, 6]);
|
||||
} else if (ipv4 > ipv6) {
|
||||
setFamilies([4]);
|
||||
} else if (ipv4 < ipv6) {
|
||||
setFamilies([6]);
|
||||
} else {
|
||||
setFamilies([]);
|
||||
}
|
||||
};
|
||||
const vrfContent = config.content.vrf[queryVrf]?.[queryType];
|
||||
|
||||
const handleChange = (e) => {
|
||||
setValue(e.field, e.value);
|
||||
e.field === "query_location"
|
||||
? handleLocChange(e)
|
||||
: e.field === "query_type"
|
||||
? setQueryType(e.value)
|
||||
: e.field === "query_vrf"
|
||||
? setQueryVrf(e.value)
|
||||
: e.field === "query_target"
|
||||
? setQueryTarget(e.value)
|
||||
: null;
|
||||
};
|
||||
const validFqdnQueryType =
|
||||
["ping", "traceroute", "bgp_route"].includes(queryType) &&
|
||||
fqdnTarget &&
|
||||
queryVrf === "default"
|
||||
? fqdnTarget
|
||||
: 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_type" });
|
||||
register({ name: "query_vrf" });
|
||||
}, [register]);
|
||||
Object.keys(errors).length >= 1 && console.error(errors);
|
||||
return (
|
||||
<Box
|
||||
maxW={["100%", "100%", "75%", "75%"]}
|
||||
w="100%"
|
||||
p={0}
|
||||
mx="auto"
|
||||
my={4}
|
||||
textAlign="left"
|
||||
ref={ref}
|
||||
{...props}
|
||||
useEffect(() => {
|
||||
register({ name: "query_location" });
|
||||
register({ name: "query_type" });
|
||||
register({ name: "query_vrf" });
|
||||
}, [register]);
|
||||
Object.keys(errors).length >= 1 && console.error(errors);
|
||||
return (
|
||||
<Box
|
||||
maxW={["100%", "100%", "75%", "75%"]}
|
||||
w="100%"
|
||||
p={0}
|
||||
mx="auto"
|
||||
my={4}
|
||||
textAlign="left"
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<FormRow>
|
||||
<FormField
|
||||
label={config.web.text.query_location}
|
||||
name="query_location"
|
||||
error={errors.query_location}
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<FormRow>
|
||||
<FormField
|
||||
label={config.web.text.query_location}
|
||||
name="query_location"
|
||||
error={errors.query_location}
|
||||
>
|
||||
<QueryLocation onChange={handleChange} locations={config.networks} />
|
||||
</FormField>
|
||||
<FormField
|
||||
label={config.web.text.query_type}
|
||||
name="query_type"
|
||||
error={errors.query_type}
|
||||
labelAddOn={
|
||||
vrfContent && <HelpModal item={vrfContent} name="query_type" />
|
||||
}
|
||||
>
|
||||
<QueryType onChange={handleChange} queryTypes={config.queries.list} />
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
{availVrfs.length > 1 && (
|
||||
<FormField
|
||||
label={config.web.text.query_vrf}
|
||||
name="query_vrf"
|
||||
error={errors.query_vrf}
|
||||
>
|
||||
<QueryVrf
|
||||
placeholder={config.web.text.query_vrf}
|
||||
vrfs={availVrfs}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
<FormField
|
||||
label={config.web.text.query_target}
|
||||
name="query_target"
|
||||
error={errors.query_target}
|
||||
fieldAddOn={
|
||||
queryLocation.length !== 0 &&
|
||||
validFqdnQueryType && (
|
||||
<ResolvedTarget
|
||||
queryTarget={queryTarget}
|
||||
fqdnTarget={validFqdnQueryType}
|
||||
setTarget={handleChange}
|
||||
families={families}
|
||||
availVrfs={availVrfs}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{queryType === "bgp_community" &&
|
||||
config.queries.bgp_community.mode === "select" ? (
|
||||
<CommunitySelect
|
||||
name="query_target"
|
||||
register={register}
|
||||
unregister={unregister}
|
||||
onChange={handleChange}
|
||||
communities={config.queries.bgp_community.communities}
|
||||
/>
|
||||
) : (
|
||||
<QueryTarget
|
||||
name="query_target"
|
||||
placeholder={config.web.text.query_target}
|
||||
register={register}
|
||||
unregister={unregister}
|
||||
resolveTarget={["ping", "traceroute", "bgp_route"].includes(
|
||||
queryType
|
||||
)}
|
||||
value={queryTarget}
|
||||
setFqdn={setFqdnTarget}
|
||||
setTarget={handleChange}
|
||||
displayValue={displayTarget}
|
||||
setDisplayValue={setDisplayTarget}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow mt={0} justifyContent="flex-end">
|
||||
<Flex
|
||||
w="100%"
|
||||
maxW="100%"
|
||||
ml="auto"
|
||||
my={2}
|
||||
mr={[0, 0, 2, 2]}
|
||||
flexDirection="column"
|
||||
flex="0 0 0"
|
||||
>
|
||||
<SubmitButton isLoading={isSubmitting} />
|
||||
</Flex>
|
||||
</FormRow>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
<QueryLocation
|
||||
onChange={handleChange}
|
||||
locations={config.networks}
|
||||
label={config.web.text.query_location}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label={config.web.text.query_type}
|
||||
name="query_type"
|
||||
error={errors.query_type}
|
||||
labelAddOn={
|
||||
vrfContent && <HelpModal item={vrfContent} name="query_type" />
|
||||
}
|
||||
>
|
||||
<QueryType
|
||||
onChange={handleChange}
|
||||
queryTypes={config.queries.list}
|
||||
label={config.web.text.query_type}
|
||||
/>
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
{availVrfs.length > 1 && (
|
||||
<FormField
|
||||
label={config.web.text.query_vrf}
|
||||
name="query_vrf"
|
||||
error={errors.query_vrf}
|
||||
>
|
||||
<QueryVrf
|
||||
label={config.web.text.query_vrf}
|
||||
vrfs={availVrfs}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
<FormField
|
||||
label={config.web.text.query_target}
|
||||
name="query_target"
|
||||
error={errors.query_target}
|
||||
fieldAddOn={
|
||||
queryLocation.length !== 0 &&
|
||||
validFqdnQueryType && (
|
||||
<ResolvedTarget
|
||||
queryTarget={queryTarget}
|
||||
fqdnTarget={validFqdnQueryType}
|
||||
setTarget={handleChange}
|
||||
families={families}
|
||||
availVrfs={availVrfs}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{queryType === "bgp_community" &&
|
||||
config.queries.bgp_community.mode === "select" ? (
|
||||
<CommunitySelect
|
||||
label={config.queries.bgp_community.display_name}
|
||||
name="query_target"
|
||||
register={register}
|
||||
unregister={unregister}
|
||||
onChange={handleChange}
|
||||
communities={config.queries.bgp_community.communities}
|
||||
/>
|
||||
) : (
|
||||
<QueryTarget
|
||||
name="query_target"
|
||||
placeholder={config.web.text.query_target}
|
||||
register={register}
|
||||
unregister={unregister}
|
||||
resolveTarget={["ping", "traceroute", "bgp_route"].includes(
|
||||
queryType
|
||||
)}
|
||||
value={queryTarget}
|
||||
setFqdn={setFqdnTarget}
|
||||
setTarget={handleChange}
|
||||
displayValue={displayTarget}
|
||||
setDisplayValue={setDisplayTarget}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</FormRow>
|
||||
<FormRow mt={0} justifyContent="flex-end">
|
||||
<Flex
|
||||
w="100%"
|
||||
maxW="100%"
|
||||
ml="auto"
|
||||
my={2}
|
||||
mr={[0, 0, 2, 2]}
|
||||
flexDirection="column"
|
||||
flex="0 0 0"
|
||||
>
|
||||
<SubmitButton isLoading={isSubmitting} />
|
||||
</Flex>
|
||||
</FormRow>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
HyperglassForm.displayName = "HyperglassForm";
|
||||
|
|
|
|||
|
|
@ -1,40 +1,44 @@
|
|||
import React from "react";
|
||||
import ChakraSelect from "~/components/ChakraSelect";
|
||||
|
||||
const buildLocations = (networks) => {
|
||||
const locations = [];
|
||||
networks.map((net) => {
|
||||
const netLocations = [];
|
||||
net.locations.map((loc) => {
|
||||
netLocations.push({
|
||||
label: loc.display_name,
|
||||
value: loc.name,
|
||||
group: net.display_name,
|
||||
});
|
||||
});
|
||||
locations.push({ label: net.display_name, options: netLocations });
|
||||
const buildLocations = networks => {
|
||||
const locations = [];
|
||||
networks.map(net => {
|
||||
const netLocations = [];
|
||||
net.locations.map(loc => {
|
||||
netLocations.push({
|
||||
label: loc.display_name,
|
||||
value: loc.name,
|
||||
group: net.display_name
|
||||
});
|
||||
});
|
||||
return locations;
|
||||
locations.push({ label: net.display_name, options: netLocations });
|
||||
});
|
||||
return locations;
|
||||
};
|
||||
|
||||
export default ({ locations, onChange }) => {
|
||||
const options = buildLocations(locations);
|
||||
const handleChange = (e) => {
|
||||
const selected = [];
|
||||
e &&
|
||||
e.map((sel) => {
|
||||
selected.push(sel.value);
|
||||
});
|
||||
onChange({ field: "query_location", value: selected });
|
||||
};
|
||||
return (
|
||||
<ChakraSelect
|
||||
size="lg"
|
||||
name="query_location"
|
||||
onChange={handleChange}
|
||||
options={options}
|
||||
isMulti
|
||||
closeMenuOnSelect={false}
|
||||
/>
|
||||
);
|
||||
const QueryLocation = ({ locations, onChange, label }) => {
|
||||
const options = buildLocations(locations);
|
||||
const handleChange = e => {
|
||||
const selected = [];
|
||||
e &&
|
||||
e.map(sel => {
|
||||
selected.push(sel.value);
|
||||
});
|
||||
onChange({ field: "query_location", value: selected });
|
||||
};
|
||||
return (
|
||||
<ChakraSelect
|
||||
size="lg"
|
||||
name="query_location"
|
||||
onChange={handleChange}
|
||||
options={options}
|
||||
isMulti
|
||||
closeMenuOnSelect={false}
|
||||
aria-label={label}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
QueryLocation.displayName = "QueryLocation";
|
||||
export default QueryLocation;
|
||||
|
|
|
|||
|
|
@ -1,74 +1,71 @@
|
|||
import React, { useEffect } from "react";
|
||||
import styled from "@emotion/styled";
|
||||
import * as React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Input, useColorMode } from "@chakra-ui/core";
|
||||
|
||||
const StyledInput = styled(Input)`
|
||||
&::placeholder {
|
||||
color: ${(props) => props.placeholderColor};
|
||||
}
|
||||
`;
|
||||
|
||||
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.400", light: "gray.400" };
|
||||
const placeholderColor = { dark: "whiteAlpha.700", light: "gray.600" };
|
||||
|
||||
const QueryTarget = ({
|
||||
placeholder,
|
||||
register,
|
||||
unregister,
|
||||
setFqdn,
|
||||
name,
|
||||
value,
|
||||
setTarget,
|
||||
resolveTarget,
|
||||
displayValue,
|
||||
setDisplayValue,
|
||||
placeholder,
|
||||
register,
|
||||
unregister,
|
||||
setFqdn,
|
||||
name,
|
||||
value,
|
||||
setTarget,
|
||||
resolveTarget,
|
||||
displayValue,
|
||||
setDisplayValue
|
||||
}) => {
|
||||
const { colorMode } = useColorMode();
|
||||
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} />
|
||||
<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]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
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]
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
QueryTarget.displayName = "QueryTarget";
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
import React from "react";
|
||||
import ChakraSelect from "~/components/ChakraSelect";
|
||||
|
||||
export default ({ queryTypes, onChange }) => {
|
||||
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}
|
||||
/>
|
||||
);
|
||||
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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
QueryType.displayName = "QueryType";
|
||||
export default QueryType;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import React from "react";
|
||||
import ChakraSelect from "~/components/ChakraSelect";
|
||||
|
||||
export default ({ vrfs, onChange }) => {
|
||||
return (
|
||||
<ChakraSelect
|
||||
size="lg"
|
||||
onChange={e => onChange({ field: "query_vrf", value: e.value })}
|
||||
name="query_vrf"
|
||||
options={vrfs}
|
||||
/>
|
||||
);
|
||||
const QueryVrf = ({ vrfs, onChange, label }) => {
|
||||
return (
|
||||
<ChakraSelect
|
||||
size="lg"
|
||||
onChange={e => onChange({ field: "query_vrf", value: e.value })}
|
||||
name="query_vrf"
|
||||
options={vrfs}
|
||||
aria-label={label}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
QueryVrf.displayName = "QueryVrf";
|
||||
export default QueryVrf;
|
||||
|
|
|
|||
|
|
@ -1,111 +1,130 @@
|
|||
import React from "react";
|
||||
import { Box, PseudoBox, Spinner, useColorMode, useTheme } from "@chakra-ui/core";
|
||||
import {
|
||||
Box,
|
||||
PseudoBox,
|
||||
Spinner,
|
||||
useColorMode,
|
||||
useTheme
|
||||
} from "@chakra-ui/core";
|
||||
import { FiSearch } from "react-icons/fi";
|
||||
import { opposingColor } from "~/util";
|
||||
|
||||
const btnProps = {
|
||||
display: "inline-flex",
|
||||
appearance: "none",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: "all 250ms",
|
||||
userSelect: "none",
|
||||
position: "relative",
|
||||
whiteSpace: "nowrap",
|
||||
verticalAlign: "middle",
|
||||
lineHeight: "1.2",
|
||||
outline: "none",
|
||||
as: "button",
|
||||
type: "submit",
|
||||
borderRadius: "md",
|
||||
fontWeight: "semibold"
|
||||
display: "inline-flex",
|
||||
appearance: "none",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: "all 250ms",
|
||||
userSelect: "none",
|
||||
position: "relative",
|
||||
whiteSpace: "nowrap",
|
||||
verticalAlign: "middle",
|
||||
lineHeight: "1.2",
|
||||
outline: "none",
|
||||
as: "button",
|
||||
type: "submit",
|
||||
borderRadius: "md",
|
||||
fontWeight: "semibold"
|
||||
};
|
||||
|
||||
const btnSizeMap = {
|
||||
lg: {
|
||||
height: 12,
|
||||
minWidth: 12,
|
||||
fontSize: "lg",
|
||||
px: 6
|
||||
},
|
||||
md: {
|
||||
height: 10,
|
||||
minWidth: 10,
|
||||
fontSize: "md",
|
||||
px: 4
|
||||
},
|
||||
sm: {
|
||||
height: 8,
|
||||
minWidth: 8,
|
||||
fontSize: "sm",
|
||||
px: 3
|
||||
},
|
||||
xs: {
|
||||
height: 6,
|
||||
minWidth: 6,
|
||||
fontSize: "xs",
|
||||
px: 2
|
||||
}
|
||||
lg: {
|
||||
height: 12,
|
||||
minWidth: 12,
|
||||
fontSize: "lg",
|
||||
px: 6
|
||||
},
|
||||
md: {
|
||||
height: 10,
|
||||
minWidth: 10,
|
||||
fontSize: "md",
|
||||
px: 4
|
||||
},
|
||||
sm: {
|
||||
height: 8,
|
||||
minWidth: 8,
|
||||
fontSize: "sm",
|
||||
px: 3
|
||||
},
|
||||
xs: {
|
||||
height: 6,
|
||||
minWidth: 6,
|
||||
fontSize: "xs",
|
||||
px: 2
|
||||
}
|
||||
};
|
||||
|
||||
export default React.forwardRef(
|
||||
(
|
||||
{
|
||||
isLoading = false,
|
||||
isDisabled = false,
|
||||
isActive = false,
|
||||
isFullWidth = false,
|
||||
size = "lg",
|
||||
loadingText,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const _isDisabled = isDisabled || isLoading;
|
||||
const { colorMode } = useColorMode();
|
||||
const theme = useTheme();
|
||||
const btnBg = { dark: theme.colors.primary[300], light: theme.colors.primary[500] };
|
||||
const btnBgActive = { dark: theme.colors.primary[400], light: theme.colors.primary[600] };
|
||||
const btnBgHover = { dark: theme.colors.primary[200], light: theme.colors.primary[400] };
|
||||
const btnColor = opposingColor(theme, btnBg[colorMode]);
|
||||
const btnColorActive = opposingColor(theme, btnBgActive[colorMode]);
|
||||
const btnColorHover = opposingColor(theme, btnBgHover[colorMode]);
|
||||
const btnSize = btnSizeMap[size];
|
||||
return (
|
||||
<PseudoBox
|
||||
ref={ref}
|
||||
disabled={_isDisabled}
|
||||
aria-disabled={_isDisabled}
|
||||
width={isFullWidth ? "full" : undefined}
|
||||
data-active={isActive ? "true" : undefined}
|
||||
bg={btnBg[colorMode]}
|
||||
color={btnColor}
|
||||
_active={{ bg: btnBgActive[colorMode], color: btnColorActive }}
|
||||
_hover={{ bg: btnBgHover[colorMode], color: btnColorHover }}
|
||||
_focus={{ boxShadow: theme.shadows.outline }}
|
||||
{...btnProps}
|
||||
{...btnSize}
|
||||
{...props}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Spinner
|
||||
position={loadingText ? "relative" : "absolute"}
|
||||
mr={loadingText ? 2 : 0}
|
||||
color="currentColor"
|
||||
size="1em"
|
||||
/>
|
||||
) : (
|
||||
<FiSearch color={btnColor} />
|
||||
)}
|
||||
{isLoading
|
||||
? loadingText || (
|
||||
<Box as="span" opacity="0">
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
: children}
|
||||
</PseudoBox>
|
||||
);
|
||||
}
|
||||
const SubmitButton = React.forwardRef(
|
||||
(
|
||||
{
|
||||
isLoading = false,
|
||||
isDisabled = false,
|
||||
isActive = false,
|
||||
isFullWidth = false,
|
||||
size = "lg",
|
||||
loadingText,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const _isDisabled = isDisabled || isLoading;
|
||||
const { colorMode } = useColorMode();
|
||||
const theme = useTheme();
|
||||
const btnBg = {
|
||||
dark: theme.colors.primary[300],
|
||||
light: theme.colors.primary[500]
|
||||
};
|
||||
const btnBgActive = {
|
||||
dark: theme.colors.primary[400],
|
||||
light: theme.colors.primary[600]
|
||||
};
|
||||
const btnBgHover = {
|
||||
dark: theme.colors.primary[200],
|
||||
light: theme.colors.primary[400]
|
||||
};
|
||||
const btnColor = opposingColor(theme, btnBg[colorMode]);
|
||||
const btnColorActive = opposingColor(theme, btnBgActive[colorMode]);
|
||||
const btnColorHover = opposingColor(theme, btnBgHover[colorMode]);
|
||||
const btnSize = btnSizeMap[size];
|
||||
return (
|
||||
<PseudoBox
|
||||
ref={ref}
|
||||
disabled={_isDisabled}
|
||||
aria-disabled={_isDisabled}
|
||||
aria-label="Submit Query"
|
||||
width={isFullWidth ? "full" : undefined}
|
||||
data-active={isActive ? "true" : undefined}
|
||||
bg={btnBg[colorMode]}
|
||||
color={btnColor}
|
||||
_active={{ bg: btnBgActive[colorMode], color: btnColorActive }}
|
||||
_hover={{ bg: btnBgHover[colorMode], color: btnColorHover }}
|
||||
_focus={{ boxShadow: theme.shadows.outline }}
|
||||
{...btnProps}
|
||||
{...btnSize}
|
||||
{...props}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Spinner
|
||||
position={loadingText ? "relative" : "absolute"}
|
||||
mr={loadingText ? 2 : 0}
|
||||
color="currentColor"
|
||||
size="1em"
|
||||
/>
|
||||
) : (
|
||||
<FiSearch color={btnColor} />
|
||||
)}
|
||||
{isLoading
|
||||
? loadingText || (
|
||||
<Box as="span" opacity="0">
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
: children}
|
||||
</PseudoBox>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SubmitButton.displayName = "SubmitButton";
|
||||
export default SubmitButton;
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
import { generate } from "namor";
|
||||
|
||||
const range = (len) => {
|
||||
const arr = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr.push(i);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
export const newPerson = () => {
|
||||
const statusChance = Math.random();
|
||||
return {
|
||||
name: generate({ words: 2, numbers: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100),
|
||||
progress: Math.floor(Math.random() * 100),
|
||||
status:
|
||||
statusChance > 0.66 ? "relationship" : statusChance > 0.33 ? "complicated" : "single",
|
||||
};
|
||||
};
|
||||
|
||||
export default function makeData(...lens) {
|
||||
const makeDataLevel = (depth = 0) => {
|
||||
const len = lens[depth];
|
||||
return range(len).map((d) => {
|
||||
return {
|
||||
...newPerson(),
|
||||
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return makeDataLevel();
|
||||
}
|
||||
|
|
@ -4,18 +4,18 @@ const { configFile } = envVars;
|
|||
const config = require(String(configFile));
|
||||
|
||||
module.exports = {
|
||||
webpack(config) {
|
||||
const { alias } = config.resolve;
|
||||
config.resolve.alias = {
|
||||
...alias,
|
||||
...aliases
|
||||
};
|
||||
return config;
|
||||
},
|
||||
poweredByHeader: false,
|
||||
env: {
|
||||
_NODE_ENV_: config.NODE_ENV,
|
||||
_HYPERGLASS_URL_: config._HYPERGLASS_URL_,
|
||||
_HYPERGLASS_CONFIG_: config._HYPERGLASS_CONFIG_
|
||||
}
|
||||
webpack(config) {
|
||||
const { alias } = config.resolve;
|
||||
config.resolve.alias = {
|
||||
...alias,
|
||||
...aliases
|
||||
};
|
||||
return config;
|
||||
},
|
||||
poweredByHeader: false,
|
||||
env: {
|
||||
_NODE_ENV_: config.NODE_ENV,
|
||||
_HYPERGLASS_URL_: config._HYPERGLASS_URL_,
|
||||
_HYPERGLASS_CONFIG_: config._HYPERGLASS_CONFIG_
|
||||
}
|
||||
};
|
||||
|
|
|
|||
190
hyperglass/ui/package.json
vendored
190
hyperglass/ui/package.json
vendored
|
|
@ -1,99 +1,103 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "1.0.0",
|
||||
"description": "UI for hyperglass, the modern network looking glass",
|
||||
"author": "Matt Love",
|
||||
"license": "BSD-3-Clause-Clear",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "node nextdev",
|
||||
"build": "next build && next export -o ../hyperglass/static/ui",
|
||||
"start": "next start"
|
||||
"name": "ui",
|
||||
"version": "1.0.0",
|
||||
"description": "UI for hyperglass, the modern network looking glass",
|
||||
"author": "Matt Love",
|
||||
"license": "BSD-3-Clause-Clear",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "node nextdev",
|
||||
"build": "next build && next export -o ../hyperglass/static/ui",
|
||||
"start": "next start",
|
||||
"clean": "rimraf --no-glob ./.next ./out",
|
||||
"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/core": "^0.7.0",
|
||||
"@emotion/core": "^10.0.28",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@styled-system/should-forward-prop": "^5.1.5",
|
||||
"axios": "^0.19.2",
|
||||
"axios-hooks": "^1.9.0",
|
||||
"chroma-js": "^2.1.0",
|
||||
"dayjs": "^1.8.25",
|
||||
"emotion-theming": "^10.0.27",
|
||||
"framer-motion": "^1.10.0",
|
||||
"lodash": "^4.17.15",
|
||||
"next": "^9.3.1",
|
||||
"react": "^16.13.1",
|
||||
"react-countdown": "^2.2.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hook-form": "^5.1.1",
|
||||
"react-icons": "^3.9.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-select": "^3.0.8",
|
||||
"react-string-replace": "^0.4.4",
|
||||
"react-table": "^7.0.4",
|
||||
"react-textfit": "^1.1.0",
|
||||
"string-format": "^2.0.0",
|
||||
"styled-system": "^5.1.5",
|
||||
"use-media": "^1.4.0",
|
||||
"yup": "^0.28.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
||||
"@typescript-eslint/parser": "^2.24.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"es-check": "^5.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb": "^18.1.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-config-react-app": "^5.2.0",
|
||||
"eslint-import-resolver-webpack": "^0.12.1",
|
||||
"eslint-plugin-flowtype": "^4.6.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"express": "^4.17.1",
|
||||
"http-proxy-middleware": "0.20.0",
|
||||
"prettier": "^1.19.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"max-len": [
|
||||
"error",
|
||||
100
|
||||
],
|
||||
"react/prop-types": 0,
|
||||
"react/jsx-filename-extension": 0,
|
||||
"react/jsx-props-no-spreading": 0,
|
||||
"no-bitwise": 0,
|
||||
"object-shorthand": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-unused-expressions": 0,
|
||||
"no-nested-ternary": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"camelcase": 0
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/core": "^0.6.1",
|
||||
"@emotion/core": "^10.0.28",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@styled-system/should-forward-prop": "^5.1.5",
|
||||
"axios": "^0.19.2",
|
||||
"axios-hooks": "^1.9.0",
|
||||
"chroma-js": "^2.1.0",
|
||||
"dayjs": "^1.8.25",
|
||||
"emotion-theming": "^10.0.27",
|
||||
"framer-motion": "^1.10.0",
|
||||
"lodash": "^4.17.15",
|
||||
"namor": "^2.0.2",
|
||||
"next": "^9.3.1",
|
||||
"react": "^16.13.1",
|
||||
"react-countdown": "^2.2.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hook-form": "^5.1.1",
|
||||
"react-icons": "^3.9.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-select": "^3.0.8",
|
||||
"react-string-replace": "^0.4.4",
|
||||
"react-table": "^7.0.4",
|
||||
"react-textfit": "^1.1.0",
|
||||
"string-format": "^2.0.0",
|
||||
"styled-system": "^5.1.5",
|
||||
"use-media": "^1.4.0",
|
||||
"yup": "^0.28.3"
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"prettier",
|
||||
"prettier/react"
|
||||
],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
||||
"@typescript-eslint/parser": "^2.24.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb": "^18.1.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-config-react-app": "^5.2.0",
|
||||
"eslint-import-resolver-webpack": "^0.12.1",
|
||||
"eslint-plugin-flowtype": "^4.6.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"express": "^4.17.1",
|
||||
"http-proxy-middleware": "0.20.0",
|
||||
"prettier": "^1.19.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"max-len": [
|
||||
"error",
|
||||
100
|
||||
],
|
||||
"react/prop-types": 0,
|
||||
"react/jsx-filename-extension": 0,
|
||||
"react/jsx-props-no-spreading": 0,
|
||||
"no-bitwise": 0,
|
||||
"object-shorthand": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-unused-expressions": 0,
|
||||
"no-nested-ternary": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"camelcase": 0
|
||||
},
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"prettier",
|
||||
"prettier/react"
|
||||
],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"settings": {
|
||||
"import/core-modules": [
|
||||
"styled-jsx/css"
|
||||
],
|
||||
"import/resolver": {
|
||||
"webpack": {
|
||||
"config": "webpack.config.js"
|
||||
}
|
||||
}
|
||||
"settings": {
|
||||
"import/core-modules": [
|
||||
"styled-jsx/css"
|
||||
],
|
||||
"import/resolver": {
|
||||
"webpack": {
|
||||
"config": "webpack.config.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,23 +2,24 @@ import React from "react";
|
|||
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx) {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return { ...initialProps };
|
||||
}
|
||||
static async getInitialProps(ctx) {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return { ...initialProps };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<body>
|
||||
<script src="noflash.js" />
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head></Head>
|
||||
<body>
|
||||
<script src="noflash.js" />
|
||||
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument;
|
||||
|
|
|
|||
|
|
@ -2,97 +2,97 @@ import React from "react";
|
|||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
Button,
|
||||
CSSReset,
|
||||
Flex,
|
||||
Heading,
|
||||
Text,
|
||||
ThemeProvider,
|
||||
useColorMode
|
||||
Button,
|
||||
CSSReset,
|
||||
Flex,
|
||||
Heading,
|
||||
Text,
|
||||
ThemeProvider,
|
||||
useColorMode
|
||||
} from "@chakra-ui/core";
|
||||
import { inRange } from "lodash";
|
||||
import { defaultTheme } from "~/theme";
|
||||
|
||||
const ColorModeProvider = dynamic(
|
||||
() => import("@chakra-ui/core").then(mod => mod.ColorModeProvider),
|
||||
{ ssr: false }
|
||||
() => 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>
|
||||
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>
|
||||
);
|
||||
<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>
|
||||
);
|
||||
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 };
|
||||
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
|
||||
const msg = err ? err.message : res.req?.url || "Error";
|
||||
return { msg, statusCode };
|
||||
};
|
||||
|
||||
export default ErrorPage;
|
||||
|
|
|
|||
3211
hyperglass/ui/yarn.lock
vendored
3211
hyperglass/ui/yarn.lock
vendored
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue