Merge branch 'develop' into generic-commands

# Conflicts:
#	hyperglass/configuration/main.py
#	hyperglass/ui/components/form/queryType.tsx
#	hyperglass/ui/components/lookingGlass.tsx
This commit is contained in:
checktheroads 2021-06-19 13:48:14 -07:00
commit 029649e44f
107 changed files with 5508 additions and 8556 deletions

View file

@ -1,13 +1,12 @@
---
name: Feature Request
about: Suggest an idea for this project
labels: enhancement
assignees: checktheroads
about: Suggest an idea for hyperglass
labels: feature
---
<!--
If the answer to any of these questions is "no", your feature request will most likely be rejected (but will still be considered).
- Is the new feature only applicable to one Network Operating System (https://hyperglass.io/docs/platforms)?
- Is the new feature _only_ applicable to one Network Operating System (https://hyperglass.io/docs/platforms)?
- Would the new feature work only on mobile, or only on desktop?
- Would the new feature only support IPv4, or IPv6?
- Is the new feature something that can be reasonably customized by hyperglass end-users?

View file

@ -1,7 +1,7 @@
---
name: Bug Report
about: Report a problem or unexpected behavior
labels: bug
labels: possible-bug
assignees: checktheroads
---

26
.github/ISSUE_TEMPLATE/3-new-nos.yaml vendored Normal file
View file

@ -0,0 +1,26 @@
---
name: New Network Operating System
description: Request native support for a network operating system
labels: feature
body:
- type: markdown
attributes:
value: >
In order to natively support a new network operating system for hyperglass, please make sure it is supported by either Netmiko or Scrapli ([see here](https://hyperglass.io/docs/platforms)).
- type: input
attributes:
label: Manufacturer
description: What is the network vendor that manufactures the NOS? For example, for Juniper Junos, this would be Juniper.
validations:
required: true
- type: input
attributes:
label: Platform Name
description: What is the name of this NOS? For example, for Juniper Junos, this would be Junos
validations:
required: true
- type: textarea
attributes:
label: Commands
description: >
Please provide the commands required to execute hyperglass commands ([see here](https://hyperglass.io/docs/commands#adding-a-custom-command-set)). If you do not know the commands, it is likely that this request will be either denied or may take a long time to implement.

View file

@ -1 +1 @@
blank_issues_enabled: true
blank_issues_enabled: false

View file

@ -2,13 +2,37 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
# 1.0.1 - 2021-06-17
### Fixed
- [#150](https://github.com/checktheroads/hyperglass/issues/150): Fix handling of BIRD AS_PATH/Community targets.
# 1.0.1 - 2021-06-17
### Fixed
- UI: fix body overflow issue
# 1.0.0 - 2021-05-30
### BREAKING CHANGES
- The `external_link`, `help`, and `terms` parameters no longer exist and have been replaced with generic `links` and `menus` options.
- The transitionary `frr_ssh` and `bird_ssh` NOS parameters no longer exist — `frr` and `bird` can now be used for SSH-based connectivity. hyperglass-agent users must now use `frr_legacy` and `bird_legacy` until hyperglass-agent is fully deprecated.
### Fixed
- [#139](https://github.com/checktheroads/hyperglass/issues/139): Fix an issue where the API cannot be queried by device name.
### Changed
- Updated UI dependencies
### Added
- [#140](https://github.com/checktheroads/hyperglass/issues/140): Genericize links and menus so that multiple links and/or menus can be defined and fully customized.
# 1.0.0-beta.82 - 2021-04-22
### BREAKING CHANGE
**NodeJS 14.15 or later is required**. See [the docs](https://hyperglass.io/docs/getting-started) for installation instructions.
**NodeJS 14.15 or later is required**. See [the docs](https://hyperglass.dev/docs/getting-started) for installation instructions.
### Fixed
- [#135](https://github.com/checktheroads/hyperglass/issues/135): Fix an issue where Juniper indirect next-hops were empty.
@ -78,7 +102,7 @@ Moving forward, the `name` field is only used to define the name of the VRF **as
**NOTICE**: *[hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) will be deprecated soon. Use `frr_ssh` or `bird_ssh` for SSH connectivity in the meantime.*
### Added
- FRR & BIRD may now be accessed via standard SSH using the `frr_ssh` and `bird_ssh` NOS. [See the docs](https://hyperglass.io/docs/platforms#caveats) for important caveats.
- FRR & BIRD may now be accessed via standard SSH using the `frr_ssh` and `bird_ssh` NOS. [See the docs](https://hyperglass.dev/docs/platforms#caveats) for important caveats.
### Changed
- `port` in `devices.yaml` now defaults to 22 if not specified.
@ -188,7 +212,7 @@ Moving forward, the `name` field is only used to define the name of the VRF **as
### Added
- [#87](https://github.com/checktheroads/hyperglass/issues/87): [TNSR] Support. To add a TNSR device, use the `tnsr` [NOS key](https://hyperglass.io/docs/adding-devices#all-device-parameters).
- [#87](https://github.com/checktheroads/hyperglass/issues/87): [TNSR] Support. To add a TNSR device, use the `tnsr` [NOS key](https://hyperglass.dev/docs/adding-devices#all-device-parameters).
### Fixed
@ -212,7 +236,7 @@ When hyperglass starts up, it will check to see if `~/hyperglass` or `/etc/hyper
### Added
- [#81](https://github.com/checktheroads/hyperglass/issues/81): Add support for SSH key authentication. See [the docs](https://hyperglass.io/docs/adding-devices#credential) for more details.
- [#81](https://github.com/checktheroads/hyperglass/issues/81): Add support for SSH key authentication. See [the docs](https://hyperglass.dev/docs/adding-devices#credential) for more details.
## 1.0.0-beta.60 - 2020-10-10
@ -394,7 +418,7 @@ $ hyperglass-agent send-certificate
### Added
- New NOS: **VyOS**. [See docs for important caveats](https://hyperglass.io/docs/commands).
- New NOS: **VyOS**. [See docs for important caveats](https://hyperglass.dev/docs/commands).
### Fixed
@ -407,13 +431,13 @@ $ hyperglass-agent send-certificate
### Added
- Opengraph images are now automatically generated in the correct format from any valid image file.
- Better color mode toggle icons (they now match [hyperglass.io](https://hyperglass.io)).
- Better color mode toggle icons (they now match [hyperglass.dev](https://hyperglass.dev)).
### Changed
- Improved SEO & Accessibility for UI.
- Default traceroute help link now points to new docs site.
- Slightly different default black & white colors (they now match [hyperglass.io](https://hyperglass.io)).
- Slightly different default black & white colors (they now match [hyperglass.dev](https://hyperglass.dev)).
- Various docs site improvements
### Fixed
@ -464,4 +488,4 @@ $ hyperglass-agent send-certificate
### Changed
- **BREAKING CHANGE**: The `logo` section now requires the full path for logo files. See [the docs](https://hyperglass.io/docs/ui/logo) for details.
- **BREAKING CHANGE**: The `logo` section now requires the full path for logo files. See [the docs](https://hyperglass.dev/docs/ui/logo) for details.

View file

@ -8,7 +8,7 @@ This isnt an exhaustive list of things that you cant do. Rather, take it i
This code of conduct applies to all spaces managed by the hyperglass community. This includes mailing lists, the issue tracker, Telegram groups, Gitter communities, and any other forums created by the project team which the community uses for communication. In addition, violations of this code outside these spaces may affect a person's ability to participate within them.
If you believe someone is violating the code of conduct, we ask that you report it by emailing [conduct@hyperglass.io](mailto:conduct@hyperglass.io).
If you believe someone is violating the code of conduct, we ask that you report it by emailing [matt@hyperglass.dev](mailto:matt@hyperglass.dev).
**Be friendly and patient.** Be welcoming. We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.

View file

@ -16,3 +16,14 @@ Because I've been solo-maintaining and building hyperglass since around April 20
- **IPv6 Support**
- Any new device support must include IPv6 commands
- All frontend and backend code must support IPv6, both for running the application and processing queries
## Branches
The following are the primary branches used for development and release management:
| Branch Name | Function |
| :---------- | :--------------------- |
| `main` | Tagged Stable Releases |
| `develop` | Ongoing Development |
Pull requests should be made against the `develop` branch.

View file

@ -1,6 +1,6 @@
The Clear BSD License
Copyright (c) 2020 Matthew Love
Copyright (c) 2021 Matthew Love
All rights reserved.
Redistribution and use in source and binary forms, with or without

View file

@ -11,7 +11,7 @@
<div align="center">
[**Documentation**](https://hyperglass.io)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[**Screenshots**](https://hyperglass.io/screenshots)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[**Live Demo**](https://demo.hyperglass.io/)
[**Documentation**](https://hyperglass.dev)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[**Live Demo**](https://demo.hyperglass.dev/)
[![PyPI](https://img.shields.io/pypi/v/hyperglass?style=for-the-badge)](https://pypi.org/project/hyperglass/)
![PyPI - Downloads](https://img.shields.io/pypi/dm/hyperglass?color=%2340798C&style=for-the-badge)
@ -47,7 +47,7 @@ hyperglass is intended to make implementing a looking glass too easy not to do,
- Nokia SR OS
- TNSR
- VyOS
- Configurable support for any other [supported platform](https://hyperglass.io/docs/platforms)
- Configurable support for any other [supported platform](https://hyperglass.dev/docs/platforms)
- Optionally access devices via an SSH proxy/jump server
- VRF support
- Access List/prefix-list style query control to whitelist or blacklist query targets on a per-VRF basis
@ -56,20 +56,20 @@ hyperglass is intended to make implementing a looking glass too easy not to do,
- Query multiple devices simultaneously
- Browser-based DNS-over-HTTPS resolution of FQDN queries
*To request support for a specific platform, please [submit a Github Issue](https://github.com/checktheroads/hyperglass/issues/new) with the **enhancement** label.*
*To request support for a specific platform, please [submit a Github Issue](https://github.com/checktheroads/hyperglass/issues/new) with the **feature** label.*
### [Get Started →](https://hyperglass.io/)
### [Get Started →](https://hyperglass.dev/docs/introduction)
## Community
- [Slack](https://slack.netbox.dev/)
- [Slack](https://netdev.chat/)
- [Telegram](https://t.me/hyperglasslg)
Any users, potential users, or contributors of hyperglass are welcome to join and discuss usage, feature requests, bugs, and other things.
**hyperglass is developed with the express intention of being free to the networking community**.
*However, the hyperglass demo does cost [@checktheroads](https://github.com/checktheroads) $60/year for the [hyperglass.io](https://hyperglass.io) domain. If you're feeling particularly helpful and want to help offset that cost, small donations are welcome.*
*However, if you're feeling particularly helpful or generous, small donations are welcome.*
[![Donate](https://img.shields.io/badge/Donate-blue.svg?logo=paypal&style=for-the-badge)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZQFH3BB2B5M3E&source=url)

3
docs/.gitignore vendored
View file

@ -1,3 +1,6 @@
# Project
theme-og
# Dependencies
/node_modules

View file

@ -9,8 +9,8 @@ description: Install hyperglass-agent on your system
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
:::important In Progress
Documentation for [hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) is in progress!
:::caution Deprecation Warning
[hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) is going to be deprecated soon. See [Issue #143](https://github.com/checktheroads/hyperglass/issues/143) and [here](platforms.mdx) for more detail.
:::
## Installation
@ -76,6 +76,6 @@ Now that Python 3.6+ is installed, you can install the hyperglass agent:
$ pip3 install hyperglass-agent
```
:::important More coming soon
Documentation for [hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) is in progress!
:::caution Deprecation Warning
[hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) is going to be deprecated soon. See [Issue #143](https://github.com/checktheroads/hyperglass/issues/143) and [here](platforms.mdx) for more detail.
:::

View file

@ -9,6 +9,10 @@ description: hyperglass-agent configuration parameters.
import M from "../../src/components/MiniNote";
import PL from "../../src/components/PageLink";
:::caution Deprecation Warning
[hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) is going to be deprecated soon. See [Issue #143](https://github.com/checktheroads/hyperglass/issues/143) and [here](platforms.mdx) for more detail.
:::
<div class="table--full-width" />
## Configuration File
@ -62,3 +66,7 @@ By default, hyperglass-agent writes all log messages to a log file located at `/
| `directory` | FilePath | `/tmp/` | Directory at which to write the log file `hyperglass-agent.log`. Use `false` to disable file logging. |
| `format` | String | `'text'` | `'text'` for plain text non-structured logging, `'json'` for JSON structured logging. If JSON is enabled, the log file name will be `hyperglass-agent.log.json` |
| `max_size` | String | `'50MB'` | Maximum log file size before old logs are overwritten. |
:::caution Deprecation Warning
[hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) is going to be deprecated soon. See [Issue #143](https://github.com/checktheroads/hyperglass/issues/143) and [here](platforms.mdx) for more detail.
:::

View file

@ -8,7 +8,11 @@ description: Configure hyperglass-agent
import M from "../../src/components/MiniNote";
:::caution Time & NTP
:::caution Deprecation Warning
[hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) is going to be deprecated soon. See [Issue #143](https://github.com/checktheroads/hyperglass/issues/143) and [here](platforms.mdx) for more detail.
:::
:::important Time & NTP
Before you get too far, check to make sure your hyperglass server and hyperglass-agent system are both properly synchronized with an NTP server. During the setup process and on every interaction, hyperglass and hyperglass-agent exchange [JWT](https://jwt.io/) tokens with a relatively short window (**60 seconds, by default**) in which to validate the payload. If the system clock on either system is askew by too much, this exchange can fail.
:::

View file

@ -20,7 +20,7 @@ If your system runs on:
You should be able to proceed with the automatic installation:
```shell-session
$ curl https://install.hyperglass.io | sudo bash
$ curl https://install.hyperglass.dev | sudo bash
```
:::caution Piping to bash

View file

@ -7,7 +7,7 @@ description: Welcome to hyperglass
import Link from "@docusaurus/Link";
import useBaseUrl from "@docusaurus/useBaseUrl";
import classnames from "classnames";
import clsx from "clsx";
import styles from "./styles.module.css";
## What is hyperglass?
@ -44,7 +44,7 @@ hyperglass was created with the lofty goal of benefiting the internet community
- Browser-based DNS-over-HTTPS resolution of FQDN queries
<Link
className={classnames(
className={clsx(
"button button--outline button--secondary button--lg",
styles.getStarted
)}

View file

@ -6,9 +6,6 @@ keywords: [configuration, messages, text, ui, customization]
description: hyperglass messages configuration
---
import Link from "@docusaurus/Link";
import Code from "../src/components/JSXCode";
All user-facing status messages can be customized if needed.
<div class="table--full-width" />
@ -20,14 +17,14 @@ All user-facing status messages can be customized if needed.
| `authentication_error` | String | `'Authentication error occurred.'` | Displayed when hyperglass is unable to authenticate to a configured device. Usually, this indicates a configuration error. `{device_name}` and `{error}` will be replaced with the device in question and the specific connection error. |
| `connection_error` | String | `'Error connecting to {device_name}: {error}'` | Displayed when hyperglass is unable to connect to a configured device. Usually, this indicates a configuration error. `{device_name}` and `{error}` will be replaced with the device in question and the specific connection error. |
| `feature_not_enabled` | String | `'{feature} is not enabled for {device_name}.'` | Displayed when a query type is submitted that is not supported or disabled. The UI performs validation of supported query types prior to submitting any requests, so this is primarily relevant to the hyperglass API. `{feature}` and `{device_name}` will be replaced with the disabled feature and the selected device/location. |
| `general` | String | `'Something went wrong.'` | Displayed when generalized errors occur. Seeing this error message may indicate a bug in hyperglass, as most other errors produced are highly contextual. If you see this in the wild, try enabling <Link to="/docs/configuration#global-settings"><Code>debug</Code></Link> mode and review the logs to pinpoint the source of the error. |
| `general` | String | `'Something went wrong.'` | Displayed when generalized errors occur. Seeing this error message may indicate a bug in hyperglass, as most other errors produced are highly contextual. If you see this in the wild, try enabling [`debug`](parameters.mdx#global-settings) mode and review the logs to pinpoint the source of the error. |
| `invalid_field` | String | `'{input} is an invalid {field}.'` | Displayed when a query field contains an invalid or unsupported value. `{input}` and `{field}` will be replaced with the invalid input value and corresponding field name. |
| `invalid_input` | String | `'{target} is not a valid {query_type} target.'` | Displayed when a query target's value is invalid in relation to the corresponding query type. `{target}` and `{query_type}` will be replaced with the invalid target and corresponding query type. |
| `no_input` | String | `'{field} must be specified.'` | Displayed when no a required field is not specified. `{field}` will be replaced with the `display_name` of the field that was omitted. |
| `no_output` | String | `'The query completed, but no matching results were found.'` | Displayed when hyperglass can connect to a device and execute a query, but the response is empty. |
| `no_response` | String | `'No response.'` | Displayed when hyperglass can connect to a device, but no output is able to be read. Seeing this error may indicate a bug in hyperglass or one of its dependencies. If you see this in the wild, try enabling <Link to="/docs/configuration#global-settings"><Code>debug</Code></Link> mode and review the logs to pinpoint the source of the error. |
| `no_response` | String | `'No response.'` | Displayed when hyperglass can connect to a device, but no output is able to be read. Seeing this error may indicate a bug in hyperglass or one of its dependencies. If you see this in the wild, try enabling [`debug`](parameters.mdx#global-settings) mode and review the logs to pinpoint the source of the error. |
| `parsing_error` | String | `'An error occurred while parsing the query output.'` | Displayed when hyperglass can connect to a device and execute a query, but the response cannot be parsed. |
| `request_timeout` | String | `'Request timed out.'` | Displayed when the <Link to="/docs/configuration#global-settings"><Code>request_timeout</Code></Link> time expires. |
| `request_timeout` | String | `'Request timed out.'` | Displayed when the [`request_timeout`](parameters.mdx#global-settings) time expires. |
| `vrf_not_associated` | String | `'VRF {vrf_name} is not associated with {device_name}.'` | Displayed when a query request's VRF field value contains a VRF that is not configured or associated with the corresponding location/device. The UI automatically filters out VRFs that are not configured on a selected device, so this error is most likely to appear when using the hyperglass API. `{vrf_name}` and `{device_name}` will be replaced with the VRF in question and corresponding device. |
| `vrf_not_found` | String | `'VRF {vrf_name} is not defined.'` | Displayed when a query VRF is not configured on any devices. The UI only shows configured VRFs, so this error is most likely to appear when using the hyperglass API. `{vrf_name}` will be replaced with the VRF in question. |

View file

@ -5,104 +5,110 @@ sidebar_label: Supported Platforms
description: Platforms supported by hyperglass
---
## HTTP
import Native from "../src/components/Native";
<div class="table--full-width" />
The following platforms use [hyperglass-agent](agent/installation.mdx) for connection handling. When configuring the `nos` property of a device, use the value in the **Key** column.
:::caution hyperglass agent is being deprecated
While hyperglass-agent was a cool idea, maintaining the two codebases and protocol sets has proven cumbersome, with very little benefit. As of **v1.0.0-beta.76**, `frr_ssh` and `bird_ssh` are available for use as replacements.
:::
| Name | Key |
| :-------- | :----- |
| BIRD | `bird` |
| FRRouting | `frr` |
## SSH
The following platforms use [Netmiko](https://github.com/ktbyers/netmiko) or [Scrapli](https://github.com/carlmontanari/scrapli) for connection handling. When configuring the `nos` property of a device, use the value in the **Key** column.
| Name | Key |
| :------------------------ | :-------------------- |
| A10 | `a10` |
| Accedian | `accedian` |
| Alcatel AOS | `alcatel_aos` |
| Alcatel SROS | `alcatel_sros` |
| Apresia Systems AEOS | `apresia_aeos` |
| Arista vEOS | `arista_eos` |
| Aruba | `aruba_os` |
| Avaya ERS | `avaya_ers` |
| Avaya VSP | `avaya_vsp` |
| BIRD | `bird_ssh` |
| Calix B6 | `calix_b6` |
| Check Point GAiA | `checkpoint_gaia` |
| Ciena SAOS | `ciena_saos` |
| Cisco ASA | `cisco_asa` |
| Cisco IOS | `cisco_ios` |
| Cisco NX-OS | `cisco_nxos` |
| Cisco SG-300 | `cisco_s300` |
| Cisco IOS-XE | `cisco_xe` |
| Cisco IOS-XR | `cisco_xr` |
| Citrix Netscaler | `netscaler` |
| CloudGenix ION | `cloudgenix_ion` |
| Coriant | `coriant` |
| Dell OS6 | `dell_os6` |
| Dell OS9 | `dell_os9` |
| Dell OS10 | `dell_os10` |
| Dell PowerConnect | `dell_powerconnect` |
| Endace | `endace` |
| Eltex | `eltex` |
| Eltex ESR | `eltex_esr` |
| Enterasys | `enterasys` |
| Extreme | `extreme` |
| Extreme ERS | `extreme_ers` |
| Extreme EXOS | `extreme_exos` |
| Extreme/Brocade NetIron | `extreme_netiron` |
| Extreme NOS | `extreme_nos` |
| Extreme/Brocade SLX | `extreme_slx` |
| Extreme/Brocade VDX | `extreme_vdx` |
| Extreme VSP | `extreme_vsp` |
| Extreme Wing | `extreme_wing` |
| F5 LTM | `f5_ltm` |
| F5 TMSH | `f5_tmsh` |
| F5 Linux | `f5_linux` |
| Flex VNF | `flexvnf` |
| Fortinet | `fortinet` |
| FRRouting | `frr_ssh` |
| Generic Terminal Server | `generic_termserver` |
| HPE/3COM Comware | `hp_comware` |
| HPE ProCurve | `hp_procurve` |
| Huawei | `huawei` |
| Huawei VRPv8 | `huawei_vrpv8` |
| IPInfusion OcNOS | `ipinfusion_ocnos` |
| Juniper | `juniper` |
| Juniper JunOS | `juniper_junos` |
| Juniper ScreenOS | `juniper_screenos` |
| Keymile | `keymile` |
| Keymile NOS | `keymile_nos` |
| Linux | `linux` |
| Mikrotik RouterOS | `mikrotik_routeros` |
| Mikrotik SwitchOS | `mikrotik_switchos` |
| Mellanox | `mellanox` |
| Mellanox MLNX-OS | `mellanox_mlnxos` |
| MRV LX | `mrv_lx` |
| MRV Optiswitch | `mrv_optiswitch` |
| Nokia SROS | `nokia_sros` |
| OneAccess OneOS | `oneaccess_oneos` |
| OVS Linux | `ovs_linux` |
| Palo Alto Networks PAN-OS | `paloalto_panos` |
| Pluribus | `pluribus` |
| Quanta Mesh | `quanta_mesh` |
| RAD ETX | `rad_etx` |
| Ruckus/Brocade FastIron | `ruckus_fastiron` |
| Ruijie OS | `ruijie_os` |
| TNSR | `tnsr` |
| Ubuiquiti EdgeRouter | `ubiquiti_edge` |
| Ubuiquiti EdgeSwitch | `ubiquiti_edgeswitch` |
| Vyatta VyOS | `vyatta_vyos` |
| VyOS | `vyos` |
Platforms in <Native>green</Native> are natively supported, which means hyperglass has predefined commands built in. Other platforms should also work, you'll just need to [add a custom command profile](commands.mdx).
If needed, for troubleshooting purposes or otherwise, the driver for a given device [can be overridden](adding-devices#all-device-paramters) with the `driver` parameter. Generally, Netmiko is more stable, supports more platforms, but is slower; while Scrapli can be less reliable, supports fewer platforms, but is significantly faster.
| Name | Key | Default Driver |
| :--------------------------------- | :-------------------- | :------------- |
| A10 | `a10` | Netmiko |
| Accedian | `accedian` | Netmiko |
| Alcatel AOS | `alcatel_aos` | Netmiko |
| Alcatel SROS | `alcatel_sros` | Netmiko |
| Apresia Systems AEOS | `apresia_aeos` | Netmiko |
| <Native>Arista vEOS</Native> | `arista_eos` | Netmiko |
| Aruba | `aruba_os` | Netmiko |
| Avaya ERS | `avaya_ers` | Netmiko |
| Avaya VSP | `avaya_vsp` | Netmiko |
| <Native>BIRD</Native> | `bird` | Scrapli |
| Calix B6 | `calix_b6` | Netmiko |
| Check Point GAiA | `checkpoint_gaia` | Netmiko |
| Ciena SAOS | `ciena_saos` | Netmiko |
| Cisco ASA | `cisco_asa` | Netmiko |
| <Native>Cisco IOS</Native> | `cisco_ios` | Scrapli |
| <Native>Cisco NX-OS</Native> | `cisco_nxos` | Scrapli |
| Cisco SG-300 | `cisco_s300` | Netmiko |
| <Native>Cisco IOS-XE</Native> | `cisco_xe` | Scrapli |
| <Native>Cisco IOS-XR</Native> | `cisco_xr` | Scrapli |
| Citrix Netscaler | `netscaler` | Netmiko |
| CloudGenix ION | `cloudgenix_ion` | Netmiko |
| Coriant | `coriant` | Netmiko |
| Dell OS6 | `dell_os6` | Netmiko |
| Dell OS9 | `dell_os9` | Netmiko |
| Dell OS10 | `dell_os10` | Netmiko |
| Dell PowerConnect | `dell_powerconnect` | Netmiko |
| Endace | `endace` | Netmiko |
| Eltex | `eltex` | Netmiko |
| Eltex ESR | `eltex_esr` | Netmiko |
| Enterasys | `enterasys` | Netmiko |
| Extreme | `extreme` | Netmiko |
| Extreme ERS | `extreme_ers` | Netmiko |
| Extreme EXOS | `extreme_exos` | Netmiko |
| Extreme/Brocade NetIron | `extreme_netiron` | Netmiko |
| Extreme NOS | `extreme_nos` | Netmiko |
| Extreme/Brocade SLX | `extreme_slx` | Netmiko |
| Extreme/Brocade VDX | `extreme_vdx` | Netmiko |
| Extreme VSP | `extreme_vsp` | Netmiko |
| Extreme Wing | `extreme_wing` | Netmiko |
| F5 LTM | `f5_ltm` | Netmiko |
| F5 TMSH | `f5_tmsh` | Netmiko |
| F5 Linux | `f5_linux` | Netmiko |
| Flex VNF | `flexvnf` | Netmiko |
| Fortinet | `fortinet` | Netmiko |
| <Native>FRRouting</Native> | `frr` | Scrapli |
| Generic Terminal Server | `generic_termserver` | Netmiko |
| HPE/3COM Comware | `hp_comware` | Netmiko |
| HPE ProCurve | `hp_procurve` | Netmiko |
| <Native>Huawei</Native> | `huawei` | Netmiko |
| Huawei VRPv8 | `huawei_vrpv8` | Netmiko |
| IPInfusion OcNOS | `ipinfusion_ocnos` | Netmiko |
| <Native>Juniper</Native> | `juniper` | Scrapli |
| <Native>Juniper JunOS</Native> | `juniper_junos` | Scrapli |
| Juniper ScreenOS | `juniper_screenos` | Netmiko |
| Keymile | `keymile` | Netmiko |
| Keymile NOS | `keymile_nos` | Netmiko |
| Linux | `linux` | Netmiko |
| <Native>Mikrotik RouterOS</Native> | `mikrotik_routeros` | Netmiko |
| <Native>Mikrotik SwitchOS</Native> | `mikrotik_switchos` | Netmiko |
| Mellanox | `mellanox` | Netmiko |
| Mellanox MLNX-OS | `mellanox_mlnxos` | Netmiko |
| MRV LX | `mrv_lx` | Netmiko |
| MRV Optiswitch | `mrv_optiswitch` | Netmiko |
| <Native>Nokia SROS</Native> | `nokia_sros` | Netmiko |
| OneAccess OneOS | `oneaccess_oneos` | Netmiko |
| OVS Linux | `ovs_linux` | Netmiko |
| Palo Alto Networks PAN-OS | `paloalto_panos` | Netmiko |
| Pluribus | `pluribus` | Netmiko |
| Quanta Mesh | `quanta_mesh` | Netmiko |
| RAD ETX | `rad_etx` | Netmiko |
| Ruckus/Brocade FastIron | `ruckus_fastiron` | Netmiko |
| Ruijie OS | `ruijie_os` | Netmiko |
| <Native>TNSR</Native> | `tnsr` | Scrapli |
| Ubuiquiti EdgeRouter | `ubiquiti_edge` | Netmiko |
| Ubuiquiti EdgeSwitch | `ubiquiti_edgeswitch` | Netmiko |
| Vyatta VyOS | `vyatta_vyos` | Netmiko |
| <Native>VyOS</Native> | `vyos` | Netmiko |
## HTTP
The following platforms use [hyperglass-agent](agent/installation.mdx) for connection handling. When configuring the `nos` property of a device, use the value in the **Key** column.
:::caution hyperglass agent is being deprecated
While hyperglass-agent was a cool idea, maintaining the two codebases and protocol sets has proven cumbersome, with very little benefit. As of **v1.0.0**, `frr_legacy` and `bird_legacy` are temporarily available to mimic previous behavior with hyperglass-agent.
:::
| Name | Key |
| :-------- | :------------ |
| BIRD | `bird_legacy` |
| FRRouting | `frr_legacy` |
## Caveats

View file

@ -20,7 +20,7 @@ While hyperglass is, to the extent possible, fully [asynchronous](https://docs.p
To combat this, hyperglass uses the above worker strategy. **Ultimately, it's important to provision the appropriate number of CPU cores, corresponding to the number of concurrent sessions you might expect to have in your environment** (keeping in mind that if your system supports hyperthreading, each core equates to two workers).
:::note
When [debug](configuration.mdx#global-settings) is set to `true`, the number of workers is set to 1.
When [debug](parameters.mdx#global-settings) is set to `true`, the number of workers is set to 1.
:::
### Memory

View file

@ -6,7 +6,6 @@ keywords: [configuration, api, rest]
description: Configure the REST API
---
import Link from "@docusaurus/Link";
import MiniNote from "../src/components/MiniNote";
import Code from "../src/components/JSXCode";
import PageLink from "../src/components/PageLink";
@ -17,18 +16,18 @@ FastAPI provides built in support for both [Swagger UI](https://swagger.io/tools
## Settings
| Parameter | Type | Default | Description |
| :------------ | :-----: | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enable` | Boolean | `true` | Enable or disable the API documentation. |
| `title` | String | `'{site_title} API Documentation'` | API documentation title. `{site_title}` will be replaced with the <Link to="/docs/configuration#global-settings"><Code>site_title</Code></Link> parameter. |
| `description` | String | | API documentation description appearing below the title. |
| `base_url` | String | `'https://lg.example.net'` | The base URL for your hyperglass site. Used by OpenAPI for dynamically creating hyperlinks. |
| `mode` | String | `'redoc'` | OpenAPI UI library to use for the hyperglass API docs. <MiniNote>Must be <Code>redoc</Code> or <Code>swagger</Code></MiniNote> |
| `uri` | String | `'/api/docs'` | HTTP URI/path where API documentation can be accessed. |
| `openapi_uri` | String | `'/openapi.json'` | Path to the automatically generated `openapi.json` file. |
| `queries` | | | `/queries` endpoint settings <PageLink to="#queries">➡️</PageLink> |
| `query` | | | `/query` endpoint settings <PageLink to="#query">➡️</PageLink> |
| `devices` | | | `/devices` endpoint settings <PageLink to="#devices">➡️</PageLink> |
| Parameter | Type | Default | Description |
| :------------ | :-----: | :--------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
| `enable` | Boolean | `true` | Enable or disable the API documentation. |
| `title` | String | `'{site_title} API Documentation'` | API documentation title. `{site_title}` will be replaced with the [`site_title`](parameters.mdx#global-settings) parameter. |
| `description` | String | | API documentation description appearing below the title. |
| `base_url` | String | `'https://lg.example.net'` | The base URL for your hyperglass site. Used by OpenAPI for dynamically creating hyperlinks. |
| `mode` | String | `'redoc'` | OpenAPI UI library to use for the hyperglass API docs. <MiniNote>Must be <Code>redoc</Code> or <Code>swagger</Code></MiniNote> |
| `uri` | String | `'/api/docs'` | HTTP URI/path where API documentation can be accessed. |
| `openapi_uri` | String | `'/openapi.json'` | Path to the automatically generated `openapi.json` file. |
| `queries` | | | `/queries` endpoint settings <PageLink to="#queries">➡️</PageLink> |
| `query` | | | `/query` endpoint settings <PageLink to="#query">➡️</PageLink> |
| `devices` | | | `/devices` endpoint settings <PageLink to="#devices">➡️</PageLink> |
### `queries`

View file

@ -6,7 +6,6 @@ keywords: [hyperglass, looking glass, web ui, gui, theme, colors, branding]
description: Customize the Web UI
---
import Link from "@docusaurus/Link";
import R from "../../src/components/Required";
import MiniNote from "../../src/components/MiniNote";
import Code from "../../src/components/JSXCode";
@ -14,7 +13,7 @@ import PageLink from "../../src/components/PageLink";
## Build
hyperglass is build with [NextJS](https://nextjs.org/), a [React](https://reactjs.org/)-based UI framework that supports server-side rendering and static exporting, which contribute to hyperglass's speed and SEO-friendliness. At startup, hyperglass creates a new "UI build", which is a static export of the site and includes some elements of the [configuration](parameters).
hyperglass is build with [NextJS](https://nextjs.org/), a [React](https://reactjs.org/)-based UI framework that supports server-side rendering and static exporting, which contribute to hyperglass's speed and SEO-friendliness. At startup, hyperglass creates a new "UI build", which is a static export of the site and includes some elements of the [configuration](parameters.mdx).
This UI build process can be run manually via the hyperglass CLI:
@ -45,8 +44,8 @@ The `web` subsection contains multiple subsections of its own, should you wish t
| `greeting` | Greeting Modal | <PageLink to="#greeting">➡️</PageLink> |
| `logo` | Logo & Favicons | <PageLink to="logo">➡️</PageLink> |
| `opengraph` | [OpenGraph](https://ogp.me/) | <PageLink to="#opengraph">➡️</PageLink> |
| `help_menu` | Help Menu | <PageLink to="#help_menu">➡️</PageLink> |
| `terms` | Terms & Conditions | <PageLink to="#terms">➡️</PageLink> |
| `links` | Footer Links | <PageLink to="#links">➡️</PageLink> |
| `menus` | Footer Menus | <PageLink to="#menus">➡️</PageLink> |
| `text` | Text, title, & names | <PageLink to="text">➡️</PageLink> |
| `theme` | Colors & Fonts | <PageLink to="theme">➡️</PageLink> |
@ -66,13 +65,55 @@ If your organization's policy allows, and you don't mind, I request that you kee
| :-------- | :----: | :------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | String | `'cloudflare'` | DNS over HTTPS provider for in-browser DNS resolution. Cloudflare & Google supported. <MiniNote>Must be <Code>cloudflare</Code> or <Code>google</Code></MiniNote> |
### `external_link`
### `links`
| Parameter | Type | Default | Description |
| :-------- | :-----: | :---------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enable` | Boolean | `true` | Enable or disable the display of an external link |
| `title` | String | `'PeeringDB'` | Link title/label |
| `url` | String | `'https://www.peeringdb.com/asn/{primary_asn}'` | Target URL. `{primary_asn}` will be replaced with the `primary_asn` value from <Link to="/docs/configuration#global-settings">Global Settings</Link> |
Specify an array/list of links to show in the footer. By default, a link to your ASN's PeeringDB page is used.
| Parameter | Type | Default | Description |
| :---------- | :-----: | :---------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | String | `'PeeringDB'` | Link title/label |
| `url` | String | `'https://www.peeringdb.com/asn/{primary_asn}'` | Target URL. `{primary_asn}` will be replaced with the `primary_asn` value from [Global Settings](parameters.mdx#global-settings) |
| `show_icon` | Boolean | `true` | Show an icon on the right side of the link indicating that the link will take the user away from the hyperglass page. |
| `side` | String | `'left'` | Show the link on the `'left'` or `'right'` side of the footer. |
| `order` | Integer | `0` | Specify the order of the links (left to right). During rendering, `links` and [`menus`](#menus) are merged, so the order is used to sort both. |
#### Example
```yaml title="hyperglass.yaml"
web:
links:
- title: Website
url: https://www.example.com
side: right
order: 5
- title: PeeringDB
url: https://www.peeringdb.com/asn/{primary_asn}
side: left
order: 2
```
### `menus`
Specify an array/list of menus to show in the footer. A menu's content can be plain text or markdown. By default, generic help and terms & conditions menus are shown.
| Parameter | Type | Default | Description |
| :-------- | :-----: | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | String | | Menu title/label |
| `content` | String | | Menu content. Can be any string content, or a path to a plain text or markdown file. |
| `side` | String | `'left'` | Show the menu on the `'left'` or `'right'` side of the footer. |
| `order` | Integer | `0` | Specify the order of the links (left to right). During rendering, [`links`](#links) and `menus` are merged, so the order is used to sort both. |
#### Example
```yaml title="hyperglass.yaml"
web:
menus:
- title: Help
content: "Please contact [support@example.com](mailto:support@example.com) to get support."
- title: Terms & Conditions
content: /etc/hyperglass/terms-and-conditions.md
side: right
```
### `greeting`
@ -96,19 +137,3 @@ By default, [this Opengraph image](/opengraph.jpg) is set. If you define one wit
| Parameter | Type | Description |
| :-------- | :----: | :---------------------- |
| `image` | String | Path to opengraph image |
### `help_menu`
| Parameter | Type | Default | Description |
| :-------- | :-----: | :------- | :------------------------------------------------------------------------------------------- |
| `enable` | Boolean | `true` | Enable or display the display of the help menu |
| `file` | String | | Path to a plain text or markdown file with content to override the default help menu content |
| `title` | String | `'Help'` | Help menu title |
### `terms`
| Parameter | Type | Default | Description |
| :-------- | :-----: | :-------- | :-------------------------------------------------------------------------------------------- |
| `enable` | Boolean | `true` | Enable or display the display of terms & conditions |
| `file` | String | | Path to a plain text or markdown file with content to override the default terms & conditions |
| `title` | String | `'Terms'` | Terms & conditions title |

View file

@ -7,7 +7,7 @@ keywords:
description: See how to the hyperglass UI should be configured.
---
## Example
This is an example of every UI configuration option. You don't need to specify any of these for hyperglass to work; all parameters are optional.
```yaml title="hyperglass.yaml"
web:
@ -15,46 +15,67 @@ web:
enable: true
dns_provider:
name: cloudflare
external_link:
url: https://cloudflare-dns.com/dns-query
greeting:
button: Continue
enable: true
title: PeeringDB
url: https://www.peeringdb.com/asn/{primary_asn}
help_menu:
enable: true
file: null
title: Help
file: /etc/hyperglass/greeting.md
required: true
title: Welcome
links:
- order: 0
show_icon: true
side: left
title: PeeringDB
url: https://www.peeringdb.com/asn/{primary_asn}
logo:
dark: /home/ubuntu/hyperglass/hyperglass-light.svg
light: /home/ubuntu/hyperglass/hyperglass-dark.svg
favicon: /home/ubuntu/hyperglass/hyperglass-icon.svg
dark: /Users/ml/dev/hyperglass/hyperglass/images/hyperglass-dark.svg
favicon: /Users/ml/dev/hyperglass/hyperglass/images/hyperglass-icon.svg
height: null
width: "75%"
light: /Users/ml/dev/hyperglass/hyperglass/images/hyperglass-light.svg
width: 100%
menus:
- content: /etc/hyperglass/terms.md
order: 0
side: left
title: Terms
- content: /etc/hyperglass/help.md
order: 0
side: left
title: Help
opengraph:
image: /home/ubuntu/hyperglass/hyperglass-opengraph.png
terms:
enable: true
file: null
title: Terms
image: /etc/hyperglass/opengraph.png
text:
cache: Results will be cached for 2 minutes.
fqdn_tooltip: "Use {protocol}"
cache_icon: Cached from {time} UTC
cache_prefix: "Results cached for "
complete_time: Completed in {seconds}
fqdn_error: Unable to resolve {fqdn}
fqdn_error_button: Try Again
fqdn_message: Your browser has resolved {fqdn} to
fqdn_tooltip: Use {protocol}
no_communities: No Communities
query_location: Location
query_target: Target
query_type: Query Type
query_vrf: Routing Table
subtitle: AS65001
rpki_invalid: Invalid
rpki_unknown: No ROAs Exist
rpki_unverified: Not Verified
rpki_valid: Valid
subtitle: Network Looking Glass
title: hyperglass
title_mode: text_only
title_mode: logo_subtitle
theme:
default_color_mode: light
colors:
black: "#121212"
black: "#000"
blue: "#314cb6"
cyan: "#118ab2"
danger: "#d84b4b"
dark: "#010101"
error: "#ff6b35"
gray: "#c1c7cc"
green: "#35b246"
light: "#f5f6f7"
orange: "#ff6b35"
pink: "#f2607d"
primary: "#118ab2"
@ -64,8 +85,9 @@ web:
success: "#35b246"
teal: "#35b299"
warning: "#edae49"
white: "#f7f7f7"
white: "#fff"
yellow: "#edae49"
default_color_mode: null
fonts:
body: Nunito
mono: Fira Code

View file

@ -31,9 +31,9 @@ The `title_mode` parameter behaves in the following manner:
<div className="table--full-width" />
| Mode | Behavior |
| --------------- | ------------------------------------------------------------------- |
| `text_only` | Shows the [`title`](#text) and [`subtitle`](#text) only. |
| `logo_only` | Shows the [`logo`](ui/logo) only. |
| `logo_subtitle` | Shows the [`logo`](ui/logo) and [`subtitle`](#text) only. |
| `all` | Shows the [`logo`](ui/logo), [`title`](#text), [`subtitle`](#text). |
| Mode | Behavior |
| --------------- | ----------------------------------------------------------------------- |
| `text_only` | Shows the [`title`](#text) and [`subtitle`](#text) only. |
| `logo_only` | Shows the [`logo`](ui/logo.mdx) only. |
| `logo_subtitle` | Shows the [`logo`](ui/logo.mdx) and [`subtitle`](#text) only. |
| `all` | Shows the [`logo`](ui/logo.mdx), [`title`](#text), [`subtitle`](#text). |

View file

@ -5,30 +5,38 @@ const { googleTrackingId, algoliaKey } = process.env;
const docusaurusConfig = {
title: "hyperglass",
tagline: "the network looking glass that tries to make the internet better.",
url: "https://hyperglass.io",
url: "https://hyperglass.dev",
baseUrl: "/",
favicon: "img/favicon.ico",
organizationName: "checktheroads",
projectName: "hyperglass",
themeConfig: {
image: "opengraph.jpg",
googleAnalytics: {
trackingID: googleTrackingId || " ",
anonymizeIP: false,
},
algolia: {
apiKey: algoliaKey || "dev",
indexName: "hyperglass",
},
prism: {
additionalLanguages: ["shell-session", "ini", "nginx", "yaml"],
theme: require("./src/prism-dracula"),
},
navbar: {
links: [
items: [
{ to: "docs/introduction", label: "Docs", position: "left" },
{ to: "screenshots", label: "Screenshots", position: "left" },
{ href: "https://demo.hyperglass.io", label: "Demo", position: "left" },
{
href: "https://demo.hyperglass.dev",
label: "Demo",
position: "left",
},
{
href: githubURL,
position: "right",
className: "header-github-link",
"aria-label": "GitHub Repository",
},
],
},
footer: {
style: "dark",
links: [
{
title: "Docs",
@ -50,18 +58,14 @@ const docusaurusConfig = {
{
title: "Community",
items: [
{
label: "Slack",
href: "https://netdev.chat",
},
{
label: "Telegram",
href: "https://t.me/hyperglasslg",
},
{
label: "Gitter",
href: "https://gitter.im/hyperglass",
},
{
label: "Keybase",
href: "https://keybase.io/team/hyperglass",
},
],
},
{
@ -89,12 +93,11 @@ const docusaurusConfig = {
editUrl: githubURL + "/edit/v1.0.0/docs/",
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
customCss: [require.resolve("./src/css/custom.css")],
},
},
],
],
plugins: ["@docusaurus/plugin-google-analytics"],
};
module.exports = docusaurusConfig;

View file

@ -18,7 +18,7 @@ const prettier = require("prettier");
console.log("Added entry to sitemap:", path, route);
return `
<url>
<loc>${`https://hyperglass.io/${route}`}</loc>
<loc>${`https://hyperglass.dev/${route}`}</loc>
</url>
`;
})

View file

@ -1,22 +1,30 @@
{
"name": "docs",
"version": "0.0.0",
"name": "hyperglass-docs",
"version": "1.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy"
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "^2.0.0-alpha.58",
"@docusaurus/preset-classic": "^2.0.0-alpha.58",
"classnames": "^2.2.6",
"@docusaurus/core": "^2.0.0-beta.0",
"@docusaurus/preset-classic": "^2.0.0-beta.0",
"@mdx-js/react": "^1.6.22",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.1.1",
"globby": "^11.0.1",
"prismjs": "^1.19.0",
"prop-types": "^15.7.2",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"url-loader": "^4.1.1",
"use-media": "^1.4.0"
},
"browserslist": {
@ -32,7 +40,13 @@
]
},
"devDependencies": {
"prettier": "^2.0.5"
"@docusaurus/module-type-aliases": "^2.0.0-beta.0",
"@tsconfig/docusaurus": "^1.0.2",
"@types/react": "^17.0.8",
"@types/react-helmet": "^6.1.1",
"@types/react-router-dom": "^5.1.7",
"prettier": "^2.0.5",
"typescript": "^4.3.2"
},
"prettier": {
"[yaml]": {

View file

@ -1,9 +0,0 @@
import React from "react";
import styles from "./styles.module.css";
export default ({ hex }) => (
<div className={styles.colorCode}>
<span style={{ backgroundColor: hex }} className={styles.color} />
<span className={styles.code}>{hex}</span>
</div>
);

View file

@ -0,0 +1,18 @@
import * as React from "react";
import styles from "./styles.module.css";
type ColorProps = {
hex: string;
};
const Color = (props: React.PropsWithChildren<ColorProps>): JSX.Element => {
const { hex } = props;
return (
<div className={styles.colorCode}>
<span style={{ backgroundColor: hex }} className={styles.color} />
<span className={styles.code}>{hex}</span>
</div>
);
};
export default Color;

View file

@ -1,23 +0,0 @@
import * as React from "react";
export const Moon = ({ color, size = "1.5rem", ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
style={{
height: size,
width: size,
}}
strokeWidth={0}
stroke="currentColor"
fill="currentColor"
{...props}
>
<path
d="M14.53 10.53a7 7 0 01-9.058-9.058A7.003 7.003 0 008 15a7.002 7.002 0 006.53-4.47z"
fill={color || "currentColor"}
fillRule="evenodd"
clipRule="evenodd"
/>
</svg>
);

View file

@ -1,21 +0,0 @@
import * as React from "react";
export const Sun = ({ color, size = "1.5rem", ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
style={{
height: size,
width: size,
}}
strokeWidth={0}
stroke="currentColor"
fill="currentColor"
{...props}
>
<path
d="M256 32a224 224 0 00-161.393 69.035h323.045A224 224 0 00256 32zM79.148 118.965a224 224 0 00-16.976 25.16H449.74a224 224 0 00-16.699-25.16H79.148zm-27.222 45.16A224 224 0 0043.3 186.25h425.271a224 224 0 00-8.586-22.125H51.926zM36.783 210.25a224 224 0 00-3.02 19.125h444.368a224 224 0 00-3.113-19.125H36.783zm-4.752 45.125A224 224 0 0032 256a224 224 0 00.64 16.5h446.534A224 224 0 00480 256a224 224 0 00-.021-.625H32.03zm4.67 45.125a224 224 0 003.395 15.125h431.578a224 224 0 003.861-15.125H36.701zm14.307 45.125a224 224 0 006.017 13.125H454.82a224 224 0 006.342-13.125H51.008zm26.316 45.125a224 224 0 009.04 11.125H425.86a224 224 0 008.727-11.125H77.324zm45.62 45.125A224 224 0 00136.247 445h239.89a224 224 0 0012.936-9.125h-266.13z"
fill={color || "currentColor"}
/>
</svg>
);

View file

@ -1,30 +0,0 @@
import * as React from "react";
import classnames from "classnames";
import { Sun } from "./Sun";
import { Moon } from "./Moon";
import styles from "./styles.module.css";
const iconMap = { true: Moon, false: Sun };
export const ColorModeToggle = ({ isDark, toggle, ...props }) => {
const Icon = iconMap[isDark];
const handleToggle = () => {
if (isDark) {
return toggle(false);
} else {
return toggle(true);
}
};
const label = `${isDark ? "Hurt" : "Rest"} your eyes`;
return (
<button
aria-label={label}
title={label}
className={classnames(styles.colorModeToggle)}
onClick={handleToggle}
{...props}
>
<Icon />
</button>
);
};

View file

@ -1,40 +0,0 @@
.colorModeToggle {
color: var(--ifm-color-black);
box-sizing: border-box;
background-color: transparent;
border-radius: var(--ifm-button-border-radius);
border: 1px solid transparent;
transition: color var(--ifm-button-transition-duration)
cubic-bezier(0.08, 0.52, 0.52, 1),
background var(--ifm-button-transition-duration)
cubic-bezier(0.08, 0.52, 0.52, 1),
border-color var(--ifm-button-transition-duration)
cubic-bezier(0.08, 0.52, 0.52, 1);
cursor: pointer;
display: inline-flex;
font-size: calc(0.875rem * var(--ifm-button-size-multiplier));
line-height: 1.5;
outline: 0;
text-align: center;
text-decoration: none;
user-select: none;
vertical-align: middle;
white-space: nowrap;
padding: 6px 12px;
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.colorModeToggle:hover {
border: 1px solid var(--ifm-color-black);
color: var(--ifm-color-gray-700);
}
html[data-theme="dark"] .colorModeToggle {
color: var(--ifm-color-white);
}
html[data-theme="dark"] .colorModeToggle:hover {
border: 1px solid var(--ifm-color-gray-500);
color: var(--ifm-color-gray-500);
}

View file

@ -1,7 +0,0 @@
import React from "react";
export default ({ year = false, ...props }) => {
const date = new Date();
const granularity = year ? date.getFullYear() : date.toString();
return <span {...props}>{granularity}</span>;
};

View file

@ -0,0 +1,16 @@
import * as React from "react";
type DatetimeProps = {
year?: boolean;
};
const Datetime = (
props: React.PropsWithChildren<DatetimeProps>
): JSX.Element => {
const { year } = props;
const date = new Date();
const granularity = year ? date.getFullYear() : date.toString();
return <span {...props}>{granularity}</span>;
};
export default Datetime;

View file

@ -1,12 +0,0 @@
import React from "react";
import classnames from "classnames";
import styles from "./fonts.module.css";
export default ({ name }) => {
const fontClass = { Nunito: "fontBody", "Fira Code": "fontMono" };
return (
<a href={`https://fonts.google.com/specimen/${name.split(" ").join("+")}`}>
<span className={classnames(styles.font, styles[fontClass[name]])}>{name}</span>
</a>
);
};

View file

@ -0,0 +1,19 @@
import * as React from "react";
import clsx from "clsx";
import styles from "./fonts.module.css";
type FontProps = {
name: string;
};
const Font = (props: React.PropsWithChildren<FontProps>): JSX.Element => {
const { name } = props;
const fontClass = { Nunito: "fontBody", "Fira Code": "fontMono" };
return (
<a href={`https://fonts.google.com/specimen/${name.split(" ").join("+")}`}>
<span className={clsx(styles.font, styles[fontClass[name]])}>{name}</span>
</a>
);
};
export default Font;

View file

@ -1,47 +0,0 @@
import * as React from "react";
import classnames from "classnames";
import useThemeContext from "@theme/hooks/useThemeContext";
import styles from "./styles.module.css";
const Icon = ({ color, size = "1.5rem", ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
style={{
height: size,
width: size,
}}
strokeWidth={0}
stroke="currentColor"
fill="currentColor"
{...props}
>
<path
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"
fill={color || "currentColor"}
fillRule="evenodd"
/>
</svg>
);
export const GithubButton = ({ href, ...props }) => {
const { isDarkTheme } = useThemeContext();
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className={classnames(
styles.githubButton,
isDarkTheme ? "button--secondary" : "button--primary",
"button button--outline"
)}
aria-label="Github"
title="Github"
{...props}
>
<Icon />
</a>
);
};

View file

@ -1,46 +0,0 @@
/* @media screen and (max-width: 997px) {
.githubButton {
display: none !important;
}
} */
.githubButton {
color: var(--ifm-color-black);
box-sizing: border-box;
background-color: transparent;
border-radius: var(--ifm-button-border-radius);
border: 1px solid transparent;
transition: color var(--ifm-button-transition-duration)
cubic-bezier(0.08, 0.52, 0.52, 1),
background var(--ifm-button-transition-duration)
cubic-bezier(0.08, 0.52, 0.52, 1),
border-color var(--ifm-button-transition-duration)
cubic-bezier(0.08, 0.52, 0.52, 1);
cursor: pointer;
display: inline-flex;
font-size: calc(0.875rem * var(--ifm-button-size-multiplier));
line-height: 1.5;
outline: 0;
text-align: center;
text-decoration: none;
user-select: none;
vertical-align: middle;
white-space: nowrap;
padding: 6px 12px;
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.githubButton:hover {
border: 1px solid var(--ifm-color-black);
color: var(--ifm-color-gray-700);
}
html[data-theme="dark"] .githubButton {
color: var(--ifm-color-white);
}
html[data-theme="dark"] .githubButton:hover {
border: 1px solid var(--ifm-color-gray-500);
color: var(--ifm-color-gray-500);
}

View file

@ -1,4 +0,0 @@
import React from "react";
import styles from "./styles.module.css";
export default ({ children }) => <span className={styles.code}>{children}</span>;

View file

@ -0,0 +1,8 @@
import * as React from "react";
import styles from "./styles.module.css";
const JSXCode = (props: React.ComponentProps<"span">): JSX.Element => {
return <span className={styles.code} {...props} />;
};
export default JSXCode;

View file

@ -1,17 +0,0 @@
import React from "react";
export default ({ children, newLine = true }) => (
<>
{newLine && <br />}
<span
style={{
fontSize: "var(--ifm-font-size-sm)",
color: "var(--ifm-blockquote-color)",
display: "inline-block",
fontStyle: "italic"
}}
>
{children}
</span>
</>
);

View file

@ -0,0 +1,28 @@
import * as React from "react";
type MiniNodeProps = {
newLine: boolean;
};
const MiniNote = (
props: React.PropsWithChildren<MiniNodeProps>
): JSX.Element => {
const { newLine, children } = props;
return (
<>
{newLine && <br />}
<span
style={{
fontSize: "var(--ifm-font-size-sm)",
color: "var(--ifm-blockquote-color)",
display: "inline-block",
fontStyle: "italic",
}}
>
{children}
</span>
</>
);
};
export default MiniNote;

View file

@ -0,0 +1,9 @@
import * as React from "react";
import styles from "./styles.module.css";
const Native = (props: React.ComponentProps<"span">): JSX.Element => {
const { children } = props;
return <span className={styles.Native}>{children}</span>;
};
export default Native;

View file

@ -1,8 +0,0 @@
import React from "react";
import Link from "@docusaurus/Link";
export default ({ children, to }) => (
<Link to={to} style={{ textDecoration: "unset" }}>
{children}
</Link>
);

View file

@ -0,0 +1,10 @@
import * as React from "react";
import Link from "@docusaurus/Link";
import type { LinkProps } from "@docusaurus/Link";
const PageLink = (props: React.PropsWithChildren<LinkProps>): JSX.Element => {
return <Link style={{ textDecoration: "unset" }} {...props} />;
};
export default PageLink;

View file

@ -1,15 +0,0 @@
import React from "react";
import Code from "./JSXCode";
const patternMap = {
aspath_asdot: String.raw`^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$`,
aspath_asplain: String.raw`^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$`,
community_decimal: String.raw`^[0-9]{1,10}$`,
community_extended: String.raw`^([0-9]{0,5})\:([0-9]{1,5})$`,
community_large: String.raw`^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$`
};
export default ({ pattern }) => {
const thisPattern = patternMap[pattern];
return <Code>'{thisPattern}'</Code>;
};

View file

@ -0,0 +1,25 @@
import * as React from "react";
import { useMemo } from "react";
import Code from "./JSXCode";
const PATTERN_MAP = {
aspath_asdot: String.raw`^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$`,
aspath_asplain: String.raw`^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$`,
community_decimal: String.raw`^[0-9]{1,10}$`,
community_extended: String.raw`^([0-9]{0,5})\:([0-9]{1,5})$`,
community_large: String.raw`^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$`,
};
type RegexPatternProps = {
pattern: keyof typeof PATTERN_MAP;
};
const RegexPattern = (
props: React.PropsWithChildren<RegexPatternProps>
): JSX.Element => {
const { pattern } = props;
const thisPattern = useMemo<string>(() => PATTERN_MAP[pattern], [pattern]);
return <Code>'{thisPattern}'</Code>;
};
export default RegexPattern;

View file

@ -1,5 +0,0 @@
import React from "react";
export default () => (
<span style={{ color: "var(--ifm-color-danger)", display: "inline-block" }}>*</span>
);

View file

@ -0,0 +1,11 @@
import * as React from "react";
const Required = (): JSX.Element => {
return (
<span style={{ color: "var(--ifm-color-danger)", display: "inline-block" }}>
*
</span>
);
};
export default Required;

View file

@ -1,25 +1,31 @@
.code {
background-color: var(--ifm-code-background);
color: var(--ifm-code-color);
font-family: var(--ifm-font-family-monospace);
font-size: var(--ifm-code-font-size);
border-radius: var(--ifm-code-border-radius);
margin: 0;
padding: var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal);
font-style: normal;
background-color: var(--ifm-code-background);
color: var(--ifm-code-color);
font-family: var(--ifm-font-family-monospace);
font-size: var(--ifm-code-font-size);
border-radius: var(--ifm-code-border-radius);
margin: 0;
padding: var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal);
font-style: normal;
}
.color {
border-radius: var(--ifm-global-radius);
display: inline-flex;
height: 1.5rem;
width: 1.5rem;
margin-right: 0.5rem;
padding: var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal);
border-radius: var(--ifm-global-radius);
display: inline-flex;
height: 1.5rem;
width: 1.5rem;
margin-right: 0.5rem;
padding: var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal);
}
.colorCode {
display: flex;
align-items: center;
justify-content: space-between;
display: flex;
align-items: center;
justify-content: space-between;
}
.Native {
/* color: var(--ifm-link-color); */
color: var(--ifm-color-success);
font-weight: bold;
}

View file

@ -24,6 +24,13 @@
--ifm-color-primary-light: #ff807e;
--ifm-color-primary-lighter: #ff918f;
--ifm-color-primary-lightest: #ffc4c3;
--ifm-color-success: #35b246;
--ifm-color-success-dark: #30a03f;
--ifm-color-success-darker: #2d973b;
--ifm-color-success-darkest: #257d31;
--ifm-color-success-light: #3ac44d;
--ifm-color-success-lighter: #43c755;
--ifm-color-success-lightest: #5dcf6d;
--ifm-code-font-size: 95%;
--ifm-font-family-base: "Nunito", system-ui, -apple-system, Segoe UI, Roboto,
Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, "Segoe UI",
@ -32,6 +39,9 @@
--ifm-font-family-monospace: "Fira Code", SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
--ifm-code-padding-vertical: 0.05rem;
--hgd-footer-link-color: var(--ifm-color-emphasis-700);
--hgd-link-hover-color: var(--ifm-color-secondary-darker);
--ifm-link-color: var(--ifm-color-secondary);
}
:root {
@ -54,6 +64,12 @@ html[data-theme="dark"]:root {
--ifm-navbar-background-color: var(--ifm-background-color);
--ifm-menu-color-active: var(--ifm-color-primary);
--ifm-blockquote-color: var(--ifm-color-emphasis-400);
--ifm-link-color: var(--ifm-color-primary);
--hgd-footer-link-color: var(--ifm-color-emphasis-400);
--hgd-link-hover-color: var(--ifm-color-primary-darker);
--ifm-code-background: rgba(255, 255, 255, 0.08);
--ifm-code-color: var(--ifm-color-secondary-lightest);
--ifm-footer-link-color: ;
}
.admonition.alert {
@ -92,11 +108,6 @@ html[data-theme="dark"]:root .admonition.alert.admonition-note {
opacity: 0.9;
}
html[data-theme="dark"] .footer.footer--dark {
--ifm-footer-color: var(--ifm-color-emphasis-400);
--ifm-navbar-background-color: var(--ifm-background-color);
}
.footer.footer--dark {
--ifm-footer-color: var(--ifm-color-emphasis-600);
--ifm-footer-link-color: var(--ifm-footer-color);
@ -108,14 +119,8 @@ html[data-theme="dark"] .footer.footer--dark {
font-size: 0.8rem;
}
html[data-theme="dark"] {
--ifm-code-background: rgba(255, 255, 255, 0.08);
--ifm-code-color: var(--ifm-color-secondary-lightest);
--ifm-footer-link-color: ;
/* #ace8cd
#b6a7e2
#dc7381
*/
.footer .footer__items .footer__item .footer__link-item {
color: var(--hgd-footer-link-color);
}
.admonition {
@ -167,20 +172,13 @@ h6 code {
color: var(--ifm-color-primary);
}
a {
color: var(--ifm-color-secondary);
}
.contents a.contents__link--active[class] {
color: var(--ifm-color-secondary);
font-weight: bold;
}
a:hover {
color: var(--ifm-color-secondary-darker);
}
html[data-theme="dark"] a {
color: var(--ifm-color-primary);
color: var(--hgd-link-hover-color);
}
html[data-theme="dark"] .contents a.contents__link--active[class] {
@ -188,14 +186,14 @@ html[data-theme="dark"] .contents a.contents__link--active[class] {
font-weight: bold;
}
html[data-theme="dark"] a:hover {
color: var(--ifm-color-primary-light);
}
html[data-theme="dark"] .navbar .navbar__items a.navbar__brand {
color: var(--ifm-color-primary);
}
nav.navbar.navbar--fixed-top {
border-bottom: 1px solid var(--ifm-toc-border-color);
}
.navbar .navbar__items a.navbar__brand {
color: var(--ifm-color-black);
}
@ -211,6 +209,9 @@ html[data-theme="dark"] .navbar .navbar__items a.navbar__brand {
width: 100%;
display: block;
}
.navbar .navbar__inner .navbar__items a.navbar__brand img.logo-at-home {
display: none;
}
}
.navbar .navbar__search .navbar__search-input {
@ -250,3 +251,41 @@ html[data-theme="dark"]
.table-of-contents .table-of-contents__link {
color: var(--ifm-font-color-base);
}
.table-of-contents .table-of-contents__link:hover {
color: var(--hgd-link-hover-color);
}
.footer .footer__col:first-child {
text-align: left;
}
.footer .footer__col:not(:first-child):not(:last-child) {
text-align: center;
}
.footer .footer__col:last-child {
text-align: right;
}
.header-github-link {
transition: opacity 0.2s ease-in-out;
}
.header-github-link:hover {
opacity: 0.75;
}
.header-github-link:before {
content: "";
width: 24px;
height: 24px;
display: flex;
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
no-repeat;
}
html[data-theme="dark"] .header-github-link:before {
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
no-repeat;
}

1
docs/src/hooks/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./useLogoSrc";

View file

@ -0,0 +1,38 @@
import { useMemo } from "react";
import useBaseUrl from "@docusaurus/useBaseUrl";
import { useLocation } from "@docusaurus/router";
import useMedia from "use-media";
type UseLogoSrc = {
sources: { light: string; dark: string };
className: string | null;
};
export function useLogoSrc(): UseLogoSrc {
let className = null;
const { pathname } = useLocation();
const isSmall = useMedia({ maxWidth: "997px" });
if (pathname === "/") {
className = "logo-at-home";
}
const sourcesIcon = {
light: useBaseUrl("hyperglass-icon-light.svg"),
dark: useBaseUrl("hyperglass-icon-dark.svg"),
};
const sourcesFull = {
light: useBaseUrl("hyperglass-light.svg"),
dark: useBaseUrl("hyperglass-dark.svg"),
};
const sources = useMemo(() => {
if (isSmall) {
return sourcesFull;
}
if (!isSmall && pathname === "/") {
return sourcesIcon;
}
return sourcesFull;
}, [isSmall, pathname]);
return { sources, className };
}

View file

@ -1,5 +1,5 @@
import React from "react";
import classnames from "classnames";
import clsx from "clsx";
import Layout from "@theme/Layout";
import Link from "@docusaurus/Link";
import useBaseUrl from "@docusaurus/useBaseUrl";
@ -27,12 +27,10 @@ function Home() {
"reactjs",
]}
>
<header className={classnames("hero", styles.heroBanner)}>
<header className={clsx("hero", styles.heroBanner)}>
<div className="container">
<h1 className={classnames("hero__title", styles.title)}>
hyperglass
</h1>
<h2 className={classnames("hero__subtitle", styles.subTitle)}>
<h1 className={clsx("hero__title", styles.title)}>hyperglass</h1>
<h2 className={clsx("hero__subtitle", styles.subTitle)}>
the <span className={styles.tagPrimary}>network looking glass</span>{" "}
that tries to
<span className={styles.tagSecondary}>
@ -43,7 +41,7 @@ function Home() {
</h2>
<div className={styles.buttons}>
<Link
className={classnames(
className={clsx(
"button button--outline button--secondary button--lg",
styles.homeBtn,
styles.btnSecondary
@ -53,7 +51,7 @@ function Home() {
Set up your Looking Glass
</Link>
<Link
className={classnames(
className={clsx(
"button button--outline button--primary button--lg",
styles.homeBtn,
styles.btnPrimary
@ -69,16 +67,16 @@ function Home() {
<section className={styles.content}>
<div className="container">
<div className="row">
<div className={classnames("col col--4")}>
<div className={clsx("col col--4")}>
<section className={styles.content}>
<div className="container">
<div className="row">
<div className={classnames("col col--12")}></div>
<div className={clsx("col col--12")}></div>
</div>
</div>
</section>
</div>
<div className={classnames("col col--8")}></div>
<div className={clsx("col col--8")}></div>
</div>
</div>
</section>

View file

@ -1,5 +1,5 @@
import * as React from "react";
import classnames from "classnames";
import clsx from "clsx";
import Layout from "@theme/Layout";
import Link from "@docusaurus/Link";
import useBaseUrl from "@docusaurus/useBaseUrl";
@ -11,13 +11,11 @@ function Screenshots() {
description="hyperglass screenshots"
keywords={["hyperglass", "screenshots"]}
>
<header className={classnames("hero", styles.heroBanner)}>
<div className={classnames("container", styles.smallerTitleContainer)}>
<h1 className={classnames("hero__title", styles.title)}>
Coming Soon!
</h1>
<header className={clsx("hero", styles.heroBanner)}>
<div className={clsx("container", styles.smallerTitleContainer)}>
<h1 className={clsx("hero__title", styles.title)}>Coming Soon!</h1>
<h2
className={classnames(
className={clsx(
"hero__subtitle",
styles.subTitle,
styles.smallerTitle
@ -29,7 +27,7 @@ function Screenshots() {
</h2>
<div className={styles.buttons}>
<Link
className={classnames(
className={clsx(
"button button--outline button--secondary button--lg",
styles.homeBtn,
styles.btnSecondary
@ -45,16 +43,16 @@ function Screenshots() {
<section className={styles.content}>
<div className="container">
<div className="row">
<div className={classnames("col col--4")}>
<div className={clsx("col col--4")}>
<section className={styles.content}>
<div className="container">
<div className="row">
<div className={classnames("col col--12")}></div>
<div className={clsx("col col--12")}></div>
</div>
</div>
</section>
</div>
<div className={classnames("col col--8")}></div>
<div className={clsx("col col--8")}></div>
</div>
</div>
</section>

View file

@ -1,6 +1,6 @@
// @flow
export default {
module.exports = {
plain: {
color: "rgb(241, 250, 140)",
backgroundColor: "#282A36",

View file

@ -1,84 +0,0 @@
// Original: https://raw.githubusercontent.com/PrismJS/prism-themes/master/themes/prism-ghcolors.css
var theme = {
plain: {
color: "#393A34",
backgroundColor: "#f6f8fa",
},
styles: [
{
types: ["comment", "prolog", "doctype", "cdata"],
style: {
color: "#999988",
fontStyle: "italic",
},
},
{
types: ["namespace"],
style: {
opacity: 0.7,
},
},
{
types: ["string", "attr-value"],
style: {
color: "#e3116c",
},
},
{
types: ["punctuation", "operator"],
style: {
color: "#393A34",
},
},
{
types: [
"entity",
"url",
"symbol",
"number",
"boolean",
"variable",
"constant",
"property",
"regex",
"inserted",
],
style: {
color: "#36acaa",
},
},
{
types: ["atrule", "keyword", "attr-name", "selector"],
style: {
color: "#00a4db",
},
},
{
types: ["function", "deleted", "tag"],
style: {
color: "#d73a49",
},
},
{
types: ["function-variable"],
style: {
color: "#6f42c1",
},
},
{
types: ["tag", "selector", "keyword"],
style: {
color: "#00009f",
},
},
{
types: ["important"],
style: {
color: "#d73a49",
fontStyle: "italic",
},
},
],
};
export default theme;

View file

@ -1,277 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
import React, { useEffect, useState, useRef } from "react";
import classnames from "classnames";
import Highlight, { defaultProps } from "prism-react-renderer";
import Prism from "prism-react-renderer/prism";
import darkTheme from "./dracula";
import lightTheme from "./github";
import Clipboard from "clipboard";
import rangeParser from "parse-numeric-range";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import useThemeContext from "@theme/hooks/useThemeContext";
import styles from "./styles.module.css";
const highlightLinesRangeRegex = /{([\d,-]+)}/;
const getHighlightDirectiveRegex = (
languages = ["js", "jsBlock", "jsx", "python", "html", "ini"]
) => {
// supported types of comments
const comments = {
js: {
start: "\\/\\/",
end: "",
},
jsBlock: {
start: "\\/\\*",
end: "\\*\\/",
},
jsx: {
start: "\\{\\s*\\/\\*",
end: "\\*\\/\\s*\\}",
},
python: {
start: "#",
end: "",
},
html: {
start: "<!--",
end: "-->",
},
ini: { start: "#", end: "" },
};
// supported directives
const directives = [
"highlight-next-line",
"highlight-start",
"highlight-end",
].join("|");
// to be more reliable, the opening and closing comment must match
const commentPattern = languages
.map(
(lang) =>
`(?:${comments[lang].start}\\s*(${directives})\\s*${comments[lang].end})`
)
.join("|");
// white space is allowed, but otherwise it should be on it's own line
return new RegExp(`^\\s*(?:${commentPattern})\\s*$`);
};
// select comment styles based on language
const highlightDirectiveRegex = (lang) => {
switch (lang) {
case "js":
case "javascript":
case "ts":
case "typescript":
return getHighlightDirectiveRegex(["js", "jsBlock"]);
case "jsx":
case "tsx":
return getHighlightDirectiveRegex(["js", "jsBlock", "jsx"]);
case "html":
return getHighlightDirectiveRegex(["js", "jsBlock", "html"]);
case "python":
case "py":
return getHighlightDirectiveRegex(["python"]);
case "ini":
return getHighlightDirectiveRegex(["ini"]);
default:
// all comment types
return getHighlightDirectiveRegex();
}
};
const codeBlockTitleRegex = /title=".*"/;
export default ({ children, className: languageClassName, metastring }) => {
(typeof global !== "undefined" ? global : window).Prism = Prism;
require("prismjs/components/prism-shell-session");
require("prismjs/components/prism-nginx");
require("prismjs/components/prism-ini");
const {
siteConfig: {
themeConfig: { prism = {} },
},
} = useDocusaurusContext();
const [showCopied, setShowCopied] = useState(false);
const [mounted, setMounted] = useState(false);
// The Prism theme on SSR is always the default theme but the site theme
// can be in a different mode. React hydration doesn't update DOM styles
// that come from SSR. Hence force a re-render after mounting to apply the
// current relevant styles. There will be a flash seen of the original
// styles seen using this current approach but that's probably ok. Fixing
// the flash will require changing the theming approach and is not worth it
// at this point.
useEffect(() => {
setMounted(true);
}, []);
const target = useRef(null);
const button = useRef(null);
let highlightLines = [];
let codeBlockTitle = "";
const { isDarkTheme } = useThemeContext();
const lightModeTheme = prism.theme || lightTheme || darkTheme;
const darkModeTheme = prism.darkTheme || darkTheme || lightTheme;
const prismTheme = isDarkTheme ? darkModeTheme : lightModeTheme;
if (metastring && highlightLinesRangeRegex.test(metastring)) {
const highlightLinesRange = metastring.match(highlightLinesRangeRegex)[1];
highlightLines = rangeParser
.parse(highlightLinesRange)
.filter((n) => n > 0);
}
if (metastring && codeBlockTitleRegex.test(metastring)) {
codeBlockTitle = metastring
.match(codeBlockTitleRegex)[0]
.split("title=")[1]
.replace(/"+/g, "");
}
useEffect(() => {
let clipboard;
if (button.current) {
clipboard = new Clipboard(button.current, {
target: () => target.current,
});
}
return () => {
if (clipboard) {
clipboard.destroy();
}
};
}, [button.current, target.current]);
let language =
languageClassName && languageClassName.replace(/language-/, "");
if (!language && prism.defaultLanguage) {
language = prism.defaultLanguage;
}
// only declaration OR directive highlight can be used for a block
let code = children.replace(/\n$/, "");
if (highlightLines.length === 0 && language !== undefined) {
let range = "";
const directiveRegex = highlightDirectiveRegex(language);
// go through line by line
const lines = children.replace(/\n$/, "").split("\n");
let blockStart;
// loop through lines
for (let index = 0; index < lines.length; ) {
const line = lines[index];
// adjust for 0-index
const lineNumber = index + 1;
const match = line.match(directiveRegex);
if (match !== null) {
const directive = match
.slice(1)
.reduce((final, item) => final || item, undefined);
switch (directive) {
case "highlight-next-line":
range += `${lineNumber},`;
break;
case "highlight-start":
blockStart = lineNumber;
break;
case "highlight-end":
range += `${blockStart}-${lineNumber - 1},`;
break;
default:
break;
}
lines.splice(index, 1);
} else {
// lines without directives are unchanged
index += 1;
}
}
highlightLines = rangeParser.parse(range);
code = lines.join("\n");
}
const handleCopyCode = () => {
window.getSelection().empty();
setShowCopied(true);
setTimeout(() => setShowCopied(false), 2000);
};
return (
<Highlight
{...defaultProps}
key={mounted}
theme={prismTheme}
code={code}
language={language}
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<>
{codeBlockTitle && (
<div style={style} className={styles.codeBlockTitle}>
{codeBlockTitle}
</div>
)}
<div className={styles.codeBlockContent}>
{/* <button
ref={button}
type="button"
aria-label="Copy code to clipboard"
className={classnames(styles.copyButton, {
[styles.copyButtonWithTitle]: codeBlockTitle,
})}
onClick={handleCopyCode}>
{showCopied ? 'Copied' : 'Copy'}
</button> */}
<div
tabIndex="0"
className={classnames(className, styles.codeBlock, {
[styles.codeBlockWithTitle]: codeBlockTitle,
})}
>
<div ref={target} className={styles.codeBlockLines} style={style}>
{tokens.map((line, i) => {
if (line.length === 1 && line[0].content === "") {
line[0].content = "\n"; // eslint-disable-line no-param-reassign
}
const lineProps = getLineProps({ line, key: i });
if (highlightLines.includes(i + 1)) {
lineProps.className = `${lineProps.className} docusaurus-highlight-code-line`;
}
return (
<div key={i} {...lineProps}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</div>
);
})}
</div>
</div>
</div>
</>
)}
</Highlight>
);
};

View file

@ -1,70 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.codeBlockContent {
position: relative;
}
.codeBlockTitle {
font-family: var(--ifm-font-family-monospace);
font-weight: bold;
padding: var(--ifm-pre-padding);
border-bottom: 1px solid var(--ifm-color-emphasis-200);
width: 100%;
border-top-left-radius: var(--ifm-global-radius);
border-top-right-radius: var(--ifm-global-radius);
}
.codeBlock {
overflow: auto;
border-radius: var(--ifm-global-radius);
}
.codeBlockWithTitle {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: var(--ifm-global-radius);
border-bottom-right-radius: var(--ifm-global-radius);
}
.copyButton {
background: rgba(0, 0, 0, 0.3);
border: none;
border-radius: var(--ifm-global-radius);
color: var(--ifm-color-content);
cursor: pointer;
line-height: 12px;
opacity: 0;
outline: none;
padding: 4px 8px;
position: absolute;
right: var(--ifm-pre-padding);
top: var(--ifm-pre-padding);
visibility: hidden;
transition: opacity 200ms ease-in-out, visibility 200ms ease-in-out,
bottom 200ms ease-in-out;
}
.copyButtonWithTitle {
top: calc(var(--ifm-pre-padding));
}
.codeBlockTitle:hover + .codeBlockContent .copyButton,
.codeBlockContent:hover > .copyButton {
visibility: visible;
opacity: 1;
}
.codeBlockLines {
font-family: var(--ifm-font-family-monospace);
font-size: inherit;
line-height: var(--ifm-pre-line-height);
white-space: pre;
float: left;
min-width: 100%;
padding: var(--ifm-pre-padding);
}

View file

@ -1,122 +0,0 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
import classnames from "classnames";
import Link from "@docusaurus/Link";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import useBaseUrl from "@docusaurus/useBaseUrl";
import styles from "./styles.module.css";
function FooterLink({ to, href, label, ...props }) {
const toUrl = useBaseUrl(to);
return (
<Link
className={classnames(styles.footerLink, "footer__link-item")}
{...(href
? {
target: "_blank",
rel: "noopener noreferrer",
href,
}
: {
to: toUrl,
})}
{...props}
>
{label}
</Link>
);
}
const FooterLogo = ({ url, alt }) => (
<img className="footer__logo" alt={alt} src={url} />
);
function Footer() {
const context = useDocusaurusContext();
const { siteConfig = {} } = context;
const { themeConfig = {} } = siteConfig;
const { footer } = themeConfig;
const { copyright, links = [], logo = {} } = footer || {};
const logoUrl = useBaseUrl(logo.src);
if (!footer) {
return null;
}
return (
<footer
className={classnames("footer", {
"footer--dark": footer.style === "dark",
})}
>
<div className="container">
{links && links.length > 0 && (
<div className="row footer__links">
{links.map((linkItem, i) => (
<div
key={i}
className={classnames("col footer__col", styles.footerCol)}
>
{linkItem.title != null ? (
<h4 className="footer__title">{linkItem.title}</h4>
) : null}
{linkItem.items != null &&
Array.isArray(linkItem.items) &&
linkItem.items.length > 0 ? (
<ul className="footer__items">
{linkItem.items.map((item, key) =>
item.html ? (
<li
key={key}
className="footer__item"
dangerouslySetInnerHTML={{
__html: item.html,
}}
/>
) : (
<li key={item.href || item.to} className="footer__item">
<FooterLink {...item} />
</li>
)
)}
</ul>
) : null}
</div>
))}
</div>
)}
{(logo || copyright) && (
<div className="text--center">
{logo && logo.src && (
<div className="margin-bottom--sm">
{logo.href ? (
<a
href={logo.href}
target="_blank"
rel="noopener noreferrer"
className={styles.footerLogoLink}
>
<FooterLogo alt={logo.alt} url={logoUrl} />
</a>
) : (
<FooterLogo alt={logo.alt} url={logoUrl} />
)}
</div>
)}
{copyright}
</div>
)}
</div>
</footer>
);
}
export default Footer;

View file

@ -1,35 +0,0 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.footerLogoLink {
opacity: 0.5;
transition: opacity 0.15s ease-in-out;
}
.footerLogoLink:hover {
opacity: 1;
}
.footerCol:first-child {
text-align: left;
}
.footerCol:not(:first-child):not(:last-child) {
text-align: center;
}
.footerCol:last-child {
text-align: right;
}
html[data-theme="dark"] .footerLink[class] {
color: var(--ifm-color-emphasis-400);
}
.footerLink[class] {
color: var(--ifm-color-emphasis-700);
}

View file

@ -1,83 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
import Head from "@docusaurus/Head";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ThemeProvider from "@theme/ThemeProvider";
import UserPreferencesProvider from "@theme/UserPreferencesProvider";
import AnnouncementBar from "@theme/AnnouncementBar";
import Navbar from "../Navbar";
import Footer from "../Footer";
import "./styles.css";
function Layout(props) {
const { siteConfig = {} } = useDocusaurusContext();
const {
favicon,
title: siteTitle,
themeConfig: { image: defaultImage },
url: siteUrl,
} = siteConfig;
const {
children,
title,
noFooter,
description,
image,
keywords,
permalink,
version,
} = props;
const metaTitle = title ? `${title} | ${siteTitle}` : siteTitle;
const metaImage = image || defaultImage;
const metaImageUrl = useBaseUrl(metaImage, { absolute: true });
const faviconUrl = useBaseUrl(favicon);
return (
<ThemeProvider>
<UserPreferencesProvider>
<Head>
<html lang="en" />
{metaTitle && <title>{metaTitle}</title>}
{metaTitle && <meta property="og:title" content={metaTitle} />}
{favicon && <link rel="shortcut icon" href={faviconUrl} />}
{description && <meta name="description" content={description} />}
{description && (
<meta property="og:description" content={description} />
)}
{version && <meta name="docsearch:version" content={version} />}
{keywords && keywords.length && (
<meta name="keywords" content={keywords.join(",")} />
)}
{metaImage && <meta property="og:image" content={metaImageUrl} />}
{metaImage && <meta property="og:image:width" content="1200" />}
{metaImage && <meta property="og:image:height" content="630" />}
{metaImage && (
<meta property="twitter:image" content={metaImageUrl} />
)}
{metaImage && (
<meta name="twitter:image:alt" content={`Image for ${metaTitle}`} />
)}
{permalink && (
<meta property="og:url" content={siteUrl + permalink} />
)}
{permalink && <link rel="canonical" href={siteUrl + permalink} />}
<meta name="twitter:card" content="summary_large_image" />
</Head>
<AnnouncementBar />
<Navbar />
<div className="main-wrapper">{children}</div>
{!noFooter && <Footer />}
</UserPreferencesProvider>
</ThemeProvider>
);
}
export default Layout;

View file

@ -1,26 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
html,
body {
height: 100%;
}
body {
margin: 0;
transition: var(--ifm-transition-fast) ease color;
}
#__docusaurus {
min-height: 100%;
display: flex;
flex-direction: column;
}
.main-wrapper {
flex: 1 0 auto;
}

View file

@ -0,0 +1,49 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
import type { Props } from "@theme/Logo";
import clsx from "clsx";
import Link from "@docusaurus/Link";
import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { useThemeConfig } from "@docusaurus/theme-common";
import { useLogoSrc } from "../../hooks";
const Logo = (props: Props): JSX.Element => {
const { sources, className } = useLogoSrc();
const { isClient } = useDocusaurusContext();
const {
navbar: { title, logo = { src: "" } },
} = useThemeConfig();
const { imageClassName, titleClassName, ...propsRest } = props;
const logoLink = useBaseUrl(logo.href || "/");
return (
<Link
to={logoLink}
{...propsRest}
{...(logo.target && { target: logo.target })}
>
{sources.light && (
<ThemedImage
key={isClient}
className={clsx(imageClassName, className)}
sources={sources}
alt={logo.alt || title || "Logo"}
/>
)}
{title != null && <strong className={titleClassName}>{title}</strong>}
</Link>
);
};
export default Logo;

View file

@ -1,386 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useCallback, useState, useEffect } from "react";
import clsx from "clsx";
import Link from "@docusaurus/Link";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import useBaseUrl from "@docusaurus/useBaseUrl";
import SearchBar from "../SearchBar";
import useThemeContext from "@theme/hooks/useThemeContext";
import useHideableNavbar from "@theme/hooks/useHideableNavbar";
import useLockBodyScroll from "@theme/hooks/useLockBodyScroll";
import useWindowSize, { windowSizes } from "@theme/hooks/useWindowSize";
import useLogo from "@theme/hooks/useLogo";
import useMedia from "use-media";
import { ColorModeToggle } from "../../components/ColorModeToggle";
import { GithubButton } from "../../components/GithubButton";
import Logo from "../../components/Logo";
import styles from "./styles.module.css";
// retrocompatible with v1
const DefaultNavItemPosition = "right";
function NavLink({
activeBasePath,
activeBaseRegex,
to,
href,
label,
activeClassName = "navbar__link--active",
prependBaseUrlToHref,
...props
}) {
const toUrl = useBaseUrl(to);
const activeBaseUrl = useBaseUrl(activeBasePath);
const normalizedHref = useBaseUrl(href, { forcePrependBaseUrl: true });
return (
<Link
{...(href
? {
target: "_blank",
rel: "noopener noreferrer",
href: prependBaseUrlToHref ? normalizedHref : href,
}
: {
isNavLink: true,
activeClassName,
to: toUrl,
...(activeBasePath || activeBaseRegex
? {
isActive: (_match, location) =>
activeBaseRegex
? new RegExp(activeBaseRegex).test(location.pathname)
: location.pathname.startsWith(activeBaseUrl),
}
: null),
})}
{...props}
>
{label}
</Link>
);
}
function NavItem({
items,
position = DefaultNavItemPosition,
className,
...props
}) {
const navLinkClassNames = (extraClassName, isDropdownItem = false) =>
clsx(
styles.navLink,
{
"navbar__item navbar__link": !isDropdownItem,
dropdown__link: isDropdownItem,
},
extraClassName
);
if (!items) {
return <NavLink className={navLinkClassNames(className)} {...props} />;
}
return (
<div
className={clsx("navbar__item", "dropdown", "dropdown--hoverable", {
"dropdown--left": position === "left",
"dropdown--right": position === "right",
})}
>
<NavLink
className={navLinkClassNames(className)}
{...props}
onClick={(e) => e.preventDefault()}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.target.parentNode.classList.toggle("dropdown--show");
}
}}
>
{props.label}
</NavLink>
<ul className="dropdown__menu">
{items.map(
({ className: childItemClassName, ...childItemProps }, i) => (
<li key={i}>
<NavLink
activeClassName="dropdown__link--active"
className={navLinkClassNames(childItemClassName, true)}
{...childItemProps}
/>
</li>
)
)}
</ul>
</div>
);
}
function MobileNavItem({ items, position: _position, className, ...props }) {
// Need to destructure position from props so that it doesn't get passed on.
const navLinkClassNames = (extraClassName, isSubList = false) =>
clsx(
"menu__link",
{
"menu__link--sublist": isSubList,
},
extraClassName
);
if (!items) {
return (
<li className="menu__list-item">
<NavLink className={navLinkClassNames(className)} {...props} />
</li>
);
}
return (
<li className="menu__list-item">
<NavLink className={navLinkClassNames(className, true)} {...props}>
{props.label}
</NavLink>
<ul className="menu__list">
{items.map(
({ className: childItemClassName, ...childItemProps }, i) => (
<li className="menu__list-item" key={i}>
<NavLink
activeClassName="menu__link--active"
className={navLinkClassNames(childItemClassName)}
{...childItemProps}
onClick={props.onClick}
/>
</li>
)
)}
</ul>
</li>
);
}
// If split links by left/right
// if position is unspecified, fallback to right (as v1)
function splitLinks(links) {
const leftLinks = links.filter(
(linkItem) => (linkItem.position ?? DefaultNavItemPosition) === "left"
);
const rightLinks = links.filter(
(linkItem) => (linkItem.position ?? DefaultNavItemPosition) === "right"
);
return {
leftLinks,
rightLinks,
};
}
function Navbar() {
const {
siteConfig: {
themeConfig: {
navbar: { title, links = [], hideOnScroll = false } = {},
disableDarkMode = false,
},
organizationName,
projectName,
baseUrl,
},
} = useDocusaurusContext();
const [sidebarShown, setSidebarShown] = useState(false);
const [isSearchBarExpanded, setIsSearchBarExpanded] = useState(false);
const { isDarkTheme, setLightTheme, setDarkTheme } = useThemeContext();
const { navbarRef, isNavbarVisible } = useHideableNavbar(hideOnScroll);
const { logoLink, logoLinkProps, logoImageUrl, logoAlt } = useLogo();
useLockBodyScroll(sidebarShown);
const showSidebar = useCallback(() => {
setSidebarShown(true);
}, [setSidebarShown]);
const hideSidebar = useCallback(() => {
setSidebarShown(false);
}, [setSidebarShown]);
const onToggleChange = useCallback(
(checked) => {
checked ? setDarkTheme() : setLightTheme();
},
[setLightTheme, setDarkTheme]
);
const windowSize = useWindowSize();
const isMobile = useMedia({ maxWidth: 997 });
useEffect(() => {
if (windowSize === windowSizes.desktop) {
setSidebarShown(false);
}
}, [windowSize]);
const { leftLinks, rightLinks } = splitLinks(links);
return (
<nav
ref={navbarRef}
className={clsx("navbar", "navbar--light", "navbar--fixed-top", {
"navbar-sidebar--show": sidebarShown,
[styles.navbarHideable]: hideOnScroll,
[styles.navbarHidden]: !isNavbarVisible,
[styles.navBarBorder]: !isMobile,
})}
>
<div className="navbar__inner">
<div className="navbar__items">
{!isMobile && (
<Link
className="navbar__brand"
to={baseUrl}
aria-label="Home"
title="Home"
>
<Logo size={32} className={styles.logo} />
{title != null && (
<strong
className={isSearchBarExpanded ? styles.hideLogoText : ""}
>
{title}
</strong>
)}
</Link>
)}
{links != null && links.length !== 0 && (
<div
aria-label="Navigation bar toggle"
className="navbar__toggle"
role="button"
tabIndex={0}
onClick={showSidebar}
onKeyDown={showSidebar}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="30"
height="30"
viewBox="0 0 30 30"
role="img"
focusable="false"
>
<title>Menu</title>
<path
stroke="currentColor"
strokeLinecap="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M4 7h22M4 15h22M4 23h22"
/>
</svg>
</div>
)}
{leftLinks.map((linkItem, i) => (
<NavItem {...linkItem} key={i} />
))}
</div>
<div className="navbar__items navbar__items--right">
{isMobile && (
<Link
className="navbar__brand"
to={baseUrl}
aria-label="Home"
title="Home"
>
<Logo size={32} className={styles.logo} />
{title != null && (
<strong
className={isSearchBarExpanded ? styles.hideLogoText : ""}
>
{title}
</strong>
)}
</Link>
)}
{!isMobile && (
<SearchBar
handleSearchBarToggle={setIsSearchBarExpanded}
isSearchBarExpanded={isSearchBarExpanded}
style={{ marginLeft: "0.5rem", marginRight: "0.5rem" }}
/>
)}
{!disableDarkMode && !isMobile && (
<ColorModeToggle
toggle={onToggleChange}
isDark={isDarkTheme}
style={{ marginLeft: "0.5rem", marginRight: "0.5rem" }}
/>
)}
{!isMobile && (
<GithubButton
href={`https://github.com/${organizationName}/${projectName}`}
/>
)}
</div>
</div>
<div
role="presentation"
className="navbar-sidebar__backdrop"
onClick={hideSidebar}
/>
<div className="navbar-sidebar">
<div className="navbar-sidebar__brand">
<Link
className="navbar__brand"
onClick={hideSidebar}
to={logoLink}
{...logoLinkProps}
>
<Logo size={32} className={styles.logo} />
{title != null && (
<strong
className={
isSearchBarExpanded ? styles.hideLogoText : "navbar__title"
}
>
{title}
</strong>
)}
</Link>
{sidebarShown && (
<GithubButton
href={`https://github.com/${organizationName}/${projectName}`}
/>
)}
{!disableDarkMode && sidebarShown && (
<ColorModeToggle toggle={onToggleChange} isDark={isDarkTheme} />
)}
</div>
<div className="navbar-sidebar__items">
<div className="menu">
<ul className="menu__list">
{links.map((linkItem, i) => (
<MobileNavItem {...linkItem} onClick={hideSidebar} key={i} />
))}
<div style={{ margin: 5, marginTop: 15 }} />
<SearchBar
handleSearchBarToggle={setIsSearchBarExpanded}
isSearchBarExpanded={isSearchBarExpanded}
/>
</ul>
</div>
</div>
</div>
</nav>
);
}
export default Navbar;

View file

@ -1,89 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
@media screen and (max-width: 997px) {
.displayOnlyInLargeViewport {
display: none !important;
}
}
@media (max-width: 360px) {
.hideLogoText {
display: none;
}
}
.navbarHideable {
transition: top 0.2s ease-in-out;
}
.navbarHidden {
top: calc(var(--ifm-navbar-height) * -1) !important;
}
.navbarItems {
justify-content: flex-start;
}
.navbarItemsRight[class] {
flex: 0 0;
}
.navLink[class] {
position: relative;
margin-left: 2rem;
margin-right: 2rem;
color: var(--ifm-font-color-base);
}
.navLink[class]:hover {
color: var(--ifm-color-emphasis-700);
}
html[data-theme="dark"] .navLink[class]:hover {
color: var(--ifm-color-secondary);
}
.navLink[class]:global(.navbar__link--active) {
color: inherit;
font-weight: bold;
}
.navLink[class]:global(.navbar__link--active)::after {
position: absolute;
left: 0;
width: 100%;
height: 1px;
background: var(--ifm-color-content);
content: "";
transition: width 0.5s, opacity 0.5s, transform 0.5s;
transform: translateY(-6px);
}
.toggle {
margin-right: 20px;
}
.navbarMain {
background-color: var(--ifm-background-color);
box-shadow: none;
}
.logo {
margin: 4px;
}
html[data-theme="dark"] .navbarOther {
background-color: var(--ifm-background-color);
}
.navbarOther {
background-color: var(--ifm-color-white);
}
.navBarBorder {
border-bottom: 1px solid var(--ifm-toc-border-color);
}

View file

@ -1,250 +0,0 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useCallback, useState } from "react";
import classnames from "classnames";
import Link from "@docusaurus/Link";
import { useLocation } from "react-router-dom";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import useBaseUrl from "@docusaurus/useBaseUrl";
import SearchBar from "@theme/SearchBar";
import useThemeContext from "@theme/hooks/useThemeContext";
import useHideableNavbar from "@theme/hooks/useHideableNavbar";
import useLockBodyScroll from "@theme/hooks/useLockBodyScroll";
import useMedia from "use-media";
import { ColorModeToggle } from "../../components/ColorModeToggle";
import { GithubButton } from "../../components/GithubButton";
import Logo from "../../components/Logo";
import styles from "./styles.module.css";
function NavLink({ to, href, label, position, ...props }) {
const toUrl = useBaseUrl(to);
return (
<Link
className={classnames(styles.navLink, "navbar__item navbar__link")}
{...(href
? {
target: "_blank",
rel: "noopener noreferrer",
href,
}
: {
activeClassName: "navbar__link--active",
to: toUrl,
})}
{...props}
>
{label}
</Link>
);
}
function Navbar() {
const context = useDocusaurusContext();
const { siteConfig = {} } = context;
const { baseUrl, themeConfig = {} } = siteConfig;
const { navbar = {}, disableDarkMode = false } = themeConfig;
const { title, logo = {}, links = [], hideOnScroll = false } = navbar;
const [sidebarShown, setSidebarShown] = useState(false);
const [isSearchBarExpanded, setIsSearchBarExpanded] = useState(false);
const { pathname } = useLocation();
const { isDarkTheme, setLightTheme, setDarkTheme } = useThemeContext();
const { navbarRef, isNavbarVisible } = useHideableNavbar(hideOnScroll);
const isMobile = useMedia({ maxWidth: 997 });
useLockBodyScroll(sidebarShown);
const showSidebar = useCallback(() => {
setSidebarShown(true);
}, [setSidebarShown]);
const hideSidebar = useCallback(() => {
setSidebarShown(false);
}, [setSidebarShown]);
const onToggleChange = (checked) => {
checked ? setDarkTheme() : setLightTheme();
};
const logoLink = logo.href || baseUrl;
const isExternalLogoLink = /http/.test(logoLink);
const logoLinkProps = isExternalLogoLink
? {
rel: "noopener noreferrer",
target: "_blank",
}
: null;
const logoSrc = logo.srcDark && isDarkTheme ? logo.srcDark : logo.src;
return (
<nav
ref={navbarRef}
className={classnames("navbar", "navbar--light", "navbar--fixed-top", {
[styles.navbarMain]: pathname === "/",
[styles.navbarOther]: pathname !== "/",
"navbar-sidebar--show": sidebarShown,
[styles.navbarHideable]: hideOnScroll,
[styles.navbarHidden]: !isNavbarVisible,
})}
>
<div className="navbar__inner">
<div className={classnames("navbar__items", styles.navbarItems)}>
{!isMobile && (
<Link
className="navbar__brand"
to={baseUrl}
aria-label="Home"
title="Home"
>
<Logo size={32} className={styles.logo} />
{title != null && (
<strong
className={isSearchBarExpanded ? styles.hideLogoText : ""}
>
{title}
</strong>
)}
</Link>
)}
<div
aria-label="Navigation bar toggle"
className="navbar__toggle"
role="button"
tabIndex={0}
onClick={showSidebar}
onKeyDown={showSidebar}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="30"
height="30"
viewBox="0 0 30 30"
role="img"
focusable="false"
>
<title>Menu</title>
<path
stroke="currentColor"
strokeLinecap="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M4 7h22M4 15h22M4 23h22"
/>
</svg>
</div>
{links
.filter((linkItem) => linkItem.position !== "right")
.map((linkItem, i) => (
<NavLink {...linkItem} key={i} />
))}
</div>
<div
className={classnames(
"navbar__items",
"navbar__items--right",
styles.navbarItemsRight
)}
>
{isMobile && (
<Link
className="navbar__brand"
to={baseUrl}
aria-label="Home"
title="Home"
>
<Logo size={32} className={styles.logo} />
{title != null && (
<strong
className={isSearchBarExpanded ? styles.hideLogoText : ""}
>
{title}
</strong>
)}
</Link>
)}
{!isMobile && (
<SearchBar
handleSearchBarToggle={setIsSearchBarExpanded}
isSearchBarExpanded={isSearchBarExpanded}
style={{ marginLeft: "0.5rem", marginRight: "0.5rem" }}
/>
)}
{!disableDarkMode && !isMobile && (
<ColorModeToggle
toggle={onToggleChange}
isDark={isDarkTheme}
style={{ marginLeft: "0.5rem", marginRight: "0.5rem" }}
/>
)}
{!isMobile && (
<GithubButton
href={`https://github.com/${siteConfig.organizationName}/${siteConfig.projectName}`}
/>
)}
</div>
</div>
<div
role="presentation"
className="navbar-sidebar__backdrop"
onClick={hideSidebar}
/>
<div className="navbar-sidebar">
<div className="navbar-sidebar__brand">
<Link
className="navbar__brand"
to={baseUrl}
aria-label="Home"
title="Home"
>
<Logo size={32} className={styles.logo} />
{title != null && (
<strong
className={isSearchBarExpanded ? styles.hideLogoText : ""}
>
{title}
</strong>
)}
</Link>
{sidebarShown && (
<GithubButton
href={`https://github.com/${siteConfig.organizationName}/${siteConfig.projectName}`}
/>
)}
{!disableDarkMode && sidebarShown && (
<ColorModeToggle toggle={onToggleChange} isDark={isDarkTheme} />
)}
</div>
<div className="navbar-sidebar__items">
<div className="menu">
<ul className="menu__list">
{links.map((linkItem, i) => (
<li className="menu__list-item" key={i}>
<NavLink
className="menu__link"
{...linkItem}
onClick={hideSidebar}
/>
</li>
))}
<div style={{ margin: 5, marginTop: 15 }} />
<SearchBar
handleSearchBarToggle={setIsSearchBarExpanded}
isSearchBarExpanded={isSearchBarExpanded}
/>
</ul>
</div>
</div>
</div>
</nav>
);
}
export default Navbar;

View file

@ -1,76 +0,0 @@
@media screen and (max-width: 997px) {
.displayOnlyInLargeViewport {
display: none !important;
}
}
@media (max-width: 360px) {
.hideLogoText {
display: none;
}
}
.navbarHideable {
transition: top 0.2s ease-in-out;
}
.navbarHidden {
top: calc(var(--ifm-navbar-height) * -1) !important;
}
.navbarItems {
justify-content: flex-start;
}
.navbarItemsRight[class] {
flex: 0 0;
}
.navLink[class] {
position: relative;
margin-left: 2rem;
margin-right: 2rem;
}
.navLink[class]:hover {
color: var(--ifm-color-white);
}
html[data-theme="dark"] .navLink[class]:hover {
color: var(--ifm-color-secondary);
}
.navLink[class]:global(.navbar__link--active) {
color: inherit;
font-weight: bold;
}
.navLink[class]:global(.navbar__link--active)::after {
position: absolute;
left: 0;
width: 100%;
height: 1px;
background: var(--ifm-color-content);
content: "";
transition: width 0.5s, opacity 0.5s, transform 0.5s;
transform: translateY(-6px);
}
.toggle {
margin-right: 20px;
}
.navbarMain {
background-color: var(--ifm-background-color);
box-shadow: none;
}
.logo {
margin: 4px;
}
html[data-theme="dark"] .navbarOther {
background-color: var(--ifm-background-color);
}
.navbarOther {
background-color: var(--ifm-color-white);
}

File diff suppressed because one or more lines are too long

View file

@ -1,120 +0,0 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useState, useRef, useCallback } from "react";
import classnames from "classnames";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { useHistory } from "@docusaurus/router";
import "./styles.css";
const Search = ({ handleSearchBarToggle, isSearchBarExpanded, ...props }) => {
const [algoliaLoaded, setAlgoliaLoaded] = useState(false);
const searchBarRef = useRef(null);
const { siteConfig = {} } = useDocusaurusContext();
const {
themeConfig: { algolia },
} = siteConfig;
const history = useHistory();
function initAlgolia(focus) {
window.docsearch({
appId: algolia.appId,
apiKey: algolia.apiKey,
indexName: algolia.indexName,
inputSelector: "#search_input_react",
algoliaOptions: algolia.algoliaOptions,
// Override algolia's default selection event, allowing us to do client-side
// navigation and avoiding a full page refresh.
handleSelected: (_input, _event, suggestion) => {
// Use an anchor tag to parse the absolute url into a relative url
// Alternatively, we can use new URL(suggestion.url) but its not supported in IE
const a = document.createElement("a");
a.href = suggestion.url;
// Algolia use closest parent element id #__docusaurus when a h1 page title does not have an id
// So, we can safely remove it. See https://github.com/facebook/docusaurus/issues/1828 for more details.
const routePath =
`#__docusaurus` === a.hash
? `${a.pathname}`
: `${a.pathname}${a.hash}`;
history.push(routePath);
},
});
if (focus) {
searchBarRef.current.focus();
}
}
const loadAlgolia = (focus = true) => {
if (algoliaLoaded) {
return;
}
Promise.all([import("docsearch.js"), import("./algolia.css")]).then(
([{ default: docsearch }]) => {
setAlgoliaLoaded(true);
window.docsearch = docsearch;
initAlgolia(focus);
}
);
};
const handleSearchIcon = useCallback(() => {
loadAlgolia();
if (algoliaLoaded) {
searchBarRef.current.focus();
}
handleSearchBarToggle(!isSearchBarExpanded);
}, [isSearchBarExpanded]);
const handleSearchInputBlur = useCallback(() => {
handleSearchBarToggle(!isSearchBarExpanded);
}, [isSearchBarExpanded]);
const handleSearchInput = useCallback((e) => {
const needFocus = e.type !== "mouseover";
loadAlgolia(needFocus);
});
return (
<div className="navbar__search" key="search-box" {...props}>
<span
aria-label="expand searchbar"
role="button"
className={classnames("search-icon", {
"search-icon-hidden": isSearchBarExpanded,
})}
onClick={handleSearchIcon}
onKeyDown={handleSearchIcon}
tabIndex={0}
/>
<input
id="search_input_react"
type="search"
placeholder="Search"
aria-label="Search"
className={classnames(
"navbar__search-input",
{ "search-bar-expanded": isSearchBarExpanded },
{ "search-bar": !isSearchBarExpanded }
)}
onMouseOver={handleSearchInput}
onFocus={handleSearchInput}
onBlur={handleSearchInputBlur}
ref={searchBarRef}
/>
</div>
);
};
export default Search;

View file

@ -1,40 +0,0 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.search-icon {
background-image: var(--ifm-navbar-search-input-icon);
height: auto;
width: 24px;
cursor: pointer;
padding: 8px;
line-height: 32px;
background-repeat: no-repeat;
background-position: center;
display: none;
}
.search-icon-hidden {
visibility: hidden;
}
@media (max-width: 360px) {
.search-bar {
width: 0 !important;
background: none !important;
padding: 0 !important;
transition: none !important;
}
.search-bar-expanded {
width: 9rem !important;
}
.search-icon {
display: inline;
vertical-align: sub;
}
}

View file

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2759.9 544.87">
<g fill="#40798C">
<g fill="#ff5e5b">
<g>
<path d="M931.39,683.15q20.28,21.23,20.28,62.74l0,134.13c0,3.35-.85,5.94-2.51,7.76a10.52,10.52,0,0,1-14.15,0c-1.67-1.82-2.51-4.4-2.5-7.75l0-131.86q0-35.58-14.12-51.79t-45.62-16.21q-35.13,0-56.35,21.65t-21.24,57.71l0,120.44q0,5-2.51,7.76a9.12,9.12,0,0,1-7.07,2.74,9,9,0,0,1-6.84-3A10.66,10.66,0,0,1,776,880l.12-305.23q0-5,2.51-7.76a10.52,10.52,0,0,1,14.15,0c1.66,1.83,2.5,4.41,2.5,7.76l0,131.39a71.74,71.74,0,0,1,30.13-32.83q20.07-11.4,46.08-11.39Q911.09,661.93,931.39,683.15Z" transform="translate(-102 -476.45)" />
<path d="M1201.26,663a9.19,9.19,0,0,1,6.39,2.51,8.56,8.56,0,0,1,2.73,6.62,14.35,14.35,0,0,1-.91,4.1l-131,290.12q-2.75,5.48-8.22,5.47a8.83,8.83,0,0,1-6.16-2.28,7.66,7.66,0,0,1-2.5-5.93l.91-3.65,32.88-72.08L999.6,676.1a14.33,14.33,0,0,1-.91-4.11,8.65,8.65,0,0,1,3.19-6.84,10.93,10.93,0,0,1,7.3-2.73q5,0,7.76,5.48l88,198.49.91-2.28,86.76-194.78C1194.42,665.08,1197.31,662.94,1201.26,663Z" transform="translate(-102 -476.45)" />

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 7 KiB

View file

@ -1,25 +1,7 @@
import * as React from "react";
export default ({ color = "currentColor", size = 32, ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1072 1072"
width={size}
{...props}
>
<g fill={color}>
<path
d="M1464,1000a515,515,0,0,1-4.41,66.71l1.28-9.57a502,502,0,0,1-34.62,127.3l3.63-8.6a504.73,504.73,0,0,1-65,111.35l5.63-7.28a508.39,508.39,0,0,1-90.59,90.59l7.28-5.63a504.73,504.73,0,0,1-111.35,65l8.6-3.63a502,502,0,0,1-127.3,34.62l9.57-1.28a510.21,510.21,0,0,1-133.42,0l9.57,1.28a502,502,0,0,1-127.3-34.62l8.6,3.63a504.73,504.73,0,0,1-111.35-65l7.28,5.63a508.39,508.39,0,0,1-90.59-90.59l5.63,7.28a504.73,504.73,0,0,1-65-111.35l3.63,8.6a502,502,0,0,1-34.62-127.3l1.28,9.57a510.21,510.21,0,0,1,0-133.42l-1.28,9.57a502,502,0,0,1,34.62-127.3l-3.63,8.6a504.73,504.73,0,0,1,65-111.35l-5.63,7.28a508.39,508.39,0,0,1,90.59-90.59l-7.28,5.63a504.73,504.73,0,0,1,111.35-65l-8.6,3.63a502,502,0,0,1,127.3-34.62l-9.57,1.28a510.21,510.21,0,0,1,133.42,0l-9.57-1.28a502,502,0,0,1,127.3,34.62l-8.6-3.63a504.73,504.73,0,0,1,111.35,65l-7.28-5.63a508.39,508.39,0,0,1,90.59,90.59l-5.63-7.28a504.73,504.73,0,0,1,65,111.35l-3.63-8.6a502,502,0,0,1,34.62,127.3l-1.28-9.57A515,515,0,0,1,1464,1000c0,18.83,16.54,36.87,36,36s36-15.82,36-36c-.08-55.42-8.52-111.43-25.65-164.17-16.55-51-40.5-99.86-71.58-143.54a557.67,557.67,0,0,0-116.28-120.13c-42.63-32.66-90.23-57.89-140.61-76.39-51.82-19-107.06-29.25-162.18-31.39a533.2,533.2,0,0,0-312.23,86.46c-44.52,29.28-85.17,64.88-119.06,106A563.88,563.88,0,0,0,540,724.75c-14.59,24.29-26.7,49.77-37.45,76-20.92,51-32.75,105.19-37,160.07a534.52,534.52,0,0,0,177.42,438.88C682.81,1435,727.64,1465,776.25,1487a556.45,556.45,0,0,0,79.53,29.37c28.09,7.84,56.76,12.87,85.71,16.24,55.9,6.52,112.74,3.31,167.85-7.85a534.41,534.41,0,0,0,277.91-154.19c36.66-38.42,68.14-82.48,91.8-130,24.24-48.73,41.65-100.88,49.89-154.72A572.24,572.24,0,0,0,1536,1000c0-18.83-16.58-36.87-36-36S1464,979.82,1464,1000Z"
transform="translate(-463.99 -463.99)"
/>
<path
d="M807.35,1347.71a396.14,396.14,0,0,0,193.6,51c12.55,0,24.58-11.06,24-24s-10.55-24-24-24c-15.93,0-32.51-.36-48.23-3.13,6.6,1.17.54,0-.61-.12q-3-.46-6.09-1-5.61-.94-11.21-2.06-11.52-2.3-22.89-5.34a365.56,365.56,0,0,1-43.22-14.52c-1.06-.42-2.11-.86-3.16-1.3l2.58,1.09a64.07,64.07,0,0,1-6.25-2.75q-4.65-2.1-9.25-4.33c-7.14-3.46-14.15-7.17-21-11.07-10.93-6.17-26.72-3-32.83,8.61-6,11.37-3.07,26.25,8.61,32.84Z"
transform="translate(-463.99 -463.99)"
/>
<path
d="M723.68,729.37c-55.5,55.73-94.41,128.21-108.25,205.78A398.38,398.38,0,0,0,647.77,1177a391.15,391.15,0,0,0,75.91,106.88c8.87,8.91,25.18,9.54,33.94,0s9.47-24.43,0-33.94q-9.81-9.85-18.91-20.38-4-4.68-7.9-9.51c-.65-.8-1.29-1.61-1.93-2.41s-2.5-4.54-1-1.24c1.37,3,.22.27-.47-.64s-1.55-2.07-2.31-3.11a367.19,367.19,0,0,1-29.26-46.63Q689.5,1154,684,1141.42c-.41-1-.8-1.93-1.23-2.88,2.49,5.5,1.11,2.65.6,1.39-.78-1.94-1.57-3.88-2.33-5.83q-2.73-7-5.16-14a367.36,367.36,0,0,1-14-53.89c-.81-4.51-1.52-9-2.18-13.55-.63-4.26.16,1,.2,1.56-.08-1.07-.27-2.16-.4-3.22q-.5-4.05-.88-8.1-1.4-14.33-1.69-28.74t.55-29q.39-7,1.06-13.89.35-3.66.77-7.3c.12-1.08.26-2.15.38-3.23.59-4.9-.79,5.22,0-.05A386.85,386.85,0,0,1,671.76,906q4-13.21,9-26.09c.76-2,1.53-3.9,2.32-5.84l.3-.73c1-2.4.79-1.93-.6,1.39a14,14,0,0,1,1.23-2.88q2.82-6.45,5.87-12.79a367.61,367.61,0,0,1,30.71-52.19q2.24-3.17,4.53-6.28c1-1.43,6.28-8,1.84-2.55,3.24-4,6.41-8.07,9.74-12q10-11.79,20.91-22.73c8.87-8.91,9.56-25.16,0-33.94s-24.48-9.51-33.94,0Z"
transform="translate(-463.99 -463.99)"
/>
</g>
</svg>
);
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1072 1072">
<g fill="#ff5e5b">
<path d="M1464,1000a515,515,0,0,1-4.41,66.71l1.28-9.57a502,502,0,0,1-34.62,127.3l3.63-8.6a504.73,504.73,0,0,1-65,111.35l5.63-7.28a508.39,508.39,0,0,1-90.59,90.59l7.28-5.63a504.73,504.73,0,0,1-111.35,65l8.6-3.63a502,502,0,0,1-127.3,34.62l9.57-1.28a510.21,510.21,0,0,1-133.42,0l9.57,1.28a502,502,0,0,1-127.3-34.62l8.6,3.63a504.73,504.73,0,0,1-111.35-65l7.28,5.63a508.39,508.39,0,0,1-90.59-90.59l5.63,7.28a504.73,504.73,0,0,1-65-111.35l3.63,8.6a502,502,0,0,1-34.62-127.3l1.28,9.57a510.21,510.21,0,0,1,0-133.42l-1.28,9.57a502,502,0,0,1,34.62-127.3l-3.63,8.6a504.73,504.73,0,0,1,65-111.35l-5.63,7.28a508.39,508.39,0,0,1,90.59-90.59l-7.28,5.63a504.73,504.73,0,0,1,111.35-65l-8.6,3.63a502,502,0,0,1,127.3-34.62l-9.57,1.28a510.21,510.21,0,0,1,133.42,0l-9.57-1.28a502,502,0,0,1,127.3,34.62l-8.6-3.63a504.73,504.73,0,0,1,111.35,65l-7.28-5.63a508.39,508.39,0,0,1,90.59,90.59l-5.63-7.28a504.73,504.73,0,0,1,65,111.35l-3.63-8.6a502,502,0,0,1,34.62,127.3l-1.28-9.57A515,515,0,0,1,1464,1000c0,18.83,16.54,36.87,36,36s36-15.82,36-36c-.08-55.42-8.52-111.43-25.65-164.17-16.55-51-40.5-99.86-71.58-143.54a557.67,557.67,0,0,0-116.28-120.13c-42.63-32.66-90.23-57.89-140.61-76.39-51.82-19-107.06-29.25-162.18-31.39a533.2,533.2,0,0,0-312.23,86.46c-44.52,29.28-85.17,64.88-119.06,106A563.88,563.88,0,0,0,540,724.75c-14.59,24.29-26.7,49.77-37.45,76-20.92,51-32.75,105.19-37,160.07a534.52,534.52,0,0,0,177.42,438.88C682.81,1435,727.64,1465,776.25,1487a556.45,556.45,0,0,0,79.53,29.37c28.09,7.84,56.76,12.87,85.71,16.24,55.9,6.52,112.74,3.31,167.85-7.85a534.41,534.41,0,0,0,277.91-154.19c36.66-38.42,68.14-82.48,91.8-130,24.24-48.73,41.65-100.88,49.89-154.72A572.24,572.24,0,0,0,1536,1000c0-18.83-16.58-36.87-36-36S1464,979.82,1464,1000Z" transform="translate(-463.99 -463.99)" />
<path d="M807.35,1347.71a396.14,396.14,0,0,0,193.6,51c12.55,0,24.58-11.06,24-24s-10.55-24-24-24c-15.93,0-32.51-.36-48.23-3.13,6.6,1.17.54,0-.61-.12q-3-.46-6.09-1-5.61-.94-11.21-2.06-11.52-2.3-22.89-5.34a365.56,365.56,0,0,1-43.22-14.52c-1.06-.42-2.11-.86-3.16-1.3l2.58,1.09a64.07,64.07,0,0,1-6.25-2.75q-4.65-2.1-9.25-4.33c-7.14-3.46-14.15-7.17-21-11.07-10.93-6.17-26.72-3-32.83,8.61-6,11.37-3.07,26.25,8.61,32.84Z" transform="translate(-463.99 -463.99)" />
<path d="M723.68,729.37c-55.5,55.73-94.41,128.21-108.25,205.78A398.38,398.38,0,0,0,647.77,1177a391.15,391.15,0,0,0,75.91,106.88c8.87,8.91,25.18,9.54,33.94,0s9.47-24.43,0-33.94q-9.81-9.85-18.91-20.38-4-4.68-7.9-9.51c-.65-.8-1.29-1.61-1.93-2.41s-2.5-4.54-1-1.24c1.37,3,.22.27-.47-.64s-1.55-2.07-2.31-3.11a367.19,367.19,0,0,1-29.26-46.63Q689.5,1154,684,1141.42c-.41-1-.8-1.93-1.23-2.88,2.49,5.5,1.11,2.65.6,1.39-.78-1.94-1.57-3.88-2.33-5.83q-2.73-7-5.16-14a367.36,367.36,0,0,1-14-53.89c-.81-4.51-1.52-9-2.18-13.55-.63-4.26.16,1,.2,1.56-.08-1.07-.27-2.16-.4-3.22q-.5-4.05-.88-8.1-1.4-14.33-1.69-28.74t.55-29q.39-7,1.06-13.89.35-3.66.77-7.3c.12-1.08.26-2.15.38-3.23.59-4.9-.79,5.22,0-.05A386.85,386.85,0,0,1,671.76,906q4-13.21,9-26.09c.76-2,1.53-3.9,2.32-5.84l.3-.73c1-2.4.79-1.93-.6,1.39a14,14,0,0,1,1.23-2.88q2.82-6.45,5.87-12.79a367.61,367.61,0,0,1,30.71-52.19q2.24-3.17,4.53-6.28c1-1.43,6.28-8,1.84-2.55,3.24-4,6.41-8.07,9.74-12q10-11.79,20.91-22.73c8.87-8.91,9.56-25.16,0-33.94s-24.48-9.51-33.94,0Z" transform="translate(-463.99 -463.99)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

7
docs/static/hyperglass-icon-light.svg vendored Normal file
View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1072 1072">
<g fill="#40798C">
<path d="M1464,1000a515,515,0,0,1-4.41,66.71l1.28-9.57a502,502,0,0,1-34.62,127.3l3.63-8.6a504.73,504.73,0,0,1-65,111.35l5.63-7.28a508.39,508.39,0,0,1-90.59,90.59l7.28-5.63a504.73,504.73,0,0,1-111.35,65l8.6-3.63a502,502,0,0,1-127.3,34.62l9.57-1.28a510.21,510.21,0,0,1-133.42,0l9.57,1.28a502,502,0,0,1-127.3-34.62l8.6,3.63a504.73,504.73,0,0,1-111.35-65l7.28,5.63a508.39,508.39,0,0,1-90.59-90.59l5.63,7.28a504.73,504.73,0,0,1-65-111.35l3.63,8.6a502,502,0,0,1-34.62-127.3l1.28,9.57a510.21,510.21,0,0,1,0-133.42l-1.28,9.57a502,502,0,0,1,34.62-127.3l-3.63,8.6a504.73,504.73,0,0,1,65-111.35l-5.63,7.28a508.39,508.39,0,0,1,90.59-90.59l-7.28,5.63a504.73,504.73,0,0,1,111.35-65l-8.6,3.63a502,502,0,0,1,127.3-34.62l-9.57,1.28a510.21,510.21,0,0,1,133.42,0l-9.57-1.28a502,502,0,0,1,127.3,34.62l-8.6-3.63a504.73,504.73,0,0,1,111.35,65l-7.28-5.63a508.39,508.39,0,0,1,90.59,90.59l-5.63-7.28a504.73,504.73,0,0,1,65,111.35l-3.63-8.6a502,502,0,0,1,34.62,127.3l-1.28-9.57A515,515,0,0,1,1464,1000c0,18.83,16.54,36.87,36,36s36-15.82,36-36c-.08-55.42-8.52-111.43-25.65-164.17-16.55-51-40.5-99.86-71.58-143.54a557.67,557.67,0,0,0-116.28-120.13c-42.63-32.66-90.23-57.89-140.61-76.39-51.82-19-107.06-29.25-162.18-31.39a533.2,533.2,0,0,0-312.23,86.46c-44.52,29.28-85.17,64.88-119.06,106A563.88,563.88,0,0,0,540,724.75c-14.59,24.29-26.7,49.77-37.45,76-20.92,51-32.75,105.19-37,160.07a534.52,534.52,0,0,0,177.42,438.88C682.81,1435,727.64,1465,776.25,1487a556.45,556.45,0,0,0,79.53,29.37c28.09,7.84,56.76,12.87,85.71,16.24,55.9,6.52,112.74,3.31,167.85-7.85a534.41,534.41,0,0,0,277.91-154.19c36.66-38.42,68.14-82.48,91.8-130,24.24-48.73,41.65-100.88,49.89-154.72A572.24,572.24,0,0,0,1536,1000c0-18.83-16.58-36.87-36-36S1464,979.82,1464,1000Z" transform="translate(-463.99 -463.99)" />
<path d="M807.35,1347.71a396.14,396.14,0,0,0,193.6,51c12.55,0,24.58-11.06,24-24s-10.55-24-24-24c-15.93,0-32.51-.36-48.23-3.13,6.6,1.17.54,0-.61-.12q-3-.46-6.09-1-5.61-.94-11.21-2.06-11.52-2.3-22.89-5.34a365.56,365.56,0,0,1-43.22-14.52c-1.06-.42-2.11-.86-3.16-1.3l2.58,1.09a64.07,64.07,0,0,1-6.25-2.75q-4.65-2.1-9.25-4.33c-7.14-3.46-14.15-7.17-21-11.07-10.93-6.17-26.72-3-32.83,8.61-6,11.37-3.07,26.25,8.61,32.84Z" transform="translate(-463.99 -463.99)" />
<path d="M723.68,729.37c-55.5,55.73-94.41,128.21-108.25,205.78A398.38,398.38,0,0,0,647.77,1177a391.15,391.15,0,0,0,75.91,106.88c8.87,8.91,25.18,9.54,33.94,0s9.47-24.43,0-33.94q-9.81-9.85-18.91-20.38-4-4.68-7.9-9.51c-.65-.8-1.29-1.61-1.93-2.41s-2.5-4.54-1-1.24c1.37,3,.22.27-.47-.64s-1.55-2.07-2.31-3.11a367.19,367.19,0,0,1-29.26-46.63Q689.5,1154,684,1141.42c-.41-1-.8-1.93-1.23-2.88,2.49,5.5,1.11,2.65.6,1.39-.78-1.94-1.57-3.88-2.33-5.83q-2.73-7-5.16-14a367.36,367.36,0,0,1-14-53.89c-.81-4.51-1.52-9-2.18-13.55-.63-4.26.16,1,.2,1.56-.08-1.07-.27-2.16-.4-3.22q-.5-4.05-.88-8.1-1.4-14.33-1.69-28.74t.55-29q.39-7,1.06-13.89.35-3.66.77-7.3c.12-1.08.26-2.15.38-3.23.59-4.9-.79,5.22,0-.05A386.85,386.85,0,0,1,671.76,906q4-13.21,9-26.09c.76-2,1.53-3.9,2.32-5.84l.3-.73c1-2.4.79-1.93-.6,1.39a14,14,0,0,1,1.23-2.88q2.82-6.45,5.87-12.79a367.61,367.61,0,0,1,30.71-52.19q2.24-3.17,4.53-6.28c1-1.43,6.28-8,1.84-2.55,3.24-4,6.41-8.07,9.74-12q10-11.79,20.91-22.73c8.87-8.91,9.56-25.16,0-33.94s-24.48-9.51-33.94,0Z" transform="translate(-463.99 -463.99)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2759.9 544.87">
<g fill="#ff5e5b">
<g fill="#40798C">
<g>
<path d="M931.39,683.15q20.28,21.23,20.28,62.74l0,134.13c0,3.35-.85,5.94-2.51,7.76a10.52,10.52,0,0,1-14.15,0c-1.67-1.82-2.51-4.4-2.5-7.75l0-131.86q0-35.58-14.12-51.79t-45.62-16.21q-35.13,0-56.35,21.65t-21.24,57.71l0,120.44q0,5-2.51,7.76a9.12,9.12,0,0,1-7.07,2.74,9,9,0,0,1-6.84-3A10.66,10.66,0,0,1,776,880l.12-305.23q0-5,2.51-7.76a10.52,10.52,0,0,1,14.15,0c1.66,1.83,2.5,4.41,2.5,7.76l0,131.39a71.74,71.74,0,0,1,30.13-32.83q20.07-11.4,46.08-11.39Q911.09,661.93,931.39,683.15Z" transform="translate(-102 -476.45)" />
<path d="M1201.26,663a9.19,9.19,0,0,1,6.39,2.51,8.56,8.56,0,0,1,2.73,6.62,14.35,14.35,0,0,1-.91,4.1l-131,290.12q-2.75,5.48-8.22,5.47a8.83,8.83,0,0,1-6.16-2.28,7.66,7.66,0,0,1-2.5-5.93l.91-3.65,32.88-72.08L999.6,676.1a14.33,14.33,0,0,1-.91-4.11,8.65,8.65,0,0,1,3.19-6.84,10.93,10.93,0,0,1,7.3-2.73q5,0,7.76,5.48l88,198.49.91-2.28,86.76-194.78C1194.42,665.08,1197.31,662.94,1201.26,663Z" transform="translate(-102 -476.45)" />

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 7 KiB

View file

@ -1,3 +1,3 @@
User-agent: *
Disallow: /*__*
Sitemap: https://hyperglass.io/sitemap.xml
Sitemap: https://hyperglass.dev/sitemap.xml

4
docs/tsconfig.json Normal file
View file

@ -0,0 +1,4 @@
{
"extends": "@tsconfig/docusaurus/tsconfig.json",
"include": ["src/"]
}

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,6 @@
# Standard Library
import os
import copy
import json
from typing import Dict, List, Sequence, Generator
from pathlib import Path
@ -18,6 +17,7 @@ from hyperglass.log import (
enable_syslog_logging,
)
from hyperglass.util import set_app_path, set_cache_env, current_log_level
from hyperglass.defaults import CREDIT, DEFAULT_DETAILS
from hyperglass.constants import (
SUPPORTED_QUERY_TYPES,
PARSED_RESPONSE_FIELDS,
@ -300,17 +300,6 @@ content_greeting = get_markdown(
content_vrf = _build_vrf_help()
content_help_params = copy.copy(content_params)
content_help_params["title"] = params.web.help_menu.title
content_help = get_markdown(
config_path=params.web.help_menu, default=DEFAULT_HELP, params=content_help_params
)
content_terms_params = copy.copy(content_params)
content_terms_params["title"] = params.web.terms.title
content_terms = get_markdown(
config_path=params.web.terms, default=DEFAULT_TERMS, params=content_terms_params
)
content_credit = CREDIT.format(version=__version__)
networks = _build_networks()
@ -342,8 +331,6 @@ _frontend_params.update(
"networks": networks,
"parsed_data_fields": PARSED_RESPONSE_FIELDS,
"content": {
"help_menu": content_help,
"terms": content_terms,
"credit": content_credit,
"vrf": content_vrf,
"greeting": content_greeting,

View file

@ -4,7 +4,7 @@
from datetime import datetime
__name__ = "hyperglass"
__version__ = "1.0.0-beta.82"
__version__ = "1.0.2"
__author__ = "Matt Love"
__copyright__ = f"Copyright {datetime.now().year} Matthew Love"
__license__ = "BSD 3-Clause Clear License"
@ -62,7 +62,7 @@ FUNC_COLOR_MAP = {
"danger": "red",
}
TRANSPORT_REST = ("frr", "bird")
TRANSPORT_REST = ("frr_legacy", "bird_legacy")
SCRAPE_HELPERS = {
"arista": "arista_eos",
@ -76,14 +76,14 @@ SCRAPE_HELPERS = {
DRIVER_MAP = {
# TODO: Troubleshoot Arista with Scrapli, broken after upgrading to 2021.1.30.
# "arista_eos": "scrapli", # noqa: E800
"bird_ssh": "scrapli",
"bird": "scrapli",
"cisco_ios": "scrapli",
"cisco_xe": "scrapli",
"cisco_xr": "scrapli",
"cisco_nxos": "scrapli",
"juniper": "scrapli",
"tnsr": "scrapli",
"frr_ssh": "scrapli",
"frr": "hyperglass_agent",
"bird": "hyperglass_agent",
"frr": "scrapli",
"frr_legacy": "hyperglass_agent",
"bird_legacy": "hyperglass_agent",
}

View file

@ -1,8 +1,8 @@
"""Constant store for large default values."""
CREDIT = """
Powered by [**hyperglass**](https://hyperglass.io) version {version}. \
Source code licensed [_BSD 3-Clause Clear_](https://hyperglass.io/docs/license/).
Powered by [**hyperglass**](https://hyperglass.dev) version {version}. \
Source code licensed [_BSD 3-Clause Clear_](https://hyperglass.dev/docs/license/).
"""
DEFAULT_TERMS = """
@ -44,7 +44,7 @@ Sends 5 ICMP echo requests to the target.
"traceroute": """
Performs UDP Based traceroute to the target. \
For information about how to interpret traceroute results, [click here]\
(https://hyperglass.io/traceroute_nanog.pdf).
(https://hyperglass.dev/traceroute_nanog.pdf).
""",
}
@ -79,5 +79,5 @@ Sends 5 ICMP echo requests to the target.
Performs UDP Based traceroute to the target.
For information about how to interpret traceroute results, [click here]\
(https://hyperglass.io/traceroute_nanog.pdf).
(https://hyperglass.dev/traceroute_nanog.pdf).
"""

View file

@ -12,11 +12,7 @@ from operator import attrgetter
# Project
from hyperglass.log import log
from hyperglass.constants import ( # APPEND_NEWLINE,
TRANSPORT_REST,
TARGET_FORMAT_SPACE,
TARGET_JUNIPER_ASPATH,
)
from hyperglass.constants import TRANSPORT_REST, TARGET_FORMAT_SPACE
from hyperglass.configuration import commands
@ -24,12 +20,7 @@ class Construct:
"""Construct SSH commands/REST API parameters from validated query data."""
def __init__(self, device, query_data):
"""Initialize command construction.
Arguments:
device {object} -- Device object
query_data {object} -- Validated query object
"""
"""Initialize command construction."""
log.debug(
"Constructing {} query for '{}'",
query_data.query_type,
@ -61,8 +52,8 @@ class Construct:
)
if v is not None and self.query_data.query_target.version == v.version
]
elif self.query_data.query_type in ("bgp_aspath", "bgp_community"):
with Formatter(self.device.nos, self.query_data.query_type) as formatter:
# For AS Path/Community queries, AFIs are just enabled VRF -> AFI
# definitions, no IP version checking is performed (since there is no IP).
self.afis = [
@ -73,39 +64,10 @@ class Construct:
)
if v is not None
]
# For devices that follow Juniper's AS_PATH regex standards,
# filter out Cisco-style special characters.
if (
self.device.nos in TARGET_JUNIPER_ASPATH
and self.query_data.query_type in ("bgp_aspath",)
):
query = str(self.query_data.query_target)
asns = re.findall(r"\d+", query)
was_modified = False
if bool(re.match(r"^\_", query)):
# Replace `_65000` with `.* 65000`
asns.insert(0, r".*")
was_modified = True
if bool(re.match(r".*(\_)$", query)):
# Replace `65000_` with `65000 .*`
asns.append(r".*")
was_modified = True
if was_modified:
self.target = " ".join(asns)
else:
self.target = query
self.target = formatter(self.query_data.query_target)
def json(self, afi):
"""Return JSON version of validated query for REST devices.
Arguments:
afi {object} -- AFI object
Returns:
{str} -- JSON query string
"""
"""Return JSON version of validated query for REST devices."""
log.debug("Building JSON query for {q}", q=repr(self.query_data))
return _json.dumps(
{
@ -118,14 +80,7 @@ class Construct:
)
def scrape(self, afi):
"""Return formatted command for 'Scrape' endpoints (SSH).
Arguments:
afi {object} -- AFI object
Returns:
{str} -- Command string
"""
"""Return formatted command for 'Scrape' endpoints (SSH)."""
if self.device.structured_output:
cmd_paths = (
self.device.nos,
@ -144,11 +99,7 @@ class Construct:
)
def queries(self):
"""Return queries for each enabled AFI.
Returns:
{list} -- List of queries to run
"""
"""Return queries for each enabled AFI."""
query = []
for afi in self.afis:
@ -159,3 +110,92 @@ class Construct:
log.debug("Constructed query: {}", query)
return query
class Formatter:
"""Modify query target based on the device's NOS requirements and the query type."""
def __init__(self, nos: str, query_type: str) -> None:
"""Initialize target formatting."""
self.nos = nos
self.query_type = query_type
def __enter__(self):
"""Get the relevant formatter."""
return self._get_formatter()
def __exit__(self, exc_type, exc_value, exc_traceback):
"""Handle context exit."""
if exc_type is not None:
log.error(exc_traceback)
pass
def _get_formatter(self):
if self.nos in ("juniper", "juniper_junos"):
if self.query_type == "bgp_aspath":
return self._juniper_bgp_aspath
if self.nos in ("bird", "bird_ssh"):
if self.query_type == "bgp_aspath":
return self._bird_bgp_aspath
elif self.query_type == "bgp_community":
return self._bird_bgp_community
return self._default
def _default(self, target: str) -> str:
"""Don't format targets by default."""
return target
def _juniper_bgp_aspath(self, target: str) -> str:
"""Convert from Cisco AS_PATH format to Juniper format."""
query = str(target)
asns = re.findall(r"\d+", query)
was_modified = False
if bool(re.match(r"^\_", query)):
# Replace `_65000` with `.* 65000`
asns.insert(0, r".*")
was_modified = True
if bool(re.match(r".*(\_)$", query)):
# Replace `65000_` with `65000 .*`
asns.append(r".*")
was_modified = True
if was_modified:
modified = " ".join(asns)
log.debug("Modified target '{}' to '{}'", target, modified)
return modified
return query
def _bird_bgp_aspath(self, target: str) -> str:
"""Convert from Cisco AS_PATH format to BIRD format."""
# Extract ASNs from query target string
asns = re.findall(r"\d+", target)
was_modified = False
if bool(re.match(r"^\_", target)):
# Replace `_65000` with `.* 65000`
asns.insert(0, "*")
was_modified = True
if bool(re.match(r".*(\_)$", target)):
# Replace `65000_` with `65000 .*`
asns.append("*")
was_modified = True
asns.insert(0, "[=")
asns.append("=]")
result = " ".join(asns)
if was_modified:
log.debug("Modified target '{}' to '{}'", target, result)
return result
def _bird_bgp_community(self, target: str) -> str:
"""Convert from standard community format to BIRD format."""
parts = target.split(":")
return f'({",".join(parts)})'

View file

@ -37,11 +37,11 @@ from .ssh import SSHConnection
SCRAPLI_DRIVER_MAP = {
"arista_eos": AsyncEOSDriver,
"bird_ssh": AsyncGenericDriver,
"bird": AsyncGenericDriver,
"cisco_ios": AsyncIOSXEDriver,
"cisco_nxos": AsyncNXOSDriver,
"cisco_xr": AsyncIOSXRDriver,
"frr_ssh": AsyncGenericDriver,
"frr": AsyncGenericDriver,
"juniper": AsyncJunosDriver,
"tnsr": AsyncGenericDriver,
}
@ -49,6 +49,8 @@ SCRAPLI_DRIVER_MAP = {
driver_global_args = {
# Per-NOS driver keyword arguments
"tnsr": {"comms_prompt_pattern": r"\S+\s\S+[\#\>]"},
"frr": {"comms_ansi": True},
"bird": {"comms_ansi": True},
}
@ -112,7 +114,6 @@ class ScrapliConnection(SSHConnection):
driver.logger = log.bind(
logger_name=f"scrapli.{driver.host}:{driver.port}-driver"
)
try:
responses = ()
async with driver as connection:

View file

@ -125,6 +125,7 @@ class Query(BaseModel):
def export_dict(self, pretty=False):
"""Create dictionary representation of instance."""
if pretty:
items = {
"query_location": self.device.name,
@ -148,19 +149,26 @@ class Query(BaseModel):
@validator("query_type")
def validate_query_type(cls, value):
"""Ensure query_type is enabled."""
query = params.queries[value]
if not query.enable:
raise InputInvalid(
params.messages.feature_not_enabled,
level="warning",
feature=query.display_name,
)
return value
@validator("query_location")
def validate_query_location(cls, value):
"""Ensure query_location is defined."""
if value not in devices._ids:
valid_id = value in devices._ids
valid_hostname = value in devices.hostnames
if not any((valid_id, valid_hostname)):
raise InputInvalid(
params.messages.invalid_field,
level="warning",
@ -172,13 +180,16 @@ class Query(BaseModel):
@validator("query_vrf")
def validate_query_vrf(cls, value, values):
"""Ensure query_vrf is defined."""
vrf_object = get_vrf_object(value)
device = devices[values["query_location"]]
device_vrf = None
for vrf in device.vrfs:
if vrf == vrf_object:
device_vrf = vrf
break
if device_vrf is None:
raise InputInvalid(
params.messages.vrf_not_associated,

View file

@ -19,11 +19,11 @@ from .mikrotik_switchos import MikrotikSwitchOS
_NOS_MAP = {
"arista_eos": AristaEOSCommands,
"bird_ssh": BIRDCommands,
"bird": BIRDCommands,
"cisco_ios": CiscoIOSCommands,
"cisco_nxos": CiscoNXOSCommands,
"cisco_xr": CiscoXRCommands,
"frr_ssh": FRRCommands,
"frr": FRRCommands,
"huawei": HuaweiCommands,
"juniper": JuniperCommands,
"mikrotik_routeros": MikrotikRouterOS,
@ -38,11 +38,11 @@ class Commands(HyperglassModelExtra):
"""Base class for command definitions."""
arista_eos: CommandGroup = AristaEOSCommands()
bird_ssh: CommandGroup = BIRDCommands()
bird: CommandGroup = BIRDCommands()
cisco_ios: CommandGroup = CiscoIOSCommands()
cisco_nxos: CommandGroup = CiscoNXOSCommands()
cisco_xr: CommandGroup = CiscoXRCommands()
frr_ssh: CommandGroup = FRRCommands()
frr: CommandGroup = FRRCommands()
huawei: CommandGroup = HuaweiCommands()
juniper: CommandGroup = JuniperCommands()
mikrotik_routeros: CommandGroup = MikrotikRouterOS()

View file

@ -363,5 +363,7 @@ class Devices(HyperglassModelExtra):
for device in self.objects:
if device._id == accessor:
return device
elif device.name == accessor:
return device
raise AttributeError(f"No device named '{accessor}'")

View file

@ -1,7 +1,7 @@
"""Validate branding configuration variables."""
# Standard Library
from typing import Union, Optional
from typing import Union, Optional, Sequence
from pathlib import Path
# Third Party
@ -18,6 +18,7 @@ from pydantic import (
from pydantic.color import Color
# Project
from hyperglass.defaults import DEFAULT_HELP, DEFAULT_TERMS
from hyperglass.constants import DNS_OVER_HTTPS, FUNC_COLOR_MAP
# Local
@ -31,6 +32,7 @@ TitleMode = constr(regex=("logo_only|text_only|logo_title|logo_subtitle|all"))
ColorMode = constr(regex=r"light|dark")
DOHProvider = constr(regex="|".join(DNS_OVER_HTTPS.keys()))
Title = constr(max_length=32)
Side = constr(regex=r"left|right")
class Analytics(HyperglassModel):
@ -64,20 +66,36 @@ class Credit(HyperglassModel):
enable: StrictBool = True
class ExternalLink(HyperglassModel):
"""Validation model for external link."""
class Link(HyperglassModel):
"""Validation model for generic link."""
enable: StrictBool = True
title: StrictStr = "PeeringDB"
url: HttpUrl = "https://www.peeringdb.com/asn/{primary_asn}"
title: StrictStr
url: HttpUrl
show_icon: StrictBool = True
side: Side = "left"
order: StrictInt = 0
class HelpMenu(HyperglassModel):
"""Validation model for generic help menu."""
class Menu(HyperglassModel):
"""Validation model for generic menu."""
enable: StrictBool = True
file: Optional[FilePath]
title: StrictStr = "Help"
title: StrictStr
content: StrictStr
side: Side = "left"
order: StrictInt = 0
@validator("content")
def validate_content(cls, value):
"""Read content from file if a path is provided."""
if len(value) < 260:
path = Path(value)
if path.exists():
with path.open("r") as f:
return f.read()
else:
return value
return value
class Greeting(HyperglassModel):
@ -107,14 +125,6 @@ class Logo(HyperglassModel):
height: Optional[Union[StrictInt, Percentage]]
class Terms(HyperglassModel):
"""Validation model for terms & conditions."""
enable: StrictBool = True
file: Optional[FilePath]
title: StrictStr = "Terms"
class Text(HyperglassModel):
"""Validation model for params.branding.text."""
@ -236,11 +246,15 @@ class Web(HyperglassModel):
credit: Credit = Credit()
dns_provider: DnsOverHttps = DnsOverHttps()
external_link: ExternalLink = ExternalLink()
links: Sequence[Link] = [
Link(title="PeeringDB", url="https://www.peeringdb.com/asn/{primary_asn}")
]
menus: Sequence[Menu] = [
Menu(title="Terms", content=DEFAULT_TERMS),
Menu(title="Help", content=DEFAULT_HELP),
]
greeting: Greeting = Greeting()
help_menu: HelpMenu = HelpMenu()
logo: Logo = Logo()
opengraph: OpenGraph = OpenGraph()
terms: Terms = Terms()
text: Text = Text()
theme: Theme = Theme()

View file

@ -1,16 +1,37 @@
import { useMemo } from 'react';
import { Button, Menu, MenuButton, MenuList } from '@chakra-ui/react';
import { Markdown } from '~/components';
import { useColorValue, useBreakpointValue } from '~/context';
import { useOpposingColor } from '~/hooks';
import { useColorValue, useBreakpointValue, useConfig } from '~/context';
import { useOpposingColor, useStrf } from '~/hooks';
import type { IConfig } from '~/types';
import type { TFooterButton } from './types';
/**
* Filter the configuration object based on values that are strings for formatting.
*/
function getConfigFmt(config: IConfig): Record<string, string> {
const fmt = {} as Record<string, string>;
for (const [k, v] of Object.entries(config)) {
if (typeof v === 'string') {
fmt[k] = v;
}
}
return fmt;
}
export const FooterButton: React.FC<TFooterButton> = (props: TFooterButton) => {
const { content, title, side, ...rest } = props;
const config = useConfig();
const fmt = useMemo(() => getConfigFmt(config), []);
const fmtContent = useStrf(content, fmt);
const placement = side === 'left' ? 'top' : side === 'right' ? 'top-end' : undefined;
const bg = useColorValue('white', 'gray.900');
const color = useOpposingColor(bg);
const size = useBreakpointValue({ base: 'xs', lg: 'sm' });
return (
<Menu placement={placement} preventOverflow isLazy>
<MenuButton
@ -32,11 +53,12 @@ export const FooterButton: React.FC<TFooterButton> = (props: TFooterButton) => {
boxShadow="2xl"
textAlign="left"
overflowY="auto"
whiteSpace="normal"
mx={{ base: 1, lg: 2 }}
maxW={{ base: '100%', lg: '50vw' }}
{...rest}
>
<Markdown content={content} />
<Markdown content={fmtContent} />
</MenuList>
</Menu>
);

View file

@ -1,27 +1,43 @@
import { useMemo } from 'react';
import dynamic from 'next/dynamic';
import { Button, Flex, Link, Icon, HStack, useToken } from '@chakra-ui/react';
import { Flex, Icon, HStack, useToken } from '@chakra-ui/react';
import { If } from '~/components';
import { useConfig, useMobile, useColorValue, useBreakpointValue } from '~/context';
import { useStrf } from '~/hooks';
import { FooterButton } from './button';
import { ColorModeToggle } from './colorMode';
import { FooterLink } from './link';
import { isLink, isMenu } from './types';
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
import type { TLink, TMenu } from '~/types';
const CodeIcon = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiCode));
const ExtIcon = dynamic<MeronexIcon>(() => import('@meronex/icons/go').then(i => i.GoLinkExternal));
function buildItems(links: TLink[], menus: TMenu[]): [(TLink | TMenu)[], (TLink | TMenu)[]] {
const leftLinks = links.filter(link => link.side === 'left');
const leftMenus = menus.filter(menu => menu.side === 'left');
const rightLinks = links.filter(link => link.side === 'right');
const rightMenus = menus.filter(menu => menu.side === 'right');
const left = [...leftLinks, ...leftMenus].sort((a, b) => (a.order > b.order ? 1 : -1));
const right = [...rightLinks, ...rightMenus].sort((a, b) => (a.order > b.order ? 1 : -1));
return [left, right];
}
export const Footer: React.FC = () => {
const { web, content, primary_asn } = useConfig();
const footerBg = useColorValue('blackAlpha.50', 'whiteAlpha.100');
const footerColor = useColorValue('black', 'white');
const extUrl = useStrf(web.external_link.url, { primary_asn }) ?? '/';
const size = useBreakpointValue({ base: useToken('sizes', 4), lg: useToken('sizes', 6) });
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' });
const isMobile = useMobile();
const [left, right] = useMemo(() => buildItems(web.links, web.menus), []);
return (
<HStack
px={6}
@ -30,30 +46,42 @@ export const Footer: React.FC = () => {
zIndex={1}
as="footer"
bg={footerBg}
whiteSpace="nowrap"
color={footerColor}
spacing={{ base: 8, lg: 6 }}
d={{ base: 'inline-block', lg: 'flex' }}
overflowY={{ base: 'auto', lg: 'unset' }}
justifyContent={{ base: 'center', lg: 'space-between' }}
>
<If c={web.terms.enable}>
<FooterButton side="left" content={content.terms} title={web.terms.title} />
</If>
<If c={web.help_menu.enable}>
<FooterButton side="left" content={content.help_menu} title={web.help_menu.title} />
</If>
<If c={web.external_link.enable}>
<Button
as={Link}
isExternal
href={extUrl}
size={btnSize}
variant="ghost"
rightIcon={<ExtIcon />}
aria-label={web.external_link.title}
>
{web.external_link.title}
</Button>
</If>
{left.map(item => {
if (isLink(item)) {
const url = useStrf(item.url, { primary_asn }) ?? '/';
const icon: Partial<ButtonProps & LinkProps> = {};
if (item.show_icon) {
icon.rightIcon = <ExtIcon />;
}
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
} else if (isMenu(item)) {
return (
<FooterButton key={item.title} side="left" content={item.content} title={item.title} />
);
}
})}
{!isMobile && <Flex p={0} flex="1 0 auto" maxWidth="100%" mr="auto" />}
{right.map(item => {
if (isLink(item)) {
const url = useStrf(item.url, { primary_asn }) ?? '/';
const icon: Partial<ButtonProps & LinkProps> = {};
if (item.show_icon) {
icon.rightIcon = <ExtIcon />;
}
return <FooterLink href={url} title={item.title} {...icon} />;
} else if (isMenu(item)) {
return <FooterButton side="right" content={item.content} title={item.title} />;
}
})}
<If c={web.credit.enable}>
<FooterButton
side="right"

View file

@ -0,0 +1,13 @@
import { Button, Link, useBreakpointValue } from '@chakra-ui/react';
import type { TFooterLink } from './types';
export const FooterLink: React.FC<TFooterLink> = (props: TFooterLink) => {
const { title } = props;
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' });
return (
<Button as={Link} isExternal size={btnSize} variant="ghost" aria-label={title} {...props}>
{title}
</Button>
);
};

View file

@ -1,4 +1,5 @@
import type { ButtonProps, MenuListProps } from '@chakra-ui/react';
import type { ButtonProps, LinkProps, MenuListProps } from '@chakra-ui/react';
import type { TLink, TMenu } from '~/types';
type TFooterSide = 'left' | 'right';
@ -8,8 +9,18 @@ export interface TFooterButton extends Omit<MenuListProps, 'title'> {
content: string;
}
export type TFooterLink = ButtonProps & LinkProps & { title: string };
export type TFooterItems = 'help' | 'credit' | 'terms';
export interface TColorModeToggle extends ButtonProps {
size?: string;
}
export function isLink(item: TLink | TMenu): item is TLink {
return 'url' in item;
}
export function isMenu(item: TLink | TMenu): item is TMenu {
return 'content' in item;
}

View file

@ -13,7 +13,9 @@ export const FormField: React.FC<TField> = (props: TField) => {
const errorColor = useColorValue('red.500', 'red.300');
const opacity = useBooleanValue(hiddenLabels, 0, undefined);
const { errors } = useFormContext();
const {
formState: { errors },
} = useFormContext();
const error = name in errors && (errors[name] as FieldError);

View file

@ -27,7 +27,9 @@ export const QueryLocation: React.FC<TQuerySelectField> = (props: TQuerySelectFi
const { onChange, label } = props;
const { networks } = useConfig();
const { errors } = useFormContext();
const {
formState: { errors },
} = useFormContext();
const { selections } = useLGState();
const { exportState } = useLGMethods();

View file

@ -58,7 +58,7 @@ export const QueryTarget: React.FC<TQueryTarget> = (props: TQueryTarget) => {
return (
<>
<input hidden readOnly name={name} ref={register} value={queryTarget.value} />
<input {...register} hidden readOnly value={queryTarget.value} />
<If c={queryType.value === 'bgp_community' && queries.bgp_community.mode === 'select'}>
<Select
size="lg"

View file

@ -31,7 +31,9 @@ export const QueryType: React.FC<TQuerySelectField> = (props: TQuerySelectField)
// queries,
// networks,
// } = useConfig();
const { errors } = useFormContext();
const {
formState: { errors },
} = useFormContext();
const { selections, availableTypes, queryType } = useLGState();
const { exportState } = useLGMethods();

View file

@ -1,6 +1,6 @@
import type { FormControlProps } from '@chakra-ui/react';
import type { Control } from 'react-hook-form';
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs } from '~/types';
import type { UseFormRegister } from 'react-hook-form';
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs, TFormData } from '~/types';
export interface TField extends FormControlProps {
name: string;
@ -29,13 +29,13 @@ export interface TCommunitySelect {
name: string;
onChange: OnChange;
communities: TBGPCommunity[];
register: Control['register'];
register: UseFormRegister<TFormData>;
}
export interface TQueryTarget {
name: string;
placeholder: string;
register: Control['register'];
register: UseFormRegister<TFormData>;
onChange(e: OnChangeArgs): void;
}

View file

@ -38,7 +38,7 @@ export const Frame: React.FC<TFrame> = (props: TFrame) => {
>
<Header resetForm={handleReset} />
<Flex
px={2}
px={4}
py={0}
w="100%"
as="main"

View file

@ -19,7 +19,7 @@ import {
} from '~/components';
import { useConfig } from '~/context';
import { useStrf, useGreeting, useDevice, useLGState, useLGMethods } from '~/hooks';
import { isString } from '~/types';
import { isQueryType, isQueryContent, isString, isQueryField } from '~/types';
import type { TFormData, TDeviceVrf, OnChangeArgs } from '~/types';
@ -238,7 +238,11 @@ export const LookingGlass: React.FC = () => {
function handleChange(e: OnChangeArgs): void {
// Signal the field & value to react-hook-form.
setValue(e.field, e.value);
if (isQueryField(e.field)) {
setValue(e.field, e.value);
} else {
throw new Error(`Field '${e.field}' is not a valid form field.`);
}
if (e.field === 'query_location' && Array.isArray(e.value)) {
handleLocChange(e.value);
@ -269,11 +273,10 @@ export const LookingGlass: React.FC = () => {
}
useEffect(() => {
register({ name: 'query_group', required: true });
register({ name: 'query_location', required: true });
register({ name: 'query_target', required: true });
register({ name: 'query_type', required: true });
register({ name: 'query_vrf' });
register('query_location', { required: true });
register('query_target', { required: true });
register('query_type', { required: true });
register('query_vrf');
}, [register]);
return (

View file

@ -1,5 +1,4 @@
import type { State } from '@hookstate/core';
import type { QueryFunctionContext } from 'react-query';
import type * as ReactGA from 'react-ga';
import type {
TDevice,
@ -11,6 +10,9 @@ import type {
TDirective,
} from '~/types';
export type LGQueryKey = [string, TFormQuery];
export type DNSQueryKey = [string, { target: string | null; family: 4 | 6 }];
export interface TOpposingOptions {
light?: string;
dark?: string;
@ -24,26 +26,6 @@ export type TUseGreetingReturn = {
greetingReady(): boolean;
};
export interface TUseLGQueryFn {
pageParam?: QueryFunctionContext['pageParam'];
queryKey: [string, TFormQuery];
}
export interface TUseASNDetailFn {
pageParam?: QueryFunctionContext['pageParam'];
queryKey: string;
}
interface TUseDNSQueryParams {
target: string;
family: 4 | 6;
}
export interface TUseDNSQueryFn {
pageParam?: QueryFunctionContext['pageParam'];
queryKey: [string | null, TUseDNSQueryParams];
}
export type TUseDevice = (
/**
* Device's ID, e.g. the device.name field.

View file

@ -1,11 +1,10 @@
import { useQuery } from 'react-query';
import type { QueryObserverResult } from 'react-query';
import type { QueryFunctionContext, QueryObserverResult, QueryFunction } from 'react-query';
import type { TASNQuery } from '~/types';
import type { TUseASNDetailFn } from './types';
async function query(ctx: TUseASNDetailFn): Promise<TASNQuery> {
const [asn] = ctx.queryKey;
const query: QueryFunction<TASNQuery, string> = async (ctx: QueryFunctionContext) => {
const asn = ctx.queryKey;
const res = await fetch('https://api.asrank.caida.org/v2/graphql', {
mode: 'cors',
method: 'POST',
@ -14,14 +13,16 @@ async function query(ctx: TUseASNDetailFn): Promise<TASNQuery> {
body: JSON.stringify({ query: `{ asn(asn:\"${asn}\"){ organization { orgName } } }` }),
});
return await res.json();
}
};
/**
* Query the Caida AS Rank API to get an ASN's organization name for the AS Path component.
* @see https://api.asrank.caida.org/v2/docs
*/
export function useASNDetail(asn: string): QueryObserverResult<TASNQuery> {
return useQuery(asn, query, {
return useQuery<TASNQuery, unknown, TASNQuery, string>({
queryKey: asn,
queryFn: query,
refetchOnWindowFocus: false,
refetchInterval: false,
refetchOnMount: false,

View file

@ -3,14 +3,16 @@ import { useConfig } from '~/context';
import { fetchWithTimeout } from '~/util';
import { useGoogleAnalytics } from './useGoogleAnalytics';
import type { QueryObserverResult } from 'react-query';
import type { QueryFunction, QueryFunctionContext, QueryObserverResult } from 'react-query';
import type { DnsOverHttps } from '~/types';
import type { TUseDNSQueryFn } from './types';
import type { DNSQueryKey } from './types';
/**
* Perform a DNS over HTTPS query using the application/dns-json MIME type.
*/
async function dnsQuery(ctx: TUseDNSQueryFn): Promise<DnsOverHttps.Response | undefined> {
const query: QueryFunction<DnsOverHttps.Response, DNSQueryKey> = async (
ctx: QueryFunctionContext<DNSQueryKey>,
) => {
const [url, { target, family }] = ctx.queryKey;
const controller = new AbortController();
@ -33,7 +35,7 @@ async function dnsQuery(ctx: TUseDNSQueryFn): Promise<DnsOverHttps.Response | un
}
return json;
}
};
/**
* Query the configured DNS over HTTPS provider for the provided target. If `family` is `4`, only
@ -56,7 +58,9 @@ export function useDNSQuery(
trackEvent({ category: 'DNS', action: 'Query', label: target, dimension1: `IPv${family}` });
}
return useQuery([web.dns_provider.url, { target, family }], dnsQuery, {
return useQuery<DnsOverHttps.Response, unknown, DnsOverHttps.Response, DNSQueryKey>({
queryKey: [web.dns_provider.url, { target, family }],
queryFn: query,
cacheTime: cache.timeout * 1000,
});
}

Some files were not shown because too many files have changed in this diff Show more