forked from mirrors/thatmattlove-hyperglass
tooling overhaul
This commit is contained in:
parent
ae2753b695
commit
cd6bf7a162
60 changed files with 3310 additions and 3539 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,6 +16,7 @@ __pycache__/
|
||||||
|
|
||||||
# Pyenv
|
# Pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
.venv
|
||||||
|
|
||||||
# MyPy
|
# MyPy
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
|
|
|
||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -3,5 +3,5 @@
|
||||||
"eslint.workingDirectories": ["./hyperglass/ui"],
|
"eslint.workingDirectories": ["./hyperglass/ui"],
|
||||||
"python.linting.mypyEnabled": false,
|
"python.linting.mypyEnabled": false,
|
||||||
"python.linting.enabled": false,
|
"python.linting.enabled": false,
|
||||||
"prettier.configPath": "./hyperglass/ui/.prettierrc"
|
"biome.lspBin": "./hyperglass/ui/node_modules/.bin/biome"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ def build_ui(timeout: int) -> None:
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.state import use_state
|
from hyperglass.state import use_state
|
||||||
from hyperglass.configuration import init_user_config
|
from hyperglass.configuration import init_user_config
|
||||||
from hyperglass.util.frontend import build_frontend
|
from hyperglass.frontend import build_frontend
|
||||||
|
|
||||||
# Populate configuration to Redis prior to accessing it.
|
# Populate configuration to Redis prior to accessing it.
|
||||||
init_user_config()
|
init_user_config()
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ from pathlib import Path
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.log import log
|
from hyperglass.log import log
|
||||||
|
from hyperglass.util import copyfiles, check_path, dotenv_to_dict
|
||||||
|
from hyperglass.state import use_state
|
||||||
|
|
||||||
# Local
|
|
||||||
from .files import copyfiles, check_path, dotenv_to_dict
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
# Project
|
# Project
|
||||||
|
|
@ -56,7 +56,6 @@ async def read_package_json() -> t.Dict[str, t.Any]:
|
||||||
package_json_file = Path(__file__).parent.parent / "ui" / "package.json"
|
package_json_file = Path(__file__).parent.parent / "ui" / "package.json"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
with package_json_file.open("r") as file:
|
with package_json_file.open("r") as file:
|
||||||
package_json = json.load(file)
|
package_json = json.load(file)
|
||||||
|
|
||||||
|
|
@ -82,7 +81,7 @@ async def node_initial(timeout: int = 180, dev_mode: bool = False) -> str:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proc = await asyncio.create_subprocess_shell(
|
proc = await asyncio.create_subprocess_shell(
|
||||||
cmd="yarn --silent --emoji false",
|
cmd="pnpm install",
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.PIPE,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
cwd=ui_path,
|
cwd=ui_path,
|
||||||
|
|
@ -178,10 +177,8 @@ def generate_opengraph(
|
||||||
log.debug("Copied {} to {}", str(image_path), str(target_path))
|
log.debug("Copied {} to {}", str(image_path), str(target_path))
|
||||||
|
|
||||||
with Image.open(copied) as src:
|
with Image.open(copied) as src:
|
||||||
|
|
||||||
# Only resize the image if it needs to be resized
|
# Only resize the image if it needs to be resized
|
||||||
if src.size[0] != max_width or src.size[1] != max_height:
|
if src.size[0] != max_width or src.size[1] != max_height:
|
||||||
|
|
||||||
# Resize image while maintaining aspect ratio
|
# Resize image while maintaining aspect ratio
|
||||||
log.debug("Opengraph image is not 1200x630, resizing...")
|
log.debug("Opengraph image is not 1200x630, resizing...")
|
||||||
src.thumbnail((max_width, max_height))
|
src.thumbnail((max_width, max_height))
|
||||||
|
|
@ -290,6 +287,10 @@ async def build_frontend( # noqa: C901
|
||||||
dot_env_file = Path(__file__).parent.parent / "ui" / ".env"
|
dot_env_file = Path(__file__).parent.parent / "ui" / ".env"
|
||||||
env_config = {}
|
env_config = {}
|
||||||
|
|
||||||
|
ui_config_file = Path(__file__).parent.parent / "ui" / "hyperglass.json"
|
||||||
|
|
||||||
|
ui_config_file.write_text(params.export_json(by_alias=True))
|
||||||
|
|
||||||
package_json = await read_package_json()
|
package_json = await read_package_json()
|
||||||
|
|
||||||
# Set NextJS production/development mode and base URL based on
|
# Set NextJS production/development mode and base URL based on
|
||||||
|
|
@ -52,6 +52,12 @@ HyperglassConsole = Console(
|
||||||
"warning": "bold yellow",
|
"warning": "bold yellow",
|
||||||
"error": "bold red",
|
"error": "bold red",
|
||||||
"success": "bold green",
|
"success": "bold green",
|
||||||
|
"critical": "bold bright_red",
|
||||||
|
"logging.level.info": "bold cyan",
|
||||||
|
"logging.level.warning": "bold yellow",
|
||||||
|
"logging.level.error": "bold red",
|
||||||
|
"logging.level.critical": "bold bright_red",
|
||||||
|
"logging.level.success": "bold green",
|
||||||
"subtle": "rgb(128,128,128)",
|
"subtle": "rgb(128,128,128)",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -146,12 +152,13 @@ def init_logger(level: str = "INFO"):
|
||||||
|
|
||||||
if sys.stdout.isatty():
|
if sys.stdout.isatty():
|
||||||
# Use Rich for logging if hyperglass started from a TTY.
|
# Use Rich for logging if hyperglass started from a TTY.
|
||||||
|
|
||||||
_loguru_logger.add(
|
_loguru_logger.add(
|
||||||
sink=RichHandler(
|
sink=RichHandler(
|
||||||
console=HyperglassConsole,
|
console=HyperglassConsole,
|
||||||
rich_tracebacks=True,
|
rich_tracebacks=True,
|
||||||
level=level,
|
level=level,
|
||||||
tracebacks_show_locals=True,
|
tracebacks_show_locals=level == "DEBUG",
|
||||||
log_time_format="[%Y%m%d %H:%M:%S]",
|
log_time_format="[%Y%m%d %H:%M:%S]",
|
||||||
),
|
),
|
||||||
format=_FMT_BASIC,
|
format=_FMT_BASIC,
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@ from gunicorn.arbiter import Arbiter # type: ignore
|
||||||
from gunicorn.app.base import BaseApplication # type: ignore
|
from gunicorn.app.base import BaseApplication # type: ignore
|
||||||
|
|
||||||
# Local
|
# Local
|
||||||
from .log import CustomGunicornLogger, log, init_logger, setup_lib_logging
|
from .log import log, init_logger, setup_lib_logging
|
||||||
from .util import get_node_version
|
from .util import get_node_version
|
||||||
from .plugins import InputPluginManager, OutputPluginManager, register_plugin, init_builtin_plugins
|
|
||||||
from .constants import MIN_NODE_VERSION, MIN_PYTHON_VERSION, __version__
|
from .constants import MIN_NODE_VERSION, MIN_PYTHON_VERSION, __version__
|
||||||
|
|
||||||
# Ensure the Python version meets the minimum requirements.
|
# Ensure the Python version meets the minimum requirements.
|
||||||
|
|
@ -34,12 +33,18 @@ if node_major < MIN_NODE_VERSION:
|
||||||
from .util import cpu_count
|
from .util import cpu_count
|
||||||
from .state import use_state
|
from .state import use_state
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
from .configuration import init_user_config
|
|
||||||
from .util.frontend import build_frontend
|
|
||||||
|
log_level = "INFO" if Settings.debug is False else "DEBUG"
|
||||||
|
|
||||||
|
setup_lib_logging(log_level)
|
||||||
|
init_logger(log_level)
|
||||||
|
|
||||||
|
|
||||||
async def build_ui() -> bool:
|
async def build_ui() -> bool:
|
||||||
"""Perform a UI build prior to starting the application."""
|
"""Perform a UI build prior to starting the application."""
|
||||||
|
from .frontend import build_frontend
|
||||||
|
|
||||||
state = use_state()
|
state = use_state()
|
||||||
await build_frontend(
|
await build_frontend(
|
||||||
dev_mode=Settings.dev_mode,
|
dev_mode=Settings.dev_mode,
|
||||||
|
|
@ -54,6 +59,8 @@ async def build_ui() -> bool:
|
||||||
def register_all_plugins() -> None:
|
def register_all_plugins() -> None:
|
||||||
"""Validate and register configured plugins."""
|
"""Validate and register configured plugins."""
|
||||||
|
|
||||||
|
from .plugins import register_plugin, init_builtin_plugins
|
||||||
|
|
||||||
state = use_state()
|
state = use_state()
|
||||||
|
|
||||||
# Register built-in plugins.
|
# Register built-in plugins.
|
||||||
|
|
@ -78,6 +85,8 @@ def register_all_plugins() -> None:
|
||||||
|
|
||||||
def unregister_all_plugins() -> None:
|
def unregister_all_plugins() -> None:
|
||||||
"""Unregister all plugins."""
|
"""Unregister all plugins."""
|
||||||
|
from .plugins import InputPluginManager, OutputPluginManager
|
||||||
|
|
||||||
for manager in (InputPluginManager, OutputPluginManager):
|
for manager in (InputPluginManager, OutputPluginManager):
|
||||||
manager().reset()
|
manager().reset()
|
||||||
|
|
||||||
|
|
@ -91,7 +100,12 @@ def on_starting(server: "Arbiter") -> None:
|
||||||
|
|
||||||
register_all_plugins()
|
register_all_plugins()
|
||||||
|
|
||||||
asyncio.run(build_ui())
|
if not Settings.disable_ui:
|
||||||
|
asyncio.run(build_ui())
|
||||||
|
|
||||||
|
|
||||||
|
def when_ready(server: "Arbiter") -> None:
|
||||||
|
"""Gunicorn post-start hook."""
|
||||||
|
|
||||||
log.success(
|
log.success(
|
||||||
"Started hyperglass {} on http://{} with {!s} workers",
|
"Started hyperglass {} on http://{} with {!s} workers",
|
||||||
|
|
@ -141,6 +155,8 @@ class HyperglassWSGI(BaseApplication):
|
||||||
def start(*, log_level: str, workers: int, **kwargs) -> None:
|
def start(*, log_level: str, workers: int, **kwargs) -> None:
|
||||||
"""Start hyperglass via gunicorn."""
|
"""Start hyperglass via gunicorn."""
|
||||||
|
|
||||||
|
from .log import CustomGunicornLogger
|
||||||
|
|
||||||
HyperglassWSGI(
|
HyperglassWSGI(
|
||||||
app="hyperglass.api:app",
|
app="hyperglass.api:app",
|
||||||
options={
|
options={
|
||||||
|
|
@ -152,6 +168,7 @@ def start(*, log_level: str, workers: int, **kwargs) -> None:
|
||||||
"loglevel": log_level,
|
"loglevel": log_level,
|
||||||
"bind": Settings.bind(),
|
"bind": Settings.bind(),
|
||||||
"on_starting": on_starting,
|
"on_starting": on_starting,
|
||||||
|
"when_ready": when_ready,
|
||||||
"command": shutil.which("gunicorn"),
|
"command": shutil.which("gunicorn"),
|
||||||
"logger_class": CustomGunicornLogger,
|
"logger_class": CustomGunicornLogger,
|
||||||
"worker_class": "uvicorn.workers.UvicornWorker",
|
"worker_class": "uvicorn.workers.UvicornWorker",
|
||||||
|
|
@ -163,21 +180,15 @@ def start(*, log_level: str, workers: int, **kwargs) -> None:
|
||||||
|
|
||||||
def run(_workers: int = None):
|
def run(_workers: int = None):
|
||||||
"""Run hyperglass."""
|
"""Run hyperglass."""
|
||||||
try:
|
from .configuration import init_user_config
|
||||||
init_user_config()
|
|
||||||
|
|
||||||
|
try:
|
||||||
log.debug("System settings: {!r}", Settings)
|
log.debug("System settings: {!r}", Settings)
|
||||||
|
|
||||||
workers, log_level = 1, "DEBUG"
|
init_user_config()
|
||||||
|
|
||||||
if Settings.debug is False:
|
workers = 1 if Settings.debug else cpu_count(2)
|
||||||
workers, log_level = cpu_count(2), "WARNING"
|
|
||||||
|
|
||||||
if _workers is not None:
|
|
||||||
workers = _workers
|
|
||||||
|
|
||||||
init_logger(log_level)
|
|
||||||
setup_lib_logging(log_level)
|
|
||||||
start(log_level=log_level, workers=workers)
|
start(log_level=log_level, workers=workers)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
# Handle app exceptions.
|
# Handle app exceptions.
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ class Params(ParamsPublic, HyperglassModel):
|
||||||
return self.export_dict(
|
return self.export_dict(
|
||||||
include={
|
include={
|
||||||
"cache": {"show_text", "timeout"},
|
"cache": {"show_text", "timeout"},
|
||||||
"debug": ...,
|
|
||||||
"developer_mode": ...,
|
"developer_mode": ...,
|
||||||
"primary_asn": ...,
|
"primary_asn": ...,
|
||||||
"request_timeout": ...,
|
"request_timeout": ...,
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,6 @@ class ConfigPathItem(Path):
|
||||||
value = Settings.default_app_path.joinpath(
|
value = Settings.default_app_path.joinpath(
|
||||||
*(p for p in value.parts if p not in Settings.app_path.parts)
|
*(p for p in value.parts if p not in Settings.app_path.parts)
|
||||||
)
|
)
|
||||||
print(f"{value=}")
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ class HyperglassSettings(BaseSettings):
|
||||||
|
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
dev_mode: bool = False
|
dev_mode: bool = False
|
||||||
|
disable_ui: bool = False
|
||||||
app_path: DirectoryPath = _default_app_path
|
app_path: DirectoryPath = _default_app_path
|
||||||
redis_host: str = "localhost"
|
redis_host: str = "localhost"
|
||||||
redis_password: t.Optional[SecretStr]
|
redis_password: t.Optional[SecretStr]
|
||||||
|
|
@ -58,7 +59,6 @@ class HyperglassSettings(BaseSettings):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
if self.container:
|
if self.container:
|
||||||
self.app_path = self.default_app_path
|
self.app_path = self.default_app_path
|
||||||
print(self)
|
|
||||||
|
|
||||||
def __rich_console__(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
|
def __rich_console__(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
|
||||||
"""Render a Rich table representation of hyperglass settings."""
|
"""Render a Rich table representation of hyperglass settings."""
|
||||||
|
|
|
||||||
1
hyperglass/ui/.gitignore
vendored
1
hyperglass/ui/.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env*
|
.env*
|
||||||
|
hyperglass.json
|
||||||
custom.*[js, html]
|
custom.*[js, html]
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
# dev/test files
|
# dev/test files
|
||||||
|
|
|
||||||
40
hyperglass/ui/biome.json
Normal file
40
hyperglass/ui/biome.json
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignore": ["node_modules", "dist", ".next/", "favicon-formats.ts", "custom.*[js, html]"]
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"complexity": {
|
||||||
|
"noUselessTypeConstraint": "off",
|
||||||
|
"noBannedTypes": "off"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"noInferrableTypes": "off",
|
||||||
|
"noNonNullAssertion": "off"
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"useExhaustiveDependencies": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"indentStyle": "space",
|
||||||
|
"lineWidth": 100,
|
||||||
|
"indentWidth": 2
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"semicolons": "always",
|
||||||
|
"arrowParentheses": "asNeeded",
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
useColorMode,
|
useColorMode,
|
||||||
useColorValue,
|
useColorValue,
|
||||||
useBreakpointValue,
|
useBreakpointValue,
|
||||||
useHyperglassConfig,
|
// useHyperglassConfig,
|
||||||
} from '~/hooks';
|
} from '~/hooks';
|
||||||
|
|
||||||
import type { UseDisclosureReturn } from '@chakra-ui/react';
|
import type { UseDisclosureReturn } from '@chakra-ui/react';
|
||||||
|
|
@ -56,7 +56,7 @@ export const Debugger = (): JSX.Element => {
|
||||||
useBreakpointValue({ base: 'SMALL', md: 'MEDIUM', lg: 'LARGE', xl: 'X-LARGE' }) ?? 'UNKNOWN';
|
useBreakpointValue({ base: 'SMALL', md: 'MEDIUM', lg: 'LARGE', xl: 'X-LARGE' }) ?? 'UNKNOWN';
|
||||||
const tagSize = useBreakpointValue({ base: 'sm', lg: 'lg' }) ?? 'lg';
|
const tagSize = useBreakpointValue({ base: 'sm', lg: 'lg' }) ?? 'lg';
|
||||||
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' }) ?? 'sm';
|
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' }) ?? 'sm';
|
||||||
const { refetch } = useHyperglassConfig();
|
// const { refetch } = useHyperglassConfig();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HStack
|
<HStack
|
||||||
|
|
@ -92,14 +92,14 @@ export const Debugger = (): JSX.Element => {
|
||||||
>
|
>
|
||||||
View Theme
|
View Theme
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/* <Button
|
||||||
size={btnSize}
|
size={btnSize}
|
||||||
colorScheme="purple"
|
colorScheme="purple"
|
||||||
leftIcon={<DynamicIcon icon={{ hi: 'HiOutlineDownload' }} />}
|
leftIcon={<DynamicIcon icon={{ hi: 'HiOutlineDownload' }} />}
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
>
|
>
|
||||||
Reload Config
|
Reload Config
|
||||||
</Button>
|
</Button> */}
|
||||||
<Tag size={tagSize} colorScheme="teal">
|
<Tag size={tagSize} colorScheme="teal">
|
||||||
{mediaSize}
|
{mediaSize}
|
||||||
</Tag>
|
</Tag>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,8 @@ export const Footer = (): JSX.Element => {
|
||||||
icon.rightIcon = <DynamicIcon icon={{ go: 'GoLinkExternal' }} />;
|
icon.rightIcon = <DynamicIcon icon={{ go: 'GoLinkExternal' }} />;
|
||||||
}
|
}
|
||||||
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
|
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
|
||||||
} else if (isMenu(item)) {
|
}
|
||||||
|
if (isMenu(item)) {
|
||||||
return (
|
return (
|
||||||
<FooterButton key={item.title} side="left" content={item.content} title={item.title} />
|
<FooterButton key={item.title} side="left" content={item.content} title={item.title} />
|
||||||
);
|
);
|
||||||
|
|
@ -77,7 +78,8 @@ export const Footer = (): JSX.Element => {
|
||||||
icon.rightIcon = <DynamicIcon icon={{ go: 'GoLinkExternal' }} />;
|
icon.rightIcon = <DynamicIcon icon={{ go: 'GoLinkExternal' }} />;
|
||||||
}
|
}
|
||||||
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
|
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
|
||||||
} else if (isMenu(item)) {
|
}
|
||||||
|
if (isMenu(item)) {
|
||||||
return (
|
return (
|
||||||
<FooterButton key={item.title} side="right" content={item.content} title={item.title} />
|
<FooterButton key={item.title} side="right" content={item.content} title={item.title} />
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,10 @@ export const LocationCard = (props: LocationCardProps): JSX.Element => {
|
||||||
? // Highlight red when there are no overlapping query types for the locations selected.
|
? // Highlight red when there are no overlapping query types for the locations selected.
|
||||||
errorBorder
|
errorBorder
|
||||||
: isChecked && !hasError
|
: isChecked && !hasError
|
||||||
? // Highlight blue when any location is selected and there is no error.
|
? // Highlight blue when any location is selected and there is no error.
|
||||||
checkedBorder
|
checkedBorder
|
||||||
: // Otherwise, no border.
|
: // Otherwise, no border.
|
||||||
'transparent',
|
'transparent',
|
||||||
|
|
||||||
[hasError, isChecked, checkedBorder, errorBorder],
|
[hasError, isChecked, checkedBorder, errorBorder],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -103,23 +103,24 @@ export const LookingGlassForm = (): JSX.Element => {
|
||||||
const isFqdn = isFqdnQuery(form.queryTarget, directive?.fieldType ?? null);
|
const isFqdn = isFqdnQuery(form.queryTarget, directive?.fieldType ?? null);
|
||||||
|
|
||||||
if (greetingReady && !isFqdn) {
|
if (greetingReady && !isFqdn) {
|
||||||
return setStatus('results');
|
setStatus('results');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (greetingReady && isFqdn) {
|
if (greetingReady && isFqdn) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
return resolvedOpen();
|
resolvedOpen();
|
||||||
} else {
|
return;
|
||||||
console.group('%cSomething went wrong', 'color:red;');
|
|
||||||
console.table({
|
|
||||||
'Greeting Required': web.greeting.required,
|
|
||||||
'Greeting Ready': greetingReady,
|
|
||||||
'Query Target': form.queryTarget,
|
|
||||||
'Query Type': form.queryType,
|
|
||||||
'Is FQDN': isFqdn,
|
|
||||||
});
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
}
|
||||||
|
console.group('%cSomething went wrong', 'color:red;');
|
||||||
|
console.table({
|
||||||
|
'Greeting Required': web.greeting.required,
|
||||||
|
'Greeting Ready': greetingReady,
|
||||||
|
'Query Target': form.queryTarget,
|
||||||
|
'Query Type': form.queryType,
|
||||||
|
'Is FQDN': isFqdn,
|
||||||
|
});
|
||||||
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLocChange = (locations: string[]) =>
|
const handleLocChange = (locations: string[]) =>
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,5 @@ export const Cell = (props: CellProps): JSX.Element => {
|
||||||
rpki_state: <RPKIState state={data.value} active={data.row.values.active} />,
|
rpki_state: <RPKIState state={data.value} active={data.row.values.active} />,
|
||||||
weight: <Weight weight={data.value} winningWeight={rawData.winning_weight} />,
|
weight: <Weight weight={data.value} winningWeight={rawData.winning_weight} />,
|
||||||
};
|
};
|
||||||
return component[cellId] ?? <> </>;
|
return component[cellId] ?? '';
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,7 @@ export const ASPath = (props: ASPathProps): JSX.Element => {
|
||||||
paths.push(
|
paths.push(
|
||||||
<DynamicIcon
|
<DynamicIcon
|
||||||
icon={{ fa: 'FaChevronRight' }}
|
icon={{ fa: 'FaChevronRight' }}
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: index makes sense in this case.
|
||||||
key={`separator-${i}`}
|
key={`separator-${i}`}
|
||||||
color={color[+active]}
|
color={color[+active]}
|
||||||
boxSize={5}
|
boxSize={5}
|
||||||
|
|
@ -127,6 +128,7 @@ export const ASPath = (props: ASPathProps): JSX.Element => {
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
paths.push(
|
paths.push(
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: index makes sense in this case.
|
||||||
<Text fontSize="sm" as="span" whiteSpace="pre" fontFamily="mono" key={`as-${asnStr}-${i}`}>
|
<Text fontSize="sm" as="span" whiteSpace="pre" fontFamily="mono" key={`as-${asnStr}-${i}`}>
|
||||||
{asnStr}
|
{asnStr}
|
||||||
</Text>,
|
</Text>,
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,14 @@ function buildOptions(devices: DeviceGroup[]): OptionGroup<LocationOption>[] {
|
||||||
avatar: loc.avatar,
|
avatar: loc.avatar,
|
||||||
description: loc.description,
|
description: loc.description,
|
||||||
},
|
},
|
||||||
} as SingleOption),
|
}) as SingleOption,
|
||||||
)
|
)
|
||||||
.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
|
.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
|
||||||
return { label, options };
|
return { label: label ?? '', options };
|
||||||
})
|
})
|
||||||
.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
|
.sort((a, b) =>
|
||||||
|
(a.label ?? 0) < (b.label ?? 0) ? -1 : (a.label ?? 0) > (b.label ?? 0) ? 1 : 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QueryLocation = (props: QueryLocationProps): JSX.Element => {
|
export const QueryLocation = (props: QueryLocationProps): JSX.Element => {
|
||||||
|
|
@ -58,7 +60,8 @@ export const QueryLocation = (props: QueryLocationProps): JSX.Element => {
|
||||||
const element = useMemo(() => {
|
const element = useMemo(() => {
|
||||||
if (locationDisplayMode === 'dropdown') {
|
if (locationDisplayMode === 'dropdown') {
|
||||||
return 'select';
|
return 'select';
|
||||||
} else if (locationDisplayMode === 'gallery') {
|
}
|
||||||
|
if (locationDisplayMode === 'gallery') {
|
||||||
return 'cards';
|
return 'cards';
|
||||||
}
|
}
|
||||||
const groups = options.length;
|
const groups = options.length;
|
||||||
|
|
@ -159,7 +162,8 @@ export const QueryLocation = (props: QueryLocationProps): JSX.Element => {
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else if (element === 'select') {
|
}
|
||||||
|
if (element === 'select') {
|
||||||
return (
|
return (
|
||||||
<Select<LocationOption, true>
|
<Select<LocationOption, true>
|
||||||
isMulti
|
isMulti
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,7 @@ function useOptions() {
|
||||||
const filtered = useFormState(s => s.filtered);
|
const filtered = useFormState(s => s.filtered);
|
||||||
return useMemo((): OptionsOrGroup<QueryTypeOption> => {
|
return useMemo((): OptionsOrGroup<QueryTypeOption> => {
|
||||||
const groupNames = new Set(
|
const groupNames = new Set(
|
||||||
filtered.types
|
filtered.types.filter(t => t.groups.length > 0).flatMap(t => t.groups),
|
||||||
.filter(t => t.groups.length > 0)
|
|
||||||
.map(t => t.groups)
|
|
||||||
.flat(),
|
|
||||||
);
|
);
|
||||||
const optGroups: OptionGroup<QueryTypeOption>[] = Array.from(groupNames).map(group => ({
|
const optGroups: OptionGroup<QueryTypeOption>[] = Array.from(groupNames).map(group => ({
|
||||||
label: group,
|
label: group,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
// biome-ignore lint/suspicious/noExplicitAny: type guard
|
||||||
/* eslint @typescript-eslint/explicit-module-boundary-types: 0 */
|
|
||||||
|
|
||||||
export function isStackError(error: any): error is Error {
|
export function isStackError(error: any): error is Error {
|
||||||
return typeof error !== 'undefined' && error !== null && 'message' in error;
|
return typeof error !== 'undefined' && error !== null && 'message' in error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: type guard
|
||||||
export function isFetchError(error: any): error is Response {
|
export function isFetchError(error: any): error is Response {
|
||||||
return typeof error !== 'undefined' && error !== null && 'statusText' in error;
|
return typeof error !== 'undefined' && error !== null && 'statusText' in error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: type guard
|
||||||
export function isLGError(error: any): error is QueryResponse {
|
export function isLGError(error: any): error is QueryResponse {
|
||||||
return typeof error !== 'undefined' && error !== null && 'output' in error;
|
return typeof error !== 'undefined' && error !== null && 'output' in error;
|
||||||
}
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ export function isLGError(error: any): error is QueryResponse {
|
||||||
/**
|
/**
|
||||||
* Returns true if the response is an LG error, false if not.
|
* Returns true if the response is an LG error, false if not.
|
||||||
*/
|
*/
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: type guard
|
||||||
export function isLGOutputOrError(data: any): data is QueryResponse {
|
export function isLGOutputOrError(data: any): data is QueryResponse {
|
||||||
return typeof data !== 'undefined' && data !== null && data?.level !== 'success';
|
return typeof data !== 'undefined' && data !== null && data?.level !== 'success';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,17 +107,20 @@ const _Result: React.ForwardRefRenderFunction<HTMLDivElement, ResultProps> = (
|
||||||
const errorMsg = useMemo(() => {
|
const errorMsg = useMemo(() => {
|
||||||
if (isLGError(error)) {
|
if (isLGError(error)) {
|
||||||
return error.output as string;
|
return error.output as string;
|
||||||
} else if (isLGOutputOrError(data)) {
|
|
||||||
return data.output as string;
|
|
||||||
} else if (isFetchError(error)) {
|
|
||||||
return startCase(error.statusText);
|
|
||||||
} else if (isStackError(error) && error.message.toLowerCase().startsWith('timeout')) {
|
|
||||||
return messages.requestTimeout;
|
|
||||||
} else if (isStackError(error)) {
|
|
||||||
return startCase(error.message);
|
|
||||||
} else {
|
|
||||||
return messages.general;
|
|
||||||
}
|
}
|
||||||
|
if (isLGOutputOrError(data)) {
|
||||||
|
return data.output as string;
|
||||||
|
}
|
||||||
|
if (isFetchError(error)) {
|
||||||
|
return startCase(error.statusText);
|
||||||
|
}
|
||||||
|
if (isStackError(error) && error.message.toLowerCase().startsWith('timeout')) {
|
||||||
|
return messages.requestTimeout;
|
||||||
|
}
|
||||||
|
if (isStackError(error)) {
|
||||||
|
return startCase(error.message);
|
||||||
|
}
|
||||||
|
return messages.general;
|
||||||
}, [error, data, messages.general, messages.requestTimeout]);
|
}, [error, data, messages.general, messages.requestTimeout]);
|
||||||
|
|
||||||
const errorLevel = useMemo<ErrorLevels>(() => {
|
const errorLevel = useMemo<ErrorLevels>(() => {
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ export const useControlStyle = <Opt extends SingleOption, IsMulti extends boolea
|
||||||
boxShadow: isError
|
boxShadow: isError
|
||||||
? `0 0 0 1px ${invalidBorder}`
|
? `0 0 0 1px ${invalidBorder}`
|
||||||
: isFocused
|
: isFocused
|
||||||
? `0 0 0 1px ${focusBorder}`
|
? `0 0 0 1px ${focusBorder}`
|
||||||
: undefined,
|
: undefined,
|
||||||
'&:hover': { borderColor: isFocused ? focusBorder : borderHover },
|
'&:hover': { borderColor: isFocused ? focusBorder : borderHover },
|
||||||
'&:hover > div > span': { backgroundColor: borderHover },
|
'&:hover > div > span': { backgroundColor: borderHover },
|
||||||
'&:focus': { borderColor: isError ? invalidBorder : focusBorder },
|
'&:focus': { borderColor: isError ? invalidBorder : focusBorder },
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export const TableRow = (props: TableRowProps): JSX.Element => {
|
||||||
{ borderTop: '1px', borderTopColor: 'blackAlpha.100' },
|
{ borderTop: '1px', borderTopColor: 'blackAlpha.100' },
|
||||||
{ borderTop: '1px', borderTopColor: 'whiteAlpha.100' },
|
{ borderTop: '1px', borderTopColor: 'whiteAlpha.100' },
|
||||||
);
|
);
|
||||||
let bg;
|
let bg = undefined;
|
||||||
|
|
||||||
if (highlight) {
|
if (highlight) {
|
||||||
bg = `${String(highlightBg)}.${alpha}`;
|
bg = `${String(highlightBg)}.${alpha}`;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
export const CustomJavascript = (props: React.PropsWithChildren<Dict>): JSX.Element => {
|
export const CustomJavascript = (props: React.PropsWithChildren<Dict>): JSX.Element => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
if (typeof children === 'string' && children !== '') {
|
if (typeof children === 'string' && children !== '') {
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: required for injecting custom JS
|
||||||
return <script id="custom-javascript" dangerouslySetInnerHTML={{ __html: children }} />;
|
return <script id="custom-javascript" dangerouslySetInnerHTML={{ __html: children }} />;
|
||||||
}
|
}
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|
@ -19,6 +20,7 @@ export const CustomJavascript = (props: React.PropsWithChildren<Dict>): JSX.Elem
|
||||||
export const CustomHtml = (props: React.PropsWithChildren<Dict>): JSX.Element => {
|
export const CustomHtml = (props: React.PropsWithChildren<Dict>): JSX.Element => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
if (typeof children === 'string' && children !== '') {
|
if (typeof children === 'string' && children !== '') {
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: required for injecting custom HTML
|
||||||
return <div id="custom-html" dangerouslySetInnerHTML={{ __html: children }} />;
|
return <div id="custom-html" dangerouslySetInnerHTML={{ __html: children }} />;
|
||||||
}
|
}
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class IconError extends Error {
|
||||||
this.original = original;
|
this.original = original;
|
||||||
this.library = library;
|
this.library = library;
|
||||||
this.iconName = iconName;
|
this.iconName = iconName;
|
||||||
this.stack = this.stack + `\nOriginal object: '${JSON.stringify(this.original)}'`;
|
this.stack += `\nOriginal object: '${JSON.stringify(this.original)}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get message(): string {
|
get message(): string {
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ type MDProps = {
|
||||||
node: Dict;
|
node: Dict;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* eslint @typescript-eslint/no-explicit-any: off */
|
// biome-ignore lint/suspicious/noExplicitAny: reasons!
|
||||||
function hasNode<C>(p: any): p is C & MDProps {
|
function hasNode<C>(p: any): p is C & MDProps {
|
||||||
return 'node' in p;
|
return 'node' in p;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { expect, describe, it } from 'vitest';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { useBooleanValue } from './use-boolean-value';
|
import { useBooleanValue } from './use-boolean-value';
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ export function useBooleanValue<T extends unknown, F extends unknown>(
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (status) {
|
if (status) {
|
||||||
return ifTrue;
|
return ifTrue;
|
||||||
} else {
|
|
||||||
return ifFalse;
|
|
||||||
}
|
}
|
||||||
|
return ifFalse;
|
||||||
}, [status, ifTrue, ifFalse]);
|
}, [status, ifTrue, ifFalse]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { expect, describe, it } from 'vitest';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { useDevice } from './use-device';
|
import { useDevice } from './use-device';
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,7 @@ export type UseDeviceReturn = (
|
||||||
export function useDevice(): UseDeviceReturn {
|
export function useDevice(): UseDeviceReturn {
|
||||||
const { devices } = useConfig();
|
const { devices } = useConfig();
|
||||||
|
|
||||||
const locations = useMemo<Device[]>(
|
const locations = useMemo<Device[]>(() => devices.flatMap(group => group.locations), [devices]);
|
||||||
() => devices.map(group => group.locations).flat(),
|
|
||||||
[devices],
|
|
||||||
);
|
|
||||||
|
|
||||||
function getDevice(id: string): Nullable<Device> {
|
function getDevice(id: string): Nullable<Device> {
|
||||||
return locations.find(device => device.id === id) ?? null;
|
return locations.find(device => device.id === id) ?? null;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'isomorphic-fetch';
|
import 'isomorphic-fetch';
|
||||||
|
import { expect, describe, it } from 'vitest';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
|
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
|
||||||
|
|
@ -16,7 +17,7 @@ const CloudflareWrapper = (props: React.PropsWithChildren<Dict<JSX.Element>>) =>
|
||||||
cache: { timeout: 120 },
|
cache: { timeout: 120 },
|
||||||
|
|
||||||
web: { dnsProvider: { url: 'https://cloudflare-dns.com/dns-query' } },
|
web: { dnsProvider: { url: 'https://cloudflare-dns.com/dns-query' } },
|
||||||
} as jest.Mocked<Config>;
|
} as Config;
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<HyperglassContext.Provider value={config} {...props} />
|
<HyperglassContext.Provider value={config} {...props} />
|
||||||
|
|
@ -28,7 +29,7 @@ const GoogleWrapper = (props: React.PropsWithChildren<Dict<JSX.Element>>) => {
|
||||||
const config = {
|
const config = {
|
||||||
cache: { timeout: 120 },
|
cache: { timeout: 120 },
|
||||||
web: { dnsProvider: { url: 'https://dns.google/resolve' } },
|
web: { dnsProvider: { url: 'https://dns.google/resolve' } },
|
||||||
} as jest.Mocked<Config>;
|
} as Config;
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<HyperglassContext.Provider value={config} {...props} />
|
<HyperglassContext.Provider value={config} {...props} />
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const query: QueryFunction<DnsOverHttps.Response, DNSQueryKey> = async (
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
||||||
let json;
|
let json = undefined;
|
||||||
const type = family === 4 ? 'A' : family === 6 ? 'AAAA' : '';
|
const type = family === 4 ? 'A' : family === 6 ? 'AAAA' : '';
|
||||||
|
|
||||||
if (url !== null) {
|
if (url !== null) {
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,7 @@ interface FormStateType<Opt extends SingleOption = SingleOption> {
|
||||||
setSelection<
|
setSelection<
|
||||||
Opt extends SingleOption,
|
Opt extends SingleOption,
|
||||||
K extends keyof FormSelections<Opt> = keyof FormSelections<Opt>,
|
K extends keyof FormSelections<Opt> = keyof FormSelections<Opt>,
|
||||||
>(
|
>(field: K, value: FormSelections[K]): void;
|
||||||
field: K,
|
|
||||||
value: FormSelections[K],
|
|
||||||
): void;
|
|
||||||
setTarget(update: Partial<Target>): void;
|
setTarget(update: Partial<Target>): void;
|
||||||
getDirective(): Directive | null;
|
getDirective(): Directive | null;
|
||||||
reset(): void;
|
reset(): void;
|
||||||
|
|
@ -155,7 +152,7 @@ const formState: StateCreator<FormStateType> = (set, get) => ({
|
||||||
|
|
||||||
// Determine all unique group names.
|
// Determine all unique group names.
|
||||||
const allGroups = allDevices.map(dev =>
|
const allGroups = allDevices.map(dev =>
|
||||||
Array.from(new Set(dev.directives.map(dir => dir.groups).flat())),
|
Array.from(new Set(dev.directives.flatMap(dir => dir.groups))),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get group names that are common between all selected locations.
|
// Get group names that are common between all selected locations.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { expect, describe, it } from 'vitest';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import { userEvent } from '@testing-library/user-event';
|
||||||
import { useGreeting } from './use-greeting';
|
import { useGreeting } from './use-greeting';
|
||||||
|
|
||||||
const TRUE = JSON.stringify(true);
|
const TRUE = JSON.stringify(true);
|
||||||
|
|
@ -26,16 +27,16 @@ const TestComponent = (): JSX.Element => {
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
<button id="ack-false-required" type="button" onClick={() => ack(false, true)}>
|
<button id="ack-false-required" type="button" onClick={() => ack(false, true)}>
|
||||||
{`Don't acknowledge, is required`}
|
Don't acknowledge, is required
|
||||||
</button>
|
</button>
|
||||||
<button id="ack-true-required" type="button" onClick={() => ack(true, true)}>
|
<button id="ack-true-required" type="button" onClick={() => ack(true, true)}>
|
||||||
{`Acknowledge, is required`}
|
Acknowledge, is required
|
||||||
</button>
|
</button>
|
||||||
<button id="ack-false-not-required" type="button" onClick={() => ack(false, false)}>
|
<button id="ack-false-not-required" type="button" onClick={() => ack(false, false)}>
|
||||||
{`Don't Acknowledge, not required`}
|
Don't Acknowledge, not required
|
||||||
</button>
|
</button>
|
||||||
<button id="ack-true-not-required" type="button" onClick={() => ack(true, false)}>
|
<button id="ack-true-not-required" type="button" onClick={() => ack(true, false)}>
|
||||||
{`Acknowledge, not required`}
|
Acknowledge, not required
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { expect, describe, it } from 'vitest';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import userEvent from '@testing-library/user-event';
|
import { userEvent } from '@testing-library/user-event';
|
||||||
import { ChakraProvider, useColorMode, useColorModeValue, extendTheme } from '@chakra-ui/react';
|
import { ChakraProvider, useColorMode, useColorModeValue, extendTheme } from '@chakra-ui/react';
|
||||||
import { useOpposingColor } from './use-opposing-color';
|
import { useOpposingColor } from './use-opposing-color';
|
||||||
|
|
||||||
|
|
@ -32,17 +33,17 @@ describe('useOpposingColor Hook', () => {
|
||||||
const test1 = container.querySelector('#test1');
|
const test1 = container.querySelector('#test1');
|
||||||
const test2 = container.querySelector('#test2');
|
const test2 = container.querySelector('#test2');
|
||||||
|
|
||||||
expect(test1).toHaveStyle('color: black;');
|
expect(test1).toHaveStyle('color: rgb(0, 0, 0);');
|
||||||
expect(test2).toHaveStyle('color: black;');
|
expect(test2).toHaveStyle('color: rgb(0, 0, 0);');
|
||||||
|
|
||||||
await userEvent.click(getByRole('button'));
|
await userEvent.click(getByRole('button'));
|
||||||
|
|
||||||
expect(test1).toHaveStyle('color: white;');
|
expect(test1).toHaveStyle('color: rgb(255, 255, 255);');
|
||||||
expect(test2).toHaveStyle('color: white;');
|
expect(test2).toHaveStyle('color: rgb(255, 255, 255);');
|
||||||
|
|
||||||
await userEvent.click(getByRole('button'));
|
await userEvent.click(getByRole('button'));
|
||||||
|
|
||||||
expect(test1).toHaveStyle('color: black;');
|
expect(test1).toHaveStyle('color: rgb(0, 0, 0);');
|
||||||
expect(test2).toHaveStyle('color: black;');
|
expect(test2).toHaveStyle('color: rgb(0, 0, 0);');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,13 @@ export function useIsDarkCallback(): UseIsDarkCallbackReturn {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(color: string): boolean => {
|
(color: string): boolean => {
|
||||||
|
let opposing = color;
|
||||||
if (typeof color === 'string' && color.match(/[a-zA-Z]+\.[a-zA-Z0-9]+/g)) {
|
if (typeof color === 'string' && color.match(/[a-zA-Z]+\.[a-zA-Z0-9]+/g)) {
|
||||||
color = getColor(theme, color, color);
|
opposing = getColor(theme, color, color);
|
||||||
}
|
}
|
||||||
let opposingShouldBeDark = true;
|
let opposingShouldBeDark = true;
|
||||||
try {
|
try {
|
||||||
opposingShouldBeDark = isLight(color)(theme);
|
opposingShouldBeDark = isLight(opposing)(theme);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|
@ -46,9 +47,8 @@ export function useOpposingColor(color: string, options?: OpposingColorOptions):
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (isBlack) {
|
if (isBlack) {
|
||||||
return options?.dark ?? 'black';
|
return options?.dark ?? 'black';
|
||||||
} else {
|
|
||||||
return options?.light ?? 'white';
|
|
||||||
}
|
}
|
||||||
|
return options?.light ?? 'white';
|
||||||
}, [isBlack, options?.dark, options?.light]);
|
}, [isBlack, options?.dark, options?.light]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { expect, describe, it } from 'vitest';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { useStrf } from './use-strf';
|
import { useStrf } from './use-strf';
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ function formatAsPath(path: number[]): string {
|
||||||
|
|
||||||
function formatCommunities(comms: string[]): string {
|
function formatCommunities(comms: string[]): string {
|
||||||
const commsStr = comms.map(c => ` - ${c}`);
|
const commsStr = comms.map(c => ` - ${c}`);
|
||||||
return '\n' + commsStr.join('\n');
|
return `\n ${commsStr.join('\n')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatBool(val: boolean): string {
|
function formatBool(val: boolean): string {
|
||||||
|
|
@ -85,9 +85,8 @@ export function useTableToString(
|
||||||
function getFmtFunc(accessor: keyof Route): TableToStringFormatter {
|
function getFmtFunc(accessor: keyof Route): TableToStringFormatter {
|
||||||
if (isFormatted(accessor)) {
|
if (isFormatted(accessor)) {
|
||||||
return tableFormatMap[accessor];
|
return tableFormatMap[accessor];
|
||||||
} else {
|
|
||||||
return String;
|
|
||||||
}
|
}
|
||||||
|
return String;
|
||||||
}
|
}
|
||||||
|
|
||||||
function doFormat(target: string[], data: QueryResponse | undefined): string {
|
function doFormat(target: string[], data: QueryResponse | undefined): string {
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
/**
|
|
||||||
* Jest Testing Configuration
|
|
||||||
*
|
|
||||||
* @see https://nextjs.org/docs/testing
|
|
||||||
* @type {import('@jest/types').Config.InitialOptions}
|
|
||||||
*/
|
|
||||||
const jestConfig = {
|
|
||||||
collectCoverageFrom: ['**/*.{ts,tsx}', '!**/*.d.ts', '!**/node_modules/**'],
|
|
||||||
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
|
|
||||||
testEnvironment: 'jsdom',
|
|
||||||
transform: { '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }] },
|
|
||||||
transformIgnorePatterns: ['/node_modules/', '^.+\\.module\\.(css|sass|scss)$'],
|
|
||||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
||||||
moduleNameMapper: {
|
|
||||||
'^@/components/(.*)$': '<rootDir>/components/$1',
|
|
||||||
'^~/components': ['components/index'],
|
|
||||||
'^~/components/(.*)$': '<rootDir>/components/$1',
|
|
||||||
'^~/context': '<rootDir>/context/index',
|
|
||||||
'^~/context/(.*)$': '<rootDir>/context/$1',
|
|
||||||
'^~/hooks': '<rootDir>/hooks/index',
|
|
||||||
'^~/hooks/(.*)$': '<rootDir>/hooks/$1',
|
|
||||||
'^~/state': '<rootDir>/state/index',
|
|
||||||
'^~/state/(.*)$': '<rootDir>/state/$1',
|
|
||||||
'^~/types': '<rootDir>/types/index',
|
|
||||||
'^~/types/(.*)$': '<rootDir>/types/$1',
|
|
||||||
'^~/util': '<rootDir>/util/index',
|
|
||||||
'^~/util/(.*)$': '<rootDir>/util/$1',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = jestConfig;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
|
||||||
|
|
@ -20,18 +20,19 @@ app
|
||||||
|
|
||||||
const devProxy = {
|
const devProxy = {
|
||||||
'/api/query/': {
|
'/api/query/': {
|
||||||
target: process.env.HYPERGLASS_URL + 'api/query/',
|
target: `${process.env.HYPERGLASS_URL}api/query/`,
|
||||||
pathRewrite: { '^/api/query/': '' },
|
pathRewrite: { '^/api/query/': '' },
|
||||||
},
|
},
|
||||||
'/ui/props/': {
|
'/ui/props/': {
|
||||||
target: process.env.HYPERGLASS_URL + 'ui/props/',
|
target: `${process.env.HYPERGLASS_URL}ui/props/`,
|
||||||
pathRewrite: { '^/ui/props/': '' },
|
pathRewrite: { '^/ui/props/': '' },
|
||||||
},
|
},
|
||||||
'/images': { target: process.env.HYPERGLASS_URL + 'images', pathRewrite: { '^/images': '' } },
|
'/images': { target: `${process.env.HYPERGLASS_URL}images`, pathRewrite: { '^/images': '' } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up the proxy.
|
// Set up the proxy.
|
||||||
if (dev) {
|
if (dev) {
|
||||||
|
// biome-ignore lint/complexity/noForEach: not messing with Next's example code.
|
||||||
Object.keys(devProxy).forEach(context => {
|
Object.keys(devProxy).forEach(context => {
|
||||||
server.use(proxyMiddleware(context, devProxy[context]));
|
server.use(proxyMiddleware(context, devProxy[context]));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
181
hyperglass/ui/package.json
vendored
181
hyperglass/ui/package.json
vendored
|
|
@ -1,91 +1,94 @@
|
||||||
{
|
{
|
||||||
"version": "2.0.0-dev",
|
"version": "2.0.0-dev",
|
||||||
"name": "ui",
|
"name": "ui",
|
||||||
"description": "UI for hyperglass, the modern network looking glass",
|
"description": "UI for hyperglass, the modern network looking glass",
|
||||||
"author": "Matt Love",
|
"author": "Matt Love",
|
||||||
"license": "BSD-3-Clause-Clear",
|
"license": "BSD-3-Clause-Clear",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --ext .ts --ext .tsx",
|
"lint": "biome lint .",
|
||||||
"dev": "node nextdev",
|
"dev": "node nextdev",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"format": "prettier --config ./.prettierrc -c -w .",
|
"format": "biome format --write .",
|
||||||
"format:check": "prettier --config ./.prettierrc -c .",
|
"format:check": "biome format .",
|
||||||
"build": "export NODE_OPTIONS=--openssl-legacy-provider; next build && next export -o ../hyperglass/static/ui",
|
"build": "export NODE_OPTIONS=--openssl-legacy-provider; next build && next export -o ../hyperglass/static/ui",
|
||||||
"test": "jest"
|
"test": "vitest --run"
|
||||||
},
|
},
|
||||||
"browserslist": "> 0.25%, not dead",
|
"browserslist": "> 0.25%, not dead",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^2.5.5",
|
"@chakra-ui/react": "^2.5.5",
|
||||||
"@chakra-ui/theme": "3.0.1",
|
"@chakra-ui/theme": "3.0.1",
|
||||||
"@chakra-ui/theme-tools": "^2.0.17",
|
"@chakra-ui/theme-tools": "^2.0.17",
|
||||||
"@chakra-ui/utils": "^2.0.15",
|
"@chakra-ui/utils": "^2.0.15",
|
||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.10.6",
|
||||||
"@hookform/devtools": "^4.3.0",
|
"@hookform/devtools": "^4.3.0",
|
||||||
"@hookform/resolvers": "^2.9.10",
|
"@hookform/resolvers": "^2.9.10",
|
||||||
"@tanstack/react-query": "^4.22.0",
|
"@tanstack/react-query": "^4.22.0",
|
||||||
"dagre": "^0.8.5",
|
"dagre": "^0.8.5",
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"framer-motion": "^10.11.6",
|
"framer-motion": "^10.11.6",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"merge-anything": "^4.0.1",
|
"merge-anything": "^4.0.1",
|
||||||
"next": "12.3.4",
|
"next": "13.5.6",
|
||||||
"palette-by-numbers": "^0.1.6",
|
"palette-by-numbers": "^0.1.6",
|
||||||
"plur": "^4.0.0",
|
"plur": "^4.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-countdown": "^2.3.0",
|
"react-countdown": "^2.3.0",
|
||||||
"react-device-detect": "^1.15.0",
|
"react-device-detect": "^1.15.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-fast-compare": "^3.2.1",
|
"react-fast-compare": "^3.2.1",
|
||||||
"react-flow-renderer": "^10.3.17",
|
"react-flow-renderer": "^10.3.17",
|
||||||
"react-hook-form": "^7.42.1",
|
"react-hook-form": "^7.42.1",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-if": "^4.1.4",
|
"react-if": "^4.1.4",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
"react-select": "^5.7.0",
|
"react-select": "^5.7.0",
|
||||||
"react-string-replace": "^0.5.0",
|
"react-string-replace": "^0.5.0",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"remark-gfm": "^1.0.0",
|
"remark-gfm": "^1.0.0",
|
||||||
"string-format": "^2.0.0",
|
"string-format": "^2.0.0",
|
||||||
"vest": "^3.2.8",
|
"vest": "^3.2.8",
|
||||||
"zustand": "^3.7.2"
|
"zustand": "^3.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@biomejs/biome": "1.5.3",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/jest-dom": "^6.4.2",
|
||||||
"@testing-library/react-hooks": "^7.0.2",
|
"@testing-library/react": "^14.2.1",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
"@types/dagre": "^0.7.44",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/express": "^4.17.13",
|
"@types/dagre": "^0.7.44",
|
||||||
"@types/lodash": "^4.14.177",
|
"@types/express": "^4.17.21",
|
||||||
"@types/node": "^18.15.11",
|
"@types/lodash": "^4.14.177",
|
||||||
"@types/react": "^18.0.35",
|
"@types/node": "^20.11.20",
|
||||||
"@types/react-table": "^7.7.1",
|
"@types/react": "^18.2.60",
|
||||||
"@types/string-format": "^2.0.0",
|
"@types/react-table": "^7.7.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.31.0",
|
"@types/string-format": "^2.0.0",
|
||||||
"@typescript-eslint/parser": "^4.31.0",
|
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"@typescript-eslint/parser": "^7.1.0",
|
||||||
"babel-jest": "^27.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"eslint": "^7.32.0",
|
"@vitest/ui": "^1.3.1",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint-import-resolver-typescript": "^2.4.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-import": "^2.24.2",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-json": "^3.1.0",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-json": "^3.1.0",
|
||||||
"eslint-plugin-react": "^7.25.1",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"express": "^4.17.1",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"http-proxy-middleware": "0.20.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"express": "^4.18.2",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"http-proxy-middleware": "2.0.6",
|
||||||
"jest": "^27.2.1",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"prettier": "^2.3.2",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"prettier-eslint": "^13.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"react-test-renderer": "^18.2.0",
|
"prettier": "^3.2.5",
|
||||||
"type-fest": "^3.8.0",
|
"prettier-eslint": "^16.3.0",
|
||||||
"typescript": "^5.0.4"
|
"react-test-renderer": "^18.2.0",
|
||||||
}
|
"type-fest": "^4.10.3",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"vitest": "^1.3.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,23 @@
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { Switch, Case, Default } from 'react-if';
|
|
||||||
import { Meta, Layout } from '~/components';
|
import { Meta, Layout } from '~/components';
|
||||||
import { HyperglassProvider } from '~/context';
|
import { HyperglassProvider } from '~/context';
|
||||||
import { LoadError, Loading } from '~/elements';
|
import * as config from '../hyperglass.json';
|
||||||
import { useHyperglassConfig } from '~/hooks';
|
|
||||||
|
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
|
import type { Config } from '~/types';
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
const AppComponent = (props: AppProps) => {
|
|
||||||
const { Component, pageProps } = props;
|
|
||||||
const { data, error, isLoading, ready, refetch, showError, isLoadingInitial } =
|
|
||||||
useHyperglassConfig();
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
<Case condition={isLoadingInitial}>
|
|
||||||
<Loading />
|
|
||||||
</Case>
|
|
||||||
<Case condition={showError}>
|
|
||||||
<LoadError error={error!} retry={refetch} inProgress={isLoading} />
|
|
||||||
</Case>
|
|
||||||
<Case condition={ready}>
|
|
||||||
<HyperglassProvider config={data!}>
|
|
||||||
<Meta />
|
|
||||||
<Layout>
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</Layout>
|
|
||||||
</HyperglassProvider>
|
|
||||||
</Case>
|
|
||||||
<Default>
|
|
||||||
<LoadError error={error!} retry={refetch} inProgress={isLoading} />
|
|
||||||
</Default>
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const App = (props: AppProps): JSX.Element => {
|
const App = (props: AppProps): JSX.Element => {
|
||||||
|
const { Component, pageProps } = props;
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<AppComponent {...props} />
|
<HyperglassProvider config={config as unknown as Config}>
|
||||||
|
<Meta />
|
||||||
|
<Layout>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</Layout>
|
||||||
|
</HyperglassProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ import fs from 'fs';
|
||||||
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
||||||
import { ColorModeScript } from '@chakra-ui/react';
|
import { ColorModeScript } from '@chakra-ui/react';
|
||||||
import { CustomJavascript, CustomHtml, Favicon } from '~/elements';
|
import { CustomJavascript, CustomHtml, Favicon } from '~/elements';
|
||||||
import { getHyperglassConfig, googleFontUrl } from '~/util';
|
import { googleFontUrl } from '~/util';
|
||||||
import favicons from '../favicon-formats';
|
import favicons from '../favicon-formats';
|
||||||
|
import config from '../hyperglass.json';
|
||||||
|
|
||||||
import type { DocumentContext, DocumentInitialProps } from 'next/document';
|
import type { DocumentContext, DocumentInitialProps } from 'next/document';
|
||||||
import type { ThemeConfig } from '~/types';
|
import type { ThemeConfig } from '~/types';
|
||||||
|
|
@ -18,8 +19,8 @@ interface DocumentExtra
|
||||||
class MyDocument extends Document<DocumentExtra> {
|
class MyDocument extends Document<DocumentExtra> {
|
||||||
static async getInitialProps(ctx: DocumentContext): Promise<DocumentExtra> {
|
static async getInitialProps(ctx: DocumentContext): Promise<DocumentExtra> {
|
||||||
const initialProps = await Document.getInitialProps(ctx);
|
const initialProps = await Document.getInitialProps(ctx);
|
||||||
let customJs = '',
|
let customJs = '';
|
||||||
customHtml = '';
|
let customHtml = '';
|
||||||
|
|
||||||
if (fs.existsSync('custom.js')) {
|
if (fs.existsSync('custom.js')) {
|
||||||
customJs = fs.readFileSync('custom.js').toString();
|
customJs = fs.readFileSync('custom.js').toString();
|
||||||
|
|
@ -31,18 +32,18 @@ class MyDocument extends Document<DocumentExtra> {
|
||||||
let fonts = { body: '', mono: '' };
|
let fonts = { body: '', mono: '' };
|
||||||
let defaultColorMode: 'light' | 'dark' | null = null;
|
let defaultColorMode: 'light' | 'dark' | null = null;
|
||||||
|
|
||||||
const hyperglassUrl = process.env.HYPERGLASS_URL ?? '';
|
// const hyperglassUrl = process.env.HYPERGLASS_URL ?? '';
|
||||||
const {
|
// const {
|
||||||
web: {
|
// web: {
|
||||||
theme: { fonts: themeFonts, defaultColorMode: themeDefaultColorMode },
|
// theme: { fonts: themeFonts, defaultColorMode: themeDefaultColorMode },
|
||||||
},
|
// },
|
||||||
} = await getHyperglassConfig(hyperglassUrl);
|
// } = await getHyperglassConfig(hyperglassUrl);
|
||||||
|
|
||||||
fonts = {
|
fonts = {
|
||||||
body: googleFontUrl(themeFonts.body),
|
body: googleFontUrl(config.web.theme.fonts.body),
|
||||||
mono: googleFontUrl(themeFonts.mono),
|
mono: googleFontUrl(config.web.theme.fonts.mono),
|
||||||
};
|
};
|
||||||
defaultColorMode = themeDefaultColorMode;
|
defaultColorMode = config.web.theme.defaultColorMode;
|
||||||
|
|
||||||
return { customJs, customHtml, fonts, defaultColorMode, ...initialProps };
|
return { customJs, customHtml, fonts, defaultColorMode, ...initialProps };
|
||||||
}
|
}
|
||||||
|
|
@ -63,8 +64,8 @@ class MyDocument extends Document<DocumentExtra> {
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
||||||
<link href={this.props.fonts.mono} rel="stylesheet" />
|
<link href={this.props.fonts.mono} rel="stylesheet" />
|
||||||
<link href={this.props.fonts.body} rel="stylesheet" />
|
<link href={this.props.fonts.body} rel="stylesheet" />
|
||||||
{favicons.map((favicon, idx) => (
|
{favicons.map(favicon => (
|
||||||
<Favicon key={idx} {...favicon} />
|
<Favicon key={JSON.stringify(favicon)} {...favicon} />
|
||||||
))}
|
))}
|
||||||
<CustomJavascript>{this.props.customJs}</CustomJavascript>
|
<CustomJavascript>{this.props.customJs}</CustomJavascript>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
|
||||||
5728
hyperglass/ui/pnpm-lock.yaml
generated
5728
hyperglass/ui/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -40,6 +40,7 @@
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
"types/*.d.ts",
|
"types/*.d.ts",
|
||||||
"next.config.js",
|
"next.config.js",
|
||||||
"nextdev.js"
|
"nextdev.js",
|
||||||
|
"hyperglass.json"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Theme } from './theme';
|
import type { Theme } from './theme';
|
||||||
import type { CamelCasedPropertiesDeep, CamelCasedProperties } from 'type-fest';
|
import type { CamelCasedPropertiesDeep, CamelCasedProperties } from 'type-fest';
|
||||||
|
|
||||||
type Side = 'left' | 'right';
|
type Side = 'left' | 'right' | string;
|
||||||
|
|
||||||
export type ParsedDataField = [string, keyof Route, 'left' | 'right' | 'center' | null];
|
export type ParsedDataField = [string, keyof Route, 'left' | 'right' | 'center' | null];
|
||||||
|
|
||||||
|
|
@ -92,19 +92,17 @@ interface _Web {
|
||||||
links: _Link[];
|
links: _Link[];
|
||||||
menus: _Menu[];
|
menus: _Menu[];
|
||||||
greeting: _Greeting;
|
greeting: _Greeting;
|
||||||
help_menu: { enable: boolean; title: string };
|
|
||||||
logo: _Logo;
|
logo: _Logo;
|
||||||
terms: { enable: boolean; title: string };
|
|
||||||
text: _Text;
|
text: _Text;
|
||||||
theme: _ThemeConfig;
|
theme: _ThemeConfig;
|
||||||
location_display_mode: 'auto' | 'gallery' | 'dropdown';
|
location_display_mode: 'auto' | 'gallery' | 'dropdown' | string;
|
||||||
highlight: _Highlight[];
|
highlight: _Highlight[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DirectiveBase = {
|
type _DirectiveBase = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
field_type: 'text' | 'select' | null;
|
field_type: 'text' | 'select' | null | string;
|
||||||
description: string;
|
description: string;
|
||||||
groups: string[];
|
groups: string[];
|
||||||
info: string | null;
|
info: string | null;
|
||||||
|
|
@ -125,7 +123,7 @@ type _Directive = _DirectiveBase | _DirectiveSelect;
|
||||||
interface _Device {
|
interface _Device {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
group: string;
|
group: string | null;
|
||||||
avatar: string | null;
|
avatar: string | null;
|
||||||
directives: _Directive[];
|
directives: _Directive[];
|
||||||
description: string | null;
|
description: string | null;
|
||||||
|
|
@ -144,7 +142,7 @@ interface _Cache {
|
||||||
type _Config = _ConfigDeep & _ConfigShallow;
|
type _Config = _ConfigDeep & _ConfigShallow;
|
||||||
|
|
||||||
interface _DeviceGroup {
|
interface _DeviceGroup {
|
||||||
group: string;
|
group: string | null;
|
||||||
locations: _Device[];
|
locations: _Device[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +155,6 @@ interface _ConfigDeep {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface _ConfigShallow {
|
interface _ConfigShallow {
|
||||||
debug: boolean;
|
|
||||||
developer_mode: boolean;
|
developer_mode: boolean;
|
||||||
primary_asn: string;
|
primary_asn: string;
|
||||||
request_timeout: number;
|
request_timeout: number;
|
||||||
|
|
|
||||||
6
hyperglass/ui/types/globals.d.ts
vendored
6
hyperglass/ui/types/globals.d.ts
vendored
|
|
@ -55,10 +55,16 @@ export declare global {
|
||||||
export interface ProcessEnv {
|
export interface ProcessEnv {
|
||||||
hyperglass: { favicons: import('./config').Favicon[]; version: string };
|
hyperglass: { favicons: import('./config').Favicon[]; version: string };
|
||||||
buildId: string;
|
buildId: string;
|
||||||
|
UI_PARAMS: import('./config').Config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'hyperglass.json' {
|
||||||
|
type Config = import('./config').Config;
|
||||||
|
export default Config;
|
||||||
|
}
|
||||||
|
|
||||||
declare module 'react' {
|
declare module 'react' {
|
||||||
// Enable generic typing with forwardRef.
|
// Enable generic typing with forwardRef.
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
|
import { expect, describe, it, test } from 'vitest';
|
||||||
import { all, chunkArray, entries, dedupObjectArray, andJoin, isFQDN } from './common';
|
import { all, chunkArray, entries, dedupObjectArray, andJoin, isFQDN } from './common';
|
||||||
|
|
||||||
test('all - all items are truthy', () => {
|
test('all - all items are truthy', () => {
|
||||||
|
// biome-ignore lint/suspicious/noSelfCompare: because this is a test, duh
|
||||||
expect(all(1 === 1, true, 'one' === 'one')).toBe(true);
|
expect(all(1 === 1, true, 'one' === 'one')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('all - one item is not truthy', () => {
|
test('all - one item is not truthy', () => {
|
||||||
|
// biome-ignore lint/suspicious/noSelfCompare: because this is a test, duh
|
||||||
expect(all(1 === 1, false, 'one' === 'one')).toBe(false);
|
expect(all(1 === 1, false, 'one' === 'one')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ export function entries<O, K extends keyof O = keyof O>(obj: O): [K, O[K]][] {
|
||||||
*/
|
*/
|
||||||
export async function fetchWithTimeout(
|
export async function fetchWithTimeout(
|
||||||
uri: string,
|
uri: string,
|
||||||
|
// biome-ignore lint/style/useDefaultParameterLast: goal is to match the fetch API as closely as possible.
|
||||||
options: RequestInit = {},
|
options: RequestInit = {},
|
||||||
timeout: number,
|
timeout: number,
|
||||||
controller: AbortController,
|
controller: AbortController,
|
||||||
|
|
@ -69,9 +70,8 @@ export function dedupObjectArray<E extends Record<string, unknown>, P extends ke
|
||||||
|
|
||||||
if (!x) {
|
if (!x) {
|
||||||
return acc.concat([current]);
|
return acc.concat([current]);
|
||||||
} else {
|
|
||||||
return acc;
|
|
||||||
}
|
}
|
||||||
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export class ConfigLoadError extends Error {
|
||||||
constructor(detail?: string) {
|
constructor(detail?: string) {
|
||||||
super();
|
super();
|
||||||
this.detail = detail;
|
this.detail = detail;
|
||||||
this.baseMessage = `Unable to connect to hyperglass at`;
|
this.baseMessage = 'Unable to connect to hyperglass at';
|
||||||
this.message = `${this.baseMessage} '${this.url}'`;
|
this.message = `${this.baseMessage} '${this.url}'`;
|
||||||
console.error(this);
|
console.error(this);
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ export async function getHyperglassConfig(url?: QueryFunctionContext | string):
|
||||||
let fetchUrl = '/ui/props/';
|
let fetchUrl = '/ui/props/';
|
||||||
|
|
||||||
if (typeof url === 'string') {
|
if (typeof url === 'string') {
|
||||||
fetchUrl = url.replace(/(^\/)|(\/$)/g, '') + '/ui/props/';
|
fetchUrl = `${url.replace(/(^\/)|(\/$)/g, '')}/ui/props`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import type { StateCreator, SetState, GetState, StoreApi } from 'zustand';
|
||||||
* @param store zustand store function.
|
* @param store zustand store function.
|
||||||
* @param name Store name.
|
* @param name Store name.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
export function withDev<T extends object = {}>(
|
export function withDev<T extends object = {}>(
|
||||||
store: StateCreator<T>,
|
store: StateCreator<T>,
|
||||||
name: string,
|
name: string,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { googleFontUrl } from './theme';
|
import { googleFontUrl } from './theme';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
describe('google font URL generation', () => {
|
describe('google font URL generation', () => {
|
||||||
test('no space font', () => {
|
test('no space font', () => {
|
||||||
|
|
|
||||||
18
hyperglass/ui/vitest.config.ts
Normal file
18
hyperglass/ui/vitest.config.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
/// <reference types="vitest" />
|
||||||
|
|
||||||
|
import path from 'node:path';
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
globals: true,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': path.resolve(__dirname, './'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -16,14 +16,13 @@ from .tools import (
|
||||||
run_coroutine_in_new_thread,
|
run_coroutine_in_new_thread,
|
||||||
)
|
)
|
||||||
from .typing import is_type, is_series
|
from .typing import is_type, is_series
|
||||||
from .frontend import build_ui, build_frontend
|
|
||||||
from .validation import get_driver, resolve_hostname, validate_platform
|
from .validation import get_driver, resolve_hostname, validate_platform
|
||||||
from .system_info import cpu_count, check_python, get_system_info, get_node_version
|
from .system_info import cpu_count, check_python, get_system_info, get_node_version
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"at_least",
|
"at_least",
|
||||||
"build_frontend",
|
# "build_frontend",
|
||||||
"build_ui",
|
# "build_ui",
|
||||||
"check_path",
|
"check_path",
|
||||||
"check_python",
|
"check_python",
|
||||||
"compare_dicts",
|
"compare_dicts",
|
||||||
|
|
|
||||||
135
pyproject.toml
135
pyproject.toml
|
|
@ -1,70 +1,64 @@
|
||||||
|
[project]
|
||||||
[build-system]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
requires = ["poetry-core"]
|
|
||||||
|
|
||||||
[tool.poetry]
|
|
||||||
authors = ["Matt Love <matt@hyperglass.dev>"]
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 5 - Production/Stable",
|
|
||||||
"Intended Audience :: Information Technology",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
|
||||||
"Programming Language :: TypeScript",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Topic :: Internet",
|
|
||||||
"Topic :: System :: Networking",
|
|
||||||
]
|
|
||||||
description = "hyperglass is the modern network looking glass that tries to make the internet better."
|
|
||||||
documentation = "https://hyperglass.dev"
|
|
||||||
homepage = "https://hyperglass.dev"
|
|
||||||
keywords = ["looking glass", "network automation", "isp", "bgp", "routing"]
|
|
||||||
license = "BSD-3-Clause-Clear"
|
|
||||||
name = "hyperglass"
|
name = "hyperglass"
|
||||||
readme = "README.md"
|
|
||||||
repository = "https://github.com/thatmattlove/hyperglass"
|
|
||||||
version = "2.0.0-dev"
|
version = "2.0.0-dev"
|
||||||
|
description = "hyperglass is the modern network looking glass that tries to make the internet better."
|
||||||
|
authors = [
|
||||||
|
{ name = "thatmattlove", email = "matt@hyperglass.dev" }
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"Pillow==10.2.0",
|
||||||
|
"PyJWT==2.6.0",
|
||||||
|
"PyYAML>=6.0",
|
||||||
|
"aiofiles>=23.2.1",
|
||||||
|
"distro==1.8.0",
|
||||||
|
"fastapi==0.95.1",
|
||||||
|
"favicons==0.2.2",
|
||||||
|
"gunicorn==20.1.0",
|
||||||
|
"httpx==0.24.0",
|
||||||
|
"loguru==0.7.0",
|
||||||
|
"netmiko==4.1.2",
|
||||||
|
"paramiko==3.4.0",
|
||||||
|
"psutil==5.9.4",
|
||||||
|
"py-cpuinfo==9.0.0",
|
||||||
|
"pydantic==1.10.14",
|
||||||
|
"redis==4.5.4",
|
||||||
|
"rich>=13.7.0",
|
||||||
|
"typer>=0.9.0",
|
||||||
|
"uvicorn==0.21.1",
|
||||||
|
"uvloop>=0.17.0",
|
||||||
|
"xmltodict==0.13.0",
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">= 3.11"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[project.scripts]
|
||||||
hyperglass = "hyperglass.console:run"
|
hyperglass = "hyperglass.console:run"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[build-system]
|
||||||
Pillow = "^9.5.0"
|
requires = ["hatchling"]
|
||||||
PyJWT = "^2.6.0"
|
build-backend = "hatchling.build"
|
||||||
PyYAML = "^6.0"
|
|
||||||
aiofiles = "^23.1.0"
|
|
||||||
distro = "^1.8.0"
|
|
||||||
fastapi = "^0.95.1"
|
|
||||||
favicons = "^0.2.0"
|
|
||||||
gunicorn = "^20.1.0"
|
|
||||||
httpx = "^0.24.0"
|
|
||||||
loguru = "^0.7.0"
|
|
||||||
netmiko = "^4.1.2"
|
|
||||||
paramiko = "^3.1.0"
|
|
||||||
psutil = "^5.9.4"
|
|
||||||
py-cpuinfo = "^9.0.0"
|
|
||||||
pydantic = "^1.10.7"
|
|
||||||
python = ">=3.8.1,<4.0"
|
|
||||||
redis = "^4.5.4"
|
|
||||||
rich = "^13.3.4"
|
|
||||||
typer = "^0.7.0"
|
|
||||||
uvicorn = "^0.21.1"
|
|
||||||
uvloop = "^0.17.0"
|
|
||||||
xmltodict = "^0.13.0"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.rye]
|
||||||
bandit = "^1.7.4"
|
managed = true
|
||||||
black = "^22.12.0"
|
dev-dependencies = [
|
||||||
isort = "^5.10.1"
|
"bandit>=1.7.7",
|
||||||
pep8-naming = "^0.13.2"
|
"black>=24.2.0",
|
||||||
pre-commit = "^2.20.0"
|
"isort>=5.13.2",
|
||||||
pytest = "^7.2.0"
|
"pep8-naming>=0.13.3",
|
||||||
pytest-asyncio = "^0.20.3"
|
"pre-commit>=3.6.1",
|
||||||
pytest-dependency = "^0.5.1"
|
"pytest>=8.0.1",
|
||||||
ruff = "^0.0.261"
|
"pytest-asyncio>=0.23.5",
|
||||||
stackprinter = "^0.2.10"
|
"pytest-dependency>=0.6.0",
|
||||||
taskipy = "^1.10.3"
|
"ruff>=0.2.1",
|
||||||
|
"stackprinter>=0.2.11",
|
||||||
|
"taskipy>=1.12.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.hatch.metadata]
|
||||||
|
allow-direct-references = true
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["hyperglass"]
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
|
|
@ -85,13 +79,6 @@ multi_line_output = 3
|
||||||
profile = "black"
|
profile = "black"
|
||||||
skip_glob = "hyperglass/api/examples/*.py"
|
skip_glob = "hyperglass/api/examples/*.py"
|
||||||
|
|
||||||
[tool.pyright]
|
|
||||||
exclude = ["**/node_modules", "**/ui", "**/__pycache__"]
|
|
||||||
include = ["hyperglass"]
|
|
||||||
pythonVersion = "3.9"
|
|
||||||
reportMissingImports = true
|
|
||||||
reportMissingTypeStubs = true
|
|
||||||
|
|
||||||
[tool.taskipy.tasks]
|
[tool.taskipy.tasks]
|
||||||
check = {cmd = "task lint && task ui-lint", help = "Run all lint checks"}
|
check = {cmd = "task lint && task ui-lint", help = "Run all lint checks"}
|
||||||
docs-platforms = {cmd = "python3 -c 'from hyperglass.util.docs import create_platform_list;print(create_platform_list())'"}
|
docs-platforms = {cmd = "python3 -c 'from hyperglass.util.docs import create_platform_list;print(create_platform_list())'"}
|
||||||
|
|
@ -102,12 +89,12 @@ start = {cmd = "python3 -m hyperglass.main", help = "Start hyperglass"}
|
||||||
start-asgi = {cmd = "uvicorn hyperglass.api:app", help = "Start hyperglass via Uvicorn"}
|
start-asgi = {cmd = "uvicorn hyperglass.api:app", help = "Start hyperglass via Uvicorn"}
|
||||||
test = {cmd = "pytest hyperglass --ignore hyperglass/plugins/external", help = "Run hyperglass tests"}
|
test = {cmd = "pytest hyperglass --ignore hyperglass/plugins/external", help = "Run hyperglass tests"}
|
||||||
ui-build = {cmd = "python3 -m hyperglass.console build-ui", help = "Run a UI Build"}
|
ui-build = {cmd = "python3 -m hyperglass.console build-ui", help = "Run a UI Build"}
|
||||||
ui-dev = {cmd = "yarn --cwd ./hyperglass/ui/ dev", help = "Start the Next.JS dev server"}
|
ui-dev = {cmd = "pnpm run --dir ./hyperglass/ui/ dev", help = "Start the Next.JS dev server"}
|
||||||
ui-format = {cmd = "yarn --cwd ./hyperglass/ui/ format", help = "Run Prettier"}
|
ui-format = {cmd = "pnpm run --dir ./hyperglass/ui/ format", help = "Run Prettier"}
|
||||||
ui-lint = {cmd = "yarn --cwd ./hyperglass/ui/ lint", help = "Run ESLint"}
|
ui-lint = {cmd = "pnpm run --dir ./hyperglass/ui/ lint", help = "Run ESLint"}
|
||||||
ui-typecheck = {cmd = "yarn --cwd ./hyperglass/ui/ typecheck", help = "Run TypeScript Check"}
|
ui-typecheck = {cmd = "pnpm run --dir ./hyperglass/ui/ typecheck", help = "Run TypeScript Check"}
|
||||||
upgrade = {cmd = "python3 version.py", help = "Upgrade hyperglass version"}
|
upgrade = {cmd = "python3 version.py", help = "Upgrade hyperglass version"}
|
||||||
yarn = {cmd = "yarn --cwd ./hyperglass/ui/", help = "Run a yarn command from the UI directory"}
|
pnpm = {cmd = "pnpm run --dir ./hyperglass/ui/", help = "Run a yarn command from the UI directory"}
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
exclude = [
|
exclude = [
|
||||||
|
|
@ -148,7 +135,7 @@ convention = "pep257"
|
||||||
[tool.ruff.mccabe]
|
[tool.ruff.mccabe]
|
||||||
max-complexity = 10
|
max-complexity = 10
|
||||||
|
|
||||||
[tool.ruff.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"hyperglass/main.py" = ["E402"]
|
"hyperglass/main.py" = ["E402"]
|
||||||
# Disable classmethod warning for validator decorat
|
# Disable classmethod warning for validator decorat
|
||||||
"hyperglass/configuration/models/*.py" = ["N805"]
|
"hyperglass/configuration/models/*.py" = ["N805"]
|
||||||
|
|
|
||||||
206
requirements-dev.lock
Normal file
206
requirements-dev.lock
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
# generated by rye
|
||||||
|
# use `rye lock` or `rye sync` to update this lockfile
|
||||||
|
#
|
||||||
|
# last locked with the following flags:
|
||||||
|
# pre: false
|
||||||
|
# features: []
|
||||||
|
# all-features: false
|
||||||
|
# with-sources: false
|
||||||
|
|
||||||
|
-e file:.
|
||||||
|
aiofiles==23.2.1
|
||||||
|
# via hyperglass
|
||||||
|
anyio==4.3.0
|
||||||
|
# via httpcore
|
||||||
|
# via starlette
|
||||||
|
bandit==1.7.7
|
||||||
|
bcrypt==4.1.2
|
||||||
|
# via paramiko
|
||||||
|
black==24.2.0
|
||||||
|
certifi==2024.2.2
|
||||||
|
# via httpcore
|
||||||
|
# via httpx
|
||||||
|
cffi==1.16.0
|
||||||
|
# via cryptography
|
||||||
|
# via pynacl
|
||||||
|
cfgv==3.4.0
|
||||||
|
# via pre-commit
|
||||||
|
chardet==5.2.0
|
||||||
|
# via reportlab
|
||||||
|
click==8.1.7
|
||||||
|
# via black
|
||||||
|
# via typer
|
||||||
|
# via uvicorn
|
||||||
|
colorama==0.4.6
|
||||||
|
# via taskipy
|
||||||
|
cryptography==42.0.3
|
||||||
|
# via paramiko
|
||||||
|
cssselect2==0.7.0
|
||||||
|
# via svglib
|
||||||
|
distlib==0.3.8
|
||||||
|
# via virtualenv
|
||||||
|
distro==1.8.0
|
||||||
|
# via hyperglass
|
||||||
|
fastapi==0.95.1
|
||||||
|
# via hyperglass
|
||||||
|
favicons==0.2.2
|
||||||
|
# via hyperglass
|
||||||
|
filelock==3.13.1
|
||||||
|
# via virtualenv
|
||||||
|
flake8==7.0.0
|
||||||
|
# via pep8-naming
|
||||||
|
freetype-py==2.4.0
|
||||||
|
# via rlpycairo
|
||||||
|
future==0.18.3
|
||||||
|
# via textfsm
|
||||||
|
gunicorn==20.1.0
|
||||||
|
# via hyperglass
|
||||||
|
h11==0.14.0
|
||||||
|
# via httpcore
|
||||||
|
# via uvicorn
|
||||||
|
httpcore==0.17.3
|
||||||
|
# via httpx
|
||||||
|
httpx==0.24.0
|
||||||
|
# via hyperglass
|
||||||
|
identify==2.5.35
|
||||||
|
# via pre-commit
|
||||||
|
idna==3.6
|
||||||
|
# via anyio
|
||||||
|
# via httpx
|
||||||
|
iniconfig==2.0.0
|
||||||
|
# via pytest
|
||||||
|
isort==5.13.2
|
||||||
|
loguru==0.7.0
|
||||||
|
# via hyperglass
|
||||||
|
lxml==5.1.0
|
||||||
|
# via svglib
|
||||||
|
markdown-it-py==3.0.0
|
||||||
|
# via rich
|
||||||
|
mccabe==0.7.0
|
||||||
|
# via flake8
|
||||||
|
mdurl==0.1.2
|
||||||
|
# via markdown-it-py
|
||||||
|
mypy-extensions==1.0.0
|
||||||
|
# via black
|
||||||
|
netmiko==4.1.2
|
||||||
|
# via hyperglass
|
||||||
|
nodeenv==1.8.0
|
||||||
|
# via pre-commit
|
||||||
|
ntc-templates==4.3.0
|
||||||
|
# via netmiko
|
||||||
|
packaging==23.2
|
||||||
|
# via black
|
||||||
|
# via pytest
|
||||||
|
paramiko==3.4.0
|
||||||
|
# via hyperglass
|
||||||
|
# via netmiko
|
||||||
|
# via scp
|
||||||
|
pathspec==0.12.1
|
||||||
|
# via black
|
||||||
|
pbr==6.0.0
|
||||||
|
# via stevedore
|
||||||
|
pep8-naming==0.13.3
|
||||||
|
pillow==10.2.0
|
||||||
|
# via favicons
|
||||||
|
# via hyperglass
|
||||||
|
# via reportlab
|
||||||
|
platformdirs==4.2.0
|
||||||
|
# via black
|
||||||
|
# via virtualenv
|
||||||
|
pluggy==1.4.0
|
||||||
|
# via pytest
|
||||||
|
pre-commit==3.6.2
|
||||||
|
psutil==5.9.4
|
||||||
|
# via hyperglass
|
||||||
|
# via taskipy
|
||||||
|
py-cpuinfo==9.0.0
|
||||||
|
# via hyperglass
|
||||||
|
pycairo==1.26.0
|
||||||
|
# via rlpycairo
|
||||||
|
pycodestyle==2.11.1
|
||||||
|
# via flake8
|
||||||
|
pycparser==2.21
|
||||||
|
# via cffi
|
||||||
|
pydantic==1.10.14
|
||||||
|
# via fastapi
|
||||||
|
# via hyperglass
|
||||||
|
pyflakes==3.2.0
|
||||||
|
# via flake8
|
||||||
|
pygments==2.17.2
|
||||||
|
# via rich
|
||||||
|
pyjwt==2.6.0
|
||||||
|
# via hyperglass
|
||||||
|
pynacl==1.5.0
|
||||||
|
# via paramiko
|
||||||
|
pyserial==3.5
|
||||||
|
# via netmiko
|
||||||
|
pytest==8.0.1
|
||||||
|
# via pytest-asyncio
|
||||||
|
# via pytest-dependency
|
||||||
|
pytest-asyncio==0.23.5
|
||||||
|
pytest-dependency==0.6.0
|
||||||
|
pyyaml==6.0.1
|
||||||
|
# via bandit
|
||||||
|
# via hyperglass
|
||||||
|
# via netmiko
|
||||||
|
# via pre-commit
|
||||||
|
redis==4.5.4
|
||||||
|
# via hyperglass
|
||||||
|
reportlab==4.1.0
|
||||||
|
# via favicons
|
||||||
|
# via svglib
|
||||||
|
rich==13.7.0
|
||||||
|
# via bandit
|
||||||
|
# via favicons
|
||||||
|
# via hyperglass
|
||||||
|
rlpycairo==0.3.0
|
||||||
|
# via favicons
|
||||||
|
ruff==0.2.2
|
||||||
|
scp==0.14.5
|
||||||
|
# via netmiko
|
||||||
|
setuptools==69.1.0
|
||||||
|
# via gunicorn
|
||||||
|
# via netmiko
|
||||||
|
# via nodeenv
|
||||||
|
# via pytest-dependency
|
||||||
|
six==1.16.0
|
||||||
|
# via textfsm
|
||||||
|
sniffio==1.3.0
|
||||||
|
# via anyio
|
||||||
|
# via httpcore
|
||||||
|
# via httpx
|
||||||
|
stackprinter==0.2.11
|
||||||
|
starlette==0.26.1
|
||||||
|
# via fastapi
|
||||||
|
stevedore==5.1.0
|
||||||
|
# via bandit
|
||||||
|
svglib==1.5.1
|
||||||
|
# via favicons
|
||||||
|
taskipy==1.12.2
|
||||||
|
tenacity==8.2.3
|
||||||
|
# via netmiko
|
||||||
|
textfsm==1.1.2
|
||||||
|
# via netmiko
|
||||||
|
# via ntc-templates
|
||||||
|
tinycss2==1.2.1
|
||||||
|
# via cssselect2
|
||||||
|
# via svglib
|
||||||
|
tomli==2.0.1
|
||||||
|
# via taskipy
|
||||||
|
typer==0.9.0
|
||||||
|
# via favicons
|
||||||
|
# via hyperglass
|
||||||
|
typing-extensions==4.9.0
|
||||||
|
# via pydantic
|
||||||
|
# via typer
|
||||||
|
uvicorn==0.21.1
|
||||||
|
# via hyperglass
|
||||||
|
uvloop==0.17.0
|
||||||
|
# via hyperglass
|
||||||
|
virtualenv==20.25.0
|
||||||
|
# via pre-commit
|
||||||
|
webencodings==0.5.1
|
||||||
|
# via cssselect2
|
||||||
|
# via tinycss2
|
||||||
|
xmltodict==0.13.0
|
||||||
|
# via hyperglass
|
||||||
144
requirements.lock
Normal file
144
requirements.lock
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# generated by rye
|
||||||
|
# use `rye lock` or `rye sync` to update this lockfile
|
||||||
|
#
|
||||||
|
# last locked with the following flags:
|
||||||
|
# pre: false
|
||||||
|
# features: []
|
||||||
|
# all-features: false
|
||||||
|
# with-sources: false
|
||||||
|
|
||||||
|
-e file:.
|
||||||
|
aiofiles==23.2.1
|
||||||
|
# via hyperglass
|
||||||
|
anyio==4.3.0
|
||||||
|
# via httpcore
|
||||||
|
# via starlette
|
||||||
|
bcrypt==4.1.2
|
||||||
|
# via paramiko
|
||||||
|
certifi==2024.2.2
|
||||||
|
# via httpcore
|
||||||
|
# via httpx
|
||||||
|
cffi==1.16.0
|
||||||
|
# via cryptography
|
||||||
|
# via pynacl
|
||||||
|
chardet==5.2.0
|
||||||
|
# via reportlab
|
||||||
|
click==8.1.7
|
||||||
|
# via typer
|
||||||
|
# via uvicorn
|
||||||
|
cryptography==42.0.3
|
||||||
|
# via paramiko
|
||||||
|
cssselect2==0.7.0
|
||||||
|
# via svglib
|
||||||
|
distro==1.8.0
|
||||||
|
# via hyperglass
|
||||||
|
fastapi==0.95.1
|
||||||
|
# via hyperglass
|
||||||
|
favicons==0.2.2
|
||||||
|
# via hyperglass
|
||||||
|
freetype-py==2.4.0
|
||||||
|
# via rlpycairo
|
||||||
|
future==0.18.3
|
||||||
|
# via textfsm
|
||||||
|
gunicorn==20.1.0
|
||||||
|
# via hyperglass
|
||||||
|
h11==0.14.0
|
||||||
|
# via httpcore
|
||||||
|
# via uvicorn
|
||||||
|
httpcore==0.17.3
|
||||||
|
# via httpx
|
||||||
|
httpx==0.24.0
|
||||||
|
# via hyperglass
|
||||||
|
idna==3.6
|
||||||
|
# via anyio
|
||||||
|
# via httpx
|
||||||
|
loguru==0.7.0
|
||||||
|
# via hyperglass
|
||||||
|
lxml==5.1.0
|
||||||
|
# via svglib
|
||||||
|
markdown-it-py==3.0.0
|
||||||
|
# via rich
|
||||||
|
mdurl==0.1.2
|
||||||
|
# via markdown-it-py
|
||||||
|
netmiko==4.1.2
|
||||||
|
# via hyperglass
|
||||||
|
ntc-templates==4.3.0
|
||||||
|
# via netmiko
|
||||||
|
paramiko==3.4.0
|
||||||
|
# via hyperglass
|
||||||
|
# via netmiko
|
||||||
|
# via scp
|
||||||
|
pillow==10.2.0
|
||||||
|
# via favicons
|
||||||
|
# via hyperglass
|
||||||
|
# via reportlab
|
||||||
|
psutil==5.9.4
|
||||||
|
# via hyperglass
|
||||||
|
py-cpuinfo==9.0.0
|
||||||
|
# via hyperglass
|
||||||
|
pycairo==1.26.0
|
||||||
|
# via rlpycairo
|
||||||
|
pycparser==2.21
|
||||||
|
# via cffi
|
||||||
|
pydantic==1.10.14
|
||||||
|
# via fastapi
|
||||||
|
# via hyperglass
|
||||||
|
pygments==2.17.2
|
||||||
|
# via rich
|
||||||
|
pyjwt==2.6.0
|
||||||
|
# via hyperglass
|
||||||
|
pynacl==1.5.0
|
||||||
|
# via paramiko
|
||||||
|
pyserial==3.5
|
||||||
|
# via netmiko
|
||||||
|
pyyaml==6.0.1
|
||||||
|
# via hyperglass
|
||||||
|
# via netmiko
|
||||||
|
redis==4.5.4
|
||||||
|
# via hyperglass
|
||||||
|
reportlab==4.1.0
|
||||||
|
# via favicons
|
||||||
|
# via svglib
|
||||||
|
rich==13.7.0
|
||||||
|
# via favicons
|
||||||
|
# via hyperglass
|
||||||
|
rlpycairo==0.3.0
|
||||||
|
# via favicons
|
||||||
|
scp==0.14.5
|
||||||
|
# via netmiko
|
||||||
|
setuptools==69.1.0
|
||||||
|
# via gunicorn
|
||||||
|
# via netmiko
|
||||||
|
six==1.16.0
|
||||||
|
# via textfsm
|
||||||
|
sniffio==1.3.0
|
||||||
|
# via anyio
|
||||||
|
# via httpcore
|
||||||
|
# via httpx
|
||||||
|
starlette==0.26.1
|
||||||
|
# via fastapi
|
||||||
|
svglib==1.5.1
|
||||||
|
# via favicons
|
||||||
|
tenacity==8.2.3
|
||||||
|
# via netmiko
|
||||||
|
textfsm==1.1.2
|
||||||
|
# via netmiko
|
||||||
|
# via ntc-templates
|
||||||
|
tinycss2==1.2.1
|
||||||
|
# via cssselect2
|
||||||
|
# via svglib
|
||||||
|
typer==0.9.0
|
||||||
|
# via favicons
|
||||||
|
# via hyperglass
|
||||||
|
typing-extensions==4.9.0
|
||||||
|
# via pydantic
|
||||||
|
# via typer
|
||||||
|
uvicorn==0.21.1
|
||||||
|
# via hyperglass
|
||||||
|
uvloop==0.17.0
|
||||||
|
# via hyperglass
|
||||||
|
webencodings==0.5.1
|
||||||
|
# via cssselect2
|
||||||
|
# via tinycss2
|
||||||
|
xmltodict==0.13.0
|
||||||
|
# via hyperglass
|
||||||
Loading…
Add table
Reference in a new issue