diff --git a/hyperglass/configuration/models/queries.py b/hyperglass/configuration/models/queries.py
index ea11b8b..3aea4fd 100644
--- a/hyperglass/configuration/models/queries.py
+++ b/hyperglass/configuration/models/queries.py
@@ -1,5 +1,8 @@
"""Validate query configuration parameters."""
+# Standard Library
+from typing import List
+
# Third Party
from pydantic import Field, StrictStr, StrictBool, constr
@@ -8,25 +11,7 @@ from hyperglass.models import HyperglassModel
from hyperglass.constants import SUPPORTED_QUERY_TYPES
-class HyperglassLevel3(HyperglassModel):
- """Automatic docs sorting subclass."""
-
- class Config:
- """Pydantic model configuration."""
-
- schema_extra = {"level": 3}
-
-
-class HyperglassLevel4(HyperglassModel):
- """Automatic docs sorting subclass."""
-
- class Config:
- """Pydantic model configuration."""
-
- schema_extra = {"level": 4}
-
-
-class BgpCommunityPattern(HyperglassLevel4):
+class BgpCommunityPattern(HyperglassModel):
"""Validation model for bgp_community regex patterns."""
decimal: StrictStr = Field(
@@ -54,7 +39,7 @@ class BgpCommunityPattern(HyperglassLevel4):
)
-class BgpAsPathPattern(HyperglassLevel4):
+class BgpAsPathPattern(HyperglassModel):
"""Validation model for bgp_aspath regex patterns."""
mode: constr(regex=r"asplain|asdot") = Field(
@@ -82,7 +67,15 @@ class BgpAsPathPattern(HyperglassLevel4):
)
-class BgpCommunity(HyperglassLevel3):
+class Community(HyperglassModel):
+ """Validation model for bgp_community communities."""
+
+ display_name: StrictStr
+ description: StrictStr
+ community: StrictStr
+
+
+class BgpCommunity(HyperglassModel):
"""Validation model for bgp_community configuration."""
enable: StrictBool = Field(
@@ -96,9 +89,11 @@ class BgpCommunity(HyperglassLevel3):
description="Text displayed for the BGP Community query type in the hyperglas UI.",
)
pattern: BgpCommunityPattern = BgpCommunityPattern()
+ mode: constr(regex=r"(input|select)") = "input"
+ communities: List[Community] = []
-class BgpRoute(HyperglassLevel3):
+class BgpRoute(HyperglassModel):
"""Validation model for bgp_route configuration."""
enable: StrictBool = Field(
@@ -111,7 +106,7 @@ class BgpRoute(HyperglassLevel3):
)
-class BgpAsPath(HyperglassLevel3):
+class BgpAsPath(HyperglassModel):
"""Validation model for bgp_aspath configuration."""
enable: StrictBool = Field(
@@ -127,7 +122,7 @@ class BgpAsPath(HyperglassLevel3):
pattern: BgpAsPathPattern = BgpAsPathPattern()
-class Ping(HyperglassLevel3):
+class Ping(HyperglassModel):
"""Validation model for ping configuration."""
enable: StrictBool = Field(
@@ -140,7 +135,7 @@ class Ping(HyperglassLevel3):
)
-class Traceroute(HyperglassLevel3):
+class Traceroute(HyperglassModel):
"""Validation model for traceroute configuration."""
enable: StrictBool = Field(
@@ -168,8 +163,9 @@ class Queries(HyperglassModel):
query_obj = getattr(self, query)
_map[query] = {
"name": query,
- "display_name": query_obj.display_name,
- "enable": query_obj.enable,
+ **query_obj.export_dict(
+ include={"display_name", "enable", "mode", "communities"}
+ ),
}
return _map
@@ -225,4 +221,3 @@ class Queries(HyperglassModel):
"description": "Enable, disable, or configure the Traceroute query type.",
},
}
- schema_extra = {"level": 2}
diff --git a/hyperglass/ui/components/ChakraSelect.js b/hyperglass/ui/components/ChakraSelect.js
index 356aa67..d7408dc 100644
--- a/hyperglass/ui/components/ChakraSelect.js
+++ b/hyperglass/ui/components/ChakraSelect.js
@@ -3,139 +3,151 @@ import { Text, useColorMode, useTheme } from "@chakra-ui/core";
import Select from "react-select";
import { opposingColor } from "~/util";
-export default ({ placeholder = "Select...", isFullWidth, size, children, ...props }) => {
- const theme = useTheme();
- const { colorMode } = useColorMode();
- const sizeMap = {
- lg: { height: theme.space[12] },
- md: { height: theme.space[10] },
- sm: { height: theme.space[8] }
- };
- const colorSetPrimaryBg = { dark: theme.colors.primary[300], light: theme.colors.primary[500] };
- const colorSetPrimaryColor = opposingColor(theme, colorSetPrimaryBg[colorMode]);
- const bg = { dark: theme.colors.whiteAlpha[100], light: theme.colors.white };
- const color = { dark: theme.colors.whiteAlpha[800], light: theme.colors.black };
- const borderFocused = theme.colors.secondary[500];
- const borderDisabled = theme.colors.whiteAlpha[100];
- const border = { dark: theme.colors.whiteAlpha[50], light: theme.colors.gray[100] };
- const borderRadius = theme.space[1];
- const hoverColor = { dark: theme.colors.whiteAlpha[200], light: theme.colors.gray[300] };
- const { height } = sizeMap[size];
- const optionBgActive = { dark: theme.colors.primary[400], light: theme.colors.primary[600] };
- const optionBgColor = opposingColor(theme, optionBgActive[colorMode]);
- const optionSelectedBg = {
- dark: theme.colors.whiteAlpha[400],
- light: theme.colors.blackAlpha[400]
- };
- const optionSelectedColor = opposingColor(theme, optionSelectedBg[colorMode]);
- const selectedDisabled = theme.colors.whiteAlpha[400];
- const placeholderColor = {
- dark: theme.colors.whiteAlpha[400],
- light: theme.colors.gray[400]
- };
- const menuBg = { dark: theme.colors.black, light: theme.colors.white };
- const menuColor = { dark: theme.colors.white, light: theme.colors.blackAlpha[800] };
- return (
-
+ );
+ }
+);
+
+ChakraSelect.displayName = "ChakraSelect";
+export default ChakraSelect;
diff --git a/hyperglass/ui/components/CommunitySelect.js b/hyperglass/ui/components/CommunitySelect.js
new file mode 100644
index 0000000..e085f14
--- /dev/null
+++ b/hyperglass/ui/components/CommunitySelect.js
@@ -0,0 +1,41 @@
+import * as React from "react";
+import { useEffect } from "react";
+import { Text } from "@chakra-ui/core";
+import { components } from "react-select";
+import ChakraSelect from "~/components/ChakraSelect";
+
+const CommunitySelect = ({ name, communities, onChange, register, unregister }) => {
+ const communitySelections = communities.map((c) => {
+ return { value: c.community, label: c.display_name, description: c.description };
+ });
+ const Option = ({ label, data, ...props }) => {
+ return (
+
+ {label}
+
+ {data.description}
+
+
+ );
+ };
+ useEffect(() => {
+ register({ name });
+ return () => unregister(name);
+ }, [name, register, unregister]);
+ return (
+ {
+ onChange({ field: name, value: e.value || "" });
+ }}
+ options={communitySelections}
+ components={{ Option }}
+ />
+ );
+};
+
+CommunitySelect.displayName = "CommunitySelect";
+
+export default CommunitySelect;
diff --git a/hyperglass/ui/components/HyperglassForm.js b/hyperglass/ui/components/HyperglassForm.js
index 5d0f09f..175509f 100644
--- a/hyperglass/ui/components/HyperglassForm.js
+++ b/hyperglass/ui/components/HyperglassForm.js
@@ -1,4 +1,5 @@
-import React, { useState, useEffect } from "react";
+import * as React from "react";
+import { useState, useEffect } from "react";
import { Box, Flex } from "@chakra-ui/core";
import { useForm } from "react-hook-form";
import lodash from "lodash";
@@ -9,6 +10,7 @@ import HelpModal from "~/components/HelpModal";
import QueryLocation from "~/components/QueryLocation";
import QueryType from "~/components/QueryType";
import QueryTarget from "~/components/QueryTarget";
+import CommunitySelect from "~/components/CommunitySelect";
import QueryVrf from "~/components/QueryVrf";
import ResolvedTarget from "~/components/ResolvedTarget";
import SubmitButton from "~/components/SubmitButton";
@@ -46,9 +48,9 @@ const FormRow = ({ children, ...props }) => (
const HyperglassForm = React.forwardRef(
({ isSubmitting, setSubmitting, setFormData, greetingAck, setGreetingAck, ...props }, ref) => {
const config = useConfig();
- const { handleSubmit, register, setValue, errors } = useForm({
+ const { handleSubmit, register, unregister, setValue, errors } = useForm({
validationSchema: formSchema(config),
- defaultValues: { query_vrf: "default" },
+ defaultValues: { query_vrf: "default", query_target: "" },
});
const [queryLocation, setQueryLocation] = useState([]);
@@ -136,10 +138,10 @@ const HyperglassForm = React.forwardRef(
useEffect(() => {
register({ name: "query_location" });
- register({ name: "query_target" });
register({ name: "query_type" });
register({ name: "query_vrf" });
- });
+ }, [register]);
+ Object.keys(errors).length >= 1 && console.error(errors);
return (
-
+ {queryType === "bgp_community" &&
+ config.queries.bgp_community.mode === "select" ? (
+
+ ) : (
+
+ )}
diff --git a/hyperglass/ui/components/QueryLocation.js b/hyperglass/ui/components/QueryLocation.js
index 6458c5d..abf71b8 100644
--- a/hyperglass/ui/components/QueryLocation.js
+++ b/hyperglass/ui/components/QueryLocation.js
@@ -1,15 +1,15 @@
import React from "react";
import ChakraSelect from "~/components/ChakraSelect";
-const buildLocations = networks => {
+const buildLocations = (networks) => {
const locations = [];
- networks.map(net => {
+ networks.map((net) => {
const netLocations = [];
- net.locations.map(loc => {
+ net.locations.map((loc) => {
netLocations.push({
label: loc.display_name,
value: loc.name,
- group: net.display_name
+ group: net.display_name,
});
});
locations.push({ label: net.display_name, options: netLocations });
@@ -19,10 +19,10 @@ const buildLocations = networks => {
export default ({ locations, onChange }) => {
const options = buildLocations(locations);
- const handleChange = e => {
+ const handleChange = (e) => {
const selected = [];
e &&
- e.map(sel => {
+ e.map((sel) => {
selected.push(sel.value);
});
onChange({ field: "query_location", value: selected });
@@ -34,6 +34,7 @@ export default ({ locations, onChange }) => {
onChange={handleChange}
options={options}
isMulti
+ closeMenuOnSelect={false}
/>
);
};
diff --git a/hyperglass/ui/components/QueryTarget.js b/hyperglass/ui/components/QueryTarget.js
index e04a403..c37dbdb 100644
--- a/hyperglass/ui/components/QueryTarget.js
+++ b/hyperglass/ui/components/QueryTarget.js
@@ -1,10 +1,10 @@
-import React, { useState } from "react";
+import React, { useEffect } from "react";
import styled from "@emotion/styled";
import { Input, useColorMode } from "@chakra-ui/core";
const StyledInput = styled(Input)`
&::placeholder {
- color: ${props => props.placeholderColor};
+ color: ${(props) => props.placeholderColor};
}
`;
@@ -18,13 +18,14 @@ const placeholderColor = { dark: "whiteAlpha.400", light: "gray.400" };
const QueryTarget = ({
placeholder,
register,
+ unregister,
setFqdn,
name,
value,
setTarget,
resolveTarget,
displayValue,
- setDisplayValue
+ setDisplayValue,
}) => {
const { colorMode } = useColorMode();
@@ -35,15 +36,19 @@ const QueryTarget = ({
setFqdn(false);
}
};
- const handleChange = e => {
+ const handleChange = (e) => {
setDisplayValue(e.target.value);
setTarget({ field: name, value: e.target.value });
};
- const handleKeyDown = e => {
+ const handleKeyDown = (e) => {
if ([9, 13].includes(e.keyCode)) {
handleBlur();
}
};
+ useEffect(() => {
+ register({ name });
+ return () => unregister(name);
+ }, [register, unregister, name]);
return (
<>
diff --git a/hyperglass/ui/components/ResolvedTarget.js b/hyperglass/ui/components/ResolvedTarget.js
index 0585616..595ce17 100644
--- a/hyperglass/ui/components/ResolvedTarget.js
+++ b/hyperglass/ui/components/ResolvedTarget.js
@@ -13,7 +13,7 @@ const labelBgSuccess = { dark: "success", light: "success" };
async function containingPrefix(ipAddress) {
try {
const prefixData = await axios.get("https://stat.ripe.net/data/network-info/data.json", {
- params: { resource: ipAddress }
+ params: { resource: ipAddress },
});
return prefixData.data?.data?.prefix;
} catch (err) {
@@ -36,32 +36,32 @@ const ResolvedTarget = React.forwardRef(
params: { name: fqdnTarget, type: "A" },
headers: { accept: "application/dns-json" },
crossdomain: true,
- timeout: 1000
+ timeout: 1000,
},
6: {
url: dnsUrl,
params: { name: fqdnTarget, type: "AAAA" },
headers: { accept: "application/dns-json" },
crossdomain: true,
- timeout: 1000
- }
+ timeout: 1000,
+ },
};
const [{ data: data4, loading: loading4, error: error4 }] = useAxios(params[4]);
const [{ data: data6, loading: loading6, error: error6 }] = useAxios(params[6]);
- const handleOverride = overridden => {
+ const handleOverride = (overridden) => {
setTarget({ field: "query_target", value: overridden });
};
- const isSelected = value => {
+ const isSelected = (value) => {
return labelBgStatus[value === queryTarget];
};
- const findAnswer = data => {
+ const findAnswer = (data) => {
return data?.Answer?.filter(
- answerData => answerData.type === data?.Question[0]?.type
+ (answerData) => answerData.type === data?.Question[0]?.type
)[0]?.data;
};
@@ -74,7 +74,6 @@ const ResolvedTarget = React.forwardRef(
handleOverride(findAnswer(data4));
}
}, [data4, data6]);
-
return (