1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

add Greeting modal component

This commit is contained in:
checktheroads
2020-04-16 00:26:23 -07:00
parent 2cc153d078
commit dffdffdab4
7 changed files with 262 additions and 89 deletions

View File

@@ -391,6 +391,12 @@ def _build_vrf_help():
return all_help
content_greeting = get_markdown(
config_path=params.web.greeting,
default="",
params={"title": params.web.greeting.title},
)
content_vrf = _build_vrf_help()
content_help_params = copy.copy(content_params)
@@ -436,6 +442,7 @@ _frontend_params.update(
"terms": content_terms,
"credit": content_credit,
"vrf": content_vrf,
"greeting": content_greeting,
},
}
)

View File

@@ -69,6 +69,23 @@ class HelpMenu(HyperglassModel):
title: StrictStr = "Help"
class Greeting(HyperglassModel):
"""Validation model for greeting modal."""
enable: StrictBool = False
file: Optional[FilePath]
title: StrictStr = "Welcome"
button: StrictStr = "Continue"
required: StrictBool = False
@validator("file")
def validate_file(cls, value, values):
"""Ensure file is specified if greeting is enabled."""
if values["enable"] and value is None:
raise ValueError("Greeting is enabled, but no file is specified.")
return value
class Logo(HyperglassModel):
"""Validation model for logo configuration."""
@@ -214,6 +231,7 @@ class Web(HyperglassModel):
credit: Credit = Credit()
dns_provider: DnsOverHttps = DnsOverHttps()
external_link: ExternalLink = ExternalLink()
greeting: Greeting = Greeting()
help_menu: HelpMenu = HelpMenu()
logo: Logo = Logo()
opengraph: OpenGraph = OpenGraph()

View File

@@ -0,0 +1,72 @@
import * as React from "react";
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
useColorMode,
useDisclosure,
} from "@chakra-ui/core";
import MarkDown from "~/components/MarkDown";
import { motion } from "framer-motion";
const bg = { light: "white", dark: "black" };
const color = { light: "black", dark: "white" };
const AnimatedModalContent = motion.custom(ModalContent);
const AnimatedModalOverlay = motion.custom(ModalOverlay);
const Greeting = ({ greetingConfig, content, onClickThrough }) => {
const { isOpen, onOpen, onClose } = useDisclosure(true);
const { colorMode } = useColorMode();
const handleClick = () => {
onClickThrough(true);
onClose();
};
return (
<Modal
onClose={handleClick}
isOpen={isOpen}
size="full"
isCentered
closeOnOverlayClick={!greetingConfig.required}
>
<AnimatedModalOverlay
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3, delay: 0.7 }}
/>
<AnimatedModalContent
initial={{ scale: 0.5, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.3, delay: 0.7 }}
bg={bg[colorMode]}
color={color[colorMode]}
py={4}
borderRadius="md"
maxW={["95%", "75%", "75%", "75%"]}
>
<ModalHeader>{greetingConfig.title}</ModalHeader>
{!greetingConfig.required && <ModalCloseButton />}
<ModalBody>
<MarkDown content={content} />
</ModalBody>
<ModalFooter>
<Button variantColor="primary" onClick={handleClick}>
{greetingConfig.button}
</Button>
</ModalFooter>
</AnimatedModalContent>
</Modal>
);
};
Greeting.displayName = "Greeting";
export default Greeting;

View File

@@ -16,7 +16,7 @@ import useConfig from "~/components/HyperglassProvider";
format.extend(String.prototype, {});
const formSchema = config =>
const formSchema = (config) =>
yup.object().shape({
query_location: yup
.array()
@@ -28,7 +28,7 @@ const formSchema = config =>
query_vrf: yup.string(),
query_target: yup
.string()
.required(config.messages.no_input.format({ field: config.web.text.query_target }))
.required(config.messages.no_input.format({ field: config.web.text.query_target })),
});
const FormRow = ({ children, ...props }) => (
@@ -44,11 +44,11 @@ const FormRow = ({ children, ...props }) => (
);
const HyperglassForm = React.forwardRef(
({ isSubmitting, setSubmitting, setFormData, ...props }, ref) => {
({ isSubmitting, setSubmitting, setFormData, greetingAck, setGreetingAck, ...props }, ref) => {
const config = useConfig();
const { handleSubmit, register, setValue, errors } = useForm({
validationSchema: formSchema(config),
defaultValues: { query_vrf: "default" }
defaultValues: { query_vrf: "default" },
});
const [queryLocation, setQueryLocation] = useState([]);
@@ -59,21 +59,26 @@ const HyperglassForm = React.forwardRef(
const [fqdnTarget, setFqdnTarget] = useState("");
const [displayTarget, setDisplayTarget] = useState("");
const [families, setFamilies] = useState([]);
const onSubmit = values => {
setFormData(values);
setSubmitting(true);
const onSubmit = (values) => {
if (!greetingAck && config.web.greeting.required) {
window.location.reload(false);
setGreetingAck(false);
} else {
setFormData(values);
setSubmitting(true);
}
};
const handleLocChange = locObj => {
const handleLocChange = (locObj) => {
setQueryLocation(locObj.value);
const allVrfs = [];
const deviceVrfs = [];
locObj.value.map(loc => {
locObj.value.map((loc) => {
const locVrfs = [];
config.devices[loc].vrfs.map(vrf => {
config.devices[loc].vrfs.map((vrf) => {
locVrfs.push({
label: vrf.display_name,
value: vrf.id
value: vrf.id,
});
deviceVrfs.push([{ id: vrf.id, ipv4: vrf.ipv4, ipv6: vrf.ipv6 }]);
});
@@ -89,10 +94,10 @@ const HyperglassForm = React.forwardRef(
deviceVrfs.length !== 0 &&
intersecting.length !== 0 &&
deviceVrfs
.filter(v => intersecting.every(i => i.id === v.id))
.filter((v) => intersecting.every((i) => i.id === v.id))
.reduce((a, b) => a.concat(b))
.filter(v => v.id === "default")
.map(v => {
.filter((v) => v.id === "default")
.map((v) => {
v.ipv4 === true && ipv4++;
v.ipv6 === true && ipv6++;
});
@@ -107,7 +112,7 @@ const HyperglassForm = React.forwardRef(
}
};
const handleChange = e => {
const handleChange = (e) => {
setValue(e.field, e.value);
e.field === "query_location"
? handleLocChange(e)

View File

@@ -1,13 +1,15 @@
import React, { useRef, useState } from "react";
import { Flex, useColorMode } from "@chakra-ui/core";
import { Flex, useColorMode, useDisclosure } from "@chakra-ui/core";
import { motion, AnimatePresence } from "framer-motion";
import HyperglassForm from "~/components/HyperglassForm";
import Results from "~/components/Results";
import Header from "~/components/Header";
import Footer from "~/components/Footer";
import Greeting from "~/components/Greeting";
import Meta from "~/components/Meta";
import useConfig from "~/components/HyperglassProvider";
import Debugger from "~/components/Debugger";
import useSessionStorage from "~/hooks/useSessionStorage";
const AnimatedForm = motion.custom(HyperglassForm);
@@ -19,7 +21,9 @@ const Layout = () => {
const { colorMode } = useColorMode();
const [isSubmitting, setSubmitting] = useState(false);
const [formData, setFormData] = useState({});
const [greetingAck, setGreetingAck] = useSessionStorage("hyperglass-greeting-ack", false);
const containerRef = useRef(null);
const handleFormReset = () => {
containerRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
setSubmitting(false);
@@ -68,6 +72,8 @@ const Layout = () => {
isSubmitting={isSubmitting}
setSubmitting={setSubmitting}
setFormData={setFormData}
greetingAck={greetingAck}
setGreetingAck={setGreetingAck}
/>
)}
</AnimatePresence>
@@ -75,6 +81,13 @@ const Layout = () => {
<Footer />
{config.developer_mode && <Debugger />}
</Flex>
{config.web.greeting.enable && !greetingAck && (
<Greeting
greetingConfig={config.web.greeting}
content={config.content.greeting}
onClickThrough={setGreetingAck}
/>
)}
</>
);
};

View File

@@ -0,0 +1,47 @@
/*
react-use: useSessionStorage
https://github.com/streamich/react-use/blob/master/src/useSessionStorage.ts
*/
import { useEffect, useState } from "react";
const useSessionStorage = (key, initialValue, raw) => {
const isClient = typeof window === "object";
if (!isClient) {
return [initialValue, () => {}];
}
const [state, setState] = useState(() => {
try {
const sessionStorageValue = sessionStorage.getItem(key);
if (typeof sessionStorageValue !== "string") {
sessionStorage.setItem(
key,
raw ? String(initialValue) : JSON.stringify(initialValue)
);
return initialValue;
} else {
return raw ? sessionStorageValue : JSON.parse(sessionStorageValue || "null");
}
} catch {
// If user is in private mode or has storage restriction
// sessionStorage can throw. JSON.parse and JSON.stringify
// cat throw, too.
return initialValue;
}
});
useEffect(() => {
try {
const serializedState = raw ? String(state) : JSON.stringify(state);
sessionStorage.setItem(key, serializedState);
} catch {
// If user is in private mode or has storage restriction
// sessionStorage can throw. Also JSON.stringify can throw.
}
});
return [state, setState];
};
export default useSessionStorage;