mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
tooling overhaul
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,6 +16,7 @@ __pycache__/
|
||||
|
||||
# Pyenv
|
||||
.python-version
|
||||
.venv
|
||||
|
||||
# MyPy
|
||||
.mypy_cache
|
||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -3,5 +3,5 @@
|
||||
"eslint.workingDirectories": ["./hyperglass/ui"],
|
||||
"python.linting.mypyEnabled": 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
|
||||
from hyperglass.state import use_state
|
||||
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.
|
||||
init_user_config()
|
||||
|
@@ -11,9 +11,9 @@ from pathlib import Path
|
||||
|
||||
# Project
|
||||
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:
|
||||
# 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"
|
||||
|
||||
try:
|
||||
|
||||
with package_json_file.open("r") as file:
|
||||
package_json = json.load(file)
|
||||
|
||||
@@ -82,7 +81,7 @@ async def node_initial(timeout: int = 180, dev_mode: bool = False) -> str:
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
cmd="yarn --silent --emoji false",
|
||||
cmd="pnpm install",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=ui_path,
|
||||
@@ -178,10 +177,8 @@ def generate_opengraph(
|
||||
log.debug("Copied {} to {}", str(image_path), str(target_path))
|
||||
|
||||
with Image.open(copied) as src:
|
||||
|
||||
# Only resize the image if it needs to be resized
|
||||
if src.size[0] != max_width or src.size[1] != max_height:
|
||||
|
||||
# Resize image while maintaining aspect ratio
|
||||
log.debug("Opengraph image is not 1200x630, resizing...")
|
||||
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"
|
||||
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()
|
||||
|
||||
# Set NextJS production/development mode and base URL based on
|
@@ -52,6 +52,12 @@ HyperglassConsole = Console(
|
||||
"warning": "bold yellow",
|
||||
"error": "bold red",
|
||||
"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)",
|
||||
}
|
||||
)
|
||||
@@ -146,12 +152,13 @@ def init_logger(level: str = "INFO"):
|
||||
|
||||
if sys.stdout.isatty():
|
||||
# Use Rich for logging if hyperglass started from a TTY.
|
||||
|
||||
_loguru_logger.add(
|
||||
sink=RichHandler(
|
||||
console=HyperglassConsole,
|
||||
rich_tracebacks=True,
|
||||
level=level,
|
||||
tracebacks_show_locals=True,
|
||||
tracebacks_show_locals=level == "DEBUG",
|
||||
log_time_format="[%Y%m%d %H:%M:%S]",
|
||||
),
|
||||
format=_FMT_BASIC,
|
||||
|
@@ -12,9 +12,8 @@ from gunicorn.arbiter import Arbiter # type: ignore
|
||||
from gunicorn.app.base import BaseApplication # type: ignore
|
||||
|
||||
# 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 .plugins import InputPluginManager, OutputPluginManager, register_plugin, init_builtin_plugins
|
||||
from .constants import MIN_NODE_VERSION, MIN_PYTHON_VERSION, __version__
|
||||
|
||||
# Ensure the Python version meets the minimum requirements.
|
||||
@@ -34,12 +33,18 @@ if node_major < MIN_NODE_VERSION:
|
||||
from .util import cpu_count
|
||||
from .state import use_state
|
||||
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:
|
||||
"""Perform a UI build prior to starting the application."""
|
||||
from .frontend import build_frontend
|
||||
|
||||
state = use_state()
|
||||
await build_frontend(
|
||||
dev_mode=Settings.dev_mode,
|
||||
@@ -54,6 +59,8 @@ async def build_ui() -> bool:
|
||||
def register_all_plugins() -> None:
|
||||
"""Validate and register configured plugins."""
|
||||
|
||||
from .plugins import register_plugin, init_builtin_plugins
|
||||
|
||||
state = use_state()
|
||||
|
||||
# Register built-in plugins.
|
||||
@@ -78,6 +85,8 @@ def register_all_plugins() -> None:
|
||||
|
||||
def unregister_all_plugins() -> None:
|
||||
"""Unregister all plugins."""
|
||||
from .plugins import InputPluginManager, OutputPluginManager
|
||||
|
||||
for manager in (InputPluginManager, OutputPluginManager):
|
||||
manager().reset()
|
||||
|
||||
@@ -91,7 +100,12 @@ def on_starting(server: "Arbiter") -> None:
|
||||
|
||||
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(
|
||||
"Started hyperglass {} on http://{} with {!s} workers",
|
||||
@@ -141,6 +155,8 @@ class HyperglassWSGI(BaseApplication):
|
||||
def start(*, log_level: str, workers: int, **kwargs) -> None:
|
||||
"""Start hyperglass via gunicorn."""
|
||||
|
||||
from .log import CustomGunicornLogger
|
||||
|
||||
HyperglassWSGI(
|
||||
app="hyperglass.api:app",
|
||||
options={
|
||||
@@ -152,6 +168,7 @@ def start(*, log_level: str, workers: int, **kwargs) -> None:
|
||||
"loglevel": log_level,
|
||||
"bind": Settings.bind(),
|
||||
"on_starting": on_starting,
|
||||
"when_ready": when_ready,
|
||||
"command": shutil.which("gunicorn"),
|
||||
"logger_class": CustomGunicornLogger,
|
||||
"worker_class": "uvicorn.workers.UvicornWorker",
|
||||
@@ -163,21 +180,15 @@ def start(*, log_level: str, workers: int, **kwargs) -> None:
|
||||
|
||||
def run(_workers: int = None):
|
||||
"""Run hyperglass."""
|
||||
try:
|
||||
init_user_config()
|
||||
from .configuration import init_user_config
|
||||
|
||||
try:
|
||||
log.debug("System settings: {!r}", Settings)
|
||||
|
||||
workers, log_level = 1, "DEBUG"
|
||||
init_user_config()
|
||||
|
||||
if Settings.debug is False:
|
||||
workers, log_level = cpu_count(2), "WARNING"
|
||||
workers = 1 if Settings.debug else cpu_count(2)
|
||||
|
||||
if _workers is not None:
|
||||
workers = _workers
|
||||
|
||||
init_logger(log_level)
|
||||
setup_lib_logging(log_level)
|
||||
start(log_level=log_level, workers=workers)
|
||||
except Exception as error:
|
||||
# Handle app exceptions.
|
||||
|
@@ -121,7 +121,6 @@ class Params(ParamsPublic, HyperglassModel):
|
||||
return self.export_dict(
|
||||
include={
|
||||
"cache": {"show_text", "timeout"},
|
||||
"debug": ...,
|
||||
"developer_mode": ...,
|
||||
"primary_asn": ...,
|
||||
"request_timeout": ...,
|
||||
|
@@ -142,7 +142,6 @@ class ConfigPathItem(Path):
|
||||
value = Settings.default_app_path.joinpath(
|
||||
*(p for p in value.parts if p not in Settings.app_path.parts)
|
||||
)
|
||||
print(f"{value=}")
|
||||
return value
|
||||
|
||||
def __repr__(self):
|
||||
|
@@ -43,6 +43,7 @@ class HyperglassSettings(BaseSettings):
|
||||
|
||||
debug: bool = False
|
||||
dev_mode: bool = False
|
||||
disable_ui: bool = False
|
||||
app_path: DirectoryPath = _default_app_path
|
||||
redis_host: str = "localhost"
|
||||
redis_password: t.Optional[SecretStr]
|
||||
@@ -58,7 +59,6 @@ class HyperglassSettings(BaseSettings):
|
||||
super().__init__(**kwargs)
|
||||
if self.container:
|
||||
self.app_path = self.default_app_path
|
||||
print(self)
|
||||
|
||||
def __rich_console__(self, console: "Console", options: "ConsoleOptions") -> "RenderResult":
|
||||
"""Render a Rich table representation of hyperglass settings."""
|
||||
|
1
hyperglass/ui/.gitignore
vendored
1
hyperglass/ui/.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.DS_Store
|
||||
.env*
|
||||
hyperglass.json
|
||||
custom.*[js, html]
|
||||
*.tsbuildinfo
|
||||
# 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,
|
||||
useColorValue,
|
||||
useBreakpointValue,
|
||||
useHyperglassConfig,
|
||||
// useHyperglassConfig,
|
||||
} from '~/hooks';
|
||||
|
||||
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';
|
||||
const tagSize = useBreakpointValue({ base: 'sm', lg: 'lg' }) ?? 'lg';
|
||||
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' }) ?? 'sm';
|
||||
const { refetch } = useHyperglassConfig();
|
||||
// const { refetch } = useHyperglassConfig();
|
||||
return (
|
||||
<>
|
||||
<HStack
|
||||
@@ -92,14 +92,14 @@ export const Debugger = (): JSX.Element => {
|
||||
>
|
||||
View Theme
|
||||
</Button>
|
||||
<Button
|
||||
{/* <Button
|
||||
size={btnSize}
|
||||
colorScheme="purple"
|
||||
leftIcon={<DynamicIcon icon={{ hi: 'HiOutlineDownload' }} />}
|
||||
onClick={() => refetch()}
|
||||
>
|
||||
Reload Config
|
||||
</Button>
|
||||
</Button> */}
|
||||
<Tag size={tagSize} colorScheme="teal">
|
||||
{mediaSize}
|
||||
</Tag>
|
||||
|
@@ -61,7 +61,8 @@ export const Footer = (): JSX.Element => {
|
||||
icon.rightIcon = <DynamicIcon icon={{ go: 'GoLinkExternal' }} />;
|
||||
}
|
||||
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
|
||||
} else if (isMenu(item)) {
|
||||
}
|
||||
if (isMenu(item)) {
|
||||
return (
|
||||
<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' }} />;
|
||||
}
|
||||
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
|
||||
} else if (isMenu(item)) {
|
||||
}
|
||||
if (isMenu(item)) {
|
||||
return (
|
||||
<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.
|
||||
errorBorder
|
||||
: isChecked && !hasError
|
||||
? // Highlight blue when any location is selected and there is no error.
|
||||
checkedBorder
|
||||
: // Otherwise, no border.
|
||||
'transparent',
|
||||
? // Highlight blue when any location is selected and there is no error.
|
||||
checkedBorder
|
||||
: // Otherwise, no border.
|
||||
'transparent',
|
||||
|
||||
[hasError, isChecked, checkedBorder, errorBorder],
|
||||
);
|
||||
|
@@ -103,23 +103,24 @@ export const LookingGlassForm = (): JSX.Element => {
|
||||
const isFqdn = isFqdnQuery(form.queryTarget, directive?.fieldType ?? null);
|
||||
|
||||
if (greetingReady && !isFqdn) {
|
||||
return setStatus('results');
|
||||
setStatus('results');
|
||||
return;
|
||||
}
|
||||
|
||||
if (greetingReady && isFqdn) {
|
||||
setLoading(true);
|
||||
return resolvedOpen();
|
||||
} else {
|
||||
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();
|
||||
resolvedOpen();
|
||||
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();
|
||||
}
|
||||
|
||||
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} />,
|
||||
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(
|
||||
<DynamicIcon
|
||||
icon={{ fa: 'FaChevronRight' }}
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: index makes sense in this case.
|
||||
key={`separator-${i}`}
|
||||
color={color[+active]}
|
||||
boxSize={5}
|
||||
@@ -127,6 +128,7 @@ export const ASPath = (props: ASPathProps): JSX.Element => {
|
||||
/>,
|
||||
);
|
||||
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}`}>
|
||||
{asnStr}
|
||||
</Text>,
|
||||
|
@@ -32,12 +32,14 @@ function buildOptions(devices: DeviceGroup[]): OptionGroup<LocationOption>[] {
|
||||
avatar: loc.avatar,
|
||||
description: loc.description,
|
||||
},
|
||||
} as SingleOption),
|
||||
}) as SingleOption,
|
||||
)
|
||||
.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 => {
|
||||
@@ -58,7 +60,8 @@ export const QueryLocation = (props: QueryLocationProps): JSX.Element => {
|
||||
const element = useMemo(() => {
|
||||
if (locationDisplayMode === 'dropdown') {
|
||||
return 'select';
|
||||
} else if (locationDisplayMode === 'gallery') {
|
||||
}
|
||||
if (locationDisplayMode === 'gallery') {
|
||||
return 'cards';
|
||||
}
|
||||
const groups = options.length;
|
||||
@@ -159,7 +162,8 @@ export const QueryLocation = (props: QueryLocationProps): JSX.Element => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else if (element === 'select') {
|
||||
}
|
||||
if (element === 'select') {
|
||||
return (
|
||||
<Select<LocationOption, true>
|
||||
isMulti
|
||||
|
@@ -60,10 +60,7 @@ function useOptions() {
|
||||
const filtered = useFormState(s => s.filtered);
|
||||
return useMemo((): OptionsOrGroup<QueryTypeOption> => {
|
||||
const groupNames = new Set(
|
||||
filtered.types
|
||||
.filter(t => t.groups.length > 0)
|
||||
.map(t => t.groups)
|
||||
.flat(),
|
||||
filtered.types.filter(t => t.groups.length > 0).flatMap(t => t.groups),
|
||||
);
|
||||
const optGroups: OptionGroup<QueryTypeOption>[] = Array.from(groupNames).map(group => ({
|
||||
label: group,
|
||||
|
@@ -1,14 +1,14 @@
|
||||
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
||||
/* eslint @typescript-eslint/explicit-module-boundary-types: 0 */
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: type guard
|
||||
export function isStackError(error: any): error is 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 {
|
||||
return typeof error !== 'undefined' && error !== null && 'statusText' in error;
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: type guard
|
||||
export function isLGError(error: any): error is QueryResponse {
|
||||
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.
|
||||
*/
|
||||
// biome-ignore lint/suspicious/noExplicitAny: type guard
|
||||
export function isLGOutputOrError(data: any): data is QueryResponse {
|
||||
return typeof data !== 'undefined' && data !== null && data?.level !== 'success';
|
||||
}
|
||||
|
@@ -107,17 +107,20 @@ const _Result: React.ForwardRefRenderFunction<HTMLDivElement, ResultProps> = (
|
||||
const errorMsg = useMemo(() => {
|
||||
if (isLGError(error)) {
|
||||
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]);
|
||||
|
||||
const errorLevel = useMemo<ErrorLevels>(() => {
|
||||
|
@@ -45,8 +45,8 @@ export const useControlStyle = <Opt extends SingleOption, IsMulti extends boolea
|
||||
boxShadow: isError
|
||||
? `0 0 0 1px ${invalidBorder}`
|
||||
: isFocused
|
||||
? `0 0 0 1px ${focusBorder}`
|
||||
: undefined,
|
||||
? `0 0 0 1px ${focusBorder}`
|
||||
: undefined,
|
||||
'&:hover': { borderColor: isFocused ? focusBorder : borderHover },
|
||||
'&:hover > div > span': { backgroundColor: borderHover },
|
||||
'&:focus': { borderColor: isError ? invalidBorder : focusBorder },
|
||||
|
@@ -30,7 +30,7 @@ export const TableRow = (props: TableRowProps): JSX.Element => {
|
||||
{ borderTop: '1px', borderTopColor: 'blackAlpha.100' },
|
||||
{ borderTop: '1px', borderTopColor: 'whiteAlpha.100' },
|
||||
);
|
||||
let bg;
|
||||
let bg = undefined;
|
||||
|
||||
if (highlight) {
|
||||
bg = `${String(highlightBg)}.${alpha}`;
|
||||
|
@@ -6,6 +6,7 @@
|
||||
export const CustomJavascript = (props: React.PropsWithChildren<Dict>): JSX.Element => {
|
||||
const { children } = props;
|
||||
if (typeof children === 'string' && children !== '') {
|
||||
// biome-ignore lint/security/noDangerouslySetInnerHtml: required for injecting custom JS
|
||||
return <script id="custom-javascript" dangerouslySetInnerHTML={{ __html: children }} />;
|
||||
}
|
||||
return <></>;
|
||||
@@ -19,6 +20,7 @@ export const CustomJavascript = (props: React.PropsWithChildren<Dict>): JSX.Elem
|
||||
export const CustomHtml = (props: React.PropsWithChildren<Dict>): JSX.Element => {
|
||||
const { children } = props;
|
||||
if (typeof children === 'string' && children !== '') {
|
||||
// biome-ignore lint/security/noDangerouslySetInnerHtml: required for injecting custom HTML
|
||||
return <div id="custom-html" dangerouslySetInnerHTML={{ __html: children }} />;
|
||||
}
|
||||
return <></>;
|
||||
|
@@ -47,7 +47,7 @@ class IconError extends Error {
|
||||
this.original = original;
|
||||
this.library = library;
|
||||
this.iconName = iconName;
|
||||
this.stack = this.stack + `\nOriginal object: '${JSON.stringify(this.original)}'`;
|
||||
this.stack += `\nOriginal object: '${JSON.stringify(this.original)}'`;
|
||||
}
|
||||
|
||||
get message(): string {
|
||||
|
@@ -68,7 +68,7 @@ type MDProps = {
|
||||
node: Dict;
|
||||
};
|
||||
|
||||
/* eslint @typescript-eslint/no-explicit-any: off */
|
||||
// biome-ignore lint/suspicious/noExplicitAny: reasons!
|
||||
function hasNode<C>(p: any): p is C & MDProps {
|
||||
return 'node' in p;
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { useBooleanValue } from './use-boolean-value';
|
||||
|
@@ -11,8 +11,7 @@ export function useBooleanValue<T extends unknown, F extends unknown>(
|
||||
return useMemo(() => {
|
||||
if (status) {
|
||||
return ifTrue;
|
||||
} else {
|
||||
return ifFalse;
|
||||
}
|
||||
return ifFalse;
|
||||
}, [status, ifTrue, ifFalse]);
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { useDevice } from './use-device';
|
||||
|
@@ -14,10 +14,7 @@ export type UseDeviceReturn = (
|
||||
export function useDevice(): UseDeviceReturn {
|
||||
const { devices } = useConfig();
|
||||
|
||||
const locations = useMemo<Device[]>(
|
||||
() => devices.map(group => group.locations).flat(),
|
||||
[devices],
|
||||
);
|
||||
const locations = useMemo<Device[]>(() => devices.flatMap(group => group.locations), [devices]);
|
||||
|
||||
function getDevice(id: string): Nullable<Device> {
|
||||
return locations.find(device => device.id === id) ?? null;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import 'isomorphic-fetch';
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import '@testing-library/jest-dom';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
|
||||
@@ -16,7 +17,7 @@ const CloudflareWrapper = (props: React.PropsWithChildren<Dict<JSX.Element>>) =>
|
||||
cache: { timeout: 120 },
|
||||
|
||||
web: { dnsProvider: { url: 'https://cloudflare-dns.com/dns-query' } },
|
||||
} as jest.Mocked<Config>;
|
||||
} as Config;
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<HyperglassContext.Provider value={config} {...props} />
|
||||
@@ -28,7 +29,7 @@ const GoogleWrapper = (props: React.PropsWithChildren<Dict<JSX.Element>>) => {
|
||||
const config = {
|
||||
cache: { timeout: 120 },
|
||||
web: { dnsProvider: { url: 'https://dns.google/resolve' } },
|
||||
} as jest.Mocked<Config>;
|
||||
} as Config;
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<HyperglassContext.Provider value={config} {...props} />
|
||||
|
@@ -21,7 +21,7 @@ const query: QueryFunction<DnsOverHttps.Response, DNSQueryKey> = async (
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
let json;
|
||||
let json = undefined;
|
||||
const type = family === 4 ? 'A' : family === 6 ? 'AAAA' : '';
|
||||
|
||||
if (url !== null) {
|
||||
|
@@ -61,10 +61,7 @@ interface FormStateType<Opt extends SingleOption = SingleOption> {
|
||||
setSelection<
|
||||
Opt extends SingleOption,
|
||||
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;
|
||||
getDirective(): Directive | null;
|
||||
reset(): void;
|
||||
@@ -155,7 +152,7 @@ const formState: StateCreator<FormStateType> = (set, get) => ({
|
||||
|
||||
// Determine all unique group names.
|
||||
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.
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import '@testing-library/jest-dom';
|
||||
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';
|
||||
|
||||
const TRUE = JSON.stringify(true);
|
||||
@@ -26,16 +27,16 @@ const TestComponent = (): JSX.Element => {
|
||||
Close
|
||||
</button>
|
||||
<button id="ack-false-required" type="button" onClick={() => ack(false, true)}>
|
||||
{`Don't acknowledge, is required`}
|
||||
Don't acknowledge, is required
|
||||
</button>
|
||||
<button id="ack-true-required" type="button" onClick={() => ack(true, true)}>
|
||||
{`Acknowledge, is required`}
|
||||
Acknowledge, is required
|
||||
</button>
|
||||
<button id="ack-false-not-required" type="button" onClick={() => ack(false, false)}>
|
||||
{`Don't Acknowledge, not required`}
|
||||
Don't Acknowledge, not required
|
||||
</button>
|
||||
<button id="ack-true-not-required" type="button" onClick={() => ack(true, false)}>
|
||||
{`Acknowledge, not required`}
|
||||
Acknowledge, not required
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
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 { useOpposingColor } from './use-opposing-color';
|
||||
|
||||
@@ -32,17 +33,17 @@ describe('useOpposingColor Hook', () => {
|
||||
const test1 = container.querySelector('#test1');
|
||||
const test2 = container.querySelector('#test2');
|
||||
|
||||
expect(test1).toHaveStyle('color: black;');
|
||||
expect(test2).toHaveStyle('color: black;');
|
||||
expect(test1).toHaveStyle('color: rgb(0, 0, 0);');
|
||||
expect(test2).toHaveStyle('color: rgb(0, 0, 0);');
|
||||
|
||||
await userEvent.click(getByRole('button'));
|
||||
|
||||
expect(test1).toHaveStyle('color: white;');
|
||||
expect(test2).toHaveStyle('color: white;');
|
||||
expect(test1).toHaveStyle('color: rgb(255, 255, 255);');
|
||||
expect(test2).toHaveStyle('color: rgb(255, 255, 255);');
|
||||
|
||||
await userEvent.click(getByRole('button'));
|
||||
|
||||
expect(test1).toHaveStyle('color: black;');
|
||||
expect(test2).toHaveStyle('color: black;');
|
||||
expect(test1).toHaveStyle('color: rgb(0, 0, 0);');
|
||||
expect(test2).toHaveStyle('color: rgb(0, 0, 0);');
|
||||
});
|
||||
});
|
||||
|
@@ -22,12 +22,13 @@ export function useIsDarkCallback(): UseIsDarkCallbackReturn {
|
||||
const theme = useTheme();
|
||||
return useCallback(
|
||||
(color: string): boolean => {
|
||||
let opposing = color;
|
||||
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;
|
||||
try {
|
||||
opposingShouldBeDark = isLight(color)(theme);
|
||||
opposingShouldBeDark = isLight(opposing)(theme);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@@ -46,9 +47,8 @@ export function useOpposingColor(color: string, options?: OpposingColorOptions):
|
||||
return useMemo(() => {
|
||||
if (isBlack) {
|
||||
return options?.dark ?? 'black';
|
||||
} else {
|
||||
return options?.light ?? 'white';
|
||||
}
|
||||
return options?.light ?? 'white';
|
||||
}, [isBlack, options?.dark, options?.light]);
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { useStrf } from './use-strf';
|
||||
|
@@ -29,7 +29,7 @@ function formatAsPath(path: number[]): string {
|
||||
|
||||
function formatCommunities(comms: string[]): string {
|
||||
const commsStr = comms.map(c => ` - ${c}`);
|
||||
return '\n' + commsStr.join('\n');
|
||||
return `\n ${commsStr.join('\n')}`;
|
||||
}
|
||||
|
||||
function formatBool(val: boolean): string {
|
||||
@@ -85,9 +85,8 @@ export function useTableToString(
|
||||
function getFmtFunc(accessor: keyof Route): TableToStringFormatter {
|
||||
if (isFormatted(accessor)) {
|
||||
return tableFormatMap[accessor];
|
||||
} else {
|
||||
return String;
|
||||
}
|
||||
return 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 = {
|
||||
'/api/query/': {
|
||||
target: process.env.HYPERGLASS_URL + 'api/query/',
|
||||
target: `${process.env.HYPERGLASS_URL}api/query/`,
|
||||
pathRewrite: { '^/api/query/': '' },
|
||||
},
|
||||
'/ui/props/': {
|
||||
target: process.env.HYPERGLASS_URL + 'ui/props/',
|
||||
target: `${process.env.HYPERGLASS_URL}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.
|
||||
if (dev) {
|
||||
// biome-ignore lint/complexity/noForEach: not messing with Next's example code.
|
||||
Object.keys(devProxy).forEach(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",
|
||||
"name": "ui",
|
||||
"description": "UI for hyperglass, the modern network looking glass",
|
||||
"author": "Matt Love",
|
||||
"license": "BSD-3-Clause-Clear",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts --ext .tsx",
|
||||
"dev": "node nextdev",
|
||||
"start": "next start",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"format": "prettier --config ./.prettierrc -c -w .",
|
||||
"format:check": "prettier --config ./.prettierrc -c .",
|
||||
"build": "export NODE_OPTIONS=--openssl-legacy-provider; next build && next export -o ../hyperglass/static/ui",
|
||||
"test": "jest"
|
||||
},
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^2.5.5",
|
||||
"@chakra-ui/theme": "3.0.1",
|
||||
"@chakra-ui/theme-tools": "^2.0.17",
|
||||
"@chakra-ui/utils": "^2.0.15",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@hookform/devtools": "^4.3.0",
|
||||
"@hookform/resolvers": "^2.9.10",
|
||||
"@tanstack/react-query": "^4.22.0",
|
||||
"dagre": "^0.8.5",
|
||||
"dayjs": "^1.10.4",
|
||||
"framer-motion": "^10.11.6",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-anything": "^4.0.1",
|
||||
"next": "12.3.4",
|
||||
"palette-by-numbers": "^0.1.6",
|
||||
"plur": "^4.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-countdown": "^2.3.0",
|
||||
"react-device-detect": "^1.15.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-fast-compare": "^3.2.1",
|
||||
"react-flow-renderer": "^10.3.17",
|
||||
"react-hook-form": "^7.42.1",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-if": "^4.1.4",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-select": "^5.7.0",
|
||||
"react-string-replace": "^0.5.0",
|
||||
"react-table": "^7.7.0",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"string-format": "^2.0.0",
|
||||
"vest": "^3.2.8",
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/dagre": "^0.7.44",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/lodash": "^4.14.177",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/react": "^18.0.35",
|
||||
"@types/react-table": "^7.7.1",
|
||||
"@types/string-format": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.31.0",
|
||||
"@typescript-eslint/parser": "^4.31.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^27.2.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-typescript": "^2.4.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.25.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"express": "^4.17.1",
|
||||
"http-proxy-middleware": "0.20.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jest": "^27.2.1",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier-eslint": "^13.0.0",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"type-fest": "^3.8.0",
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
"version": "2.0.0-dev",
|
||||
"name": "ui",
|
||||
"description": "UI for hyperglass, the modern network looking glass",
|
||||
"author": "Matt Love",
|
||||
"license": "BSD-3-Clause-Clear",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "biome lint .",
|
||||
"dev": "node nextdev",
|
||||
"start": "next start",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome format .",
|
||||
"build": "export NODE_OPTIONS=--openssl-legacy-provider; next build && next export -o ../hyperglass/static/ui",
|
||||
"test": "vitest --run"
|
||||
},
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^2.5.5",
|
||||
"@chakra-ui/theme": "3.0.1",
|
||||
"@chakra-ui/theme-tools": "^2.0.17",
|
||||
"@chakra-ui/utils": "^2.0.15",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@hookform/devtools": "^4.3.0",
|
||||
"@hookform/resolvers": "^2.9.10",
|
||||
"@tanstack/react-query": "^4.22.0",
|
||||
"dagre": "^0.8.5",
|
||||
"dayjs": "^1.10.4",
|
||||
"framer-motion": "^10.11.6",
|
||||
"lodash": "^4.17.21",
|
||||
"merge-anything": "^4.0.1",
|
||||
"next": "13.5.6",
|
||||
"palette-by-numbers": "^0.1.6",
|
||||
"plur": "^4.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-countdown": "^2.3.0",
|
||||
"react-device-detect": "^1.15.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-fast-compare": "^3.2.1",
|
||||
"react-flow-renderer": "^10.3.17",
|
||||
"react-hook-form": "^7.42.1",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-if": "^4.1.4",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-select": "^5.7.0",
|
||||
"react-string-replace": "^0.5.0",
|
||||
"react-table": "^7.7.0",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"string-format": "^2.0.0",
|
||||
"vest": "^3.2.8",
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.5.3",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.2.1",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/dagre": "^0.7.44",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/lodash": "^4.14.177",
|
||||
"@types/node": "^20.11.20",
|
||||
"@types/react": "^18.2.60",
|
||||
"@types/react-table": "^7.7.1",
|
||||
"@types/string-format": "^2.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||
"@typescript-eslint/parser": "^7.1.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/ui": "^1.3.1",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"express": "^4.18.2",
|
||||
"http-proxy-middleware": "2.0.6",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-eslint": "^16.3.0",
|
||||
"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 { Switch, Case, Default } from 'react-if';
|
||||
import { Meta, Layout } from '~/components';
|
||||
import { HyperglassProvider } from '~/context';
|
||||
import { LoadError, Loading } from '~/elements';
|
||||
import { useHyperglassConfig } from '~/hooks';
|
||||
import * as config from '../hyperglass.json';
|
||||
|
||||
import type { AppProps } from 'next/app';
|
||||
import type { Config } from '~/types';
|
||||
|
||||
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 { Component, pageProps } = props;
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppComponent {...props} />
|
||||
<HyperglassProvider config={config as unknown as Config}>
|
||||
<Meta />
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</HyperglassProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
@@ -2,8 +2,9 @@ import fs from 'fs';
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
||||
import { ColorModeScript } from '@chakra-ui/react';
|
||||
import { CustomJavascript, CustomHtml, Favicon } from '~/elements';
|
||||
import { getHyperglassConfig, googleFontUrl } from '~/util';
|
||||
import { googleFontUrl } from '~/util';
|
||||
import favicons from '../favicon-formats';
|
||||
import config from '../hyperglass.json';
|
||||
|
||||
import type { DocumentContext, DocumentInitialProps } from 'next/document';
|
||||
import type { ThemeConfig } from '~/types';
|
||||
@@ -18,8 +19,8 @@ interface DocumentExtra
|
||||
class MyDocument extends Document<DocumentExtra> {
|
||||
static async getInitialProps(ctx: DocumentContext): Promise<DocumentExtra> {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
let customJs = '',
|
||||
customHtml = '';
|
||||
let customJs = '';
|
||||
let customHtml = '';
|
||||
|
||||
if (fs.existsSync('custom.js')) {
|
||||
customJs = fs.readFileSync('custom.js').toString();
|
||||
@@ -31,18 +32,18 @@ class MyDocument extends Document<DocumentExtra> {
|
||||
let fonts = { body: '', mono: '' };
|
||||
let defaultColorMode: 'light' | 'dark' | null = null;
|
||||
|
||||
const hyperglassUrl = process.env.HYPERGLASS_URL ?? '';
|
||||
const {
|
||||
web: {
|
||||
theme: { fonts: themeFonts, defaultColorMode: themeDefaultColorMode },
|
||||
},
|
||||
} = await getHyperglassConfig(hyperglassUrl);
|
||||
// const hyperglassUrl = process.env.HYPERGLASS_URL ?? '';
|
||||
// const {
|
||||
// web: {
|
||||
// theme: { fonts: themeFonts, defaultColorMode: themeDefaultColorMode },
|
||||
// },
|
||||
// } = await getHyperglassConfig(hyperglassUrl);
|
||||
|
||||
fonts = {
|
||||
body: googleFontUrl(themeFonts.body),
|
||||
mono: googleFontUrl(themeFonts.mono),
|
||||
body: googleFontUrl(config.web.theme.fonts.body),
|
||||
mono: googleFontUrl(config.web.theme.fonts.mono),
|
||||
};
|
||||
defaultColorMode = themeDefaultColorMode;
|
||||
defaultColorMode = config.web.theme.defaultColorMode;
|
||||
|
||||
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 href={this.props.fonts.mono} rel="stylesheet" />
|
||||
<link href={this.props.fonts.body} rel="stylesheet" />
|
||||
{favicons.map((favicon, idx) => (
|
||||
<Favicon key={idx} {...favicon} />
|
||||
{favicons.map(favicon => (
|
||||
<Favicon key={JSON.stringify(favicon)} {...favicon} />
|
||||
))}
|
||||
<CustomJavascript>{this.props.customJs}</CustomJavascript>
|
||||
</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",
|
||||
"types/*.d.ts",
|
||||
"next.config.js",
|
||||
"nextdev.js"
|
||||
"nextdev.js",
|
||||
"hyperglass.json"
|
||||
]
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { Theme } from './theme';
|
||||
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];
|
||||
|
||||
@@ -92,19 +92,17 @@ interface _Web {
|
||||
links: _Link[];
|
||||
menus: _Menu[];
|
||||
greeting: _Greeting;
|
||||
help_menu: { enable: boolean; title: string };
|
||||
logo: _Logo;
|
||||
terms: { enable: boolean; title: string };
|
||||
text: _Text;
|
||||
theme: _ThemeConfig;
|
||||
location_display_mode: 'auto' | 'gallery' | 'dropdown';
|
||||
location_display_mode: 'auto' | 'gallery' | 'dropdown' | string;
|
||||
highlight: _Highlight[];
|
||||
}
|
||||
|
||||
type _DirectiveBase = {
|
||||
id: string;
|
||||
name: string;
|
||||
field_type: 'text' | 'select' | null;
|
||||
field_type: 'text' | 'select' | null | string;
|
||||
description: string;
|
||||
groups: string[];
|
||||
info: string | null;
|
||||
@@ -125,7 +123,7 @@ type _Directive = _DirectiveBase | _DirectiveSelect;
|
||||
interface _Device {
|
||||
id: string;
|
||||
name: string;
|
||||
group: string;
|
||||
group: string | null;
|
||||
avatar: string | null;
|
||||
directives: _Directive[];
|
||||
description: string | null;
|
||||
@@ -144,7 +142,7 @@ interface _Cache {
|
||||
type _Config = _ConfigDeep & _ConfigShallow;
|
||||
|
||||
interface _DeviceGroup {
|
||||
group: string;
|
||||
group: string | null;
|
||||
locations: _Device[];
|
||||
}
|
||||
|
||||
@@ -157,7 +155,6 @@ interface _ConfigDeep {
|
||||
}
|
||||
|
||||
interface _ConfigShallow {
|
||||
debug: boolean;
|
||||
developer_mode: boolean;
|
||||
primary_asn: string;
|
||||
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 {
|
||||
hyperglass: { favicons: import('./config').Favicon[]; version: string };
|
||||
buildId: string;
|
||||
UI_PARAMS: import('./config').Config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'hyperglass.json' {
|
||||
type Config = import('./config').Config;
|
||||
export default Config;
|
||||
}
|
||||
|
||||
declare module 'react' {
|
||||
// Enable generic typing with forwardRef.
|
||||
// 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';
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
@@ -35,6 +35,7 @@ export function entries<O, K extends keyof O = keyof O>(obj: O): [K, O[K]][] {
|
||||
*/
|
||||
export async function fetchWithTimeout(
|
||||
uri: string,
|
||||
// biome-ignore lint/style/useDefaultParameterLast: goal is to match the fetch API as closely as possible.
|
||||
options: RequestInit = {},
|
||||
timeout: number,
|
||||
controller: AbortController,
|
||||
@@ -69,9 +70,8 @@ export function dedupObjectArray<E extends Record<string, unknown>, P extends ke
|
||||
|
||||
if (!x) {
|
||||
return acc.concat([current]);
|
||||
} else {
|
||||
return acc;
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,7 @@ export class ConfigLoadError extends Error {
|
||||
constructor(detail?: string) {
|
||||
super();
|
||||
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}'`;
|
||||
console.error(this);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ export async function getHyperglassConfig(url?: QueryFunctionContext | string):
|
||||
let fetchUrl = '/ui/props/';
|
||||
|
||||
if (typeof url === 'string') {
|
||||
fetchUrl = url.replace(/(^\/)|(\/$)/g, '') + '/ui/props/';
|
||||
fetchUrl = `${url.replace(/(^\/)|(\/$)/g, '')}/ui/props`;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
|
@@ -8,7 +8,6 @@ import type { StateCreator, SetState, GetState, StoreApi } from 'zustand';
|
||||
* @param store zustand store function.
|
||||
* @param name Store name.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export function withDev<T extends object = {}>(
|
||||
store: StateCreator<T>,
|
||||
name: string,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { googleFontUrl } from './theme';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
describe('google font URL generation', () => {
|
||||
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,
|
||||
)
|
||||
from .typing import is_type, is_series
|
||||
from .frontend import build_ui, build_frontend
|
||||
from .validation import get_driver, resolve_hostname, validate_platform
|
||||
from .system_info import cpu_count, check_python, get_system_info, get_node_version
|
||||
|
||||
__all__ = (
|
||||
"at_least",
|
||||
"build_frontend",
|
||||
"build_ui",
|
||||
# "build_frontend",
|
||||
# "build_ui",
|
||||
"check_path",
|
||||
"check_python",
|
||||
"compare_dicts",
|
||||
|
135
pyproject.toml
135
pyproject.toml
@@ -1,70 +1,64 @@
|
||||
|
||||
[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"
|
||||
[project]
|
||||
name = "hyperglass"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/thatmattlove/hyperglass"
|
||||
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"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
Pillow = "^9.5.0"
|
||||
PyJWT = "^2.6.0"
|
||||
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"
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
bandit = "^1.7.4"
|
||||
black = "^22.12.0"
|
||||
isort = "^5.10.1"
|
||||
pep8-naming = "^0.13.2"
|
||||
pre-commit = "^2.20.0"
|
||||
pytest = "^7.2.0"
|
||||
pytest-asyncio = "^0.20.3"
|
||||
pytest-dependency = "^0.5.1"
|
||||
ruff = "^0.0.261"
|
||||
stackprinter = "^0.2.10"
|
||||
taskipy = "^1.10.3"
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"bandit>=1.7.7",
|
||||
"black>=24.2.0",
|
||||
"isort>=5.13.2",
|
||||
"pep8-naming>=0.13.3",
|
||||
"pre-commit>=3.6.1",
|
||||
"pytest>=8.0.1",
|
||||
"pytest-asyncio>=0.23.5",
|
||||
"pytest-dependency>=0.6.0",
|
||||
"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]
|
||||
line-length = 100
|
||||
@@ -85,13 +79,6 @@ multi_line_output = 3
|
||||
profile = "black"
|
||||
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]
|
||||
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())'"}
|
||||
@@ -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"}
|
||||
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-dev = {cmd = "yarn --cwd ./hyperglass/ui/ dev", help = "Start the Next.JS dev server"}
|
||||
ui-format = {cmd = "yarn --cwd ./hyperglass/ui/ format", help = "Run Prettier"}
|
||||
ui-lint = {cmd = "yarn --cwd ./hyperglass/ui/ lint", help = "Run ESLint"}
|
||||
ui-typecheck = {cmd = "yarn --cwd ./hyperglass/ui/ typecheck", help = "Run TypeScript Check"}
|
||||
ui-dev = {cmd = "pnpm run --dir ./hyperglass/ui/ dev", help = "Start the Next.JS dev server"}
|
||||
ui-format = {cmd = "pnpm run --dir ./hyperglass/ui/ format", help = "Run Prettier"}
|
||||
ui-lint = {cmd = "pnpm run --dir ./hyperglass/ui/ lint", help = "Run ESLint"}
|
||||
ui-typecheck = {cmd = "pnpm run --dir ./hyperglass/ui/ typecheck", help = "Run TypeScript Check"}
|
||||
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]
|
||||
exclude = [
|
||||
@@ -148,7 +135,7 @@ convention = "pep257"
|
||||
[tool.ruff.mccabe]
|
||||
max-complexity = 10
|
||||
|
||||
[tool.ruff.per-file-ignores]
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"hyperglass/main.py" = ["E402"]
|
||||
# Disable classmethod warning for validator decorat
|
||||
"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
|
Reference in New Issue
Block a user