diff --git a/docs/docs/ui.mdx b/docs/docs/ui.mdx index 2a1f2b6..4b4bc4b 100644 --- a/docs/docs/ui.mdx +++ b/docs/docs/ui.mdx @@ -21,78 +21,12 @@ The `web` subsection contains multiple subsections of its own, should you wish t | `credit` | Developer credit & GitHub Link | ➡️ | | `dns_provider` | DNS over HTTPS Provider | ➡️ | | `external_link` | Link to external site | ➡️ | +| `greeting` | Greeting Modal | ➡️ | | `logo` | Logo & Favicons | ➡️ | | `opengraph` | [OpenGraph](https://ogp.me/) | ➡️ | | `terms` | Terms & Conditions | ➡️ | | `theme` | Colors & Fonts | ➡️ | -## Example - -```yaml -web: - credit: - enable: true - dns_provider: - name: google - url: https://dns.google/resolve - external_link: - enable: true - title: PeeringDB - url: https://www.peeringdb.com/asn/{primary_asn} - help_menu: - enable: true - file: null - title: Help - logo: - dark: /images/hyperglass-light.png - favicons: ui/images/favicons/ - height: null - light: /images/hyperglass-dark.png - width: 384 - opengraph: - height: 1132 - image: /images/hyperglass-opengraph.png - width: 7355 - terms: - enable: true - file: null - title: Terms - text: - cache: Results will be cached for 2 minutes. - fqdn_tooltip: "Use {protocol}" - query_location: Location - query_target: Target - query_type: Query Type - query_vrf: Routing Table - subtitle: AS65001 - title: hyperglass - title_mode: text_only - theme: - default_color_mode: light - colors: - black: "#262626" - blue: "#314cb6" - cyan: "#118ab2" - danger: "#d84b4b" - error: "#ff6b35" - gray: "#c1c7cc" - green: "#35b246" - orange: "#ff6b35" - pink: "#f2607d" - primary: "#118ab2" - purple: "#8d30b5" - red: "#d84b4b" - secondary: "#314cb6" - success: "#35b246" - teal: "#35b299" - warning: "#edae49" - white: "#f7f7f7" - yellow: "#edae49" - fonts: - body: Nunito - mono: Fira Code -``` - ## `credit` | Parameter | Type | Default | Description | @@ -117,14 +51,24 @@ If your organization's policy allows, and you don't mind, I request that you kee | `title` | String | `'PeeringDB'` | Link title/label | | `url` | String | `'https://www.peeringdb.com/asn/{primary_asn}'` | Target URL. `{primary_asn}` will be replaced with the `primary_asn` value from Global Settings | +## `greeting` + +| Parameter | Type | Default | Description | +| :--------- | :-----: | :----------- | :------------------------------------------------------------------------------------------- | +| `enable` | Boolean | `false` | Enable or disable the greeting modal. | +| `file` | String | | Path to a [markdown](https://www.markdownguide.org/) file containing the modal body content. | +| `title` | String | `'Welcome'` | Modal title. | +| `button` | String | `'Continue'` | Button text. | +| `required` | Boolean | `false` | If `true` the user must click the button in order to submit a query. | + ## `logo` -| Parameter | Type | Default | Description | -| :-------- | :-----: | :----------------------------------------------------------- | :------------------------------------------- | -| `light` | String | `'hyperglass/hyperglass/static/images/hyperglass-dark.png'` | Path to logo that will be used in light mode | -| `dark` | String | `'hyperglass/hyperglass/static/images/hyperglass-light.png'` | Path to logo that will be used in dark mode | -| `width` | Integer | `384` | Maximum logo width in pixels | -| `height` | Integer | | Maximum logo height in pixels | +| Parameter | Type | Default | Description | +| :-------- | :-----: | :------------------------------ | :------------------------------------------- | +| `light` | String | `'images/hyperglass-dark.png'` | Path to logo that will be used in light mode | +| `dark` | String | `'images/hyperglass-light.png'` | Path to logo that will be used in dark mode | +| `width` | Integer | `384` | Maximum logo width in pixels | +| `height` | Integer | | Maximum logo height in pixels | ## `opengraph` @@ -218,3 +162,70 @@ Currently, only [Google Fonts](https://fonts.google.com/) are supported. | :-------- | :----: | :----------------------- | :-------------------------------------- | | `body` | String | | Main body font | | `mono` | String | | Monospace font, used for command output | + +## Example + +```yaml +web: + credit: + enable: true + dns_provider: + name: google + url: https://dns.google/resolve + external_link: + enable: true + title: PeeringDB + url: https://www.peeringdb.com/asn/{primary_asn} + help_menu: + enable: true + file: null + title: Help + logo: + dark: /images/hyperglass-light.png + favicons: ui/images/favicons/ + height: null + light: /images/hyperglass-dark.png + width: 384 + opengraph: + height: 1132 + image: /images/hyperglass-opengraph.png + width: 7355 + terms: + enable: true + file: null + title: Terms + text: + cache: Results will be cached for 2 minutes. + fqdn_tooltip: "Use {protocol}" + query_location: Location + query_target: Target + query_type: Query Type + query_vrf: Routing Table + subtitle: AS65001 + title: hyperglass + title_mode: text_only + theme: + default_color_mode: light + colors: + black: "#262626" + blue: "#314cb6" + cyan: "#118ab2" + danger: "#d84b4b" + error: "#ff6b35" + gray: "#c1c7cc" + green: "#35b246" + orange: "#ff6b35" + pink: "#f2607d" + primary: "#118ab2" + purple: "#8d30b5" + red: "#d84b4b" + secondary: "#314cb6" + success: "#35b246" + teal: "#35b299" + warning: "#edae49" + white: "#f7f7f7" + yellow: "#edae49" + fonts: + body: Nunito + mono: Fira Code +``` diff --git a/hyperglass/configuration/__init__.py b/hyperglass/configuration/__init__.py index 28a2827..c265196 100644 --- a/hyperglass/configuration/__init__.py +++ b/hyperglass/configuration/__init__.py @@ -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, }, } ) diff --git a/hyperglass/configuration/models/web.py b/hyperglass/configuration/models/web.py index 644f2b7..d81ae79 100644 --- a/hyperglass/configuration/models/web.py +++ b/hyperglass/configuration/models/web.py @@ -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() diff --git a/hyperglass/ui/components/Greeting.js b/hyperglass/ui/components/Greeting.js new file mode 100644 index 0000000..52184f9 --- /dev/null +++ b/hyperglass/ui/components/Greeting.js @@ -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 ( + + + + {greetingConfig.title} + {!greetingConfig.required && } + + + + + + + + + ); +}; + +Greeting.displayName = "Greeting"; + +export default Greeting; diff --git a/hyperglass/ui/components/HyperglassForm.js b/hyperglass/ui/components/HyperglassForm.js index 928173b..5d0f09f 100644 --- a/hyperglass/ui/components/HyperglassForm.js +++ b/hyperglass/ui/components/HyperglassForm.js @@ -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) diff --git a/hyperglass/ui/components/Layout.js b/hyperglass/ui/components/Layout.js index 5300605..4ce89b7 100644 --- a/hyperglass/ui/components/Layout.js +++ b/hyperglass/ui/components/Layout.js @@ -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} /> )} @@ -75,6 +81,13 @@ const Layout = () => {