From 5f036228a51ce09ff2b402adbae5962ce851a0f5 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sat, 29 May 2021 21:26:03 -0700 Subject: [PATCH 001/358] initial work on generic commands --- hyperglass/configuration/main.py | 100 +++----- hyperglass/models/commands/generic.py | 133 ++++++++++ hyperglass/models/config/devices.py | 29 +-- hyperglass/ui/components/debugger.tsx | 4 +- hyperglass/ui/components/form/field.tsx | 2 - hyperglass/ui/components/form/index.ts | 9 +- hyperglass/ui/components/form/queryGroup.tsx | 71 ++++++ hyperglass/ui/components/form/queryType.tsx | 44 +++- hyperglass/ui/components/form/row.tsx | 1 + hyperglass/ui/components/form/types.ts | 4 + hyperglass/ui/components/lookingGlass.tsx | 230 +++++++++++------- hyperglass/ui/components/results/group.tsx | 3 +- .../ui/components/results/individual.tsx | 3 +- hyperglass/ui/components/results/types.ts | 5 +- hyperglass/ui/hooks/types.ts | 9 +- hyperglass/ui/hooks/useLGState.ts | 17 +- hyperglass/ui/next.config.js | 3 + hyperglass/ui/types/config.ts | 20 ++ hyperglass/ui/types/data.ts | 2 + poetry.lock | 2 +- pyproject.toml | 1 + 21 files changed, 485 insertions(+), 207 deletions(-) create mode 100644 hyperglass/models/commands/generic.py create mode 100644 hyperglass/ui/components/form/queryGroup.tsx diff --git a/hyperglass/configuration/main.py b/hyperglass/configuration/main.py index 57104623b..029b78eff 100644 --- a/hyperglass/configuration/main.py +++ b/hyperglass/configuration/main.py @@ -4,7 +4,7 @@ import os import copy import json -from typing import Dict, List +from typing import Dict, List, Sequence, Generator from pathlib import Path # Third Party @@ -25,9 +25,11 @@ ) from hyperglass.exceptions import ConfigError, ConfigMissing from hyperglass.util.files import check_path -from hyperglass.models.commands import Commands + +# from hyperglass.models.commands import Commands +from hyperglass.models.commands.generic import Directive from hyperglass.models.config.params import Params -from hyperglass.models.config.devices import Devices +from hyperglass.models.config.devices import Devices, Device from hyperglass.configuration.defaults import ( CREDIT, DEFAULT_HELP, @@ -116,6 +118,29 @@ def _config_optional(config_path: Path) -> Dict: return config +def _get_commands(data: Dict) -> Sequence[Directive]: + commands = [] + for name, command in data.items(): + commands.append(Directive(id=name, **command)) + return commands + + +def _device_commands( + device: Dict, directives: Sequence[Directive] +) -> Generator[Directive, None, None]: + device_commands = device.get("commands", []) + for directive in directives: + if directive.id in device_commands: + yield directive + + +def _get_devices(data: Sequence[Dict], directives: Sequence[Directive]) -> Devices: + for device in data: + device_commands = list(_device_commands(device, directives)) + device["commands"] = device_commands + return Devices(data) + + user_config = _config_optional(CONFIG_MAIN) # Read raw debug value from config to enable debugging quickly. @@ -136,15 +161,16 @@ def _config_optional(config_path: Path) -> Dict: # Map imported user commands to expected schema. _user_commands = _config_optional(CONFIG_COMMANDS) log.debug("Unvalidated commands from {}: {}", CONFIG_COMMANDS, _user_commands) -commands = validate_config(config=_user_commands, importer=Commands.import_params) +commands = _get_commands(_user_commands) # Map imported user devices to expected schema. _user_devices = _config_required(CONFIG_DEVICES) log.debug("Unvalidated devices from {}: {}", CONFIG_DEVICES, _user_devices) -devices = validate_config(config=_user_devices.get("routers", []), importer=Devices) +# devices = validate_config(config=_user_devices.get("routers", []), importer=Devices) +devices = _get_devices(_user_devices.get("routers", []), commands) # Validate commands are both supported and properly mapped. -validate_nos_commands(devices.all_nos, commands) +# validate_nos_commands(devices.all_nos, commands) # Set cache configurations to environment variables, so they can be # used without importing this module (Gunicorn, etc). @@ -189,65 +215,6 @@ def _config_optional(config_path: Path) -> Dict: pass -def _build_frontend_devices(): - """Build filtered JSON structure of devices for frontend. - - Schema: - { - "device.name": { - "display_name": "device.display_name", - "vrfs": [ - "Global", - "vrf.display_name" - ] - } - } - - Raises: - ConfigError: Raised if parsing/building error occurs. - - Returns: - {dict} -- Frontend devices - """ - frontend_dict = {} - for device in devices.objects: - if device.name in frontend_dict: - frontend_dict[device.name].update( - { - "network": device.network.display_name, - "display_name": device.display_name, - "vrfs": [ - { - "id": vrf.name, - "display_name": vrf.display_name, - "default": vrf.default, - "ipv4": True if vrf.ipv4 else False, # noqa: IF100 - "ipv6": True if vrf.ipv6 else False, # noqa: IF100 - } - for vrf in device.vrfs - ], - } - ) - elif device.name not in frontend_dict: - frontend_dict[device.name] = { - "network": device.network.display_name, - "display_name": device.display_name, - "vrfs": [ - { - "id": vrf.name, - "display_name": vrf.display_name, - "default": vrf.default, - "ipv4": True if vrf.ipv4 else False, # noqa: IF100 - "ipv6": True if vrf.ipv6 else False, # noqa: IF100 - } - for vrf in device.vrfs - ], - } - if not frontend_dict: - raise ConfigError(error_msg="Unable to build network to device mapping") - return frontend_dict - - def _build_networks() -> List[Dict]: """Build filtered JSON Structure of networks & devices for Jinja templates.""" networks = [] @@ -262,6 +229,7 @@ def _build_networks() -> List[Dict]: "_id": device._id, "name": device.name, "network": device.network.display_name, + "directives": [c.frontend(params) for c in device.commands], "vrfs": [ { "_id": vrf._id, @@ -346,7 +314,7 @@ def _build_vrf_help() -> Dict: content_credit = CREDIT.format(version=__version__) networks = _build_networks() -frontend_devices = _build_frontend_devices() + _include_fields = { "cache": {"show_text", "timeout"}, "debug": ..., diff --git a/hyperglass/models/commands/generic.py b/hyperglass/models/commands/generic.py new file mode 100644 index 000000000..6a19d18e2 --- /dev/null +++ b/hyperglass/models/commands/generic.py @@ -0,0 +1,133 @@ +import json +from ipaddress import IPv4Network, IPv6Network +from typing import Optional, Sequence, Union, Dict +from typing_extensions import Literal +from pydantic import StrictStr, PrivateAttr, conint, validator, FilePath +from ..main import HyperglassModel +from ..config.params import Params +from hyperglass.configuration.markdown import get_markdown + +IPv4PrefixLength = conint(ge=0, le=32) +IPv6PrefixLength = conint(ge=0, le=128) + + +class Policy(HyperglassModel): + network: Union[IPv4Network, IPv6Network] + action: Literal["permit", "deny"] + + @validator("ge", check_fields=False) + def validate_ge(cls, value: int, values: Dict) -> int: + """Ensure ge is at least the size of the input prefix.""" + + network_len = values["network"].prefixlen + + if network_len > value: + value = network_len + + return value + + +class Policy4(Policy): + ge: IPv4PrefixLength = 0 + le: IPv4PrefixLength = 32 + + +class Policy6(Policy): + ge: IPv6PrefixLength = 0 + le: IPv6PrefixLength = 128 + + +class Input(HyperglassModel): + _type: PrivateAttr + description: StrictStr + + def is_select(self) -> bool: + return self._type == "select" + + def is_text(self) -> bool: + return self._type == "text" + + def is_ip(self) -> bool: + return self._type == "ip" + + +class Text(Input): + _type: PrivateAttr = "text" + validation: Optional[StrictStr] + + +class IPInput(Input): + _type: PrivateAttr = "ip" + validation: Union[Policy4, Policy6] + + +class Option(HyperglassModel): + name: Optional[StrictStr] + value: StrictStr + + +class Select(Input): + _type: PrivateAttr = "select" + options: Sequence[Option] + + +class Directive(HyperglassModel): + id: StrictStr + name: StrictStr + command: Union[StrictStr, Sequence[StrictStr]] + field: Union[Text, Select, IPInput, None] + info: Optional[FilePath] + attrs: Dict = {} + groups: Sequence[ + StrictStr + ] = [] # TODO: Flesh this out. Replace VRFs, but use same logic in React to filter available commands for multi-device queries. + + @validator("command") + def validate_command(cls, value: Union[str, Sequence[str]]) -> Sequence[str]: + if isinstance(value, str): + return [value] + return value + + def get_commands(self, target: str) -> Sequence[str]: + return [s.format(target=target, **self.attrs) for s in self.command] + + @property + def field_type(self) -> Literal["text", "select", None]: + if self.field.is_select(): + return "select" + elif self.field.is_text() or self.field.is_ip(): + return "text" + return None + + def frontend(self, params: Params) -> Dict: + + value = { + "name": self.name, + "field_type": self.field_type, + "groups": self.groups, + "description": self.field.description, + "info": None, + } + + if self.info is not None: + content_params = json.loads( + params.json( + include={ + "primary_asn", + "org_name", + "site_title", + "site_description", + } + ) + ) + with self.info.open() as md: + value["info"] = { + "enable": True, + "params": content_params, + "content": md.read(), + } + + if self.field_type == "select": + value["options"]: [o.export_dict() for o in self.field.options] + + return value diff --git a/hyperglass/models/config/devices.py b/hyperglass/models/config/devices.py index a353610f0..65a1ac014 100644 --- a/hyperglass/models/config/devices.py +++ b/hyperglass/models/config/devices.py @@ -3,7 +3,7 @@ # Standard Library import os import re -from typing import Any, Dict, List, Tuple, Union, Optional +from typing import Any, Dict, List, Tuple, Union, Optional, Sequence from pathlib import Path from ipaddress import IPv4Address, IPv6Address @@ -22,6 +22,8 @@ from hyperglass.util import get_driver, validate_nos, resolve_hostname from hyperglass.constants import SCRAPE_HELPERS, SUPPORTED_STRUCTURED_OUTPUT from hyperglass.exceptions import ConfigError, UnsupportedDevice +from hyperglass.models.commands.generic import Directive + # Local from .ssl import Ssl @@ -89,10 +91,8 @@ class Device(HyperglassModel): port: StrictInt = 22 ssl: Optional[Ssl] nos: StrictStr - commands: Optional[StrictStr] + commands: Sequence[Directive] vrfs: List[Vrf] = [_default_vrf] - display_vrfs: List[StrictStr] = [] - vrf_names: List[StrictStr] = [] structured_output: Optional[StrictBool] driver: Optional[SupportedDriver] @@ -172,7 +172,7 @@ def validate_ssl(cls, value, values): return value @root_validator(pre=True) - def validate_nos_commands(cls, values: "Device") -> "Device": + def validate_nos_commands(cls, values: Dict) -> Dict: """Validate & rewrite NOS, set default commands.""" nos = values.get("nos", "") @@ -205,7 +205,7 @@ def validate_nos_commands(cls, values: "Device") -> "Device": if "_telnet" in inferred: inferred = inferred.replace("_telnet", "") - values["commands"] = inferred + values["commands"] = [inferred] return values @@ -288,7 +288,6 @@ class Devices(HyperglassModelExtra): _ids: List[StrictStr] = [] hostnames: List[StrictStr] = [] vrfs: List[StrictStr] = [] - display_vrfs: List[StrictStr] = [] vrf_objects: List[Vrf] = [] objects: List[Device] = [] all_nos: List[StrictStr] = [] @@ -300,15 +299,8 @@ def __init__(self, input_params: List[Dict]) -> None: Remove unsupported characters from device names, dynamically set attributes for the devices class. Builds lists of common attributes for easy access in other modules. - - Arguments: - input_params {dict} -- Unvalidated router definitions - - Returns: - {object} -- Validated routers object """ vrfs = set() - display_vrfs = set() vrf_objects = set() all_nos = set() objects = set() @@ -328,19 +320,13 @@ def __init__(self, input_params: List[Dict]) -> None: hostnames.add(device.name) _ids.add(device._id) objects.add(device) - all_nos.add(device.commands) + all_nos.add(device.nos) for vrf in device.vrfs: # For each configured router VRF, add its name and # display_name to a class set (for automatic de-duping). vrfs.add(vrf.name) - display_vrfs.add(vrf.display_name) - - # Also add the names to a router-level list so each - # router's VRFs and display VRFs can be easily accessed. - device.display_vrfs.append(vrf.display_name) - device.vrf_names.append(vrf.name) # Add a 'default_vrf' attribute to the devices class # which contains the configured default VRF display name. @@ -367,7 +353,6 @@ def __init__(self, input_params: List[Dict]) -> None: init_kwargs["hostnames"] = list(hostnames) init_kwargs["all_nos"] = list(all_nos) init_kwargs["vrfs"] = list(vrfs) - init_kwargs["display_vrfs"] = list(vrfs) init_kwargs["vrf_objects"] = list(vrf_objects) init_kwargs["objects"] = sorted(objects, key=lambda x: x.name) diff --git a/hyperglass/ui/components/debugger.tsx b/hyperglass/ui/components/debugger.tsx index dd4b73202..dcc30688d 100644 --- a/hyperglass/ui/components/debugger.tsx +++ b/hyperglass/ui/components/debugger.tsx @@ -23,12 +23,12 @@ interface TViewer extends Pick { const Viewer: React.FC = (props: TViewer) => { const { title, isOpen, onClose, children } = props; - const bg = useColorValue('white', 'black'); + const bg = useColorValue('white', 'blackSolid.700'); const color = useColorValue('black', 'white'); return ( - + {title} diff --git a/hyperglass/ui/components/form/field.tsx b/hyperglass/ui/components/form/field.tsx index c3c6845f2..b71244c42 100644 --- a/hyperglass/ui/components/form/field.tsx +++ b/hyperglass/ui/components/form/field.tsx @@ -24,13 +24,11 @@ export const FormField: React.FC = (props: TField) => { return ( ({ value: q._id, label: q.display_name })); +// } + +type QueryGroups = Record; + +function buildOptions(networks: TNetwork[]): QueryGroups { + const options = {} as QueryGroups; + for (const net of networks) { + for (const loc of net.locations) { + for (const directive of loc.directives) { + for (const group of directive.groups) { + if (Object.keys(options).includes(group)) { + options[group] = [...options[group], loc.name]; + } else { + options[group] = [loc.name]; + } + } + } + } + } + return options; +} + +export const QueryGroup: React.FC = (props: TQueryGroup) => { + const { groups, onChange, label } = props; + const { selections, availableGroups, queryLocation, queryGroup } = useLGState(); + const { exportState } = useLGMethods(); + + // const groups = useMemo(() => buildOptions(networks), []); + // const options = useMemo( + // () => Object.keys(groups).map(key => ({ label: key, value: key })), + // [groups], + // ); + // const options = useMemo(() => groups.map(g => ({ label: g, value: g })), [ + // groups, + // ]); + const options = useMemo( + () => availableGroups.map(g => ({ label: g.value, value: g.value })), + [availableGroups.length, queryLocation.length], + ); + + function handleChange(e: TSelectOption | TSelectOption[]): void { + let value = ''; + if (!Array.isArray(e) && e !== null) { + selections.queryGroup.set(e); + value = e.value; + } else { + selections.queryGroup.set(null); + } + onChange({ field: 'query_group', value }); + } + + return ( + - + + q.enable === true) -// .map(q => ({ value: q.name, label: q.display_name })); -// } - -// function* buildOptions(networks: TNetwork[]): Generator { -// for (const net of networks) { -// for (const loc of net.locations) { -// for (const directive of loc.directives) { -// const { name } = directive; -// yield { value: name, label: name }; -// } -// } -// } -// } - export const QueryType: React.FC = (props: TQuerySelectField) => { const { onChange, label } = props; - // const { - // queries, - // networks, - // } = useConfig(); const { formState: { errors }, } = useFormContext(); const { selections, availableTypes, queryType } = useLGState(); const { exportState } = useLGMethods(); - // const options = useMemo(() => buildOptions(queries.list), [queries.list.length]); - // const options = useMemo(() => Array.from(buildOptions(networks)), []); - // const options = useMemo( - // () => uniqBy(Array.from(buildOptions(networks)), opt => opt?.label), - // [], - // ); - const options = useMemo(() => availableTypes.map(t => ({ label: t.value, value: t.value })), [ - availableTypes.length, - ]); + const options = useMemo( + () => availableTypes.map(t => ({ label: t.name.value, value: t.id.value })), + [availableTypes.length], + ); function handleChange(e: TSelectOption | TSelectOption[]): void { let value = ''; @@ -67,7 +39,6 @@ export const QueryType: React.FC = (props: TQuerySelectField) aria-label={label} onChange={handleChange} value={exportState(selections.queryType.value)} - // isError={typeof errors.query_type !== 'undefined'} isError={'query_type' in errors} /> ); diff --git a/hyperglass/ui/components/form/queryVrf.tsx b/hyperglass/ui/components/form/queryVrf.tsx deleted file mode 100644 index 4c7d2edd7..000000000 --- a/hyperglass/ui/components/form/queryVrf.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useMemo } from 'react'; -import { Select } from '~/components'; -import { useLGMethods, useLGState } from '~/hooks'; - -import type { TDeviceVrf, TSelectOption } from '~/types'; -import type { TQueryVrf } from './types'; - -function buildOptions(queryVrfs: TDeviceVrf[]): TSelectOption[] { - return queryVrfs.map(q => ({ value: q._id, label: q.display_name })); -} - -export const QueryVrf: React.FC = (props: TQueryVrf) => { - const { vrfs, onChange, label } = props; - const { selections } = useLGState(); - const { exportState } = useLGMethods(); - - const options = useMemo(() => buildOptions(vrfs), [vrfs.length]); - - function handleChange(e: TSelectOption | TSelectOption[]): void { - if (!Array.isArray(e) && e !== null) { - selections.queryVrf.set(e); - onChange({ field: 'query_vrf', value: e.value }); - } else { - selections.queryVrf.set(null); - } - } - - return ( - { - const label = net.display_name; + const label = net.displayName; const options = net.locations .map(loc => ({ label: loc.name, - value: loc._id, - group: net.display_name, + value: loc.id, + group: net.displayName, })) .sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0)); return { label, options }; @@ -43,7 +43,7 @@ export const QueryLocation: React.FC = (props: TQuerySelectFi } if (Array.isArray(e)) { const value = e.map(sel => sel!.value); - onChange({ field: 'query_location', value }); + onChange({ field: 'queryLocation', value }); selections.queryLocation.set(e); } } @@ -54,11 +54,11 @@ export const QueryLocation: React.FC = (props: TQuerySelectFi size="lg" options={options} aria-label={label} - name="query_location" + name="queryLocation" onChange={handleChange} closeMenuOnSelect={false} value={exportState(selections.queryLocation.value)} - isError={typeof errors.query_location !== 'undefined'} + isError={typeof errors.queryLocation !== 'undefined'} /> ); }; diff --git a/hyperglass/ui/components/form/queryTarget.tsx b/hyperglass/ui/components/form/queryTarget.tsx index a8a73f6fa..dace734a4 100644 --- a/hyperglass/ui/components/form/queryTarget.tsx +++ b/hyperglass/ui/components/form/queryTarget.tsx @@ -7,10 +7,10 @@ import { useLGState, useDirective } from '~/hooks'; import { isSelectDirective } from '~/types'; import type { OptionProps } from 'react-select'; -import type { TSelectOption, TDirective } from '~/types'; +import type { TSelectOption, Directive } from '~/types'; import type { TQueryTarget } from './types'; -function buildOptions(directive: Nullable): TSelectOption[] { +function buildOptions(directive: Nullable): TSelectOption[] { if (directive !== null && isSelectDirective(directive)) { return directive.options.map(o => ({ value: o.value, @@ -61,7 +61,7 @@ export const QueryTarget: React.FC = (props: TQueryTarget) => { return ( <> - + ); }; diff --git a/hyperglass/ui/components/form/resolvedTarget.tsx b/hyperglass/ui/components/form/resolvedTarget.tsx index bda17ef5e..5a65d8ed6 100644 --- a/hyperglass/ui/components/form/resolvedTarget.tsx +++ b/hyperglass/ui/components/form/resolvedTarget.tsx @@ -36,11 +36,11 @@ export const ResolvedTarget: React.FC = (props: TResolvedTarget const query4 = Array.from(families.value).includes(4); const query6 = Array.from(families.value).includes(6); - const tooltip4 = strF(web.text.fqdn_tooltip, { protocol: 'IPv4' }); - const tooltip6 = strF(web.text.fqdn_tooltip, { protocol: 'IPv6' }); + const tooltip4 = strF(web.text.fqdnTooltip, { protocol: 'IPv4' }); + const tooltip6 = strF(web.text.fqdnTooltip, { protocol: 'IPv6' }); - const [messageStart, messageEnd] = web.text.fqdn_message.split('{fqdn}'); - const [errorStart, errorEnd] = web.text.fqdn_error.split('{fqdn}'); + const [messageStart, messageEnd] = web.text.fqdnMessage.split('{fqdn}'); + const [errorStart, errorEnd] = web.text.fqdnError.split('{fqdn}'); const { data: data4, @@ -63,7 +63,7 @@ export const ResolvedTarget: React.FC = (props: TResolvedTarget const answer6 = useMemo(() => findAnswer(data6), [data6]); const handleOverride = useCallback( - (value: string): void => setTarget({ field: 'query_target', value }), + (value: string): void => setTarget({ field: 'queryTarget', value }), [setTarget], ); @@ -137,7 +137,7 @@ export const ResolvedTarget: React.FC = (props: TResolvedTarget onClick={errorClose} leftIcon={} > - {web.text.fqdn_error_button} + {web.text.fqdnErrorButton} )} diff --git a/hyperglass/ui/components/form/types.ts b/hyperglass/ui/components/form/types.ts index fc306dc0a..cf5158367 100644 --- a/hyperglass/ui/components/form/types.ts +++ b/hyperglass/ui/components/form/types.ts @@ -1,6 +1,6 @@ import type { FormControlProps } from '@chakra-ui/react'; import type { UseFormRegister } from 'react-hook-form'; -import type { TBGPCommunity, OnChangeArgs, TFormData } from '~/types'; +import type { OnChangeArgs, FormData } from '~/types'; export interface TField extends FormControlProps { name: string; @@ -21,17 +21,10 @@ export interface TQueryGroup extends TQuerySelectField { groups: string[]; } -export interface TCommunitySelect { - name: string; - onChange: OnChange; - communities: TBGPCommunity[]; - register: UseFormRegister; -} - export interface TQueryTarget { name: string; placeholder: string; - register: UseFormRegister; + register: UseFormRegister; onChange(e: OnChangeArgs): void; } diff --git a/hyperglass/ui/components/header/logo.tsx b/hyperglass/ui/components/header/logo.tsx index d62f4aa64..b044c2ff6 100644 --- a/hyperglass/ui/components/header/logo.tsx +++ b/hyperglass/ui/components/header/logo.tsx @@ -9,9 +9,9 @@ import type { TLogo } from './types'; */ function useLogo(): [string, () => void] { const { web } = useConfig(); - const { dark_format, light_format } = web.logo; + const { darkFormat, lightFormat } = web.logo; - const src = useColorValue(`/images/dark${dark_format}`, `/images/light${light_format}`); + const src = useColorValue(`/images/dark${darkFormat}`, `/images/light${lightFormat}`); // Use the hyperglass logo if the user's logo can't be loaded for whatever reason. const fallbackSrc = useColorValue( diff --git a/hyperglass/ui/components/header/title.tsx b/hyperglass/ui/components/header/title.tsx index d06dfeabc..203f83924 100644 --- a/hyperglass/ui/components/header/title.tsx +++ b/hyperglass/ui/components/header/title.tsx @@ -106,7 +106,7 @@ const All: React.FC = () => ( export const Title: React.FC = (props: TTitle) => { const { fontSize, ...rest } = props; const { web } = useConfig(); - const titleMode = web.text.title_mode; + const { titleMode } = web.text; const { isSubmitting } = useLGState(); const { resetForm } = useLGMethods(); diff --git a/hyperglass/ui/components/help/types.ts b/hyperglass/ui/components/help/types.ts index 2a47dfce3..aeefec8af 100644 --- a/hyperglass/ui/components/help/types.ts +++ b/hyperglass/ui/components/help/types.ts @@ -1,8 +1,8 @@ import type { ModalContentProps } from '@chakra-ui/react'; -import type { TQueryContent, TQueryFields } from '~/types'; +import type { QueryContent } from '~/types'; export interface THelpModal extends ModalContentProps { - item: TQueryContent | null; - name: TQueryFields; + item: QueryContent | null; + name: string; visible: boolean; } diff --git a/hyperglass/ui/components/layout/frame.tsx b/hyperglass/ui/components/layout/frame.tsx index 799612ab4..e73fd3a71 100644 --- a/hyperglass/ui/components/layout/frame.tsx +++ b/hyperglass/ui/components/layout/frame.tsx @@ -11,7 +11,7 @@ import type { TFrame } from './types'; export const Frame = (props: TFrame): JSX.Element => { const router = useRouter(); - const { developer_mode, google_analytics } = useConfig(); + const { developerMode, googleAnalytics } = useConfig(); const { isSubmitting } = useLGState(); const { resetForm } = useLGMethods(); const { initialize, trackPage } = useGoogleAnalytics(); @@ -25,7 +25,7 @@ export const Frame = (props: TFrame): JSX.Element => { } useEffect(() => { - initialize(google_analytics, developer_mode); + initialize(googleAnalytics, developerMode); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -62,10 +62,10 @@ export const Frame = (props: TFrame): JSX.Element => { {...props} />