mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
additions for new ui
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
"""Validate branding configuration variables."""
|
||||
|
||||
# Standard Library Imports
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Third Party Imports
|
||||
from pydantic import FilePath
|
||||
from pydantic import HttpUrl
|
||||
from pydantic import StrictBool
|
||||
from pydantic import StrictInt
|
||||
from pydantic import StrictStr
|
||||
from pydantic import constr
|
||||
from pydantic import root_validator
|
||||
from pydantic import validator
|
||||
from pydantic.color import Color
|
||||
|
||||
@@ -65,11 +69,14 @@ class Branding(HyperglassModel):
|
||||
"""Validation model for params.branding.help_menu."""
|
||||
|
||||
enable: StrictBool = True
|
||||
file: Optional[FilePath]
|
||||
title: StrictStr = "Help"
|
||||
|
||||
class Logo(HyperglassModel):
|
||||
"""Validation model for params.branding.logo."""
|
||||
|
||||
logo_path: StrictStr = "ui/images/hyperglass-dark.png"
|
||||
light: Optional[FilePath]
|
||||
dark: Optional[FilePath]
|
||||
width: StrictInt = 384
|
||||
height: Optional[StrictInt]
|
||||
favicons: StrictStr = "ui/images/favicons/"
|
||||
@@ -82,20 +89,72 @@ class Branding(HyperglassModel):
|
||||
chars.append("/")
|
||||
return "".join(chars)
|
||||
|
||||
@root_validator(pre=True)
|
||||
def validate_logo_model(cls, values):
|
||||
"""Set default opengraph image location.
|
||||
|
||||
Arguments:
|
||||
values {dict} -- Unvalidated model
|
||||
|
||||
Returns:
|
||||
{dict} -- Modified model
|
||||
"""
|
||||
logo_light = values.get("light")
|
||||
logo_dark = values.get("dark")
|
||||
default_logo_light = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "static/ui/images/hyperglass-light.png"
|
||||
)
|
||||
default_logo_dark = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "static/ui/images/hyperglass-dark.png"
|
||||
)
|
||||
|
||||
# Use light logo as dark logo if dark logo is undefined.
|
||||
if logo_light is not None and logo_dark is None:
|
||||
values["dark"] = logo_light
|
||||
|
||||
# Use dark logo as light logo if light logo is undefined.
|
||||
if logo_dark is not None and logo_light is None:
|
||||
values["light"] = logo_dark
|
||||
|
||||
# Set default logo paths if logo is undefined.
|
||||
if logo_light is None and logo_dark is None:
|
||||
values["light"] = default_logo_light
|
||||
values["dark"] = default_logo_dark
|
||||
|
||||
return values
|
||||
|
||||
@validator("light", "dark")
|
||||
def validate_logos(cls, value):
|
||||
"""Convert file path to URL path.
|
||||
|
||||
Arguments:
|
||||
value {FilePath} -- Path to logo file.
|
||||
|
||||
Returns:
|
||||
{str} -- Formatted logo path
|
||||
"""
|
||||
return "".join(str(value).split("static")[1::])
|
||||
|
||||
class Config:
|
||||
"""Override pydantic config."""
|
||||
|
||||
fields = {"logo_path": "path"}
|
||||
|
||||
class PeeringDb(HyperglassModel):
|
||||
"""Validation model for params.branding.peering_db."""
|
||||
class ExternalLink(HyperglassModel):
|
||||
"""Validation model for params.branding.external_link."""
|
||||
|
||||
enable: StrictBool = True
|
||||
title: StrictStr = "PeeringDB"
|
||||
url: HttpUrl = "https://www.peeringdb.com/AS{primary_asn}"
|
||||
|
||||
class Terms(HyperglassModel):
|
||||
"""Validation model for params.branding.terms."""
|
||||
|
||||
enable: StrictBool = True
|
||||
file: Optional[FilePath]
|
||||
title: StrictStr = "Terms"
|
||||
|
||||
class Text(HyperglassModel):
|
||||
"""Validation model for params.branding.text."""
|
||||
@@ -138,6 +197,6 @@ class Branding(HyperglassModel):
|
||||
font: Font = Font()
|
||||
help_menu: HelpMenu = HelpMenu()
|
||||
logo: Logo = Logo()
|
||||
peering_db: PeeringDb = PeeringDb()
|
||||
external_link: ExternalLink = ExternalLink()
|
||||
terms: Terms = Terms()
|
||||
text: Text = Text()
|
||||
|
@@ -17,6 +17,7 @@ from pydantic import validator
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
from hyperglass.configuration.models.opengraph import OpenGraph
|
||||
|
||||
|
||||
class General(HyperglassModel):
|
||||
@@ -25,6 +26,24 @@ class General(HyperglassModel):
|
||||
debug: StrictBool = False
|
||||
primary_asn: StrictStr = "65001"
|
||||
org_name: StrictStr = "The Company"
|
||||
site_description: StrictStr = "{org_name} Network Looking Glass"
|
||||
site_keywords: List[StrictStr] = [
|
||||
"hyperglass",
|
||||
"looking glass",
|
||||
"lg",
|
||||
"peer",
|
||||
"peering",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"transit",
|
||||
"community",
|
||||
"communities",
|
||||
"bgp",
|
||||
"routing",
|
||||
"network",
|
||||
"isp",
|
||||
]
|
||||
opengraph: OpenGraph = OpenGraph()
|
||||
google_analytics: StrictStr = ""
|
||||
redis_host: StrictStr = "localhost"
|
||||
redis_port: StrictInt = 6379
|
||||
@@ -34,6 +53,19 @@ class General(HyperglassModel):
|
||||
listen_port: StrictInt = 8001
|
||||
log_file: Optional[FilePath]
|
||||
|
||||
@validator("site_description")
|
||||
def validate_site_description(cls, value, values):
|
||||
"""Format the site descripion with the org_name field.
|
||||
|
||||
Arguments:
|
||||
value {str} -- site_description
|
||||
values {str} -- Values before site_description
|
||||
|
||||
Returns:
|
||||
{str} -- Formatted description
|
||||
"""
|
||||
return value.format(org_name=values["org_name"])
|
||||
|
||||
@validator("log_file")
|
||||
def validate_log_file(cls, value):
|
||||
"""Set default logfile location if none is configured.
|
||||
|
42
hyperglass/configuration/models/opengraph.py
Normal file
42
hyperglass/configuration/models/opengraph.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Standard Library Imports
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Third Party Imports
|
||||
import PIL.Image as PilImage
|
||||
from pydantic import FilePath
|
||||
from pydantic import StrictInt
|
||||
from pydantic import validator
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
|
||||
|
||||
class OpenGraph(HyperglassModel):
|
||||
"""Validation model for params.general.opengraph."""
|
||||
|
||||
width: Optional[StrictInt]
|
||||
height: Optional[StrictInt]
|
||||
image: Optional[FilePath]
|
||||
|
||||
@validator("image")
|
||||
def validate_image(cls, value, values):
|
||||
"""Set default opengraph image location.
|
||||
|
||||
Arguments:
|
||||
value {FilePath} -- Path to opengraph image file.
|
||||
|
||||
Returns:
|
||||
{Path} -- Opengraph image file path object
|
||||
"""
|
||||
if value is None:
|
||||
value = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "static/ui/images/hyperglass-opengraph.png"
|
||||
)
|
||||
with PilImage.open(value) as img:
|
||||
width, height = img.size
|
||||
values["width"] = width
|
||||
values["height"] = height
|
||||
|
||||
return "".join(str(value).split("static")[1::])
|
@@ -149,7 +149,7 @@ class Router(HyperglassModel):
|
||||
|
||||
log.debug(
|
||||
f'Field "display_name" for VRF "{vrf["name"]}" was not set. '
|
||||
f'Generated "display_name" {vrf["display_name"]}'
|
||||
f"Generated '{vrf['display_name']}'"
|
||||
)
|
||||
|
||||
# Validate the non-default VRF against the standard
|
||||
@@ -167,6 +167,7 @@ class Routers(HyperglassModelExtra):
|
||||
vrfs: List[StrictStr] = []
|
||||
display_vrfs: List[StrictStr] = []
|
||||
routers: List[Router] = []
|
||||
networks: List[StrictStr] = []
|
||||
|
||||
@classmethod
|
||||
def _import(cls, input_params):
|
||||
@@ -183,7 +184,9 @@ class Routers(HyperglassModelExtra):
|
||||
{object} -- Validated routers object
|
||||
"""
|
||||
vrfs = set()
|
||||
networks = set()
|
||||
display_vrfs = set()
|
||||
vrf_objects = set()
|
||||
routers = Routers()
|
||||
routers.routers = []
|
||||
routers.hostnames = []
|
||||
@@ -224,9 +227,19 @@ class Routers(HyperglassModelExtra):
|
||||
"display_name": vrf.display_name,
|
||||
}
|
||||
|
||||
# Add the native VRF objects to a set (for automatic
|
||||
# de-duping), but exlcude device-specific fields.
|
||||
_copy_params = {
|
||||
"deep": True,
|
||||
"exclude": {"ipv4": {"source_address"}, "ipv6": {"source_address"}},
|
||||
}
|
||||
vrf_objects.add(vrf.copy(**_copy_params))
|
||||
|
||||
# Convert the de-duplicated sets to a standard list, add lists
|
||||
# as class attributes
|
||||
routers.vrfs = list(vrfs)
|
||||
routers.display_vrfs = list(display_vrfs)
|
||||
routers.vrf_objects = list(vrf_objects)
|
||||
routers.networks = list(networks)
|
||||
|
||||
return routers
|
||||
|
@@ -10,6 +10,7 @@ from typing import List
|
||||
from typing import Optional
|
||||
|
||||
# Third Party Imports
|
||||
from pydantic import FilePath
|
||||
from pydantic import IPvAnyNetwork
|
||||
from pydantic import StrictStr
|
||||
from pydantic import constr
|
||||
@@ -19,6 +20,13 @@ from pydantic import validator
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
|
||||
|
||||
class Info(HyperglassModel):
|
||||
"""Validation model for per-VRF help files."""
|
||||
|
||||
bgp_aspath: Optional[FilePath]
|
||||
bgp_community: Optional[FilePath]
|
||||
|
||||
|
||||
class DeviceVrf4(HyperglassModel):
|
||||
"""Validation model for IPv4 AFI definitions."""
|
||||
|
||||
@@ -36,8 +44,9 @@ class DeviceVrf6(HyperglassModel):
|
||||
class Vrf(HyperglassModel):
|
||||
"""Validation model for per VRF/afi config in devices.yaml."""
|
||||
|
||||
name: str
|
||||
display_name: str
|
||||
name: StrictStr
|
||||
display_name: StrictStr
|
||||
info: Info = Info()
|
||||
ipv4: Optional[DeviceVrf4]
|
||||
ipv6: Optional[DeviceVrf6]
|
||||
access_list: List[Dict[constr(regex=("allow|deny")), IPvAnyNetwork]] = [
|
||||
@@ -71,12 +80,32 @@ class Vrf(HyperglassModel):
|
||||
li[action] = str(network)
|
||||
return value
|
||||
|
||||
def __hash__(self):
|
||||
"""Make VRF object hashable so the object can be deduplicated with set().
|
||||
|
||||
Returns:
|
||||
{int} -- Hash of VRF name
|
||||
"""
|
||||
return hash((self.name,))
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Make VRF object comparable so the object can be deduplicated with set().
|
||||
|
||||
Arguments:
|
||||
other {object} -- Object to compare
|
||||
|
||||
Returns:
|
||||
{bool} -- True if comparison attributes are the same value
|
||||
"""
|
||||
return self.name == other.name
|
||||
|
||||
|
||||
class DefaultVrf(HyperglassModel):
|
||||
"""Validation model for default routing table VRF."""
|
||||
|
||||
name: StrictStr = "default"
|
||||
display_name: StrictStr = "Global"
|
||||
info: Info = Info()
|
||||
access_list: List[Dict[constr(regex=("allow|deny")), IPvAnyNetwork]] = [
|
||||
{"allow": IPv4Network("0.0.0.0/0")},
|
||||
{"allow": IPv6Network("::/0")},
|
||||
|
110
hyperglass/models/query.py
Normal file
110
hyperglass/models/query.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""Input query validation model."""
|
||||
|
||||
# Standard Library Imports
|
||||
import operator
|
||||
from typing import Optional
|
||||
|
||||
# Third Party Imports
|
||||
from pydantic import BaseModel
|
||||
from pydantic import StrictStr
|
||||
from pydantic import validator
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration import devices
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.constants import Supported
|
||||
from hyperglass.exceptions import InputInvalid
|
||||
|
||||
|
||||
class Query(BaseModel):
|
||||
"""Validation model for input query parameters."""
|
||||
|
||||
query_location: StrictStr
|
||||
query_type: StrictStr
|
||||
query_vrf: Optional[StrictStr]
|
||||
query_target: StrictStr
|
||||
|
||||
@validator("query_location")
|
||||
def validate_query_location(cls, value):
|
||||
"""Ensure query_location is defined.
|
||||
|
||||
Arguments:
|
||||
value {str} -- Unvalidated query_location
|
||||
|
||||
Raises:
|
||||
InputInvalid: Raised if query_location is not defined.
|
||||
|
||||
Returns:
|
||||
{str} -- Valid query_location
|
||||
"""
|
||||
if value not in devices.hostnames:
|
||||
raise InputInvalid(
|
||||
params.messages.invalid_field,
|
||||
alert="warning",
|
||||
input=value,
|
||||
field=params.branding.text.query_location,
|
||||
)
|
||||
return value
|
||||
|
||||
@validator("query_type")
|
||||
def validate_query_type(cls, value):
|
||||
"""Ensure query_type is supported.
|
||||
|
||||
Arguments:
|
||||
value {str} -- Unvalidated query_type
|
||||
|
||||
Raises:
|
||||
InputInvalid: Raised if query_type is not supported.
|
||||
|
||||
Returns:
|
||||
{str} -- Valid query_type
|
||||
"""
|
||||
if value not in Supported.query_types:
|
||||
raise InputInvalid(
|
||||
params.messages.invalid_field,
|
||||
alert="warning",
|
||||
input=value,
|
||||
field=params.branding.text.query_type,
|
||||
)
|
||||
else:
|
||||
enabled = operator.attrgetter(f"{value}.enable")(params.features)
|
||||
if not enabled:
|
||||
raise InputInvalid(
|
||||
params.messages.invalid_field,
|
||||
alert="warning",
|
||||
input=value,
|
||||
field=params.branding.text.query_type,
|
||||
)
|
||||
return value
|
||||
|
||||
@validator("query_vrf", always=True, pre=True)
|
||||
def validate_query_vrf(cls, value, values):
|
||||
"""Ensure query_vrf is defined.
|
||||
|
||||
Arguments:
|
||||
value {str} -- Unvalidated query_vrf
|
||||
|
||||
Raises:
|
||||
InputInvalid: Raised if query_vrf is not defined.
|
||||
|
||||
Returns:
|
||||
{str} -- Valid query_vrf
|
||||
"""
|
||||
device = getattr(devices, values["query_location"])
|
||||
default_vrf = "default"
|
||||
if value is not None and value != default_vrf:
|
||||
for vrf in device.vrfs:
|
||||
if value == vrf.name:
|
||||
value = vrf.name
|
||||
elif value == vrf.display_name:
|
||||
value = vrf.name
|
||||
else:
|
||||
raise InputInvalid(
|
||||
params.messages.vrf_not_associated,
|
||||
alert="warning",
|
||||
vrf_name=value,
|
||||
device_name=device.display_name,
|
||||
)
|
||||
if value is None:
|
||||
value = default_vrf
|
||||
return value
|
Reference in New Issue
Block a user