fix import
|
|
@ -50,7 +50,6 @@ from hyperglass import configuration
|
|||
from hyperglass import constants
|
||||
from hyperglass import exceptions
|
||||
from hyperglass import execution
|
||||
from hyperglass import render
|
||||
from hyperglass import util
|
||||
|
||||
stackprinter.set_excepthook()
|
||||
|
|
|
|||
|
|
@ -5,14 +5,20 @@ import asyncio
|
|||
from pathlib import Path
|
||||
|
||||
# Third Party Imports
|
||||
import ujson as json
|
||||
import yaml
|
||||
from aiofile import AIOFile
|
||||
from pydantic import ValidationError
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.markdown import get_markdown
|
||||
from hyperglass.configuration.models import commands as _commands
|
||||
from hyperglass.configuration.models import params as _params
|
||||
from hyperglass.configuration.models import routers as _routers
|
||||
from hyperglass.constants import CREDIT
|
||||
from hyperglass.constants import DEFAULT_HELP
|
||||
from hyperglass.constants import DEFAULT_DETAILS
|
||||
from hyperglass.constants import DEFAULT_TERMS
|
||||
from hyperglass.constants import LOG_HANDLER
|
||||
from hyperglass.constants import LOG_HANDLER_FILE
|
||||
from hyperglass.constants import LOG_LEVELS
|
||||
|
|
@ -314,6 +320,60 @@ def _build_queries():
|
|||
return queries
|
||||
|
||||
|
||||
content_params = json.loads(
|
||||
params.general.json(
|
||||
include={"primary_asn", "org_name", "site_title", "site_description"}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _build_vrf_help():
|
||||
"""Build a dict of vrfs as keys, help content as values.
|
||||
|
||||
Returns:
|
||||
{dict} -- Formatted VRF help
|
||||
"""
|
||||
all_help = {}
|
||||
for vrf in devices.vrf_objects:
|
||||
vrf_help = {}
|
||||
for command in Supported.query_types:
|
||||
cmd = getattr(vrf.info, command)
|
||||
help_params = content_params
|
||||
if cmd.params.title is None:
|
||||
cmd.params.title = (
|
||||
f"{vrf.display_name}: {getattr(params.branding.text, command)}"
|
||||
)
|
||||
help_params.update(cmd.params.dict())
|
||||
md = asyncio.run(
|
||||
get_markdown(
|
||||
config_path=cmd,
|
||||
default=DEFAULT_DETAILS[command],
|
||||
params=help_params,
|
||||
)
|
||||
)
|
||||
vrf_help.update(
|
||||
{command: {"content": md, "enable": cmd.enable, "params": help_params}}
|
||||
)
|
||||
all_help.update({vrf.name: vrf_help})
|
||||
return all_help
|
||||
|
||||
|
||||
content_vrf = _build_vrf_help()
|
||||
|
||||
content_help = asyncio.run(
|
||||
get_markdown(
|
||||
config_path=params.branding.help_menu,
|
||||
default=DEFAULT_HELP,
|
||||
params=content_params,
|
||||
)
|
||||
)
|
||||
content_terms = asyncio.run(
|
||||
get_markdown(
|
||||
config_path=params.branding.terms, default=DEFAULT_TERMS, params=content_params
|
||||
)
|
||||
)
|
||||
content_credit = CREDIT
|
||||
|
||||
vrfs = _build_vrfs()
|
||||
queries = _build_queries()
|
||||
networks = _build_networks()
|
||||
|
|
@ -346,6 +406,12 @@ _frontend_params.update(
|
|||
"devices": frontend_devices,
|
||||
"networks": networks,
|
||||
"vrfs": vrfs,
|
||||
"content": {
|
||||
"help_menu": content_help,
|
||||
"terms": content_terms,
|
||||
"credit": content_credit,
|
||||
"vrf": content_vrf,
|
||||
},
|
||||
}
|
||||
)
|
||||
frontend_params = _frontend_params
|
||||
|
|
|
|||
63
hyperglass/configuration/markdown.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Third Party Imports
|
||||
from aiofile import AIOFile
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.util import log
|
||||
|
||||
|
||||
async def _get_file(path_obj):
|
||||
"""Read a file.
|
||||
|
||||
Arguments:
|
||||
path_obj {Path} -- Path to file.
|
||||
|
||||
Returns:
|
||||
{str} -- File contents
|
||||
"""
|
||||
async with AIOFile(path_obj, "r") as raw_file:
|
||||
file = await raw_file.read()
|
||||
return file
|
||||
|
||||
|
||||
async def format_markdown(content, params):
|
||||
"""Format content with config parameters.
|
||||
|
||||
Arguments:
|
||||
content {str} -- Unformatted content
|
||||
|
||||
Returns:
|
||||
{str} -- Formatted content
|
||||
"""
|
||||
try:
|
||||
fmt = content.format(**params)
|
||||
except KeyError:
|
||||
fmt = content
|
||||
return fmt
|
||||
|
||||
|
||||
async def get_markdown(config_path, default, params):
|
||||
"""Get markdown file if specified, or use default.
|
||||
|
||||
Format the content with config parameters.
|
||||
|
||||
Arguments:
|
||||
config_path {object} -- content config
|
||||
default {str} -- default content
|
||||
|
||||
Returns:
|
||||
{str} -- Formatted content
|
||||
"""
|
||||
log.debug(f"Getting Markdown content for '{params['title']}'")
|
||||
|
||||
if config_path.enable and config_path.file is not None:
|
||||
md = await _get_file(config_path.file)
|
||||
else:
|
||||
md = default
|
||||
|
||||
log.debug(f"Unformatted Content for '{params['title']}':\n{md}")
|
||||
|
||||
md_fmt = await format_markdown(md, params)
|
||||
|
||||
log.debug(f"Formatted Content for '{params['title']}':\n{md_fmt}")
|
||||
|
||||
return md_fmt
|
||||
|
|
@ -22,8 +22,6 @@ from hyperglass.configuration.models._utils import HyperglassModel
|
|||
class Branding(HyperglassModel):
|
||||
"""Validation model for params.branding."""
|
||||
|
||||
site_title: StrictStr = "hyperglass"
|
||||
|
||||
class Colors(HyperglassModel):
|
||||
"""Validation model for params.colors."""
|
||||
|
||||
|
|
@ -103,11 +101,11 @@ class Branding(HyperglassModel):
|
|||
logo_dark = values.get("dark")
|
||||
default_logo_light = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "static/ui/images/hyperglass-light.png"
|
||||
/ "static/images/hyperglass-light.png"
|
||||
)
|
||||
default_logo_dark = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "static/ui/images/hyperglass-dark.png"
|
||||
/ "static/images/hyperglass-dark.png"
|
||||
)
|
||||
|
||||
# Use light logo as dark logo if dark logo is undefined.
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class General(HyperglassModel):
|
|||
debug: StrictBool = False
|
||||
primary_asn: StrictStr = "65001"
|
||||
org_name: StrictStr = "The Company"
|
||||
site_title: StrictStr = "hyperglass"
|
||||
site_description: StrictStr = "{org_name} Network Looking Glass"
|
||||
site_keywords: List[StrictStr] = [
|
||||
"hyperglass",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class OpenGraph(HyperglassModel):
|
|||
if value is None:
|
||||
value = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "static/ui/images/hyperglass-opengraph.png"
|
||||
/ "static/images/hyperglass-opengraph.png"
|
||||
)
|
||||
with PilImage.open(value) as img:
|
||||
width, height = img.size
|
||||
|
|
|
|||
|
|
@ -12,19 +12,38 @@ from typing import Optional
|
|||
# Third Party Imports
|
||||
from pydantic import FilePath
|
||||
from pydantic import IPvAnyNetwork
|
||||
from pydantic import StrictBool
|
||||
from pydantic import StrictStr
|
||||
from pydantic import constr
|
||||
from pydantic import validator
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration.models._utils import HyperglassModel
|
||||
from hyperglass.configuration.models._utils import HyperglassModelExtra
|
||||
|
||||
|
||||
class InfoConfigParams(HyperglassModelExtra):
|
||||
"""Validation model for per-help params."""
|
||||
|
||||
title: Optional[StrictStr]
|
||||
|
||||
|
||||
class InfoConfig(HyperglassModel):
|
||||
"""Validation model for help configuration."""
|
||||
|
||||
enable: StrictBool = True
|
||||
file: Optional[FilePath]
|
||||
params: InfoConfigParams = InfoConfigParams()
|
||||
|
||||
|
||||
class Info(HyperglassModel):
|
||||
"""Validation model for per-VRF help files."""
|
||||
"""Validation model for per-VRF, per-Command help."""
|
||||
|
||||
bgp_aspath: Optional[FilePath]
|
||||
bgp_community: Optional[FilePath]
|
||||
bgp_aspath: InfoConfig = InfoConfig()
|
||||
bgp_community: InfoConfig = InfoConfig()
|
||||
bgp_route: InfoConfig = InfoConfig()
|
||||
ping: InfoConfig = InfoConfig()
|
||||
traceroute: InfoConfig = InfoConfig()
|
||||
|
||||
|
||||
class DeviceVrf4(HyperglassModel):
|
||||
|
|
|
|||
|
|
@ -25,24 +25,24 @@ LOG_HANDLER = {"sink": sys.stdout, "format": LOG_FMT, "level": "INFO"}
|
|||
|
||||
LOG_HANDLER_FILE = {"format": LOG_FMT, "level": "INFO"}
|
||||
|
||||
CREDIT = """
|
||||
Powered by [**hyperglass**](https://github.com/checktheroads/hyperglass). Source code \
|
||||
licensed \
|
||||
[_BSD 3-Clause Clear_](https://github.com/checktheroads/hyperglass/blob/master/LICENSE).
|
||||
"""
|
||||
|
||||
DEFAULT_TERMS = """
|
||||
---
|
||||
template: footer
|
||||
---
|
||||
By using {{ branding.site_name }}, you agree to be bound by the following terms of \
|
||||
use: All queries executed on this page are logged for analysis and troubleshooting. \
|
||||
By using {site_title}, you agree to be bound by the following terms of use:
|
||||
|
||||
All queries executed on this page are logged for analysis and troubleshooting. \
|
||||
Users are prohibited from automating queries, or attempting to process queries in \
|
||||
bulk. This service is provided on a best effort basis, and {{ general.org_name }} \
|
||||
bulk. This service is provided on a best effort basis, and {org_name} \
|
||||
makes no availability or performance warranties or guarantees whatsoever.
|
||||
"""
|
||||
|
||||
DEFAULT_DETAILS = {
|
||||
"bgp_aspath": r"""
|
||||
---
|
||||
template: bgp_aspath
|
||||
title: Supported AS Path Patterns
|
||||
---
|
||||
{{ branding.site_name }} accepts the following `AS_PATH` regular expression patterns:
|
||||
"bgp_aspath": """
|
||||
{site_title} accepts the following `AS_PATH` regular expression patterns:
|
||||
|
||||
| Expression | Match |
|
||||
| :------------------- | :-------------------------------------------- |
|
||||
|
|
@ -53,17 +53,24 @@ title: Supported AS Path Patterns
|
|||
| `_65000(_.+_)65001$` | Anything from 65001 that passed through 65000 |
|
||||
""",
|
||||
"bgp_community": """
|
||||
---
|
||||
template: bgp_community
|
||||
title: BGP Communities
|
||||
---
|
||||
{{ branding.site_name }} makes use of the following BGP communities:
|
||||
{site_title} makes use of the following BGP communities:
|
||||
|
||||
| Community | Description |
|
||||
| :-------- | :---------- |
|
||||
| `65000:1` | Example 1 |
|
||||
| `65000:2` | Example 2 |
|
||||
| `65000:3` | Example 3 |
|
||||
""",
|
||||
"bgp_route": """
|
||||
Performs BGP table lookup based on IPv4/IPv6 prefix.
|
||||
""",
|
||||
"ping": """
|
||||
Sends 5 ICMP echo requests to the target.
|
||||
""",
|
||||
"traceroute": """
|
||||
Performs UDP Based traceroute to the target. \
|
||||
For information about how to interpret traceroute results, [click here]\
|
||||
(https://hyperglass.readthedocs.io/en/latest/assets/traceroute_nanog.pdf).
|
||||
""",
|
||||
}
|
||||
|
||||
|
|
@ -108,27 +115,37 @@ ets/traceroute_nanog.pdf" target="_blank">click here</a>.
|
|||
|
||||
|
||||
DEFAULT_HELP = """
|
||||
---
|
||||
template: default_help
|
||||
---
|
||||
##### BGP Route
|
||||
|
||||
Performs BGP table lookup based on IPv4/IPv6 prefix.
|
||||
<hr>
|
||||
|
||||
---
|
||||
|
||||
##### BGP Community
|
||||
Performs BGP table lookup based on <a href="https://tools.ietf.org/html/rfc4360" target\
|
||||
="_blank">Extended</a> or <a href="https://tools.ietf.org/html/rfc8195" target=\
|
||||
"_blank">Large</a> community value.
|
||||
<hr>
|
||||
|
||||
Performs BGP table lookup based on [Extended](https://tools.ietf.org/html/rfc4360) \
|
||||
or [Large](https://tools.ietf.org/html/rfc8195) community value.
|
||||
|
||||
---
|
||||
|
||||
##### BGP AS Path
|
||||
|
||||
Performs BGP table lookup based on `AS_PATH` regular expression.
|
||||
<hr>
|
||||
|
||||
---
|
||||
|
||||
##### Ping
|
||||
|
||||
Sends 5 ICMP echo requests to the target.
|
||||
<hr>
|
||||
|
||||
---
|
||||
|
||||
##### Traceroute
|
||||
Performs UDP Based traceroute to the target.<br>For information about how to \
|
||||
interpret traceroute results, <a href="https://hyperglass.readthedocs.io/en/latest/ass\
|
||||
ets/traceroute_nanog.pdf" target="_blank">click here</a>.
|
||||
|
||||
Performs UDP Based traceroute to the target.
|
||||
|
||||
For information about how to interpret traceroute results, [click here]\
|
||||
(https://hyperglass.readthedocs.io/en/latest/assets/traceroute_nanog.pdf).
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from pathlib import Path
|
|||
|
||||
# Third Party Imports
|
||||
import aredis
|
||||
from aiofile import AIOFile
|
||||
from prometheus_client import CONTENT_TYPE_LATEST
|
||||
from prometheus_client import CollectorRegistry
|
||||
from prometheus_client import Counter
|
||||
|
|
@ -21,7 +22,8 @@ from sanic.exceptions import NotFound
|
|||
from sanic.exceptions import ServerError
|
||||
from sanic.exceptions import ServiceUnavailable
|
||||
from sanic_limiter import Limiter
|
||||
from sanic_limiter import RateLimitExceeded
|
||||
|
||||
# from sanic_limiter import RateLimitExceeded
|
||||
from sanic_limiter import get_remote_address
|
||||
|
||||
# Project Imports
|
||||
|
|
@ -37,7 +39,6 @@ from hyperglass.exceptions import RestError
|
|||
from hyperglass.exceptions import ScrapeError
|
||||
from hyperglass.execution.execute import Execute
|
||||
from hyperglass.models.query import Query
|
||||
from hyperglass.render import render_html
|
||||
from hyperglass.util import check_python
|
||||
from hyperglass.util import cpu_count
|
||||
from hyperglass.util import log
|
||||
|
|
@ -55,12 +56,22 @@ tempdir = tempfile.TemporaryDirectory(prefix="hyperglass_")
|
|||
os.environ["prometheus_multiproc_dir"] = tempdir.name
|
||||
|
||||
# Static File Definitions
|
||||
static_dir = Path(__file__).parent / "static" / "ui"
|
||||
log.debug(f"Static Files: {static_dir}")
|
||||
STATIC_DIR = Path(__file__).parent / "static"
|
||||
UI_DIR = STATIC_DIR / "ui"
|
||||
IMAGES_DIR = STATIC_DIR / "images"
|
||||
NEXT_DIR = UI_DIR / "_next"
|
||||
INDEX = UI_DIR / "index.html"
|
||||
NOTFOUND = UI_DIR / "404.html"
|
||||
NOFLASH = UI_DIR / "noflash.js"
|
||||
log.debug(f"Static Files: {STATIC_DIR}")
|
||||
|
||||
# Main Sanic App Definition
|
||||
app = Sanic(__name__)
|
||||
app.static("/ui", str(static_dir))
|
||||
app.static("/ui", str(UI_DIR))
|
||||
app.static("/_next", str(NEXT_DIR))
|
||||
app.static("/images", str(IMAGES_DIR))
|
||||
app.static("/ui/images", str(IMAGES_DIR))
|
||||
app.static("/noflash.js", str(NOFLASH))
|
||||
log.debug(app.config)
|
||||
|
||||
# Sanic Web Server Parameters
|
||||
|
|
@ -228,31 +239,35 @@ async def handle_backend_errors(request, exception):
|
|||
async def handle_404(request, exception):
|
||||
"""Render full error page for invalid URI."""
|
||||
path = request.path
|
||||
html = render_html("404", uri=path)
|
||||
# html = render_html("404", uri=path)
|
||||
client_addr = get_remote_address(request)
|
||||
count_notfound.labels(exception, path, client_addr).inc()
|
||||
log.error(f"Error: {exception}, Path: {path}, Source: {client_addr}")
|
||||
return sanic_response.html(html, status=404)
|
||||
# return sanic_response.html(html, status=404)
|
||||
|
||||
async with AIOFile(NOTFOUND, "r") as nf:
|
||||
html = await nf.read()
|
||||
return sanic_response.html(html)
|
||||
|
||||
|
||||
@app.exception(RateLimitExceeded)
|
||||
async def handle_429(request, exception):
|
||||
"""Render full error page for too many site queries."""
|
||||
html = render_html("ratelimit-site")
|
||||
client_addr = get_remote_address(request)
|
||||
count_ratelimit.labels(exception, client_addr).inc()
|
||||
log.error(f"Error: {exception}, Source: {client_addr}")
|
||||
return sanic_response.html(html, status=429)
|
||||
# @app.exception(RateLimitExceeded)
|
||||
# async def handle_429(request, exception):
|
||||
# """Render full error page for too many site queries."""
|
||||
# html = render_html("ratelimit-site")
|
||||
# client_addr = get_remote_address(request)
|
||||
# count_ratelimit.labels(exception, client_addr).inc()
|
||||
# log.error(f"Error: {exception}, Source: {client_addr}")
|
||||
# return sanic_response.html(html, status=429)
|
||||
|
||||
|
||||
@app.exception(ServerError)
|
||||
async def handle_500(request, exception):
|
||||
"""Render general error page."""
|
||||
client_addr = get_remote_address(request)
|
||||
count_errors.labels(500, exception, client_addr, None, None, None).inc()
|
||||
log.error(f"Error: {exception}, Source: {client_addr}")
|
||||
html = render_html("500")
|
||||
return sanic_response.html(html, status=500)
|
||||
# @app.exception(ServerError)
|
||||
# async def handle_500(request, exception):
|
||||
# """Render general error page."""
|
||||
# client_addr = get_remote_address(request)
|
||||
# count_errors.labels(500, exception, client_addr, None, None, None).inc()
|
||||
# log.error(f"Error: {exception}, Source: {client_addr}")
|
||||
# html = render_html("500")
|
||||
# return sanic_response.html(html, status=500)
|
||||
|
||||
|
||||
async def clear_cache():
|
||||
|
|
@ -269,8 +284,12 @@ async def clear_cache():
|
|||
@limiter.limit(rate_limit_site, error_message="Site")
|
||||
async def site(request):
|
||||
"""Serve main application front end."""
|
||||
html = await render_html("form", primary_asn=params.general.primary_asn)
|
||||
return sanic_response.html(html)
|
||||
|
||||
# html = await render_html("form", primary_asn=params.general.primary_asn)
|
||||
# return sanic_response.html(html)
|
||||
async with AIOFile(INDEX, "r") as entry:
|
||||
html = await entry.read()
|
||||
return sanic_response.html(html)
|
||||
|
||||
|
||||
@app.route("/config", methods=["GET", "OPTIONS"])
|
||||
|
|
@ -326,7 +345,7 @@ async def hyperglass_main(request):
|
|||
|
||||
# Use hashed query_data string as key for for k/v cache store so
|
||||
# each command output value is unique.
|
||||
cache_key = hash(query_data)
|
||||
cache_key = hash(str(query_data))
|
||||
|
||||
# Define cache entry expiry time
|
||||
cache_timeout = params.features.cache.timeout
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
"""Renders Jinja2 & Sass templates for use by the front end application."""
|
||||
|
||||
# Project Imports
|
||||
# flake8: noqa: F401
|
||||
from hyperglass.render.html import render_html
|
||||
from hyperglass.render.webassets import render_assets
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
"""Renders Jinja2 & Sass templates for use by the front end application."""
|
||||
|
||||
# Standard Library Imports
|
||||
from pathlib import Path
|
||||
|
||||
# Third Party Imports
|
||||
import jinja2
|
||||
import yaml
|
||||
from aiofile import AIOFile
|
||||
from markdown2 import Markdown
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration import devices
|
||||
from hyperglass.configuration import networks
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.constants import DEFAULT_DETAILS
|
||||
from hyperglass.constants import DEFAULT_HELP
|
||||
from hyperglass.constants import DEFAULT_TERMS
|
||||
from hyperglass.exceptions import ConfigError
|
||||
from hyperglass.exceptions import HyperglassError
|
||||
from hyperglass.util import log
|
||||
|
||||
# Module Directories
|
||||
WORKING_DIR = Path(__file__).resolve().parent
|
||||
JINJA_LOADER = jinja2.FileSystemLoader(str(WORKING_DIR))
|
||||
JINJA_ENV = jinja2.Environment(
|
||||
loader=JINJA_LOADER,
|
||||
autoescape=True,
|
||||
extensions=["jinja2.ext.autoescape"],
|
||||
enable_async=True,
|
||||
)
|
||||
|
||||
_MD_CONFIG = {
|
||||
"extras": {
|
||||
"break-on-newline": True,
|
||||
"code-friendly": True,
|
||||
"tables": True,
|
||||
"html-classes": {"table": "table"},
|
||||
}
|
||||
}
|
||||
MARKDOWN = Markdown(**_MD_CONFIG)
|
||||
|
||||
|
||||
async def parse_md(raw_file):
|
||||
file_list = raw_file.split("---", 2)
|
||||
file_list_len = len(file_list)
|
||||
if file_list_len == 1:
|
||||
fm = {}
|
||||
content = file_list[0]
|
||||
elif file_list_len == 3 and file_list[1].strip():
|
||||
try:
|
||||
fm = yaml.safe_load(file_list[1])
|
||||
except yaml.YAMLError as ye:
|
||||
raise ConfigError(str(ye)) from None
|
||||
content = file_list[2]
|
||||
else:
|
||||
fm = {}
|
||||
content = ""
|
||||
return (fm, content)
|
||||
|
||||
|
||||
async def get_file(path_obj):
|
||||
async with AIOFile(path_obj, "r") as raw_file:
|
||||
file = await raw_file.read()
|
||||
return file
|
||||
|
||||
|
||||
async def render_help():
|
||||
if params.branding.help_menu.file is not None:
|
||||
help_file = await get_file(params.branding.help_menu.file)
|
||||
else:
|
||||
help_file = DEFAULT_HELP
|
||||
|
||||
fm, content = await parse_md(help_file)
|
||||
|
||||
content_template = JINJA_ENV.from_string(content)
|
||||
content_rendered = await content_template.render_async(params, info=fm)
|
||||
|
||||
return {"content": MARKDOWN.convert(content_rendered), **fm}
|
||||
|
||||
|
||||
async def render_terms():
|
||||
|
||||
if params.branding.terms.file is not None:
|
||||
terms_file = await get_file(params.branding.terms.file)
|
||||
else:
|
||||
terms_file = DEFAULT_TERMS
|
||||
|
||||
fm, content = await parse_md(terms_file)
|
||||
content_template = JINJA_ENV.from_string(content)
|
||||
content_rendered = await content_template.render_async(params, info=fm)
|
||||
|
||||
return {"content": MARKDOWN.convert(content_rendered), **fm}
|
||||
|
||||
|
||||
async def render_details():
|
||||
details = []
|
||||
for vrf in devices.vrf_objects:
|
||||
detail = {"name": vrf.name, "display_name": vrf.display_name}
|
||||
info_attrs = ("bgp_aspath", "bgp_community")
|
||||
command_info = []
|
||||
for attr in info_attrs:
|
||||
file = getattr(vrf.info, attr)
|
||||
if file is not None:
|
||||
raw_content = await get_file(file)
|
||||
fm, content = await parse_md(raw_content)
|
||||
else:
|
||||
fm, content = await parse_md(DEFAULT_DETAILS[attr])
|
||||
|
||||
content_template = JINJA_ENV.from_string(content)
|
||||
content_rendered = await content_template.render_async(params, info=fm)
|
||||
content_html = MARKDOWN.convert(content_rendered)
|
||||
|
||||
command_info.append(
|
||||
{
|
||||
"id": f"{vrf.name}-{attr}",
|
||||
"name": attr,
|
||||
"frontmatter": fm,
|
||||
"content": content_html,
|
||||
}
|
||||
)
|
||||
|
||||
detail.update({"commands": command_info})
|
||||
details.append(detail)
|
||||
return details
|
||||
|
||||
|
||||
async def render_html(template_name, **kwargs):
|
||||
"""Render Jinja2 HTML templates.
|
||||
|
||||
Arguments:
|
||||
template_name {str} -- Jinja2 template name
|
||||
|
||||
Raises:
|
||||
HyperglassError: Raised if template is not found
|
||||
|
||||
Returns:
|
||||
{str} -- Rendered template
|
||||
"""
|
||||
try:
|
||||
template_file = f"templates/{template_name}.html.j2"
|
||||
template = JINJA_ENV.get_template(template_file)
|
||||
|
||||
except jinja2.TemplateNotFound as template_error:
|
||||
log.error(
|
||||
f"Error rendering Jinja2 template {str(Path(template_file).resolve())}."
|
||||
)
|
||||
raise HyperglassError(template_error)
|
||||
|
||||
rendered_help = await render_help()
|
||||
rendered_terms = await render_terms()
|
||||
rendered_details = await render_details()
|
||||
|
||||
sub_templates = {
|
||||
"details": rendered_details,
|
||||
"help": rendered_help,
|
||||
"terms": rendered_terms,
|
||||
"networks": networks,
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
return await template.render_async(params, **sub_templates)
|
||||
|
||||
|
||||
# async def generate_markdown(section, file_name=None):
|
||||
# """Render markdown as HTML.
|
||||
|
||||
# Arguments:
|
||||
# section {str} -- Section name
|
||||
|
||||
# Keyword Arguments:
|
||||
# file_name {str} -- Markdown file name (default: {None})
|
||||
|
||||
# Raises:
|
||||
# HyperglassError: Raised if YAML front matter is unreadable
|
||||
|
||||
# Returns:
|
||||
# {dict} -- Frontmatter dictionary
|
||||
# """
|
||||
# if section == "help" and params.branding.help_menu.file is not None:
|
||||
# info = await get_file(params.branding.help_menu.file)
|
||||
# elif section == "help" and params.branding.help_menu.file is None:
|
||||
# info = DEFAULT_HELP
|
||||
# elif section == "details":
|
||||
# file = WORKING_DIR.joinpath(f"templates/info/details/{file_name}.md")
|
||||
# if file.exists():
|
||||
# with file.open(mode="r") as file_raw:
|
||||
# yaml_raw = file_raw.read()
|
||||
# else:
|
||||
# yaml_raw = DEFAULT_DETAILS[file_name]
|
||||
# _, frontmatter, content = yaml_raw.split("---", 2)
|
||||
# md_config = {
|
||||
# "extras": {
|
||||
# "break-on-newline": True,
|
||||
# "code-friendly": True,
|
||||
# "tables": True,
|
||||
# "html-classes": {"table": "table"},
|
||||
# }
|
||||
# }
|
||||
# markdown = Markdown(**md_config)
|
||||
|
||||
# frontmatter_rendered = JINJA_ENV.from_string(frontmatter).render(params)
|
||||
|
||||
# if frontmatter_rendered:
|
||||
# frontmatter_loaded = yaml.safe_load(frontmatter_rendered)
|
||||
# elif not frontmatter_rendered:
|
||||
# frontmatter_loaded = {"frontmatter": None}
|
||||
|
||||
# content_rendered = await JINJA_ENV.from_string(content).render_async(
|
||||
# params, info=frontmatter_loaded
|
||||
# )
|
||||
|
||||
# help_dict = dict(content=markdown.convert(content_rendered), **frontmatter_loaded)
|
||||
# if not help_dict:
|
||||
# raise HyperglassError(f"Error reading YAML frontmatter for {file_name}")
|
||||
# return help_dict
|
||||
|
||||
|
||||
# async def render_html(template_name, **kwargs):
|
||||
# """Render Jinja2 HTML templates.
|
||||
|
||||
# Arguments:
|
||||
# template_name {str} -- Jinja2 template name
|
||||
|
||||
# Raises:
|
||||
# HyperglassError: Raised if template is not found
|
||||
|
||||
# Returns:
|
||||
# {str} -- Rendered template
|
||||
# """
|
||||
# detail_items = ("footer", "bgp_aspath", "bgp_community")
|
||||
# details = {}
|
||||
|
||||
# for details_name in detail_items:
|
||||
# details_data = await generate_markdown("details", details_name)
|
||||
# details.update({details_name: details_data})
|
||||
|
||||
# rendered_help = await generate_markdown("help")
|
||||
|
||||
# try:
|
||||
# template_file = f"templates/{template_name}.html.j2"
|
||||
# template = JINJA_ENV.get_template(template_file)
|
||||
|
||||
# except jinja2.TemplateNotFound as template_error:
|
||||
# log.error(f"Error rendering Jinja2 template {Path(template_file).resolve()}.")
|
||||
# raise HyperglassError(template_error)
|
||||
|
||||
# return await template.render_async(
|
||||
# params,
|
||||
# rendered_help=rendered_help,
|
||||
# details=details,
|
||||
# networks=networks,
|
||||
# **kwargs,
|
||||
# )
|
||||
2
hyperglass/render/templates/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
.DS_Store
|
||||
old/
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{% extends "templates/base.html.j2" %}
|
||||
|
||||
{% block content %}
|
||||
{% import "templates/errortext.html.j2" as errortext %}
|
||||
{{ errortext.errortext(branding.text.error404.title, branding.text.error404.subtitle.format(uri=uri), branding.text.error404.button) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
{% include "templates/footer.html.j2" %}
|
||||
{% endblock %}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{% extends "templates/base.html.j2" %}
|
||||
|
||||
{% block content %}
|
||||
{% import "templates/errortext.html.j2" as errortext %}
|
||||
{{ errortext.errortext(branding.text.error500.title, branding.text.error500.subtitle, branding.text.error500.button) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
{% include "templates/footer.html.j2" %}
|
||||
{% endblock %}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="h-100">
|
||||
|
||||
<head>
|
||||
<title>{{ branding.site_title }}</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ branding.logo.favicons }}apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ branding.logo.favicons }}favicon-16x16.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ branding.logo.favicons }}favicon-32x32.png" />
|
||||
<link rel="manifest" href="{{ branding.logo.favicons }}site.webmanifest" />
|
||||
<link rel="mask-icon" href="{{ branding.logo.favicons }}safari-pinned-tab.svg"
|
||||
color="{{ branding.colors.primary }}" />
|
||||
<link rel="shortcut icon" href="{{ branding.logo.favicons }}favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="{{ branding.colors.primary }}" />
|
||||
<meta name="msapplication-config" content="{{ branding.logo.favicons }}browserconfig.xml" />
|
||||
<meta name="theme-color" content="{{ branding.colors.button_submit }}" />
|
||||
<link href="ui/hyperglass.css" rel="stylesheet" type="text/css" />
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column h-100">
|
||||
{% include "templates/ratelimit-query.html.j2" %}
|
||||
{% include "templates/info.html.j2" %}
|
||||
<div class="container-fluid d-flex w-100 h-100 mx-auto flex-column" id="hg-page-container">
|
||||
|
||||
<main role="main" class="flex-shrink-0">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
<button class="animsition btn btn-link text-primary hg-back d-none" id="hg-back-btn" data-animsition-out-class="fade-out-left" data-animsition-in-class="fade-in-left">
|
||||
<i class="remixicon-arrow-left-s-line"></i>
|
||||
</button>
|
||||
{% include "templates/footer.html.j2" %}
|
||||
</div>
|
||||
<script src="ui/hyperglass.js"></script>
|
||||
{% if general.google_analytics %}
|
||||
<!--Google Analytics-->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{general.google_analytics}}">
|
||||
</script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
gtag("config", "{{ general.google_analytics }}");
|
||||
</script>
|
||||
{% endif %}
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{% macro errortext(title, subtitle, button) -%}
|
||||
|
||||
<div class="container-fluid d-flex w-100 h-100 p-3 mx-auto flex-column">
|
||||
<div class="jumbotron bg-danger">
|
||||
<h1 class="display-4">{{ title }}</h1>
|
||||
<p class="lead">{{ subtitle }}</p>
|
||||
<hr class="my-4">
|
||||
<a class="btn btn-outline-danger btn-lg" href="/" role="button">{{ button }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<nav class="navbar fixed-bottom navbar-footer bg-overlay pt-0 pb-0">
|
||||
<div class="container-fluid row no-gutters text-center p-0">
|
||||
{% if branding.terms.enable %}
|
||||
<div class="col-auto float-left">
|
||||
<div class="d-none" id="hg-footer-terms-html">{{ details.footer.content | safe }}</div>
|
||||
<a class="btn btn-link" id="hg-footer-terms-btn" href="#" data-toggle="popover" tabindex="0" role="button"
|
||||
title="{{ branding.text.terms }}"><small>{{ branding.text.terms }}</small></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if branding.help_menu.enable %}
|
||||
<div class="col-auto float-left">
|
||||
<div class="d-none" id="hg-footer-help-html">{{ rendered_help.content | safe }}</div>
|
||||
<a class="btn btn-link" id="hg-footer-help-btn" href="#" data-toggle="popover" tabindex="0" role="button"
|
||||
title="{{ branding.text.info }}"><small>{{ branding.text.info }}</small></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-auto mr-auto"></div>
|
||||
{% if branding.credit.enable %}
|
||||
<div class="col-auto float-right">
|
||||
<div class="d-none" id="hg-footer-credit-title">
|
||||
Powered by <a href="https://github.com/checktheroads/hyperglass" target="_blank">hyperglass</a>.
|
||||
</div>
|
||||
<div class="d-none" id="hg-footer-credit-content">
|
||||
Source code licensed <a href="https://github.com/checktheroads/hyperglass/blob/master/LICENSE"
|
||||
target="_blank">BSD 3-Clause Clear.</a>
|
||||
</div>
|
||||
<a class="btn btn-link" id="hg-footer-credit-btn" href="#" data-toggle="popover" tabindex="0" role="button"><small><i
|
||||
class="remixicon-code-s-slash-line"></i></small></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if branding.peering_db.enable %}
|
||||
<div class="col-auto float-right">
|
||||
<a class="btn btn-link" href="https://as{{ general.primary_asn }}.peeringdb.com/"
|
||||
target="_blank"><small>PeeringDB <i class="remixicon-share-circle-line"></i></small>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
{% extends "templates/base.html.j2" %}
|
||||
|
||||
{% block content %}
|
||||
<div id="hg-parent" class="mx-auto">
|
||||
<div class="container animsition mw-lg-75 mw-xl-50" data-animsition-out-class="fade-out-right"
|
||||
data-animsition-in-class="fade-in-left" id="hg-form">
|
||||
<div class="container-fluid d-flex justify-content-center mb-4 text-center">
|
||||
{% import "templates/title.html.j2" as title %}
|
||||
{{ title.title(branding, primary_asn, size_title="h1", size_subtitle="h4") }}
|
||||
</div>
|
||||
<form class="needs-validation" onsubmit="return false" name="queryform" id="lgForm" action="?" method="POST"
|
||||
novalidate>
|
||||
<div class="form-row mb-4">
|
||||
<div class="col-md col-sm-12 mb-4 mb-md-0">
|
||||
<select multiple class="form-control form-control-lg hg-select" id="location" data-live-search="true"
|
||||
title="{{ branding.text.query_location }}">
|
||||
{% for (netname, loc_params) in networks.items() %}
|
||||
<optgroup label="{{ netname }}">
|
||||
{% for param in loc_params %}
|
||||
<option id='{{ param.hostname }}' value='{{ param.hostname }}'
|
||||
data-tokens='{{ netname }} {{param.hostname}}' data-display-name="{{ param.display_name }}"
|
||||
data-netname="{{ netname }}">
|
||||
{{ param.display_name }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md col-sm-12">
|
||||
<select class="form-control form-control-lg hg-select custom-select-lg" id="query_type"
|
||||
title="{{ branding.text.query_type }}" required>
|
||||
{% if features.bgp_route.enable %}
|
||||
<option id="bgp_route" value="bgp_route" data-display-name="{{ branding.text.bgp_route }}">
|
||||
{{ branding.text.bgp_route }}</option>
|
||||
{% endif %}
|
||||
{% if features.bgp_community.enable %}
|
||||
<option id="bgp_community" value="bgp_community" data-display-name="{{ branding.text.bgp_community }}">
|
||||
{{ branding.text.bgp_community }}</option>
|
||||
{% endif %}
|
||||
{% if features.bgp_aspath.enable %}
|
||||
<option id="bgp_aspath" value="bgp_aspath" data-display-name="{{ branding.text.bgp_aspath }}">
|
||||
{{ branding.text.bgp_aspath }}</option>
|
||||
{% endif %}
|
||||
{% if features.ping.enable %}
|
||||
<option id="ping" value="ping" data-display-name="{{ branding.text.ping }}">{{ branding.text.ping }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% if features.traceroute.enable %}
|
||||
<option id="traceroute" value="traceroute" data-display-name="{{ branding.text.traceroute }}">
|
||||
{{ branding.text.traceroute }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row mb-4" id="hg-row-2">
|
||||
<div id="hg-container-vrf"></div>
|
||||
<div class="col" id="hg-container-target">
|
||||
<div class="input-group input-group-lg">
|
||||
<input class="form-control" type="text" placeholder="{{ branding.text.query_target }}"
|
||||
aria-label="{{ branding.text.query_target }}" aria-describedby="query_target" id="query_target" required>
|
||||
<div class="input-group-append" id="hg-target-append">
|
||||
<button class="btn btn-primary" id="hg-submit-button" type="submit">
|
||||
<div id="hg-submit-icon">
|
||||
<i class="remixicon-search-line"></i>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% include "templates/results.html.j2" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<div class="modal fade" id="hg-info-bgp_aspath" tabindex="-1" role="dialog" aria-labelledby="hg-info-title-bgp_aspath" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="hg-info-title-bgp_aspath">{{ details.bgp_aspath.title | safe }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ details.bgp_aspath.content | safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="hg-info-bgp_community" tabindex="-1" role="dialog" aria-labelledby="hg-info-title-bgp_community" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="hg-info-title-bgp_community">{{ details.bgp_community.title | safe }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ details.bgp_community.content | safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
2
hyperglass/render/templates/info/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
.DS_Store
|
||||
*.md
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
template: bgp_aspath
|
||||
link: <a href="#" id="help_link_bgpa">Supported BGP AS Path Expressions</a>
|
||||
---
|
||||
Performs BGP table lookup based on `AS_PATH` regular expression.
|
||||
|
||||
{{ info["bgp_aspath"]["link"] }}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
template: bgp_community
|
||||
link: <a href="#" onclick="help_link_bgpc">{{ general["org_name"] }} BGP Communities</a>
|
||||
---
|
||||
Performs BGP table lookup based on [Extended](https://tools.ietf.org/html/rfc4360) or [Large](https://tools.ietf.org/html/rfc8195) community value.
|
||||
|
||||
{{ info["bgp_community"]["link"] }}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
template: bgp_route
|
||||
---
|
||||
Performs BGP table lookup based on IPv4/IPv6 prefix.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
.DS_Store
|
||||
*.md
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
+++
|
||||
title = "Supported AS Path Patterns"
|
||||
+++
|
||||
{{ branding.site_name }} accepts the following `AS_PATH` regular expression patterns:
|
||||
|
||||
| Expression | Match |
|
||||
| :----------------------- | ----------------------------------------------------: |
|
||||
| `_65000$` | Originated by AS65000 |
|
||||
| `^65000\_` | Received from AS65000 |
|
||||
| `_65000_` | Via AS65000 |
|
||||
| `_65000_65001_` | Via AS65000 and AS65001 |
|
||||
| `_65000(_.+_)65001$` | Anything from AS65001 that passed through AS65000 |
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
+++
|
||||
title = "BGP Communities"
|
||||
+++
|
||||
{{ branding.site_name }} makes use of the following BGP communities:
|
||||
|
||||
| Community | Description |
|
||||
| :-------- | :---------- |
|
||||
| `65000:1` | Example 1 |
|
||||
| `65000:2` | Example 2 |
|
||||
| `65000:3` | Example 3 |
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
+++
|
||||
+++
|
||||
By using {{ branding.site_name }}, you agree to be bound by the following terms of use: All queries executed on this page are logged for analysis and troubleshooting. Users are prohibited from automating queries, or attempting to process queries in bulk. This service is provided on a best effort basis, and {{ general.org_name }} makes no availability or performance warranties or guarantees whatsoever.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
template: ping
|
||||
---
|
||||
Sends 5 ICMP echo requests to the target.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
template: traceroute
|
||||
---
|
||||
Performs UDP Based traceroute to the target.<br>For information about how to interpret traceroute results, [click here](https://hyperglass.readthedocs.io/nanog_traceroute.pdf).
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<div class="modal fade" tabindex="-1" role="dialog" id="hg-ratelimit-query">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content bg-danger">
|
||||
<div class="modal-body">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h2 class="modal-title">{{ features.rate_limit.query.title }}</h2>
|
||||
<p>{{ features.rate_limit.query.message }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-danger" data-dismiss="modal">{{ features.rate_limit.query.button }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{% extends "templates/base.html.j2" %}
|
||||
|
||||
{% block content %}
|
||||
{% import "templates/errortext.html.j2" as errortext %}
|
||||
{{ errortext.errortext(features.rate_limit.site.title, features.rate_limit.site.subtitle, features.rate_limit.site.button) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
{% include "templates/footer.html.j2" %}
|
||||
{% endblock %}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<div class="container animsition" data-animsition-out-class="fade-out-left" data-animsition-in-class="fade-in-right"
|
||||
id="hg-results">
|
||||
<div class="row align-items-center mb-md-1 mb-lg-3 mb-xl-3 px-0 py-3 py-md-1 mx-0 mw-100 mw-md-none">
|
||||
<div class="d-flex col-12 col-lg-9 justify-content-center justify-content-lg-start text-center text-lg-left pl-lg-1"
|
||||
data-href="/" id="hg-title-col">
|
||||
{% import "templates/title.html.j2" as title %}
|
||||
{{ title.title(branding, primary_asn, size_title="h1", size_subtitle="h4", direction="left") }}
|
||||
</div>
|
||||
<div id="hg-results-title"
|
||||
class="d-flex col-12 col-lg-3 mb-0 mt-md-3 mb-md-2 px-1 px-md-0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-lg-1 mx-0 mw-100 mw-md-none">
|
||||
<div class="col align-self-center">
|
||||
<div class="accordion" id="hg-accordion">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
// Jinja2-rendered theme elements
|
||||
$hg-primary: {{ colors.primary }}
|
||||
$hg-secondary: {{ colors.secondary }}
|
||||
$hg-danger: {{ colors.danger }}
|
||||
$hg-warning: {{ colors.warning }}
|
||||
$hg-background: {{ colors.background }}
|
||||
$hg-font-primary: {{ font.primary.name }}
|
||||
$hg-font-primary-size: {{ font.primary.size }}
|
||||
$hg-font-mono: {{ font.mono.name }}
|
||||
$hg-font-mono-size: {{ font.mono.size }}
|
||||
|
||||
// Functions
|
||||
@function findTextColor($color)
|
||||
$inverted: invert($color)
|
||||
@if (lightness($color) > 55%)
|
||||
$text-color: scale-color($inverted, $lightness: -90%)
|
||||
@return grayscale($text-color)
|
||||
@else
|
||||
$text-color: scale-color($inverted, $lightness: 90%)
|
||||
@return grayscale($text-color)
|
||||
|
||||
@function findOverlayColor($color)
|
||||
@if (lightness($color) > 55%)
|
||||
$darker-color: scale-color($color, $lightness: -3%)
|
||||
@return grayscale($darker-color)
|
||||
@else
|
||||
$darker-color: scale-color($color, $lightness: 5%)
|
||||
@return grayscale($darker-color)
|
||||
|
||||
@function findSubtleColor($color)
|
||||
@if (lightness($color) > 55%)
|
||||
$subtle-color: scale-color($color, $lightness: -3%)
|
||||
@return grayscale($subtle-color)
|
||||
@else
|
||||
$subtle-color: scale-color($color, $lightness: 20%)
|
||||
@return grayscale($subtle-color)
|
||||
|
||||
@function findInvertedColor($color)
|
||||
$inverted: invert($color)
|
||||
@if (lightness($inverted) > 55%)
|
||||
$border: scale-color($inverted, $lightness: -3%)
|
||||
@return grayscale($border)
|
||||
@else
|
||||
$border: scale-color($inverted, $lightness: 3%)
|
||||
@return grayscale($border)
|
||||
|
||||
@function findFieldColor($color)
|
||||
@if (lightness($color) > 55%)
|
||||
@return $color
|
||||
@else
|
||||
$darker-color: scale-color($color, $lightness: 5%)
|
||||
@return grayscale($darker-color)
|
||||
|
||||
@function findSubtleText($color)
|
||||
@if (lightness($color) > 55%)
|
||||
$subtle-color: scale-color($color, $lightness: -20%)
|
||||
@return grayscale($subtle-color)
|
||||
@else
|
||||
$subtle-color: scale-color($color, $lightness: 40%)
|
||||
@return grayscale($subtle-color)
|
||||
|
||||
// Body Color
|
||||
$body-bg: $hg-background
|
||||
$body-color: findTextColor($body-bg)
|
||||
|
||||
// Global variables
|
||||
$hg-overlay: findOverlayColor($hg-background)
|
||||
$hg-inverted: findInvertedColor($hg-background)
|
||||
$hg-field: findFieldColor($hg-background)
|
||||
$hg-border: rgba(findInvertedColor($hg-overlay), .2)
|
||||
$hg-primary-color: findTextColor($hg-primary)
|
||||
|
||||
// App variables
|
||||
$hg-loading: $hg-overlay
|
||||
$hg-footer: $hg-overlay
|
||||
$hg-overlay-bg: $hg-overlay
|
||||
$hg-overlay-color: findTextColor($hg-overlay-bg)
|
||||
$hg-inverted-bg: $hg-inverted
|
||||
$hg-inverted-color: findTextColor($hg-inverted-bg)
|
||||
$hg-field-bg: $hg-field
|
||||
$hg-field-color: findTextColor($hg-field-bg)
|
||||
|
||||
// Bootstrap Overrides
|
||||
|
||||
//// Components
|
||||
$component-active-color: findTextColor($hg-primary)
|
||||
|
||||
//// Flags
|
||||
$enable-responsive-font-sizes: true
|
||||
$enable-validation-icons: false
|
||||
$theme-colors: ("primary": $hg-primary, "secondary": $hg-secondary, "danger": $hg-danger, "warning": $hg-warning, "light": findTextColor(black), "dark": findTextColor(white), "overlay": $hg-overlay)
|
||||
|
||||
//// Fonts/Text
|
||||
$font-family-sans-serif: $hg-font-primary, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"
|
||||
$font-family-monospace: $hg-font-mono, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace
|
||||
$headings-font-weight: 400
|
||||
$font-size-base: $hg-font-primary-size
|
||||
|
||||
//// Borders
|
||||
$border-radius: .5rem
|
||||
$border-radius-lg: .7rem
|
||||
$border-radius-sm: .4rem
|
||||
|
||||
//// Popovers
|
||||
$popover-bg: $hg-field-bg
|
||||
$popover-border-color: $hg-border
|
||||
$popover-body-color: $hg-field-color
|
||||
|
||||
//// Cards
|
||||
$card-bg: $hg-field-bg
|
||||
$card-color: $hg-field-color
|
||||
$card-border-color: $hg-border
|
||||
|
||||
//// Dropdowns
|
||||
$dropdown-bg: $hg-field-bg
|
||||
$dropdown-color: $hg-field-color
|
||||
$dropdown-divider-bg: $hg-border
|
||||
$dropdown-border-color: $hg-border
|
||||
$dropdown-header-color: findSubtleText($dropdown-color)
|
||||
$dropdown-link-color: $hg-field-color
|
||||
$dropdown-link-hover-bg: $hg-primary
|
||||
$dropdown-link-hover-color: $hg-primary-color
|
||||
$dropdown-link-active-bg: $hg-primary
|
||||
$dropdown-link-active-color: $hg-primary-color
|
||||
|
||||
//// Forms
|
||||
$input-bg: $hg-field-bg
|
||||
$input-color: $hg-field-color
|
||||
$input-border-color: $hg-border
|
||||
$input-placeholder-color: findSubtleText($hg-field-color)
|
||||
$input-btn-focus-box-shadow: none
|
||||
$input-focus-bg: findSubtleColor($input-bg)
|
||||
$input-focus-color: $input-color
|
||||
$input-focus-border-color: findOverlayColor($hg-border)
|
||||
|
||||
//// Code
|
||||
$code-color: $hg-secondary
|
||||
$kbd-bg: findOverlayColor($hg-overlay)
|
||||
$kbd-color: $hg-overlay-color
|
||||
$pre-color: $body-color
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{% macro title(branding, primary_asn, size_title="h1", size_subtitle="h4", direction="none") -%}
|
||||
|
||||
{% if branding.text.title_mode == 'text_only' %}
|
||||
<div class="col p-0">
|
||||
<p class="{{ size_title }}">{{ branding.text.title }}</p>
|
||||
<p class="{{ size_subtitle }}">{{ branding.text.subtitle.format(primary_asn=primary_asn) }}</p>
|
||||
</div>
|
||||
|
||||
{% elif branding.text.title_mode == 'all' %}
|
||||
<img src="{{ branding.logo.logo_path }}" style="width: {{ branding.logo.width }}px; {% if branding.logo.height %} height: {{ branding.logo.height }}px;{% endif %}">
|
||||
<p class="{{ size_title }}">{{ branding.text.title }}<p>
|
||||
<p class="{{ size_subtitle }}">{{ branding.text.subtitle.format(primary_asn=primary_asn) }}</p>
|
||||
|
||||
{% elif branding.text.title_mode == 'logo_title' %}
|
||||
<div class="float-{{ direction }} mw-sm-100 mw-md-75 mw-lg-50 mw-xl-50">
|
||||
<img class="img-fluid hg-logo" src="{{ branding.logo.logo_path }}" style="width: {{ branding.logo.width }}px; {% if branding.logo.height %} height: {{ branding.logo.height }}px;{% endif %}">
|
||||
</div>
|
||||
<p clas="{{ size_title }}">{{ branding.text.title }}</p>
|
||||
|
||||
{% elif branding.text.title_mode == 'logo_only' %}
|
||||
<div class="float-{{ direction }} mw-sm-100 mw-md-75 mw-lg-50 mw-xl-50">
|
||||
<img class="img-fluid hg-logo" src="{{ branding.logo.logo_path }}" style="width: {{ branding.logo.width }}px; {% if branding.logo.height %} height: {{ branding.logo.height }}px;{% endif %}">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{%- endmacro %}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
"""Renders Jinja2 & Sass templates for use by the front end application."""
|
||||
|
||||
# Standard Library Imports
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Third Party Imports
|
||||
import jinja2
|
||||
from aiofile import AIOFile
|
||||
|
||||
# Project Imports
|
||||
from hyperglass.configuration import frontend_devices
|
||||
from hyperglass.configuration import frontend_networks
|
||||
from hyperglass.configuration import frontend_params
|
||||
from hyperglass.configuration import params
|
||||
from hyperglass.exceptions import HyperglassError
|
||||
from hyperglass.util import log
|
||||
|
||||
# File & Path Definitions
|
||||
CWD = Path(__file__).parent
|
||||
ROOT_DIR = CWD.parent
|
||||
FRONTEND_CONFIG = ROOT_DIR / "static/src/js/frontend.json"
|
||||
FONT_DIR = ROOT_DIR / "static/src/sass/fonts"
|
||||
FONT_CMD = ROOT_DIR / "static/src/node_modules/get-google-fonts/cli.js"
|
||||
THEME_FILE = ROOT_DIR / "static/src/sass/theme.sass"
|
||||
STATIC_DIR = ROOT_DIR / "static/src"
|
||||
|
||||
# Jinja2 Config
|
||||
JINJA_FILE_LOADER = jinja2.FileSystemLoader(str(CWD))
|
||||
JINJA_ENV = jinja2.Environment(
|
||||
loader=JINJA_FILE_LOADER,
|
||||
autoescape=True,
|
||||
extensions=["jinja2.ext.autoescape"],
|
||||
enable_async=True,
|
||||
)
|
||||
|
||||
|
||||
async def _render_frontend_config():
|
||||
"""Render user config to JSON for use by frontend."""
|
||||
log.debug("Rendering front end config...")
|
||||
|
||||
try:
|
||||
async with AIOFile(FRONTEND_CONFIG, "w+") as file:
|
||||
await file.write(
|
||||
json.dumps(
|
||||
{
|
||||
"config": frontend_params,
|
||||
"networks": frontend_networks,
|
||||
"devices": frontend_devices,
|
||||
}
|
||||
)
|
||||
)
|
||||
await file.fsync()
|
||||
log.debug("Rendered front end config")
|
||||
except Exception as e:
|
||||
raise HyperglassError(f"Error rendering front end config: {str(e)}")
|
||||
|
||||
|
||||
async def _get_fonts():
|
||||
"""Download Google fonts."""
|
||||
log.debug("Downloading theme fonts...")
|
||||
|
||||
font_base = "https://fonts.googleapis.com/css?family={p}|{m}&display=swap"
|
||||
font_primary = "+".join(params.branding.font.primary.name.split(" ")).strip()
|
||||
font_mono = "+".join(params.branding.font.mono.name.split(" ")).strip()
|
||||
font_url = font_base.format(p=font_primary + ":300,400,700", m=font_mono + ":400")
|
||||
|
||||
font_command = f"node {str(FONT_CMD)} -w -i '{font_url}' -o {str(FONT_DIR)}"
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
cmd=font_command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=STATIC_DIR,
|
||||
)
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=60)
|
||||
|
||||
for line in stdout.decode().strip().split("\n"):
|
||||
log.debug(line)
|
||||
|
||||
if proc.returncode != 0:
|
||||
output_error = stderr.decode("utf-8")
|
||||
log.error(output_error)
|
||||
raise RuntimeError(f"Error downloading font from URL {font_url}")
|
||||
|
||||
await proc.wait()
|
||||
log.debug("Downloaded theme fonts")
|
||||
|
||||
except Exception as e:
|
||||
raise HyperglassError(str(e))
|
||||
|
||||
|
||||
async def _render_theme():
|
||||
"""Render Jinja2 template to Sass file."""
|
||||
log.debug("Rendering theme elements...")
|
||||
try:
|
||||
template = JINJA_ENV.get_template("templates/theme.sass.j2")
|
||||
rendered_theme = await template.render_async(params.branding)
|
||||
|
||||
log.debug(f"Branding variables:\n{params.branding.json(indent=4)}")
|
||||
log.debug(f"Rendered theme:\n{str(rendered_theme)}")
|
||||
|
||||
async with AIOFile(THEME_FILE, "w+") as file:
|
||||
await file.write(rendered_theme)
|
||||
await file.fsync()
|
||||
log.debug("Rendered theme elements")
|
||||
except Exception as e:
|
||||
raise HyperglassError(f"Error rendering theme: {e}")
|
||||
|
||||
|
||||
async def _build_assets():
|
||||
"""Build, bundle, and minify Sass/CSS/JS web assets."""
|
||||
log.debug("Building web assets...")
|
||||
|
||||
yarn_command = "yarn --silent --emoji false --json --no-progress build"
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
cmd=yarn_command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=STATIC_DIR,
|
||||
)
|
||||
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=60)
|
||||
output_out = json.loads(stdout.decode("utf-8").split("\n")[0])
|
||||
|
||||
if proc.returncode != 0:
|
||||
output_error = json.loads(stderr.decode("utf-8").strip("\n"))
|
||||
raise RuntimeError(
|
||||
f'Error building web assets with script {output_out["data"]}:'
|
||||
f'{output_error["data"]}'
|
||||
)
|
||||
|
||||
await proc.wait()
|
||||
log.debug(f'Built web assets with script {output_out["data"]}')
|
||||
except Exception as e:
|
||||
raise HyperglassError(str(e))
|
||||
|
||||
|
||||
async def _render_all():
|
||||
"""Run all asset rendering/building functions.
|
||||
|
||||
Raises:
|
||||
HyperglassError: Raised if any downstream errors occur.
|
||||
"""
|
||||
try:
|
||||
await _render_frontend_config()
|
||||
await _get_fonts()
|
||||
await _render_theme()
|
||||
await _build_assets()
|
||||
except HyperglassError as e:
|
||||
raise HyperglassError(str(e)) from None
|
||||
|
||||
|
||||
def render_assets():
|
||||
"""Render assets."""
|
||||
start = time.time()
|
||||
|
||||
asyncio.run(_render_all())
|
||||
|
||||
end = time.time()
|
||||
elapsed = round(end - start, 2)
|
||||
log.debug(f"Rendered assets in {elapsed} seconds.")
|
||||
3
hyperglass/static/.gitignore
vendored
|
|
@ -10,4 +10,5 @@ frontend.json
|
|||
# NPM modules
|
||||
node_modules/
|
||||
# Downloaded Google Fonts
|
||||
fonts/
|
||||
fonts/
|
||||
ui/*
|
||||
|
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 577 B |
|
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 849 B After Width: | Height: | Size: 849 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 255 KiB |
BIN
hyperglass/static/images/hyperglass-opengraph.png
Executable file
|
After Width: | Height: | Size: 255 KiB |
|
|
@ -1,4 +0,0 @@
|
|||
@import './sass/hyperglass.scss';
|
||||
@import './node_modules/animsition/dist/css/animsition.min.css';
|
||||
@import './node_modules/remixicon/fonts/remixicon.css';
|
||||
@import './node_modules/bootstrap-select/dist/css/bootstrap-select.min.css';
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
// 3rd Party Libraries
|
||||
const Popper = require('popper.js');
|
||||
const bootstrap = require('bootstrap');
|
||||
const selectpicker = require('bootstrap-select');
|
||||
const animsition = require('animsition');
|
||||
|
||||
// hyperglass
|
||||
const hyperglass = require('./js/hyperglass.es6');
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
env:
|
||||
browser: true
|
||||
es6: true
|
||||
extends:
|
||||
- airbnb-base
|
||||
globals:
|
||||
Atomics: readonly
|
||||
SharedArrayBuffer: readonly
|
||||
parserOptions:
|
||||
ecmaVersion: 2018
|
||||
sourceType: module
|
||||
rules: {}
|
||||
13
hyperglass/static/src/js/.gitignore
vendored
|
|
@ -1,13 +0,0 @@
|
|||
.DS_Store
|
||||
# dev/test files
|
||||
*.tmp*
|
||||
test*
|
||||
*.log
|
||||
# generated theme file from hyperglass/hyperglass/render/templates/theme.sass.j2
|
||||
theme.sass
|
||||
# generated JSON file from ingested & validated YAML config
|
||||
frontend.json
|
||||
# NPM modules
|
||||
node_modules/
|
||||
# Downloaded Google Fonts
|
||||
fonts/
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
function footerPopoverTemplate() {
|
||||
const element = `
|
||||
<div class="popover mw-sm-75 mw-md-50 mw-lg-25" role="tooltip">
|
||||
<div class="arrow"></div>
|
||||
<h3 class="popover-header"></h3>
|
||||
<div class="popover-body"></div>
|
||||
</div>`;
|
||||
return element;
|
||||
}
|
||||
function feedbackInvalid(msg) {
|
||||
return `<div class="invalid-feedback px-1">${msg}</div>`;
|
||||
}
|
||||
function iconLoading(loc) {
|
||||
const element = `
|
||||
<i id="${loc}-spinner" class="hg-menu-icon hg-status-icon remixicon-loader-4-line text-secondary"></i>`;
|
||||
return element;
|
||||
}
|
||||
function iconError() {
|
||||
const element = '<i class="hg-menu-icon hg-status-icon remixicon-alert-line"></i>';
|
||||
return element;
|
||||
}
|
||||
function iconTimeout() {
|
||||
const element = '<i class="remixicon-time-line"></i>';
|
||||
return element;
|
||||
}
|
||||
function iconSuccess() {
|
||||
const element = '<i class="hg-menu-icon hg-status-icon remixicon-check-line"></i>';
|
||||
return element;
|
||||
}
|
||||
function supportedBtn(queryType) {
|
||||
const element = `
|
||||
<button class="btn btn-secondary hg-info-btn" id="hg-info-btn-${queryType}" data-hg-type="${queryType}" type="button">
|
||||
<div id="hg-info-icon-${queryType}">
|
||||
<i class="remixicon-information-line"></i>
|
||||
</div>
|
||||
</button>`;
|
||||
return element;
|
||||
}
|
||||
function vrfSelect(title) {
|
||||
const element = `<select class="form-control form-control-lg hg-select" id="query_vrf" title="${title}" disabled></select>`;
|
||||
return element;
|
||||
}
|
||||
function frontEndAlert(msg) {
|
||||
const element = `<div class="alert alert-danger text-center" id="hg-frontend-alert" role="alert">${msg}</div>`;
|
||||
return element;
|
||||
}
|
||||
function vrfOption(txt) {
|
||||
const element = `<option value="${txt}">${txt}</option>`;
|
||||
return element;
|
||||
}
|
||||
function tagGroup(label, value) {
|
||||
const element = `
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
${label}
|
||||
</div>
|
||||
<div class="input-group-append">
|
||||
${value}
|
||||
</div>
|
||||
</div>`;
|
||||
return element;
|
||||
}
|
||||
function tagLabel(color, id, text) {
|
||||
const element = `
|
||||
<span class="input-group-text hg-tag bg-${color}" id="hg-tag-${id}">
|
||||
${text}
|
||||
</span>`;
|
||||
return element;
|
||||
}
|
||||
|
||||
function resultsTitle(target, type, vrf, vrfText) {
|
||||
const element = `
|
||||
<div class="card-group flex-fill">
|
||||
<div class="card">
|
||||
<div class="card-body p-1 text-center">
|
||||
<h5 class="card-title mb-1">${target}</h6>
|
||||
<p class="card-text text-muted">${type}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body p-1 text-center">
|
||||
<h5 class="card-title mb-1">${vrf}</h6>
|
||||
<p class="card-text text-muted">${vrfText}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
return element;
|
||||
}
|
||||
|
||||
function outputBlock(loc) {
|
||||
const element = `
|
||||
<div class="card" id="${loc}-output">
|
||||
<div class="card-header bg-overlay" id="${loc}-heading">
|
||||
<div class="float-right hg-status-container" id="${loc}-status-container">
|
||||
<button type="button" class="float-right btn btn-loading btn-lg hg-menu-btn hg-status-btn"
|
||||
data-location="${loc}" id="${loc}-status-btn" disabled>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="float-right btn btn-loading btn-lg hg-menu-btn hg-copy-btn"
|
||||
data-clipboard-target="#${loc}-text" id="${loc}-copy-btn" disabled>
|
||||
<i class="remixicon-checkbox-multiple-blank-line hg-menu-icon hg-copy hg-copy-icon"></i>
|
||||
</button>
|
||||
<h2 class="mb-0" id="${loc}-heading-container">
|
||||
<button class="btn btn-link" type="button" data-toggle="collapse"
|
||||
data-target="#${loc}-content" aria-expanded="true" aria-controls="${loc}-content"
|
||||
id="${loc}-heading-text">
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="collapse" id="${loc}-content" aria-labelledby="${loc}-heading" data-parent="#hg-accordion">
|
||||
<div class="card-body" id="${loc}-text">
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
return element;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
footerPopoverTemplate,
|
||||
feedbackInvalid,
|
||||
iconLoading,
|
||||
iconError,
|
||||
iconTimeout,
|
||||
iconSuccess,
|
||||
supportedBtn,
|
||||
vrfSelect,
|
||||
frontEndAlert,
|
||||
vrfOption,
|
||||
tagGroup,
|
||||
tagLabel,
|
||||
resultsTitle,
|
||||
outputBlock,
|
||||
};
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import { frontEndAlert } from './components.es6';
|
||||
import jQuery from '../node_modules/jquery';
|
||||
|
||||
class InputInvalid extends Error {
|
||||
constructor(validationMsg, invalidField, fieldContainer) {
|
||||
super(validationMsg, invalidField, fieldContainer);
|
||||
this.name = this.constructor.name;
|
||||
this.message = validationMsg;
|
||||
this.field = invalidField;
|
||||
this.container = fieldContainer;
|
||||
}
|
||||
}
|
||||
|
||||
class FrontEndError extends Error {
|
||||
constructor(errorMsg, msgContainer) {
|
||||
super(errorMsg, msgContainer);
|
||||
this.name = this.constructor.name;
|
||||
this.message = errorMsg;
|
||||
this.container = msgContainer;
|
||||
this.alert = frontEndAlert(this.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
InputInvalid,
|
||||
FrontEndError,
|
||||
};
|
||||
|
|
@ -1,340 +0,0 @@
|
|||
// Module Imports
|
||||
import format from '../node_modules/string-format';
|
||||
import jQuery from '../node_modules/jquery';
|
||||
import ClipboardJS from '../node_modules/clipboard';
|
||||
|
||||
// Project Imports
|
||||
import {
|
||||
footerPopoverTemplate,
|
||||
feedbackInvalid,
|
||||
supportedBtn,
|
||||
vrfSelect,
|
||||
vrfOption,
|
||||
} from './components.es6';
|
||||
import { InputInvalid, FrontEndError } from './errors.es6';
|
||||
import {
|
||||
swapSpacing, resetResults, reloadPage, findIntersection,
|
||||
} from './util.es6';
|
||||
import { queryApp } from './query.es6';
|
||||
|
||||
// JSON Config Import
|
||||
import hgConf from './frontend.json';
|
||||
|
||||
// string-format config
|
||||
format.extend(String.prototype, {});
|
||||
|
||||
const $ = jQuery;
|
||||
|
||||
const lgForm = $('#lgForm');
|
||||
const vrfContainer = $('#hg-container-vrf');
|
||||
const queryLocation = $('#location');
|
||||
const queryType = $('#query_type');
|
||||
const queryTargetAppend = $('#hg-target-append');
|
||||
const submitIcon = $('#hg-submit-icon');
|
||||
|
||||
/* Removed liveSearch until bootstrap-select merges the fix for the mobile keyboard opening issue.
|
||||
Basically, any time an option is selected on a mobile device, the keyboard pops open which is
|
||||
super annoying. */
|
||||
queryLocation.selectpicker({
|
||||
iconBase: '',
|
||||
liveSearch: false,
|
||||
selectedTextFormat: 'count > 2',
|
||||
style: '',
|
||||
styleBase: 'form-control',
|
||||
tickIcon: 'remixicon-check-line',
|
||||
}).on('hidden.bs.select', (e) => {
|
||||
$(e.currentTarget).nextAll('.dropdown-menu.show').find('input').blur();
|
||||
});
|
||||
|
||||
queryType.selectpicker({
|
||||
iconBase: '',
|
||||
liveSearch: false,
|
||||
style: '',
|
||||
styleBase: 'form-control',
|
||||
}).on('hidden.bs.select', (e) => {
|
||||
$(e.currentTarget).nextAll('.form-control.dropdown-toggle').blur();
|
||||
});
|
||||
|
||||
$('#hg-footer-terms-btn').popover({
|
||||
html: true,
|
||||
trigger: 'manual',
|
||||
template: footerPopoverTemplate(),
|
||||
placement: 'top',
|
||||
content: $('#hg-footer-terms-html').html(),
|
||||
}).on('click', (e) => {
|
||||
$(e.currentTarget).popover('toggle');
|
||||
}).on('focusout', (e) => {
|
||||
$(e.currentTarget).popover('hide');
|
||||
});
|
||||
|
||||
$('#hg-footer-help-btn').popover({
|
||||
html: true,
|
||||
trigger: 'manual',
|
||||
placement: 'top',
|
||||
template: footerPopoverTemplate(),
|
||||
content: $('#hg-footer-help-html').html(),
|
||||
}).on('click', (e) => {
|
||||
$(e.currentTarget).popover('toggle');
|
||||
}).on('focusout', (e) => {
|
||||
$(e.currentTarget).popover('hide');
|
||||
});
|
||||
|
||||
$('#hg-footer-credit-btn').popover({
|
||||
html: true,
|
||||
trigger: 'manual',
|
||||
placement: 'top',
|
||||
title: $('#hg-footer-credit-title').html(),
|
||||
content: $('#hg-footer-credit-content').html(),
|
||||
template: footerPopoverTemplate(),
|
||||
}).on('click', (e) => {
|
||||
$(e.currentTarget).popover('toggle');
|
||||
}).on('focusout', (e) => {
|
||||
$(e.currentTarget).popover('hide');
|
||||
});
|
||||
|
||||
$(document).ready(() => {
|
||||
reloadPage();
|
||||
$('#hg-results').hide();
|
||||
$('#hg-ratelimit-query').modal('hide');
|
||||
if (location.pathname === '/') {
|
||||
$('.animsition').animsition({
|
||||
inClass: 'fade-in',
|
||||
outClass: 'fade-out',
|
||||
inDuration: 400,
|
||||
outDuration: 400,
|
||||
transition: (url) => { window.location.href = url; },
|
||||
});
|
||||
$('#hg-form').animsition('in');
|
||||
}
|
||||
});
|
||||
|
||||
queryType.on('changed.bs.select', (e) => {
|
||||
const field = $(e.currentTarget);
|
||||
const queryTypeId = field.val();
|
||||
const queryTypeBtn = $('.hg-info-btn');
|
||||
|
||||
if (field.next('button').hasClass('is-invalid')) {
|
||||
field.next('button').removeClass('is-invalid');
|
||||
field.nextAll('.invalid-feedback').remove();
|
||||
}
|
||||
if ((queryTypeId === 'bgp_community') || (queryTypeId === 'bgp_aspath')) {
|
||||
queryTypeBtn.remove();
|
||||
queryTargetAppend.prepend(supportedBtn(queryTypeId));
|
||||
} else {
|
||||
queryTypeBtn.remove();
|
||||
}
|
||||
});
|
||||
|
||||
queryLocation.on('changed.bs.select', (e, clickedIndex, isSelected, previousValue) => {
|
||||
const field = $(e.currentTarget);
|
||||
if (field.next('button').hasClass('is-invalid')) {
|
||||
field.next('button').removeClass('is-invalid');
|
||||
field.nextAll('.invalid-feedback').remove();
|
||||
}
|
||||
vrfContainer.empty().removeClass('col');
|
||||
const queryLocationIds = field.val();
|
||||
if (Array.isArray(queryLocationIds) && (queryLocationIds.length)) {
|
||||
const queryLocationNet = field[0][clickedIndex].dataset.netname;
|
||||
const selectedVrfs = () => {
|
||||
const allVrfs = [];
|
||||
$.each(queryLocationIds, (i, loc) => {
|
||||
const locVrfs = hgConf.networks[queryLocationNet][loc].vrfs;
|
||||
allVrfs.push(new Set(locVrfs));
|
||||
});
|
||||
return allVrfs;
|
||||
};
|
||||
const intersectingVrfs = Array.from(findIntersection(...selectedVrfs()));
|
||||
// Add the VRF select element
|
||||
if (vrfContainer.find('#query_vrf').length === 0) {
|
||||
vrfContainer.addClass('col').html(vrfSelect(hgConf.config.branding.text.query_vrf));
|
||||
}
|
||||
// Build the select options for each VRF in array
|
||||
const vrfHtmlList = [];
|
||||
$.each(intersectingVrfs, (i, vrf) => {
|
||||
vrfHtmlList.push(vrfOption(vrf));
|
||||
});
|
||||
// Add the options to the VRF select element, enable it, initialize Bootstrap Select
|
||||
vrfContainer.find('#query_vrf').html(vrfHtmlList.join('')).removeAttr('disabled').selectpicker({
|
||||
iconBase: '',
|
||||
liveSearch: false,
|
||||
style: '',
|
||||
styleBase: 'form-control',
|
||||
});
|
||||
if (intersectingVrfs.length === 0) {
|
||||
vrfContainer.find('#query_vrf').selectpicker('destroy');
|
||||
vrfContainer.find('#query_vrf').prop('title', hgConf.config.messages.no_matching_vrfs).prop('disabled', true);
|
||||
vrfContainer.find('#query_vrf').selectpicker({
|
||||
iconBase: '',
|
||||
liveSearch: false,
|
||||
style: '',
|
||||
styleBase: 'form-control',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
queryTargetAppend.on('click', '.hg-info-btn', () => {
|
||||
const queryTypeId = $('.hg-info-btn').data('hg-type');
|
||||
$(`#hg-info-${queryTypeId}`).modal('show');
|
||||
});
|
||||
|
||||
$('#hg-row-2').find('#query_vrf').on('hidden.bs.select', (e) => {
|
||||
$(e.currentTarget).nextAll('.form-control.dropdown-toggle').blur();
|
||||
});
|
||||
|
||||
$(document).on('InvalidInputEvent', (e, domField) => {
|
||||
const errorField = $(domField);
|
||||
if (errorField.hasClass('is-invalid')) {
|
||||
errorField.on('keyup', () => {
|
||||
errorField.removeClass('is-invalid');
|
||||
errorField.nextAll('.invalid-feedback').remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Submit Form Action
|
||||
lgForm.on('submit', (e) => {
|
||||
e.preventDefault();
|
||||
submitIcon.empty().html('<i class="remixicon-loader-4-line"></i>').addClass('hg-loading');
|
||||
const queryType = $('#query_type').val() || '';
|
||||
const queryTarget = $('#query_target').val() || '';
|
||||
const queryVrf = $('#query_vrf').val() || hgConf.networks.default_vrf.display_name;
|
||||
let queryLocation = $('#location').val() || [];
|
||||
if (!Array.isArray(queryLocation)) {
|
||||
queryLocation = new Array(queryLocation);
|
||||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
InvalidInput event positional arguments:
|
||||
1: error message to display
|
||||
2: thing to circle in red
|
||||
3: place to put error message
|
||||
*/
|
||||
if (!queryTarget) {
|
||||
const msgVars = { field: hgConf.config.branding.text.query_target };
|
||||
throw new InputInvalid(
|
||||
hgConf.config.messages.no_input.format(msgVars),
|
||||
$('#query_target'),
|
||||
$('#query_target').parent(),
|
||||
);
|
||||
}
|
||||
if (!queryType) {
|
||||
const msgVars = { field: hgConf.config.branding.text.query_type };
|
||||
throw new InputInvalid(
|
||||
hgConf.config.messages.no_input.format(msgVars),
|
||||
$('#query_type').next('.dropdown-toggle'),
|
||||
$('#query_type').next('.dropdown-toggle').parent(),
|
||||
);
|
||||
}
|
||||
if (queryLocation === undefined || queryLocation.length === 0) {
|
||||
const msgVars = { field: hgConf.config.branding.text.query_location };
|
||||
throw new InputInvalid(
|
||||
hgConf.config.messages.no_input.format(msgVars),
|
||||
$('#location').next('.dropdown-toggle'),
|
||||
$('#location').next('.dropdown-toggle').parent(),
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
$(err.field).addClass('is-invalid');
|
||||
$(err.container).find('.invalid-feedback').remove();
|
||||
$(err.container).append(feedbackInvalid(err.message));
|
||||
submitIcon.empty().removeClass('hg-loading').html('<i class="remixicon-search-line"></i>');
|
||||
$(document).trigger('InvalidInputEvent', err.field);
|
||||
return false;
|
||||
}
|
||||
const queryTypeTitle = $(`#${queryType}`).data('display-name');
|
||||
try {
|
||||
try {
|
||||
queryApp(
|
||||
queryType,
|
||||
queryTypeTitle,
|
||||
queryLocation,
|
||||
queryTarget,
|
||||
queryVrf,
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw new FrontEndError(
|
||||
hgConf.config.messages.general,
|
||||
lgForm,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
err.container.append(err.alert);
|
||||
submitIcon.empty().removeClass('hg-loading').html('<i class="remixicon-search-line"></i>');
|
||||
return false;
|
||||
}
|
||||
$('#hg-form').animsition('out', $('#hg-results'), '#');
|
||||
$('#hg-form').hide();
|
||||
swapSpacing('results');
|
||||
$('#hg-results').show();
|
||||
$('#hg-results').animsition('in');
|
||||
$('#hg-submit-spinner').remove();
|
||||
$('#hg-back-btn').removeClass('d-none');
|
||||
$('#hg-back-btn').animsition('in');
|
||||
});
|
||||
|
||||
$('#hg-title-col').on('click', (e) => {
|
||||
window.location = $(e.currentTarget).data('href');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#hg-back-btn').click(() => {
|
||||
resetResults();
|
||||
});
|
||||
|
||||
const clipboard = new ClipboardJS('.hg-copy-btn');
|
||||
clipboard.on('success', (e) => {
|
||||
const copyIcon = $(e.trigger).find('.hg-copy-icon');
|
||||
copyIcon.removeClass('remixicon-checkbox-multiple-blank-line').addClass('remixicon-checkbox-multiple-line');
|
||||
e.clearSelection();
|
||||
setTimeout(() => {
|
||||
copyIcon.removeClass('remixicon-checkbox-multiple-line').addClass('remixicon-checkbox-multiple-blank-line');
|
||||
}, 800);
|
||||
});
|
||||
clipboard.on('error', (e) => {
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
/*
|
||||
.hg-done is the class added to the ${loc}-status-btn button component, once the
|
||||
content has finished loading.
|
||||
*/
|
||||
|
||||
// On hover, change icon to show that the content can be refreshed
|
||||
$('#hg-accordion').on('mouseenter', '.hg-done', (e) => {
|
||||
$(e.currentTarget)
|
||||
.find('.hg-status-icon')
|
||||
.addClass('remixicon-repeat-line');
|
||||
});
|
||||
|
||||
$('#hg-accordion').on('mouseleave', '.hg-done', (e) => {
|
||||
$(e.currentTarget)
|
||||
.find('.hg-status-icon')
|
||||
.removeClass('remixicon-repeat-line');
|
||||
});
|
||||
|
||||
// On click, refresh the content
|
||||
$('#hg-accordion').on('click', '.hg-done', (e) => {
|
||||
const refreshQueryType = $('#query_type').val() || '';
|
||||
const refreshQueryLocation = $('#location').val() || '';
|
||||
const refreshQueryTarget = $('#query_target').val() || '';
|
||||
const refreshQueryVrf = $('#query_vrf').val() || '';
|
||||
const refreshQueryTypeTitle = $(`#${refreshQueryType}`).data('display-name');
|
||||
queryApp(
|
||||
refreshQueryType,
|
||||
refreshQueryTypeTitle,
|
||||
refreshQueryLocation,
|
||||
refreshQueryTarget,
|
||||
refreshQueryVrf,
|
||||
);
|
||||
});
|
||||
|
||||
$('#hg-ratelimit-query').on('shown.bs.modal', () => {
|
||||
$('#hg-ratelimit-query').trigger('focus');
|
||||
});
|
||||
|
||||
$('#hg-ratelimit-query').find('btn').on('click', () => {
|
||||
$('#hg-ratelimit-query').modal('hide');
|
||||
});
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
import {
|
||||
iconLoading,
|
||||
iconError,
|
||||
iconTimeout,
|
||||
iconSuccess,
|
||||
tagGroup,
|
||||
tagLabel,
|
||||
resultsTitle,
|
||||
outputBlock,
|
||||
} from './components.es6';
|
||||
import jQuery from '../node_modules/jquery';
|
||||
import hgConf from './frontend.json';
|
||||
import { resetResults } from './util.es6';
|
||||
|
||||
const $ = jQuery;
|
||||
|
||||
function queryApp(queryType, queryTypeName, locationList, queryTarget, queryVrf) {
|
||||
// $('#hg-results-title').html(
|
||||
// tagGroup(
|
||||
// tagLabel(
|
||||
// 'loading',
|
||||
// 'query-type',
|
||||
// queryTypeName,
|
||||
// ),
|
||||
// tagLabel(
|
||||
// 'primary',
|
||||
// 'query-target',
|
||||
// queryTarget,
|
||||
// ),
|
||||
// )
|
||||
// + tagGroup(
|
||||
// tagLabel(
|
||||
// 'loading',
|
||||
// 'query-vrf-loc',
|
||||
// locationList.join(', '),
|
||||
// ),
|
||||
// tagLabel(
|
||||
// 'secondary',
|
||||
// 'query-target',
|
||||
// queryVrf,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
|
||||
$('#hg-results-title').html(
|
||||
resultsTitle(queryTarget, queryTypeName, queryVrf, hgConf.config.branding.text.query_vrf),
|
||||
);
|
||||
|
||||
$('#hg-submit-icon')
|
||||
.empty()
|
||||
.removeClass('hg-loading')
|
||||
.html('<i class="remixicon-search-line"></i>');
|
||||
|
||||
$.each(locationList, (n, loc) => {
|
||||
const locationName = $(`#${loc}`).data('display-name');
|
||||
|
||||
const contentHtml = outputBlock(loc);
|
||||
|
||||
if ($(`#${loc}-output`).length) {
|
||||
$(`#${loc}-output`).replaceWith(contentHtml);
|
||||
} else {
|
||||
$('#hg-accordion').append(contentHtml);
|
||||
}
|
||||
|
||||
$(`#${loc}-heading-text`).text(locationName);
|
||||
$(`#${loc}-status-container`)
|
||||
.addClass('hg-loading')
|
||||
.find('.hg-status-btn')
|
||||
.empty()
|
||||
.html(iconLoading(loc));
|
||||
|
||||
const generateError = (errorClass, locError, text) => {
|
||||
$(`#${locError}-heading`)
|
||||
.removeClass('bg-overlay')
|
||||
.addClass(`bg-${errorClass}`);
|
||||
$(`#${locError}-heading`)
|
||||
.find('.hg-menu-btn')
|
||||
.removeClass('btn-loading')
|
||||
.addClass(`btn-${errorClass}`);
|
||||
$(`#${locError}-status-container`)
|
||||
.removeClass('hg-loading')
|
||||
.find('.hg-status-btn')
|
||||
.empty()
|
||||
.html(iconError)
|
||||
.addClass('hg-done');
|
||||
$(`#${locError}-text`).html(text);
|
||||
};
|
||||
|
||||
const timeoutError = (locError, text) => {
|
||||
$(`#${locError}-heading`)
|
||||
.removeClass('bg-overlay')
|
||||
.addClass('bg-warning');
|
||||
$(`#${locError}-heading`)
|
||||
.find('.hg-menu-btn')
|
||||
.removeClass('btn-loading')
|
||||
.addClass('btn-warning');
|
||||
$(`#${locError}-status-container`)
|
||||
.removeClass('hg-loading')
|
||||
.find('.hg-status-btn')
|
||||
.empty()
|
||||
.html(iconTimeout)
|
||||
.addClass('hg-done');
|
||||
$(`#${locError}-text`)
|
||||
.empty()
|
||||
.html(text);
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/query',
|
||||
method: 'POST',
|
||||
data: JSON.stringify({
|
||||
query_location: loc,
|
||||
query_type: queryType,
|
||||
query_target: queryTarget,
|
||||
query_vrf: queryVrf,
|
||||
response_format: 'html',
|
||||
}),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
context: document.body,
|
||||
async: true,
|
||||
timeout: hgConf.config.general.request_timeout * 1000,
|
||||
})
|
||||
.done((data, textStatus, jqXHR) => {
|
||||
const displayHtml = `<pre>${data.output}</pre>`;
|
||||
$(`#${loc}-heading`)
|
||||
.removeClass('bg-overlay')
|
||||
.addClass('bg-primary');
|
||||
$(`#${loc}-heading`)
|
||||
.find('.hg-menu-btn')
|
||||
.removeClass('btn-loading')
|
||||
.addClass('btn-primary');
|
||||
$(`#${loc}-status-container`)
|
||||
.removeClass('hg-loading')
|
||||
.find('.hg-status-btn')
|
||||
.empty()
|
||||
.html(iconSuccess)
|
||||
.addClass('hg-done');
|
||||
$(`#${loc}-text`)
|
||||
.empty()
|
||||
.html(displayHtml);
|
||||
})
|
||||
.fail((jqXHR, textStatus, errorThrown) => {
|
||||
const statusCode = jqXHR.status;
|
||||
if (textStatus === 'timeout') {
|
||||
timeoutError(loc, hgConf.config.messages.request_timeout);
|
||||
} else if (jqXHR.status === 429) {
|
||||
resetResults();
|
||||
$('#hg-ratelimit-query').modal('show');
|
||||
} else if (statusCode === 500 && textStatus !== 'timeout') {
|
||||
timeoutError(loc, hgConf.config.messages.request_timeout);
|
||||
} else if (
|
||||
jqXHR.responseJSON.alert === 'danger'
|
||||
|| jqXHR.responseJSON.alert === 'warning'
|
||||
) {
|
||||
generateError(jqXHR.responseJSON.alert, loc, jqXHR.responseJSON.output);
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
$(`#${loc}-status-btn`).removeAttr('disabled');
|
||||
$(`#${loc}-copy-btn`).removeAttr('disabled');
|
||||
});
|
||||
$(`#${locationList[0]}-content`).collapse('show');
|
||||
});
|
||||
}
|
||||
module.exports = {
|
||||
queryApp,
|
||||
};
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
// Module Imports
|
||||
import jQuery from '../node_modules/jquery';
|
||||
|
||||
const $ = jQuery;
|
||||
|
||||
const pageContainer = $('#hg-page-container');
|
||||
const formContainer = $('#hg-form');
|
||||
const titleColumn = $('#hg-title-col');
|
||||
const queryLocation = $('#location');
|
||||
const queryType = $('#query_type');
|
||||
const queryTarget = $('#query_target');
|
||||
const queryVrf = $('#query_vrf');
|
||||
const resultsContainer = $('#hg-results');
|
||||
const resultsAccordion = $('#hg-accordion');
|
||||
const resultsColumn = resultsAccordion.parent();
|
||||
const backButton = $('#hg-back-btn');
|
||||
|
||||
function swapSpacing(goTo) {
|
||||
if (goTo === 'form') {
|
||||
pageContainer.removeClass('px-0 px-md-3');
|
||||
resultsColumn.removeClass('px-0');
|
||||
titleColumn.removeClass('text-center');
|
||||
} else if (goTo === 'results') {
|
||||
pageContainer.addClass('px-0 px-md-3');
|
||||
resultsColumn.addClass('px-0');
|
||||
titleColumn.addClass('text-left');
|
||||
}
|
||||
}
|
||||
|
||||
function resetResults() {
|
||||
queryLocation.selectpicker('deselectAll');
|
||||
queryLocation.selectpicker('val', '');
|
||||
queryType.selectpicker('val', '');
|
||||
queryTarget.val('');
|
||||
queryVrf.val('');
|
||||
resultsContainer.animsition('out', formContainer, '#');
|
||||
resultsContainer.hide();
|
||||
$('.hg-info-btn').remove();
|
||||
swapSpacing('form');
|
||||
formContainer.show();
|
||||
formContainer.animsition('in');
|
||||
backButton.addClass('d-none');
|
||||
resultsAccordion.empty();
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
queryLocation.selectpicker('deselectAll');
|
||||
queryLocation.selectpicker('val', []);
|
||||
queryType.selectpicker('val', '');
|
||||
queryTarget.val('');
|
||||
queryVrf.val('');
|
||||
resultsAccordion.empty();
|
||||
}
|
||||
|
||||
function findIntersection(firstSet, ...sets) {
|
||||
const count = sets.length;
|
||||
const result = new Set(firstSet);
|
||||
firstSet.forEach((item) => {
|
||||
let i = count;
|
||||
let allHave = true;
|
||||
while (i--) {
|
||||
allHave = sets[i].has(item);
|
||||
if (!allHave) { break; }
|
||||
}
|
||||
if (!allHave) {
|
||||
result.delete(item);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
module.exports = {
|
||||
swapSpacing,
|
||||
resetResults,
|
||||
reloadPage,
|
||||
findIntersection,
|
||||
};
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
{
|
||||
"name": "hyperglass",
|
||||
"version": "1.0.0",
|
||||
"author": "https://github.com/checktheroads",
|
||||
"license": "BSD-3-Clause-Clear",
|
||||
"description": "Front end utilities for hyperglass",
|
||||
"main": "hyperglass.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.0.0-0",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"animsition": "^4.0.2",
|
||||
"bootstrap": "^4.3.1",
|
||||
"bootstrap-select": "^1.13.10",
|
||||
"clipboard": "^2.0.4",
|
||||
"get-google-fonts": "^1.2.0",
|
||||
"global": "^4.4.0",
|
||||
"jquery": "^3.4.1",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"popper.js": "^1.15.0",
|
||||
"remixicon": "^1.3.1",
|
||||
"sass": "^1.22.10",
|
||||
"string-format": "^2.0.0",
|
||||
"tinyify": "^2.5.1",
|
||||
"watchify": "^3.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"cssnano": "^4.1.10",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb-base": "^13.2.0",
|
||||
"eslint-plugin-import": "^2.18.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "parcel build --no-cache --bundle-node-modules --public-url /ui/ --out-dir ../ui --out-file hyperglass.js bundle.es6 && parcel build --no-cache --bundle-node-modules --public-url /ui/ --out-dir ../ui --out-file hyperglass.css bundle.css"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-react"
|
||||
]
|
||||
}
|
||||
}
|
||||
13
hyperglass/static/src/sass/.gitignore
vendored
|
|
@ -1,13 +0,0 @@
|
|||
.DS_Store
|
||||
# dev/test files
|
||||
*.tmp*
|
||||
test*
|
||||
*.log
|
||||
# generated theme file from hyperglass/hyperglass/render/templates/theme.sass.j2
|
||||
theme.sass
|
||||
# generated JSON file from ingested & validated YAML config
|
||||
frontend.json
|
||||
# NPM modules
|
||||
node_modules/
|
||||
# Downloaded Google Fonts
|
||||
fonts/
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// Custom Classes
|
||||
|
||||
// Imports
|
||||
@import "./fonts/fonts.css";
|
||||
@import "./theme.sass";
|
||||
@import "./node_modules/bootstrap/scss/bootstrap";
|
||||
@import "./overrides.sass";
|
||||
|
|
@ -1,467 +0,0 @@
|
|||
// Custom Utility Classes
|
||||
|
||||
@media (min-width: 576px)
|
||||
.mw-sm-25
|
||||
max-width: 25% !important
|
||||
|
||||
.mw-sm-50
|
||||
max-width: 50% !important
|
||||
|
||||
.mw-sm-75
|
||||
max-width: 75% !important
|
||||
|
||||
.mw-sm-100
|
||||
max-width: 100% !important
|
||||
|
||||
.mw-sm-none
|
||||
max-width: none !important
|
||||
|
||||
.mh-sm-25
|
||||
max-height: 25% !important
|
||||
|
||||
.mh-sm-50
|
||||
max-height: 50% !important
|
||||
|
||||
.mh-sm-75
|
||||
max-height: 75% !important
|
||||
|
||||
.mh-sm-100
|
||||
max-height: 100% !important
|
||||
|
||||
.mh-sm-none
|
||||
max-height: none !important
|
||||
|
||||
@media (min-width: 768px)
|
||||
.mw-md-25
|
||||
max-width: 25% !important
|
||||
|
||||
.mw-md-50
|
||||
max-width: 50% !important
|
||||
|
||||
.mw-md-75
|
||||
max-width: 75% !important
|
||||
|
||||
.mw-md-100
|
||||
max-width: 100% !important
|
||||
|
||||
.mw-md-none
|
||||
max-width: none !important
|
||||
|
||||
.mh-md-25
|
||||
max-height: 25% !important
|
||||
|
||||
.mh-md-50
|
||||
max-height: 50% !important
|
||||
|
||||
.mh-md-75
|
||||
max-height: 75% !important
|
||||
|
||||
.mh-md-100
|
||||
max-height: 100% !important
|
||||
|
||||
.mh-md-none
|
||||
max-height: none !important
|
||||
|
||||
@media (min-width: 992px)
|
||||
.mw-lg-25
|
||||
max-width: 25% !important
|
||||
|
||||
.mw-lg-50
|
||||
max-width: 50% !important
|
||||
|
||||
.mw-lg-75
|
||||
max-width: 75% !important
|
||||
|
||||
.mw-lg-100
|
||||
max-width: 100% !important
|
||||
|
||||
.mw-lg-none
|
||||
max-width: none !important
|
||||
|
||||
.mh-lg-25
|
||||
max-height: 25% !important
|
||||
|
||||
.mh-lg-50
|
||||
max-height: 50% !important
|
||||
|
||||
.mh-lg-75
|
||||
max-height: 75% !important
|
||||
|
||||
.mh-lg-100
|
||||
max-height: 100% !important
|
||||
|
||||
.mh-lg-none
|
||||
max-height: none !important
|
||||
|
||||
@media (min-width: 1200px)
|
||||
.mw-xl-25
|
||||
max-width: 25% !important
|
||||
|
||||
.mw-xl-50
|
||||
max-width: 50% !important
|
||||
|
||||
.mw-xl-75
|
||||
max-width: 75% !important
|
||||
|
||||
.mw-xl-100
|
||||
max-width: 100% !important
|
||||
|
||||
.mw-xl-none
|
||||
max-width: none !important
|
||||
|
||||
.mh-xl-25
|
||||
max-height: 25% !important
|
||||
|
||||
.mh-xl-50
|
||||
max-height: 50% !important
|
||||
|
||||
.mh-xl-75
|
||||
max-height: 75% !important
|
||||
|
||||
.mh-xl-100
|
||||
max-height: 100% !important
|
||||
|
||||
.mh-xl-none
|
||||
max-height: none !important
|
||||
@media (min-width: 576px)
|
||||
.mw-sm-25
|
||||
max-width: 25% !important
|
||||
|
||||
.mw-sm-50
|
||||
max-width: 50% !important
|
||||
|
||||
.mw-sm-75
|
||||
max-width: 75% !important
|
||||
|
||||
.mw-sm-100
|
||||
max-width: 100% !important
|
||||
|
||||
.mw-sm-none
|
||||
max-width: none !important
|
||||
|
||||
.mh-sm-25
|
||||
max-height: 25% !important
|
||||
|
||||
.mh-sm-50
|
||||
max-height: 50% !important
|
||||
|
||||
.mh-sm-75
|
||||
max-height: 75% !important
|
||||
|
||||
.mh-sm-100
|
||||
max-height: 100% !important
|
||||
|
||||
.mh-sm-none
|
||||
max-height: none !important
|
||||
|
||||
@media (min-width: 768px)
|
||||
.mw-md-25
|
||||
max-width: 25% !important
|
||||
|
||||
.mw-md-50
|
||||
max-width: 50% !important
|
||||
|
||||
.mw-md-75
|
||||
max-width: 75% !important
|
||||
|
||||
.mw-md-100
|
||||
max-width: 100% !important
|
||||
|
||||
.mw-md-none
|
||||
max-width: none !important
|
||||
|
||||
.mh-md-25
|
||||
max-height: 25% !important
|
||||
|
||||
.mh-md-50
|
||||
max-height: 50% !important
|
||||
|
||||
.mh-md-75
|
||||
max-height: 75% !important
|
||||
|
||||
.mh-md-100
|
||||
max-height: 100% !important
|
||||
|
||||
.mh-md-none
|
||||
max-height: none !important
|
||||
|
||||
@media (min-width: 992px)
|
||||
.mw-lg-25
|
||||
max-width: 25% !important
|
||||
|
||||
.mw-lg-50
|
||||
max-width: 50% !important
|
||||
|
||||
.mw-lg-75
|
||||
max-width: 75% !important
|
||||
|
||||
.mw-lg-100
|
||||
max-width: 100% !important
|
||||
|
||||
.mw-lg-none
|
||||
max-width: none !important
|
||||
|
||||
.mh-lg-25
|
||||
max-height: 25% !important
|
||||
|
||||
.mh-lg-50
|
||||
max-height: 50% !important
|
||||
|
||||
.mh-lg-75
|
||||
max-height: 75% !important
|
||||
|
||||
.mh-lg-100
|
||||
max-height: 100% !important
|
||||
|
||||
.mh-lg-none
|
||||
max-height: none !important
|
||||
|
||||
@media (min-width: 1200px)
|
||||
.mw-xl-25
|
||||
max-width: 25% !important
|
||||
|
||||
.mw-xl-50
|
||||
max-width: 50% !important
|
||||
|
||||
.mw-xl-75
|
||||
max-width: 75% !important
|
||||
|
||||
.mw-xl-100
|
||||
max-width: 100% !important
|
||||
|
||||
.mw-xl-none
|
||||
max-width: none !important
|
||||
|
||||
.mh-xl-25
|
||||
max-height: 25% !important
|
||||
|
||||
.mh-xl-50
|
||||
max-height: 50% !important
|
||||
|
||||
.mh-xl-75
|
||||
max-height: 75% !important
|
||||
|
||||
.mh-xl-100
|
||||
max-height: 100% !important
|
||||
|
||||
.mh-xl-none
|
||||
max-height: none !important
|
||||
|
||||
.mh-75
|
||||
max-height: 75% !important
|
||||
|
||||
// hyperglass overrides
|
||||
.hg-logo
|
||||
max-height: 60px
|
||||
max-width: 100%
|
||||
height: auto
|
||||
width: auto
|
||||
|
||||
#hg-submit-button
|
||||
border-bottom-right-radius: $border-radius-lg !important
|
||||
border-top-right-radius: $border-radius-lg !important
|
||||
|
||||
#hg-form
|
||||
margin-top: 15% !important
|
||||
margin-bottom: 10% !important
|
||||
padding: unset
|
||||
|
||||
#hg-results
|
||||
margin-top: 5% !important
|
||||
margin-bottom: 5% !important
|
||||
padding: unset
|
||||
|
||||
#hg-results-title
|
||||
line-height: unset
|
||||
|
||||
.nav-masthead
|
||||
.nav-link
|
||||
& + .nav-link
|
||||
margin-left: 1rem
|
||||
|
||||
.input-group-lg > .form-control:not(textarea),
|
||||
.input-group-lg > .custom-select,
|
||||
.bootstrap-select.form-control-lg .dropdown-toggle, .input-group-append > .btn
|
||||
height: $input-height-lg !important
|
||||
padding: $input-padding-y-lg $input-padding-x-lg !important
|
||||
|
||||
.form-control
|
||||
transition: background-color .15s ease-in-out,box-shadow .15s ease-in-out !important
|
||||
color: $input-color !important
|
||||
|
||||
.bootstrap-select > .dropdown-toggle.bs-placeholder
|
||||
color: $input-placeholder-color !important
|
||||
|
||||
.bootstrap-select .dropdown-menu li a.dropdown-item:not(.selected):hover
|
||||
background-color: $hg-overlay
|
||||
color: findTextColor($hg-overlay)
|
||||
|
||||
.hg-back:hover
|
||||
text-decoration: none
|
||||
border: 1px solid $hg-primary !important
|
||||
|
||||
.hg-back:focus
|
||||
text-decoration: none
|
||||
|
||||
.hg-back
|
||||
position: fixed !important
|
||||
width: 3rem !important
|
||||
height: 3rem !important
|
||||
left: 2rem !important
|
||||
bottom: 5rem !important
|
||||
border-radius: 50rem !important
|
||||
border: 1px solid $card-border-color !important
|
||||
background-color: $hg-field-bg !important
|
||||
transition: all .3s !important
|
||||
font-weight: 200 !important
|
||||
// font-size: 1.4rem !important
|
||||
font-size: $font-size-lg
|
||||
opacity: 1 !important
|
||||
pointer-events: auto !important
|
||||
|
||||
#hg-accordion
|
||||
.btn-link
|
||||
font-weight: 200
|
||||
// font-size: 1.4rem
|
||||
font-size: $font-size-lg
|
||||
|
||||
#hg-accordion
|
||||
.card-body
|
||||
word-wrap: normal !important
|
||||
color: findTextColor($card-bg) !important
|
||||
|
||||
.hg-menu-btn
|
||||
-webkit-transition: none
|
||||
-moz-transition: none
|
||||
-o-transition: none
|
||||
transition: none
|
||||
|
||||
.hg-menu-icon
|
||||
-webkit-transition: all 0.5s ease
|
||||
-moz-transition: all 0.5s ease
|
||||
-o-transition: all 0.5s ease
|
||||
transition: all 0.5s ease
|
||||
|
||||
#hg-title-col
|
||||
cursor: pointer
|
||||
|
||||
.tab-content
|
||||
overflow: hidden
|
||||
|
||||
.tab-pane
|
||||
& > pre
|
||||
min-height: 45vh
|
||||
max-height: 60vh
|
||||
-ms-overflow-style: none
|
||||
scrollbar-width: none
|
||||
|
||||
::-webkit-scrollbar
|
||||
width: 0px
|
||||
background: transparent
|
||||
|
||||
.hg-status-container
|
||||
display: inline-block
|
||||
text-align: center
|
||||
vertical-align: middle
|
||||
line-height: 1.5
|
||||
|
||||
.hg-loading
|
||||
animation: spinner-border .75s linear infinite
|
||||
|
||||
.accordion > .card:only-of-type
|
||||
border-bottom: $card-border-width solid $card-border-color
|
||||
border-top-left-radius: $border-radius !important
|
||||
border-top-right-radius: $border-radius !important
|
||||
border-bottom-left-radius: $border-radius !important
|
||||
border-bottom-right-radius: $border-radius !important
|
||||
|
||||
.btn:focus,.btn:active
|
||||
outline: none !important
|
||||
box-shadow: none
|
||||
|
||||
.bg-primary
|
||||
.btn-primary:hover
|
||||
color: findTextColor($hg-primary) !important
|
||||
background-color: darken($hg-primary, 10%) !important
|
||||
border-color: $hg-primary !important
|
||||
|
||||
.bg-warning
|
||||
.btn-warning:hover
|
||||
color: findTextColor($hg-warning) !important
|
||||
background-color: darken($hg-warning, 10%) !important
|
||||
border-color: $hg-warning !important
|
||||
|
||||
.bg-danger
|
||||
.btn-danger:hover
|
||||
color: findTextColor($hg-danger) !important
|
||||
background-color: darken($hg-danger, 10%) !important
|
||||
border-color: $hg-danger !important
|
||||
|
||||
.bg-overlay
|
||||
.btn-overlay:hover
|
||||
color: findTextColor($hg-overlay) !important
|
||||
background-color: darken($hg-overlay, 10%) !important
|
||||
border-color: $hg-overlay !important
|
||||
|
||||
.bg-primary
|
||||
*
|
||||
color: findTextColor($hg-primary) !important
|
||||
|
||||
.bg-danger
|
||||
*
|
||||
color: findTextColor($hg-danger) !important
|
||||
|
||||
.bg-warning
|
||||
*
|
||||
color: findTextColor($hg-warning) !important
|
||||
|
||||
.bg-overlay
|
||||
*
|
||||
color: findTextColor($hg-overlay) !important
|
||||
|
||||
.bg-primary
|
||||
::selection, ::-moz-selection
|
||||
background-color: findTextColor($hg-primary) !important
|
||||
color: $hg-primary !important
|
||||
|
||||
.bg-danger
|
||||
::selection, ::-moz-selection
|
||||
background-color: findTextColor($hg-danger) !important
|
||||
color: $hg-danger !important
|
||||
|
||||
.bg-warning
|
||||
::selection, ::-moz-selection
|
||||
background-color: findTextColor($hg-warning) !important
|
||||
color: $hg-warning !important
|
||||
|
||||
.bg-overlay
|
||||
::selection, ::-moz-selection
|
||||
background-color: findTextColor($hg-overlay) !important
|
||||
color: $hg-overlay !important
|
||||
|
||||
.bg-danger
|
||||
.btn-outline-danger
|
||||
border-color: findTextColor($hg-danger) !important
|
||||
|
||||
.bg-danger
|
||||
.btn-outline-danger:hover
|
||||
background-color: findTextColor($hg-danger) !important
|
||||
color: $hg-danger !important
|
||||
|
||||
.bg-danger
|
||||
hr
|
||||
background-color: darken($hg-danger, 10%)
|
||||
|
||||
.modal-body > p
|
||||
padding-left: 0.3rem !important
|
||||
|
||||
.modal-title
|
||||
padding-bottom: 1rem !important
|
||||
|
||||
.popover-body
|
||||
max-height: 60vh !important
|
||||
overflow-y: auto !important
|
||||
|
||||
.jumbotron.bg-danger
|
||||
margin-top: 10% !important
|
||||
margin-bottom: 10% !important
|
||||
8
hyperglass/static/ui/.gitignore
vendored
|
|
@ -1,8 +0,0 @@
|
|||
*.css
|
||||
*.js
|
||||
*.map
|
||||
*.woff
|
||||
*.woff2
|
||||
*.svg
|
||||
*.ttf
|
||||
*.eot
|
||||
5
ui/.alias.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
"~": path.join(__dirname)
|
||||
};
|
||||
1
ui/.next/BUILD_ID
Normal file
|
|
@ -0,0 +1 @@
|
|||
8MqatFOx_lsVMBtOoS25-
|
||||
28
ui/.next/build-manifest.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"devFiles": [],
|
||||
"pages": {
|
||||
"/": [
|
||||
"static/runtime/webpack-bc0bd5fbb696f901c79f.js",
|
||||
"static/chunks/commons.aec5eef0c3bc0db3cfad.js",
|
||||
"static/runtime/main-93cb3e6f7aab28bb61f5.js"
|
||||
],
|
||||
"/_app": [
|
||||
"static/runtime/webpack-bc0bd5fbb696f901c79f.js",
|
||||
"static/chunks/commons.aec5eef0c3bc0db3cfad.js",
|
||||
"static/runtime/main-93cb3e6f7aab28bb61f5.js"
|
||||
],
|
||||
"/_error": [
|
||||
"static/runtime/webpack-bc0bd5fbb696f901c79f.js",
|
||||
"static/chunks/commons.aec5eef0c3bc0db3cfad.js",
|
||||
"static/runtime/main-93cb3e6f7aab28bb61f5.js"
|
||||
],
|
||||
"/_polyfills": [
|
||||
"static/runtime/polyfills-104006257fab7491bcba.js"
|
||||
],
|
||||
"/index": [
|
||||
"static/runtime/webpack-bc0bd5fbb696f901c79f.js",
|
||||
"static/chunks/commons.aec5eef0c3bc0db3cfad.js",
|
||||
"static/runtime/main-93cb3e6f7aab28bb61f5.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
1
ui/.next/cache/next-babel-loader/00596f9133b74f0790738e194049b708.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _interopRequireDefault = require(\"@babel/runtime-corejs2/helpers/interopRequireDefault\");\n\nexports.__esModule = true;\nexports.Container = Container;\nexports.createUrl = createUrl;\nexports.default = void 0;\n\nvar _extends2 = _interopRequireDefault(require(\"@babel/runtime-corejs2/helpers/extends\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(require(\"@babel/runtime-corejs2/helpers/asyncToGenerator\"));\n\nvar _react = _interopRequireDefault(require(\"react\"));\n\nvar _utils = require(\"../next-server/lib/utils\");\n\nexports.AppInitialProps = _utils.AppInitialProps;\n\nrequire(\"../client/router\");\n/**\n* `App` component is used for initialize of pages. It allows for overwriting and full control of the `page` initialization.\n* This allows for keeping state between navigation, custom error handling, injecting additional data.\n*/\n\n\nfunction appGetInitialProps(_x) {\n return _appGetInitialProps.apply(this, arguments);\n}\n\nfunction _appGetInitialProps() {\n _appGetInitialProps = (0, _asyncToGenerator2.default)(function* (_ref) {\n var {\n Component,\n ctx\n } = _ref;\n var pageProps = yield (0, _utils.loadGetInitialProps)(Component, ctx);\n return {\n pageProps\n };\n });\n return _appGetInitialProps.apply(this, arguments);\n}\n\nclass App extends _react.default.Component {\n // Kept here for backwards compatibility.\n // When someone ended App they could call `super.componentDidCatch`.\n // @deprecated This method is no longer needed. Errors are caught at the top level\n componentDidCatch(error, _errorInfo) {\n throw error;\n }\n\n render() {\n var {\n router,\n Component,\n pageProps\n } = this.props;\n var url = createUrl(router);\n return _react.default.createElement(Component, (0, _extends2.default)({}, pageProps, {\n url: url\n }));\n }\n\n}\n\nexports.default = App;\nApp.origGetInitialProps = appGetInitialProps;\nApp.getInitialProps = appGetInitialProps;\nvar warnContainer;\nvar warnUrl;\n\nif (false) {\n warnContainer = (0, _utils.execOnce)(() => {\n console.warn(\"Warning: the `Container` in `_app` has been deprecated and should be removed. https://err.sh/zeit/next.js/app-container-deprecated\");\n });\n warnUrl = (0, _utils.execOnce)(() => {\n console.error(\"Warning: the 'url' property is deprecated. https://err.sh/zeit/next.js/url-deprecated\");\n });\n} // @deprecated noop for now until removal\n\n\nfunction Container(p) {\n if (false) warnContainer();\n return p.children;\n}\n\nfunction createUrl(router) {\n // This is to make sure we don't references the router object at call time\n var {\n pathname,\n asPath,\n query\n } = router;\n return {\n get query() {\n if (false) warnUrl();\n return query;\n },\n\n get pathname() {\n if (false) warnUrl();\n return pathname;\n },\n\n get asPath() {\n if (false) warnUrl();\n return asPath;\n },\n\n back: () => {\n if (false) warnUrl();\n router.back();\n },\n push: (url, as) => {\n if (false) warnUrl();\n return router.push(url, as);\n },\n pushTo: (href, as) => {\n if (false) warnUrl();\n var pushRoute = as ? href : '';\n var pushUrl = as || href;\n return router.push(pushRoute, pushUrl);\n },\n replace: (url, as) => {\n if (false) warnUrl();\n return router.replace(url, as);\n },\n replaceTo: (href, as) => {\n if (false) warnUrl();\n var replaceRoute = as ? href : '';\n var replaceUrl = as || href;\n return router.replace(replaceRoute, replaceUrl);\n }\n };\n}","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/0350433ef0ce6fa6f2cc8d3321c9694e.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _Set = require(\"@babel/runtime-corejs2/core-js/set\");\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nconst react_1 = require(\"react\");\n\nconst isServer = true;\n\nexports.default = () => {\n const mountedInstances = new _Set();\n let state;\n\n function emitChange(component) {\n state = component.props.reduceComponentsToState([...mountedInstances], component.props);\n\n if (component.props.handleStateChange) {\n component.props.handleStateChange(state);\n }\n }\n\n return class extends react_1.Component {\n // Used when server rendering\n static rewind() {\n const recordedState = state;\n state = undefined;\n mountedInstances.clear();\n return recordedState;\n }\n\n constructor(props) {\n super(props);\n\n if (isServer) {\n mountedInstances.add(this);\n emitChange(this);\n }\n }\n\n componentDidMount() {\n mountedInstances.add(this);\n emitChange(this);\n }\n\n componentDidUpdate() {\n emitChange(this);\n }\n\n componentWillUnmount() {\n mountedInstances.delete(this);\n emitChange(this);\n }\n\n render() {\n return null;\n }\n\n };\n};","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/046ed55319ca5c355a71420da9cfbc99.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\nvar __importStar = this && this.__importStar || function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];\n result[\"default\"] = mod;\n return result;\n};\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nconst React = __importStar(require(\"react\"));\n\nexports.RouterContext = React.createContext(null);","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/05b6560dc03de6c452896357cb2fb9f2.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nimport styled from \"@emotion/styled\";\nimport { Input, useColorMode, useTheme } from \"@chakra-ui/core\";\nconst StyledInput = styled(Input)`\n &::placeholder {\n color: ${props => props.placeholderColor};\n }\n`;\nexport default (({\n placeholder,\n register\n}) => {\n const theme = useTheme();\n const {\n colorMode\n } = useColorMode();\n const bg = colorMode === \"dark\" ? theme.colors.whiteAlpha[100] : theme.colors.white;\n const color = colorMode === \"dark\" ? theme.colors.whiteAlpha[800] : theme.colors.gray[400];\n const border = colorMode === \"dark\" ? theme.colors.whiteAlpha[50] : theme.colors.gray[100];\n const borderRadius = theme.space[1];\n const placeholderColor = colorMode === \"dark\" ? theme.colors.whiteAlpha[400] : theme.colors.gray[400];\n return __jsx(StyledInput, {\n name: \"query_target\",\n ref: register,\n placeholder: placeholder,\n placeholderColor: placeholderColor,\n size: \"lg\",\n bg: bg,\n color: color,\n borderColor: border,\n borderRadius: borderRadius\n });\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/06925473f3b64132089c8714075fd20d.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nimport ChakraSelect from \"~/components/ChakraSelect\";\n\nconst buildQueries = queryTypes => {\n const queries = [];\n queryTypes.map(q => {\n queries.push({\n value: q.name,\n label: q.display_name\n });\n });\n return queries;\n};\n\nexport default (({\n queryTypes,\n onChange\n}) => {\n const queries = buildQueries(queryTypes);\n return __jsx(ChakraSelect, {\n size: \"lg\",\n name: \"query_type\",\n onChange: e => onChange({\n field: \"query_type\",\n value: e.value\n }),\n options: queries\n });\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/06c1b86f1cd9c72faa4d2fcd7244175e.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"import chroma from \"chroma-js\";\n\nvar isDark = function isDark(color) {\n // YIQ equation from http://24ways.org/2010/calculating-color-contrast\n var rgb = chroma(color).rgb();\n var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;\n return yiq < 128;\n};\n\nvar isLight = function isLight(color) {\n return isDark(color);\n};\n\nvar opposingColor = function opposingColor(theme, color) {\n var opposing = isDark(color) ? theme.colors.white : theme.colors.black;\n return opposing;\n};\n\nvar googleFontUrl = function googleFontUrl(fontFamily) {\n var weights = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [300, 400, 700];\n var urlWeights = weights.join(\",\");\n var fontName = fontFamily.split(/, /)[0].trim().replace(/'|\"/g, \"\");\n var urlFont = fontName.split(/ /).join(\"+\");\n var urlBase = \"https://fonts.googleapis.com/css?family=\".concat(urlFont, \":\").concat(urlWeights, \"&display=swap\");\n return urlBase;\n};\n\nexport { isDark, isLight, opposingColor, googleFontUrl };","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/0acef3ff8623724f7ee39efed608abf8.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nfunction rewriteUrlForNextExport(url) {\n const [pathname, hash] = url.split('#'); // tslint:disable-next-line\n\n let [path, qs] = pathname.split('?');\n path = path.replace(/\\/$/, ''); // Append a trailing slash if this path does not have an extension\n\n if (!/\\.[^/]+\\/?$/.test(path)) path += `/`;\n if (qs) path += '?' + qs;\n if (hash) path += '#' + hash;\n return path;\n}\n\nexports.rewriteUrlForNextExport = rewriteUrlForNextExport;","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/0d12b655bf9c136bc2799ce987503e39.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _Object$keys = require(\"@babel/runtime-corejs2/core-js/object/keys\");\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nfunction getRouteMatcher(routeRegex) {\n const {\n re,\n groups\n } = routeRegex;\n return pathname => {\n const routeMatch = re.exec(pathname);\n\n if (!routeMatch) {\n return false;\n }\n\n const decode = decodeURIComponent;\n const params = {};\n\n _Object$keys(groups).forEach(slugName => {\n const g = groups[slugName];\n const m = routeMatch[g.pos];\n\n if (m !== undefined) {\n params[slugName] = ~m.indexOf('/') ? m.split('/').map(entry => decode(entry)) : g.repeat ? [decode(m)] : decode(m);\n }\n });\n\n return params;\n };\n}\n\nexports.getRouteMatcher = getRouteMatcher;","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/0e9b2d517d224dd33668c05466054b8d.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nimport { Flex, IconButton, useColorMode, useTheme } from \"@chakra-ui/core\";\nimport { motion } from \"framer-motion\";\nvar AnimatedFlex = motion.custom(Flex);\nexport default (function () {\n var theme = useTheme();\n\n var _useColorMode = useColorMode(),\n colorMode = _useColorMode.colorMode,\n toggleColorMode = _useColorMode.toggleColorMode;\n\n var bg = {\n light: theme.colors.white,\n dark: theme.colors.black\n };\n var icon = {\n light: \"moon\",\n dark: \"sun\"\n };\n return __jsx(Flex, {\n position: \"fixed\",\n as: \"header\",\n top: \"0\",\n zIndex: \"4\",\n bg: bg[colorMode],\n color: theme.colors.gray[500],\n left: \"0\",\n right: \"0\",\n width: \"full\",\n height: \"4rem\"\n }, __jsx(Flex, {\n w: \"100%\",\n mx: \"auto\",\n px: 6,\n justifyContent: \"flex-end\"\n }, __jsx(AnimatedFlex, {\n align: \"center\",\n initial: {\n opacity: 0\n },\n animate: {\n opacity: 1\n },\n transition: {\n duration: 0.6\n }\n }, __jsx(IconButton, {\n \"aria-label\": \"Switch to \".concat(colorMode === \"light\" ? \"dark\" : \"light\", \" mode\"),\n variant: \"ghost\",\n color: \"current\",\n ml: \"2\",\n fontSize: \"20px\",\n onClick: toggleColorMode,\n icon: icon[colorMode]\n }))));\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/0f2419a5941b7dd42b484cb4282daf9d.json
vendored
Normal file
1
ui/.next/cache/next-babel-loader/122d3098a1247017c977ff3c0d001d90.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _Set = require(\"@babel/runtime-corejs2/core-js/set\");\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\nvar __importDefault = this && this.__importDefault || function (mod) {\n return mod && mod.__esModule ? mod : {\n \"default\": mod\n };\n};\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nconst react_1 = __importDefault(require(\"react\"));\n\nconst side_effect_1 = __importDefault(require(\"./side-effect\"));\n\nconst amp_context_1 = require(\"./amp-context\");\n\nconst head_manager_context_1 = require(\"./head-manager-context\");\n\nconst amp_1 = require(\"./amp\");\n\nfunction defaultHead(inAmpMode = false) {\n const head = [react_1.default.createElement(\"meta\", {\n charSet: \"utf-8\"\n })];\n\n if (!inAmpMode) {\n head.push(react_1.default.createElement(\"meta\", {\n name: \"viewport\",\n content: \"width=device-width,minimum-scale=1,initial-scale=1\"\n }));\n }\n\n return head;\n}\n\nexports.defaultHead = defaultHead;\n\nfunction onlyReactElement(list, child) {\n // React children can be \"string\" or \"number\" in this case we ignore them for backwards compat\n if (typeof child === 'string' || typeof child === 'number') {\n return list;\n } // Adds support for React.Fragment\n\n\n if (child.type === react_1.default.Fragment) {\n return list.concat(react_1.default.Children.toArray(child.props.children).reduce((fragmentList, fragmentChild) => {\n if (typeof fragmentChild === 'string' || typeof fragmentChild === 'number') {\n return fragmentList;\n }\n\n return fragmentList.concat(fragmentChild);\n }, []));\n }\n\n return list.concat(child);\n}\n\nconst METATYPES = ['name', 'httpEquiv', 'charSet', 'itemProp'];\n/*\n returns a function for filtering head child elements\n which shouldn't be duplicated, like <title/>\n Also adds support for deduplicated `key` properties\n*/\n\nfunction unique() {\n const keys = new _Set();\n const tags = new _Set();\n const metaTypes = new _Set();\n const metaCategories = {};\n return h => {\n let unique = true;\n\n if (h.key && typeof h.key !== 'number' && h.key.indexOf('$') > 0) {\n const key = h.key.slice(h.key.indexOf('$') + 1);\n\n if (keys.has(key)) {\n unique = false;\n } else {\n keys.add(key);\n }\n } // eslint-disable-next-line default-case\n\n\n switch (h.type) {\n case 'title':\n case 'base':\n if (tags.has(h.type)) {\n unique = false;\n } else {\n tags.add(h.type);\n }\n\n break;\n\n case 'meta':\n for (let i = 0, len = METATYPES.length; i < len; i++) {\n const metatype = METATYPES[i];\n if (!h.props.hasOwnProperty(metatype)) continue;\n\n if (metatype === 'charSet') {\n if (metaTypes.has(metatype)) {\n unique = false;\n } else {\n metaTypes.add(metatype);\n }\n } else {\n const category = h.props[metatype];\n const categories = metaCategories[metatype] || new _Set();\n\n if (categories.has(category)) {\n unique = false;\n } else {\n categories.add(category);\n metaCategories[metatype] = categories;\n }\n }\n }\n\n break;\n }\n\n return unique;\n };\n}\n/**\n *\n * @param headElement List of multiple <Head> instances\n */\n\n\nfunction reduceComponents(headElements, props) {\n return headElements.reduce((list, headElement) => {\n const headElementChildren = react_1.default.Children.toArray(headElement.props.children);\n return list.concat(headElementChildren);\n }, []).reduce(onlyReactElement, []).reverse().concat(defaultHead(props.inAmpMode)).filter(unique()).reverse().map((c, i) => {\n const key = c.key || i;\n return react_1.default.cloneElement(c, {\n key\n });\n });\n}\n\nconst Effect = side_effect_1.default();\n/**\n * This component injects elements to `<head>` of your page.\n * To avoid duplicated `tags` in `<head>` you can use the `key` property, which will make sure every tag is only rendered once.\n */\n\nfunction Head({\n children\n}) {\n return react_1.default.createElement(amp_context_1.AmpStateContext.Consumer, null, ampState => react_1.default.createElement(head_manager_context_1.HeadManagerContext.Consumer, null, updateHead => react_1.default.createElement(Effect, {\n reduceComponentsToState: reduceComponents,\n handleStateChange: updateHead,\n inAmpMode: amp_1.isInAmpMode(ampState)\n }, children)));\n}\n\nHead.rewind = Effect.rewind;\nexports.default = Head;","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/137efa4e18d8024b33fee387b7bc1d0d.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _slicedToArray = require(\"@babel/runtime-corejs2/helpers/slicedToArray\");\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nfunction rewriteUrlForNextExport(url) {\n var _url$split = url.split('#'),\n _url$split2 = _slicedToArray(_url$split, 2),\n pathname = _url$split2[0],\n hash = _url$split2[1]; // tslint:disable-next-line\n\n\n var _pathname$split = pathname.split('?'),\n _pathname$split2 = _slicedToArray(_pathname$split, 2),\n path = _pathname$split2[0],\n qs = _pathname$split2[1];\n\n path = path.replace(/\\/$/, ''); // Append a trailing slash if this path does not have an extension\n\n if (!/\\.[^/]+\\/?$/.test(path)) path += \"/\";\n if (qs) path += '?' + qs;\n if (hash) path += '#' + hash;\n return path;\n}\n\nexports.rewriteUrlForNextExport = rewriteUrlForNextExport;","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/17bfa3ea0497b4c2c7a41a0b003e0ff5.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nimport { Box, useColorMode, useTheme } from \"@chakra-ui/core\";\nexport default (({\n children\n}) => {\n const {\n colorMode\n } = useColorMode();\n const theme = useTheme();\n const bg = {\n dark: theme.colors.gray[800],\n light: theme.colors.blackAlpha[100]\n };\n const color = {\n dark: theme.colors.white,\n light: theme.colors.black\n };\n return __jsx(Box, {\n fontFamily: \"mono\",\n mt: 5,\n p: 3,\n border: \"1px\",\n borderColor: \"inherit\",\n rounded: \"md\",\n bg: bg[colorMode],\n color: color[colorMode],\n fontSize: \"sm\",\n whiteSpace: \"pre-wrap\",\n as: \"pre\"\n }, children);\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/1abdeaef49356a3a97717c5f783b387a.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"import _extends from \"@babel/runtime-corejs2/helpers/esm/extends\";\nimport _objectWithoutProperties from \"@babel/runtime-corejs2/helpers/esm/objectWithoutProperties\";\nvar __jsx = React.createElement;\nimport React from \"react\";\nimport { Flex, FormControl, FormLabel, FormErrorMessage, useTheme, useColorMode } from \"@chakra-ui/core\";\nimport HelpModal from \"~/components/HelpModal\";\nexport default (function (_ref) {\n var label = _ref.label,\n name = _ref.name,\n error = _ref.error,\n hiddenLabels = _ref.hiddenLabels,\n helpIcon = _ref.helpIcon,\n children = _ref.children,\n props = _objectWithoutProperties(_ref, [\"label\", \"name\", \"error\", \"hiddenLabels\", \"helpIcon\", \"children\"]);\n\n var theme = useTheme();\n\n var _useColorMode = useColorMode(),\n colorMode = _useColorMode.colorMode;\n\n var labelColor = colorMode === \"dark\" ? theme.colors.whiteAlpha[600] : theme.colors.blackAlpha[600];\n return __jsx(FormControl, _extends({\n as: Flex,\n flexDirection: \"column\",\n flexGrow: 1,\n flexBasis: 0,\n w: \"100%\",\n maxW: \"100%\",\n mx: 2,\n isInvalid: error && error.message\n }, props), __jsx(FormLabel, {\n htmlFor: name,\n color: labelColor,\n pl: 1,\n opacity: hiddenLabels ? 0 : null\n }, label, (helpIcon === null || helpIcon === void 0 ? void 0 : helpIcon.enable) && __jsx(HelpModal, {\n item: helpIcon,\n name: name\n })), children, __jsx(FormErrorMessage, {\n opacity: hiddenLabels ? 0 : null\n }, error && error.message));\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/1aea3e342482e8256ab0a292c93a5ed2.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nimport { Button, Icon, Tooltip, useClipboard } from \"@chakra-ui/core\";\nexport default (function (_ref) {\n var _ref$bg = _ref.bg,\n bg = _ref$bg === void 0 ? \"secondary\" : _ref$bg,\n copyValue = _ref.copyValue;\n\n var _useClipboard = useClipboard(copyValue),\n onCopy = _useClipboard.onCopy,\n hasCopied = _useClipboard.hasCopied;\n\n return __jsx(Tooltip, {\n hasArrow: true,\n label: \"Copy Output\",\n placement: \"top\"\n }, __jsx(Button, {\n size: \"sm\",\n variantColor: bg,\n zIndex: \"1\",\n onClick: onCopy,\n mx: 1\n }, hasCopied ? __jsx(Icon, {\n name: \"check\",\n size: \"16px\"\n }) : __jsx(Icon, {\n name: \"copy\",\n size: \"16px\"\n })));\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/1fb93f4fbce4b9704fc4e1550857f4f0.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"import _extends from \"@babel/runtime-corejs2/helpers/esm/extends\";\nimport _objectWithoutProperties from \"@babel/runtime-corejs2/helpers/esm/objectWithoutProperties\";\nvar __jsx = React.createElement;\nimport React from \"react\";\nimport { Box, Collapse } from \"@chakra-ui/core\";\nimport MarkDown from \"~/components/MarkDown\";\nexport default React.forwardRef(function (_ref, ref) {\n var _ref$isOpen = _ref.isOpen,\n isOpen = _ref$isOpen === void 0 ? false : _ref$isOpen,\n content = _ref.content,\n _ref$side = _ref.side,\n side = _ref$side === void 0 ? \"left\" : _ref$side,\n title = _ref.title,\n props = _objectWithoutProperties(_ref, [\"isOpen\", \"content\", \"side\", \"title\"]);\n\n return __jsx(Collapse, _extends({\n px: 6,\n py: 4,\n w: \"auto\",\n ref: ref,\n borderBottom: \"1px\",\n display: \"flex\",\n maxWidth: \"100%\",\n isOpen: isOpen,\n flexBasis: \"auto\",\n justifyContent: side === \"left\" ? \"flex-start\" : \"flex-end\"\n }, props), __jsx(Box, {\n textAlign: side\n }, __jsx(MarkDown, {\n content: content\n })));\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/1ff3a2142de0541f5d678e56506c58d3.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nimport { AccordionItem, AccordionHeader, AccordionPanel, AccordionIcon, Alert, Box, ButtonGroup, Flex, Text, useTheme, useColorMode } from \"@chakra-ui/core\";\nimport styled from \"@emotion/styled\";\nimport useAxios from \"axios-hooks\";\nimport strReplace from \"react-string-replace\";\nimport CopyButton from \"~/components/CopyButton\";\nimport RequeryButton from \"~/components/RequeryButton\";\nimport ResultHeader from \"~/components/ResultHeader\";\nconst PreBox = styled(Box)`\n &::selection {\n background-color: ${props => props.selectionBg};\n color: ${props => props.selectionColor};\n }\n`;\n\nconst FormattedError = ({\n keywords,\n message\n}) => {\n const patternStr = `(${keywords.join(\"|\")})`;\n const pattern = new RegExp(patternStr, \"gi\");\n const errorFmt = strReplace(message, pattern, match => __jsx(Text, {\n as: \"strong\"\n }, match));\n return __jsx(Text, null, errorFmt);\n};\n\nexport default React.forwardRef(({\n config,\n device,\n timeout,\n queryLocation,\n queryType,\n queryVrf,\n queryTarget\n}, ref) => {\n var _error$response, _error$response$data, _error$response2, _error$response2$data, _error$response3, _error$response3$data;\n\n const theme = useTheme();\n const {\n colorMode\n } = useColorMode();\n const bg = {\n dark: theme.colors.gray[800],\n light: theme.colors.blackAlpha[100]\n };\n const color = {\n dark: theme.colors.white,\n light: theme.colors.black\n };\n const selectionBg = {\n dark: theme.colors.white,\n light: theme.colors.black\n };\n const selectionColor = {\n dark: theme.colors.black,\n light: theme.colors.white\n };\n const [{\n data,\n loading,\n error\n }, refetch] = useAxios({\n url: \"/query\",\n method: \"post\",\n data: {\n query_location: queryLocation,\n query_type: queryType,\n query_vrf: queryVrf,\n query_target: queryTarget\n },\n timeout: timeout\n });\n const cleanOutput = data && data.output.split(\"\\\\n\").join(\"\\n\").replace(/\\n\\n/g, \"\");\n const errorKw = error && ((_error$response = error.response) === null || _error$response === void 0 ? void 0 : (_error$response$data = _error$response.data) === null || _error$response$data === void 0 ? void 0 : _error$response$data.keywords) || [];\n const errorMsg = error && ((_error$response2 = error.response) === null || _error$response2 === void 0 ? void 0 : (_error$response2$data = _error$response2.data) === null || _error$response2$data === void 0 ? void 0 : _error$response2$data.output) || error && error.message || config.messages.general;\n return __jsx(AccordionItem, {\n isDisabled: loading,\n ref: ref,\n css: {\n \"&:last-of-type\": {\n borderBottom: \"none\"\n },\n \"&:first-of-type\": {\n borderTop: \"none\"\n }\n }\n }, __jsx(AccordionHeader, {\n justifyContent: \"space-between\"\n }, __jsx(ResultHeader, {\n config: config,\n title: device.display_name,\n loading: loading,\n error: error\n }), __jsx(Flex, null, __jsx(AccordionIcon, null))), __jsx(AccordionPanel, {\n pb: 4\n }, __jsx(Box, {\n position: \"relative\"\n }, data && __jsx(PreBox, {\n fontFamily: \"mono\",\n mt: 5,\n p: 3,\n border: \"1px\",\n borderColor: \"inherit\",\n rounded: \"md\",\n bg: bg[colorMode],\n color: color[colorMode],\n fontSize: \"sm\",\n whiteSpace: \"pre-wrap\",\n as: \"pre\",\n selectionBg: selectionBg[colorMode],\n selectionColor: selectionColor[colorMode]\n }, cleanOutput), error && __jsx(Alert, {\n rounded: \"lg\",\n my: 2,\n py: 4,\n status: ((_error$response3 = error.response) === null || _error$response3 === void 0 ? void 0 : (_error$response3$data = _error$response3.data) === null || _error$response3$data === void 0 ? void 0 : _error$response3$data.alert) || \"error\"\n }, __jsx(FormattedError, {\n keywords: errorKw,\n message: errorMsg\n })), __jsx(ButtonGroup, {\n position: \"absolute\",\n top: 0,\n right: 5,\n py: 3,\n spacing: 4\n }, __jsx(CopyButton, {\n copyValue: cleanOutput\n }), __jsx(RequeryButton, {\n isLoading: loading,\n requery: refetch\n })))));\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/2053c9061ba7b67be95cb913c78d764f.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n}); // Identify /[param]/ in route string\n\n\nvar TEST_ROUTE = /\\/\\[[^/]+?\\](?=\\/|$)/;\n\nfunction isDynamicRoute(route) {\n return TEST_ROUTE.test(route);\n}\n\nexports.isDynamicRoute = isDynamicRoute;","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/20e99df57911c70d6b67d3d1099cdcb5.json
vendored
Normal file
1
ui/.next/cache/next-babel-loader/2404679c041068d96cdbb4853be805db.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React, { useEffect, useState } from \"react\";\nimport Head from \"next/head\";\nimport { useTheme } from \"@chakra-ui/core\";\nimport { googleFontUrl } from \"~/util\";\nexport default (({\n config\n}) => {\n const theme = useTheme();\n const {\n 0: location,\n 1: setLocation\n } = useState({});\n const title = (config === null || config === void 0 ? void 0 : config.general.org_name) || \"hyperglass\";\n const description = (config === null || config === void 0 ? void 0 : config.general.site_description) || \"The modern looking glass.\";\n const siteName = `${title} - ${description}`;\n const keywords = (config === null || config === void 0 ? void 0 : config.general.site_keywords) || [\"hyperglass\", \"looking glass\", \"lg\", \"peer\", \"peering\", \"ipv4\", \"ipv6\", \"transit\", \"community\", \"communities\", \"bgp\", \"routing\", \"network\", \"isp\"];\n const author = (config === null || config === void 0 ? void 0 : config.general.org_name) || \"Matt Love, matt@hyperglass.io\";\n const language = (config === null || config === void 0 ? void 0 : config.general.language) || \"en\";\n const currentYear = new Date().getFullYear();\n const copyright = config ? `${currentYear} ${config.general.org_name}` : `${currentYear} hyperglass`;\n const ogImage = (config === null || config === void 0 ? void 0 : config.general.opengraph.image) || null;\n const ogImageHeight = (config === null || config === void 0 ? void 0 : config.general.opengraph.height) || null;\n const ogImageWidth = (config === null || config === void 0 ? void 0 : config.general.opengraph.width) || null;\n const primaryFont = googleFontUrl(theme.fonts.body);\n const monoFont = googleFontUrl(theme.fonts.mono);\n useEffect(() => {\n setLocation(window.location);\n });\n return __jsx(Head, null, __jsx(\"title\", null, title), __jsx(\"meta\", {\n charSet: \"UTF-8\"\n }), __jsx(\"meta\", {\n httpEquiv: \"Content-Type\",\n content: \"text/html\"\n }), __jsx(\"meta\", {\n name: \"description\",\n content: description\n }), __jsx(\"meta\", {\n name: \"keywords\",\n content: keywords.join(\", \")\n }), __jsx(\"meta\", {\n name: \"author\",\n content: author\n }), __jsx(\"meta\", {\n name: \"language\",\n content: language\n }), __jsx(\"meta\", {\n name: \"copyright\",\n content: copyright\n }), __jsx(\"meta\", {\n name: \"url\",\n content: location.href\n }), __jsx(\"meta\", {\n name: \"og:title\",\n content: title\n }), __jsx(\"meta\", {\n name: \"og:type\",\n content: \"website\"\n }), __jsx(\"meta\", {\n name: \"og:site_name\",\n content: siteName\n }), __jsx(\"meta\", {\n name: \"og:url\",\n content: location.href\n }), __jsx(\"meta\", {\n name: \"og:image\",\n content: ogImage\n }), __jsx(\"meta\", {\n name: \"og:description\",\n content: description\n }), __jsx(\"meta\", {\n property: \"og:image:alt\",\n content: siteName\n }), __jsx(\"meta\", {\n property: \"og:image:width\",\n content: ogImageWidth\n }), __jsx(\"meta\", {\n property: \"og:image:height\",\n content: ogImageHeight\n }), __jsx(\"link\", {\n href: primaryFont,\n rel: \"stylesheet\"\n }), __jsx(\"link\", {\n href: monoFont,\n rel: \"stylesheet\"\n }));\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/25fe2ca3c766e1f18beb2adffe044a3a.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\nvar __importStar = this && this.__importStar || function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) {\n if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];\n }\n result[\"default\"] = mod;\n return result;\n};\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar React = __importStar(require(\"react\")); // @ts-ignore for some reason the React types don't like this, but it's correct.\n\n\nexports.LoadableContext = React.createContext(null);","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/2b5732b5890be962eefe7e7846150986.json
vendored
Normal file
1
ui/.next/cache/next-babel-loader/2ddb66eaef8f05e9c0480655bbb3ecd7.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nimport dynamic from \"next/dynamic\";\nimport useAxios from \"axios-hooks\";\nimport { CSSReset, ThemeProvider } from \"@chakra-ui/core\";\nimport Layout from \"~/components/Layout\";\nimport PreConfig from \"~/components/PreConfig\";\nimport { makeTheme, defaultTheme } from \"~/theme\"; // Disable SSR for ColorModeProvider\n\nconst ColorModeProvider = dynamic(() => import(\"@chakra-ui/core\").then(mod => mod.ColorModeProvider), {\n ssr: false,\n loadableGenerated: {\n webpack: () => [require.resolveWeak(\"@chakra-ui/core\")],\n modules: [\"@chakra-ui/core\"]\n }\n});\n\nconst Index = () => {\n const [{\n data,\n loading,\n error\n }, refetch] = useAxios({\n url: \"/config\",\n method: \"get\"\n }); // const data = undefined;\n // const loading = false;\n // const error = { message: \"Shit broke\" };\n // const refetch = () => alert(\"refetched\");\n\n const userTheme = data && makeTheme(data.branding);\n return __jsx(ThemeProvider, {\n theme: data ? userTheme : defaultTheme\n }, __jsx(ColorModeProvider, null, __jsx(CSSReset, null), !data ? __jsx(PreConfig, {\n loading: loading,\n error: error,\n refresh: refetch\n }) : __jsx(Layout, {\n config: data\n })));\n};\n\nexport default Index;","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/31a456290e3dffd91007c6fcd8b118a1.json
vendored
Normal file
1
ui/.next/cache/next-babel-loader/34af63ee33ddc640fec62478fc8f01f8.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nimport { Flex, IconButton, useColorMode, useTheme } from \"@chakra-ui/core\";\nimport { motion } from \"framer-motion\";\nconst AnimatedFlex = motion.custom(Flex);\nexport default (() => {\n const theme = useTheme();\n const {\n colorMode,\n toggleColorMode\n } = useColorMode();\n const bg = {\n light: theme.colors.white,\n dark: theme.colors.black\n };\n const icon = {\n light: \"moon\",\n dark: \"sun\"\n };\n return __jsx(Flex, {\n position: \"fixed\",\n as: \"header\",\n top: \"0\",\n zIndex: \"4\",\n bg: bg[colorMode],\n color: theme.colors.gray[500],\n left: \"0\",\n right: \"0\",\n width: \"full\",\n height: \"4rem\"\n }, __jsx(Flex, {\n w: \"100%\",\n mx: \"auto\",\n px: 6,\n justifyContent: \"flex-end\"\n }, __jsx(AnimatedFlex, {\n align: \"center\",\n initial: {\n opacity: 0\n },\n animate: {\n opacity: 1\n },\n transition: {\n duration: 0.6\n }\n }, __jsx(IconButton, {\n \"aria-label\": `Switch to ${colorMode === \"light\" ? \"dark\" : \"light\"} mode`,\n variant: \"ghost\",\n color: \"current\",\n ml: \"2\",\n fontSize: \"20px\",\n onClick: toggleColorMode,\n icon: icon[colorMode]\n }))));\n});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/3577cb2f3777c260407103513e9f31d3.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n/*\nMIT License\n\nCopyright (c) Jason Miller (https://jasonformat.com/)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n\nvar _Object$create = require(\"@babel/runtime-corejs2/core-js/object/create\");\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nfunction mitt() {\n var all = _Object$create(null);\n\n return {\n on: function on(type, handler) {\n ;\n (all[type] || (all[type] = [])).push(handler);\n },\n off: function off(type, handler) {\n if (all[type]) {\n // tslint:disable-next-line:no-bitwise\n all[type].splice(all[type].indexOf(handler) >>> 0, 1);\n }\n },\n emit: function emit(type) {\n for (var _len = arguments.length, evts = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n evts[_key - 1] = arguments[_key];\n }\n\n // eslint-disable-next-line array-callback-return\n ;\n (all[type] || []).slice().map(function (handler) {\n handler.apply(void 0, evts);\n });\n }\n };\n}\n\nexports[\"default\"] = mitt;","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/366b0b9c71188d2ee599ef0c6d80270d.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"import chroma from \"chroma-js\";\n\nconst isDark = color => {\n // YIQ equation from http://24ways.org/2010/calculating-color-contrast\n const rgb = chroma(color).rgb();\n const yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;\n return yiq < 128;\n};\n\nconst isLight = color => isDark(color);\n\nconst opposingColor = (theme, color) => {\n const opposing = isDark(color) ? theme.colors.white : theme.colors.black;\n return opposing;\n};\n\nconst googleFontUrl = (fontFamily, weights = [300, 400, 700]) => {\n const urlWeights = weights.join(\",\");\n const fontName = fontFamily.split(/, /)[0].trim().replace(/'|\"/g, \"\");\n const urlFont = fontName.split(/ /).join(\"+\");\n const urlBase = `https://fonts.googleapis.com/css?family=${urlFont}:${urlWeights}&display=swap`;\n return urlBase;\n};\n\nexport { isDark, isLight, opposingColor, googleFontUrl };","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/3741f138c9336c60df8785ddec2149f1.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _Object$defineProperty = require(\"@babel/runtime-corejs2/core-js/object/define-property\");\n\nvar __importStar = this && this.__importStar || function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) {\n if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];\n }\n result[\"default\"] = mod;\n return result;\n};\n\n_Object$defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar React = __importStar(require(\"react\"));\n\nexports.HeadManagerContext = React.createContext(null);","map":null,"metadata":{},"sourceType":"script"}
|
||||
1
ui/.next/cache/next-babel-loader/3d0c3967dfbee5628d989c5fb6679842.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"var __jsx = React.createElement;\nimport React from \"react\";\nexport default function App({\n Component,\n pageProps\n}) {\n return __jsx(Component, pageProps);\n}\n\nApp.getInitialProps = async () => ({});","map":null,"metadata":{},"sourceType":"module"}
|
||||
1
ui/.next/cache/next-babel-loader/3e3bc0ab2d5749c519975b9a6243f2f1.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"ast":null,"code":"\"use strict\";\n\nvar _classCallCheck = require(\"@babel/runtime-corejs2/helpers/classCallCheck\");\n\nvar _createClass = require(\"@babel/runtime-corejs2/helpers/createClass\");\n\nvar _possibleConstructorReturn = require(\"@babel/runtime-corejs2/helpers/possibleConstructorReturn\");\n\nvar _getPrototypeOf = require(\"@babel/runtime-corejs2/helpers/getPrototypeOf\");\n\nvar _inherits = require(\"@babel/runtime-corejs2/helpers/inherits\");\n\nvar _interopRequireDefault = require(\"@babel/runtime-corejs2/helpers/interopRequireDefault\");\n\nexports.__esModule = true;\nexports[\"default\"] = void 0;\n\nvar _react = _interopRequireDefault(require(\"react\"));\n\nvar _head = _interopRequireDefault(require(\"../next-server/lib/head\"));\n\nvar statusCodes = {\n 400: 'Bad Request',\n 404: 'This page could not be found',\n 405: 'Method Not Allowed',\n 500: 'Internal Server Error'\n};\n/**\n* `Error` component used for handling errors.\n*/\n\nvar Error =\n/*#__PURE__*/\nfunction (_react$default$Compon) {\n _inherits(Error, _react$default$Compon);\n\n function Error() {\n _classCallCheck(this, Error);\n\n return _possibleConstructorReturn(this, _getPrototypeOf(Error).apply(this, arguments));\n }\n\n _createClass(Error, [{\n key: \"render\",\n value: function render() {\n var statusCode = this.props.statusCode;\n var title = this.props.title || statusCodes[statusCode] || 'An unexpected error has occurred';\n return _react[\"default\"].createElement(\"div\", {\n style: styles.error\n }, _react[\"default\"].createElement(_head[\"default\"], null, _react[\"default\"].createElement(\"title\", null, statusCode, \": \", title)), _react[\"default\"].createElement(\"div\", null, _react[\"default\"].createElement(\"style\", {\n dangerouslySetInnerHTML: {\n __html: 'body { margin: 0 }'\n }\n }), statusCode ? _react[\"default\"].createElement(\"h1\", {\n style: styles.h1\n }, statusCode) : null, _react[\"default\"].createElement(\"div\", {\n style: styles.desc\n }, _react[\"default\"].createElement(\"h2\", {\n style: styles.h2\n }, title, \".\"))));\n }\n }], [{\n key: \"getInitialProps\",\n value: function getInitialProps(_ref) {\n var res = _ref.res,\n err = _ref.err;\n var statusCode = res && res.statusCode ? res.statusCode : err ? err.statusCode : 404;\n return {\n statusCode: statusCode\n };\n }\n }]);\n\n return Error;\n}(_react[\"default\"].Component);\n\nexports[\"default\"] = Error;\nError.displayName = 'ErrorPage';\nvar styles = {\n error: {\n color: '#000',\n background: '#fff',\n fontFamily: '-apple-system, BlinkMacSystemFont, Roboto, \"Segoe UI\", \"Fira Sans\", Avenir, \"Helvetica Neue\", \"Lucida Grande\", sans-serif',\n height: '100vh',\n textAlign: 'center',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center'\n },\n desc: {\n display: 'inline-block',\n textAlign: 'left',\n lineHeight: '49px',\n height: '49px',\n verticalAlign: 'middle'\n },\n h1: {\n display: 'inline-block',\n borderRight: '1px solid rgba(0, 0, 0,.3)',\n margin: 0,\n marginRight: '20px',\n padding: '10px 23px 10px 0',\n fontSize: '24px',\n fontWeight: 500,\n verticalAlign: 'top'\n },\n h2: {\n fontSize: '14px',\n fontWeight: 'normal',\n lineHeight: 'inherit',\n margin: 0,\n padding: 0\n }\n};","map":null,"metadata":{},"sourceType":"script"}
|
||||