Merge branch 'develop' into generic-commands
# Conflicts: # hyperglass/configuration/main.py # hyperglass/ui/components/form/queryType.tsx # hyperglass/ui/components/lookingGlass.tsx
7
.github/ISSUE_TEMPLATE/1-feature-request.md
vendored
|
|
@ -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?
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/2-bug-report.md
vendored
|
|
@ -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
|
|
@ -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.
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1 +1 @@
|
|||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
|
|
|
|||
44
CHANGELOG.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ This isn’t an exhaustive list of things that you can’t 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
2
LICENSE
|
|
@ -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
|
||||
|
|
|
|||
12
README.md
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<div align="center">
|
||||
|
||||
[**Documentation**](https://hyperglass.io) | [**Screenshots**](https://hyperglass.io/screenshots) | [**Live Demo**](https://demo.hyperglass.io/)
|
||||
[**Documentation**](https://hyperglass.dev) | [**Live Demo**](https://demo.hyperglass.dev/)
|
||||
|
||||
[](https://pypi.org/project/hyperglass/)
|
||||

|
||||
|
|
@ -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.*
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZQFH3BB2B5M3E&source=url)
|
||||
|
||||
|
|
|
|||
3
docs/.gitignore
vendored
|
|
@ -1,3 +1,6 @@
|
|||
# Project
|
||||
theme-og
|
||||
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
:::
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
:::
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
:::
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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). |
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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]": {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
18
docs/src/components/Color.tsx
Normal 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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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>;
|
||||
};
|
||||
16
docs/src/components/Datetime.tsx
Normal 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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
19
docs/src/components/Font.tsx
Normal 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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import React from "react";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
export default ({ children }) => <span className={styles.code}>{children}</span>;
|
||||
8
docs/src/components/JSXCode.tsx
Normal 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;
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
28
docs/src/components/MiniNote.tsx
Normal 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;
|
||||
9
docs/src/components/Native.tsx
Normal 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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
10
docs/src/components/PageLink.tsx
Normal 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;
|
||||
|
|
@ -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>;
|
||||
};
|
||||
25
docs/src/components/RegexPattern.tsx
Normal 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;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
export default () => (
|
||||
<span style={{ color: "var(--ifm-color-danger)", display: "inline-block" }}>*</span>
|
||||
);
|
||||
11
docs/src/components/Required.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
export * from "./useLogoSrc";
|
||||
38
docs/src/hooks/useLogoSrc.ts
Normal 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 };
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
export default {
|
||||
module.exports = {
|
||||
plain: {
|
||||
color: "rgb(241, 250, 140)",
|
||||
backgroundColor: "#282A36",
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
49
docs/src/theme/Logo/index.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
2
docs/static/hyperglass-dark.svg
vendored
|
|
@ -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 |
|
|
@ -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
|
|
@ -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 |
2
docs/static/hyperglass-light.svg
vendored
|
|
@ -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 |
2
docs/static/robots.txt
vendored
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "@tsconfig/docusaurus/tsconfig.json",
|
||||
"include": ["src/"]
|
||||
}
|
||||
8692
docs/yarn.lock
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
"""
|
||||
|
|
@ -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)})'
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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}'")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
13
hyperglass/ui/components/footer/link.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||