From cd6bf7a162b56cc1645701d3260307428cda3c61 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Tue, 27 Feb 2024 17:44:19 -0500 Subject: [PATCH] tooling overhaul --- .gitignore | 1 + .vscode/settings.json | 2 +- hyperglass/cli/util.py | 2 +- .../frontend.py => frontend/__init__.py} | 13 +- hyperglass/log.py | 9 +- hyperglass/main.py | 41 +- hyperglass/models/config/params.py | 1 - hyperglass/models/fields.py | 1 - hyperglass/models/system.py | 2 +- .../{.eslintignore => .eslintignore-delete} | 0 .../ui/{.eslintrc.js => .eslintrc-delete.js} | 0 hyperglass/ui/.gitignore | 1 + hyperglass/ui/biome.json | 40 + hyperglass/ui/components/debugger.tsx | 8 +- hyperglass/ui/components/footer/footer.tsx | 6 +- hyperglass/ui/components/location-card.tsx | 8 +- .../ui/components/looking-glass-form.tsx | 25 +- hyperglass/ui/components/output/cell.tsx | 2 +- hyperglass/ui/components/output/fields.tsx | 2 + hyperglass/ui/components/query-location.tsx | 14 +- hyperglass/ui/components/query-type.tsx | 5 +- hyperglass/ui/components/results/guards.ts | 7 +- .../ui/components/results/individual.tsx | 23 +- hyperglass/ui/components/select/styles.ts | 4 +- hyperglass/ui/components/table/row.tsx | 2 +- hyperglass/ui/elements/custom.tsx | 2 + hyperglass/ui/elements/dynamic-icon.tsx | 2 +- hyperglass/ui/elements/markdown/elements.tsx | 2 +- .../ui/hooks/use-boolean-value.test.tsx | 1 + hyperglass/ui/hooks/use-boolean-value.ts | 3 +- hyperglass/ui/hooks/use-device.test.tsx | 1 + hyperglass/ui/hooks/use-device.ts | 5 +- hyperglass/ui/hooks/use-dns-query.test.tsx | 5 +- hyperglass/ui/hooks/use-dns-query.ts | 2 +- hyperglass/ui/hooks/use-form-state.ts | 7 +- hyperglass/ui/hooks/use-greeting.test.tsx | 11 +- .../ui/hooks/use-opposing-color.test.tsx | 15 +- hyperglass/ui/hooks/use-opposing-color.ts | 8 +- hyperglass/ui/hooks/use-strf.test.tsx | 1 + hyperglass/ui/hooks/use-table-to-string.ts | 5 +- hyperglass/ui/jest.config.js | 31 - hyperglass/ui/jest.setup.js | 1 - hyperglass/ui/nextdev.js | 7 +- hyperglass/ui/package.json | 181 +- hyperglass/ui/pages/_app.tsx | 40 +- hyperglass/ui/pages/_document.tsx | 29 +- hyperglass/ui/pnpm-lock.yaml | 5728 ++++++++--------- hyperglass/ui/tsconfig.json | 3 +- hyperglass/ui/types/config.ts | 13 +- hyperglass/ui/types/globals.d.ts | 6 + hyperglass/ui/util/common.test.ts | 3 + hyperglass/ui/util/common.ts | 4 +- hyperglass/ui/util/config.ts | 4 +- hyperglass/ui/util/state.ts | 1 - hyperglass/ui/util/theme.test.ts | 1 + hyperglass/ui/vitest.config.ts | 18 + hyperglass/util/__init__.py | 5 +- pyproject.toml | 135 +- requirements-dev.lock | 206 + requirements.lock | 144 + 60 files changed, 3310 insertions(+), 3539 deletions(-) rename hyperglass/{util/frontend.py => frontend/__init__.py} (97%) rename hyperglass/ui/{.eslintignore => .eslintignore-delete} (100%) rename hyperglass/ui/{.eslintrc.js => .eslintrc-delete.js} (100%) create mode 100644 hyperglass/ui/biome.json delete mode 100644 hyperglass/ui/jest.config.js delete mode 100644 hyperglass/ui/jest.setup.js create mode 100644 hyperglass/ui/vitest.config.ts create mode 100644 requirements-dev.lock create mode 100644 requirements.lock diff --git a/.gitignore b/.gitignore index d8433e6..4afa7c3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ __pycache__/ # Pyenv .python-version +.venv # MyPy .mypy_cache diff --git a/.vscode/settings.json b/.vscode/settings.json index b4759f4..18ce793 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" } diff --git a/hyperglass/cli/util.py b/hyperglass/cli/util.py index d602d33..f35fec6 100644 --- a/hyperglass/cli/util.py +++ b/hyperglass/cli/util.py @@ -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() diff --git a/hyperglass/util/frontend.py b/hyperglass/frontend/__init__.py similarity index 97% rename from hyperglass/util/frontend.py rename to hyperglass/frontend/__init__.py index 11b8580..9996058 100644 --- a/hyperglass/util/frontend.py +++ b/hyperglass/frontend/__init__.py @@ -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 diff --git a/hyperglass/log.py b/hyperglass/log.py index 06a426a..11c859f 100644 --- a/hyperglass/log.py +++ b/hyperglass/log.py @@ -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, diff --git a/hyperglass/main.py b/hyperglass/main.py index dba642f..3002f3a 100644 --- a/hyperglass/main.py +++ b/hyperglass/main.py @@ -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. diff --git a/hyperglass/models/config/params.py b/hyperglass/models/config/params.py index c64ac56..7c62aa7 100644 --- a/hyperglass/models/config/params.py +++ b/hyperglass/models/config/params.py @@ -121,7 +121,6 @@ class Params(ParamsPublic, HyperglassModel): return self.export_dict( include={ "cache": {"show_text", "timeout"}, - "debug": ..., "developer_mode": ..., "primary_asn": ..., "request_timeout": ..., diff --git a/hyperglass/models/fields.py b/hyperglass/models/fields.py index 2944d5a..2b54cc9 100644 --- a/hyperglass/models/fields.py +++ b/hyperglass/models/fields.py @@ -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): diff --git a/hyperglass/models/system.py b/hyperglass/models/system.py index 84ea94e..b7088e5 100644 --- a/hyperglass/models/system.py +++ b/hyperglass/models/system.py @@ -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.""" diff --git a/hyperglass/ui/.eslintignore b/hyperglass/ui/.eslintignore-delete similarity index 100% rename from hyperglass/ui/.eslintignore rename to hyperglass/ui/.eslintignore-delete diff --git a/hyperglass/ui/.eslintrc.js b/hyperglass/ui/.eslintrc-delete.js similarity index 100% rename from hyperglass/ui/.eslintrc.js rename to hyperglass/ui/.eslintrc-delete.js diff --git a/hyperglass/ui/.gitignore b/hyperglass/ui/.gitignore index 3e347d9..e544177 100644 --- a/hyperglass/ui/.gitignore +++ b/hyperglass/ui/.gitignore @@ -1,5 +1,6 @@ .DS_Store .env* +hyperglass.json custom.*[js, html] *.tsbuildinfo # dev/test files diff --git a/hyperglass/ui/biome.json b/hyperglass/ui/biome.json new file mode 100644 index 0000000..9ecb7e1 --- /dev/null +++ b/hyperglass/ui/biome.json @@ -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" + } + } +} diff --git a/hyperglass/ui/components/debugger.tsx b/hyperglass/ui/components/debugger.tsx index 1823851..4c2fc28 100644 --- a/hyperglass/ui/components/debugger.tsx +++ b/hyperglass/ui/components/debugger.tsx @@ -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 ( <> { > View Theme - + */} {mediaSize} diff --git a/hyperglass/ui/components/footer/footer.tsx b/hyperglass/ui/components/footer/footer.tsx index 5170048..05df35b 100644 --- a/hyperglass/ui/components/footer/footer.tsx +++ b/hyperglass/ui/components/footer/footer.tsx @@ -61,7 +61,8 @@ export const Footer = (): JSX.Element => { icon.rightIcon = ; } return ; - } else if (isMenu(item)) { + } + if (isMenu(item)) { return ( ); @@ -77,7 +78,8 @@ export const Footer = (): JSX.Element => { icon.rightIcon = ; } return ; - } else if (isMenu(item)) { + } + if (isMenu(item)) { return ( ); diff --git a/hyperglass/ui/components/location-card.tsx b/hyperglass/ui/components/location-card.tsx index 6a56447..6797ac9 100644 --- a/hyperglass/ui/components/location-card.tsx +++ b/hyperglass/ui/components/location-card.tsx @@ -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], ); diff --git a/hyperglass/ui/components/looking-glass-form.tsx b/hyperglass/ui/components/looking-glass-form.tsx index bfca496..b3da510 100644 --- a/hyperglass/ui/components/looking-glass-form.tsx +++ b/hyperglass/ui/components/looking-glass-form.tsx @@ -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[]) => diff --git a/hyperglass/ui/components/output/cell.tsx b/hyperglass/ui/components/output/cell.tsx index 6b8df5c..6bf12b7 100644 --- a/hyperglass/ui/components/output/cell.tsx +++ b/hyperglass/ui/components/output/cell.tsx @@ -25,5 +25,5 @@ export const Cell = (props: CellProps): JSX.Element => { rpki_state: , weight: , }; - return component[cellId] ?? <> ; + return component[cellId] ?? ''; }; diff --git a/hyperglass/ui/components/output/fields.tsx b/hyperglass/ui/components/output/fields.tsx index 0627f0a..1530dcf 100644 --- a/hyperglass/ui/components/output/fields.tsx +++ b/hyperglass/ui/components/output/fields.tsx @@ -119,6 +119,7 @@ export const ASPath = (props: ASPathProps): JSX.Element => { paths.push( { />, ); paths.push( + // biome-ignore lint/suspicious/noArrayIndexKey: index makes sense in this case. {asnStr} , diff --git a/hyperglass/ui/components/query-location.tsx b/hyperglass/ui/components/query-location.tsx index 793bdb6..82f58cd 100644 --- a/hyperglass/ui/components/query-location.tsx +++ b/hyperglass/ui/components/query-location.tsx @@ -32,12 +32,14 @@ function buildOptions(devices: DeviceGroup[]): OptionGroup[] { 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 ( isMulti diff --git a/hyperglass/ui/components/query-type.tsx b/hyperglass/ui/components/query-type.tsx index de460a0..7b9ac73 100644 --- a/hyperglass/ui/components/query-type.tsx +++ b/hyperglass/ui/components/query-type.tsx @@ -60,10 +60,7 @@ function useOptions() { const filtered = useFormState(s => s.filtered); return useMemo((): OptionsOrGroup => { 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[] = Array.from(groupNames).map(group => ({ label: group, diff --git a/hyperglass/ui/components/results/guards.ts b/hyperglass/ui/components/results/guards.ts index 8fd636d..a70b6f0 100644 --- a/hyperglass/ui/components/results/guards.ts +++ b/hyperglass/ui/components/results/guards.ts @@ -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'; } diff --git a/hyperglass/ui/components/results/individual.tsx b/hyperglass/ui/components/results/individual.tsx index bbffe1d..740123e 100644 --- a/hyperglass/ui/components/results/individual.tsx +++ b/hyperglass/ui/components/results/individual.tsx @@ -107,17 +107,20 @@ const _Result: React.ForwardRefRenderFunction = ( 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(() => { diff --git a/hyperglass/ui/components/select/styles.ts b/hyperglass/ui/components/select/styles.ts index 47c0311..43b1ad6 100644 --- a/hyperglass/ui/components/select/styles.ts +++ b/hyperglass/ui/components/select/styles.ts @@ -45,8 +45,8 @@ export const useControlStyle = div > span': { backgroundColor: borderHover }, '&:focus': { borderColor: isError ? invalidBorder : focusBorder }, diff --git a/hyperglass/ui/components/table/row.tsx b/hyperglass/ui/components/table/row.tsx index 501c0a4..a0497d7 100644 --- a/hyperglass/ui/components/table/row.tsx +++ b/hyperglass/ui/components/table/row.tsx @@ -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}`; diff --git a/hyperglass/ui/elements/custom.tsx b/hyperglass/ui/elements/custom.tsx index e012d54..928d375 100644 --- a/hyperglass/ui/elements/custom.tsx +++ b/hyperglass/ui/elements/custom.tsx @@ -6,6 +6,7 @@ export const CustomJavascript = (props: React.PropsWithChildren): JSX.Element => { const { children } = props; if (typeof children === 'string' && children !== '') { + // biome-ignore lint/security/noDangerouslySetInnerHtml: required for injecting custom JS return