diff --git a/CHANGELOG.md b/CHANGELOG.md
index fe8940d..87dafbc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,27 @@ 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).
-## 1.0.0-beta75 - 2021-01-28
+## 1.0.0-beta.76 - 2021-02-06
+
+**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.
+
+### Changed
+- `port` in `devices.yaml` now defaults to 22 if not specified.
+
+### Fixed
+- AS Path graph view now uses [dagre](https://github.com/dagrejs/dagre) to properly arrange each AS.
+- Added timeout argument to `hyperglass start --build` - fixes issue where running a UI build in this way failed due to a missing timeout argument error.
+
+## 1.0.0-beta.75 - 2021-01-28
### Changed
- Default UI build timeout is now 180 seconds.
- The hyperglass `build-ui` CLI command now accepts a `--timeout` argument to override the UI build timeout.
-## 1.0.0-beta74 - 2021-01-25
+## 1.0.0-beta.74 - 2021-01-25
### Changed
- The Scrapli driver no longer specifically ignores the system's SSH config file.
@@ -20,7 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- [#109](https://github.com/checktheroads/hyperglass/issues/109): Remove the custom error page, because it doesn't work and doesn't really add much.
-## 1.0.0-beta73 - 2021-01-18
+## 1.0.0-beta.73 - 2021-01-18
### Added
- [#106](https://github.com/checktheroads/hyperglass/issues/106): Add built-in support for Nokia SR OS (thanks @paunadeu!).
@@ -32,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- [#107](https://github.com/checktheroads/hyperglass/issues/107): Fix footer menu styling so it doesn't overflow the viewport, especially on mobile.
-## 1.0.0-beta72 - 2021-01-16
+## 1.0.0-beta.72 - 2021-01-16
### Fixed
- [#104](https://github.com/checktheroads/hyperglass/issues/104): Handle the usage of `juniper_junos` as a NOS. `juniper_junos` will now automatically be mapped to `juniper`.
@@ -41,7 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- **BREAKING**: The installer no longer generates a Systemd service file. While this was likely convenient for most, it introduced significant complexity and caused most installations using `~/hyperglass` as the app path to fail, with no clear way to resolve it. Further, while Systemd is arguably the most common, it is not the *only* process manager available. As such, the docs will be updated with a Systemd example, much like the current reverse proxy documentation.
-## 1.0.0-beta71 - 2021-01-10
+## 1.0.0-beta.71 - 2021-01-10
### Added
- Added Google Analytics Support. Use the `google_analytics` field for the tracking ID in `hyperglass.yaml`.
@@ -49,7 +63,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Minor frontend code improvements.
-## 1.0.0-beta70 - 2021-01-05
+## 1.0.0-beta.70 - 2021-01-05
### Fixed
@@ -58,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Query results now automatically cancel when each result panel unmounts (e.g. when one clicks the back button).
-## 1.0.0-beta69 - 2021-01-03
+## 1.0.0-beta.69 - 2021-01-03
### Fixed
@@ -69,14 +83,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Setup no longer adds example files
-## 1.0.0-beta67 - 2021-01-02
+## 1.0.0-beta.67 - 2021-01-02
### Fixed
- Fix handling of `web.theme.default_color_mode`. Starting in 1.0.0-beta.65, it was completely ignored and used the library's default of `light`. Now, it's handled properly.
- Fix table output layout issues, particularly on mobile.
-## 1.0.0-beta66 - 2021-01-02
+## 1.0.0-beta.66 - 2021-01-02
### Fixed
@@ -87,7 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `web.theme.colors.black` and `web.theme.colors.white` are now `web.theme.colors.dark` and `web.theme.colors.light respectively`
-## 1.0.0-beta65 - 2021-01-01
+## 1.0.0-beta.65 - 2021-01-01
### Added
@@ -102,7 +116,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `web.text.title` and `web.text.subtitle` now carry a 32 character limit for simpler styling.
- Various UI layout, styling improvements, and stability improvements.
-## 1.0.0-beta63 - 2020-10-18
+## 1.0.0-beta.63 - 2020-10-18
### Added
@@ -112,13 +126,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix an issue causing hyperglass custom exceptions to not be properly raised, which caused more generic error messages in the UI/API.
-## 1.0.0-beta62 - 2020-10-17
+## 1.0.0-beta.62 - 2020-10-17
### Fixed
- Fix an issue causing exceptions not to be logged to the log file (but logged to stdout).
-## 1.0.0-beta61 - 2020-10-11
+## 1.0.0-beta.61 - 2020-10-11
### POTENTIALLY BREAKING CHANGE
@@ -132,7 +146,7 @@ When hyperglass starts up, it will check to see if `~/hyperglass` or `/etc/hyper
- [#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.
-## 1.0.0-beta60 - 2020-10-10
+## 1.0.0-beta.60 - 2020-10-10
### Fixed
@@ -154,7 +168,7 @@ This would correspond with the following stanza in the Redis configuration file:
requirepass examplepassword
```
-## 1.0.0-beta59 - 2020-10-05
+## 1.0.0-beta.59 - 2020-10-05
### Added
@@ -166,7 +180,7 @@ requirepass examplepassword
- Improve output parsing scalability - parsers can now be defined on a per-NOS basis regardless of whether or not structured-data is used.
- Restructure model locations & importing to remove some complexities.
-## 1.0.0-beta58 - 2020-09-28
+## 1.0.0-beta.58 - 2020-09-28
### Changed
@@ -183,7 +197,7 @@ requirepass examplepassword
- [#77](https://github.com/checktheroads/hyperglass/issues/77): Allow dashes in FQDN validation pattern.
- [#83](https://github.com/checktheroads/hyperglass/issues/83): Fix lack of support for `protocol-nh` field in Juniper XML BGP table.
-## 1.0.0-beta57 - 2020-07-30
+## 1.0.0-beta.57 - 2020-07-30
### BREAKING CHANGE
@@ -201,7 +215,7 @@ $ hyperglass-agent send-certificate
- Refactored device, query, proxy models to no longer scrub unsupported characters from the device name for the purposes of Python class attribute accessing.
- Updated hyperglass-agent docs.
-## 1.0.0-beta56 - 2020-07-28
+## 1.0.0-beta.56 - 2020-07-28
### Changed
@@ -212,19 +226,19 @@ $ hyperglass-agent send-certificate
- [#56](https://github.com/checktheroads/hyperglass/issues/56): Fix a silent Redis connection error if the Redis server was anything other than `localhost`, preventing hyperglass from starting.
-## 1.0.0-beta55 - 2020-07-27
+## 1.0.0-beta.55 - 2020-07-27
### Changed
- Removed JS favicon build process in favor of native Python implementation ([favicons](https://github/checktheroads/favicons))
-## 1.0.0-beta54 - 2020-07-25
+## 1.0.0-beta.54 - 2020-07-25
### Fixed
- Queries to hyperglass-agent devices failed due to the error `AttributeError: 'AgentConnection' object has no attribute 'collect'`
-## 1.0.0-beta53 - 2020-07-23
+## 1.0.0-beta.53 - 2020-07-23
### Added
@@ -240,7 +254,7 @@ $ hyperglass-agent send-certificate
- UI: Error messages couldn't be copied with the copy button
-## 1.0.0-beta52 - 2020-07-19
+## 1.0.0-beta.52 - 2020-07-19
### Added
@@ -264,7 +278,7 @@ $ hyperglass-agent send-certificate
- Improve command customization docs.
- [#61](https://github.com/checktheroads/hyperglass/issues/61): Fixed copy output for table data. Output is now a bulleted list of parsed data.
-## 1.0.0-beta51 - 2020-07-13
+## 1.0.0-beta.51 - 2020-07-13
### Changed
@@ -276,7 +290,7 @@ $ hyperglass-agent send-certificate
- [#54](https://github.com/checktheroads/hyperglass/issues/54): A Junos parsing error caused routes with no communities to raise an error.
- Pre-validated config files are no longer logged on startup unless debugging is enabled.
-## 1.0.0-beta50 - 2020-07-12
+## 1.0.0-beta.50 - 2020-07-12
### Added
@@ -296,7 +310,7 @@ $ hyperglass-agent send-certificate
- [#54](https://github.com/checktheroads/hyperglass/issues/54): A Junos structured/table output parsing error caused routes with multiple next-hops to raise an error.
- RPKI validation no longer occurs twice (once on serialization of the output, once on validation of the API response).
-## 1.0.0-beta49 - 2020-07-05
+## 1.0.0-beta.49 - 2020-07-05
### Changed
@@ -308,7 +322,7 @@ $ hyperglass-agent send-certificate
- Route lookups for private (RFC 1918) addresses failed due to an unnecessary lookup to [bgp.tools](https://bgp.tools)
-## 1.0.0-beta48 - 2020-07-04
+## 1.0.0-beta.48 - 2020-07-04
### Added
@@ -320,7 +334,7 @@ $ hyperglass-agent send-certificate
- When copying the opengraph image, the copied image was not deleted.
- Default traceroute help link now _actually_ points to the new docs site.
-## 1.0.0-beta47 - 2020-07-04
+## 1.0.0-beta.47 - 2020-07-04
### Added
@@ -341,13 +355,13 @@ $ hyperglass-agent send-certificate
- Generated favicon manifest files now go to the correct directory.
- Various docs site fixes
-## 1.0.0-beta46 - 2020-06-28
+## 1.0.0-beta.46 - 2020-06-28
### Added
- Support for hyperglass-agent [0.1.5](https://github.com/checktheroads/hyperglass-agent)
-## 1.0.0-beta45 - 2020-06-27
+## 1.0.0-beta.45 - 2020-06-27
### Changed
@@ -358,7 +372,7 @@ $ hyperglass-agent send-certificate
- Webhook construction bugs that caused webhooks not to send
- Empty response handling for table output
-## 1.0.0-beta44 - 2020-06-26
+## 1.0.0-beta.44 - 2020-06-26
### Added
@@ -368,13 +382,13 @@ $ hyperglass-agent send-certificate
- If webhooks were enabled, a hung test connection to RIPEStat would cause the query to time out
-## 1.0.0-beta43 - 2020-06-22
+## 1.0.0-beta.43 - 2020-06-22
### Fixed
- Logo path handling in UI
-## 1.0.0-beta42 - 2020-06-21
+## 1.0.0-beta.42 - 2020-06-21
### Added
diff --git a/docs/docs/adding-devices.mdx b/docs/docs/adding-devices.mdx
index 1bf1def..41829b7 100644
--- a/docs/docs/adding-devices.mdx
+++ b/docs/docs/adding-devices.mdx
@@ -47,7 +47,7 @@ routers:
| `address` | String | Device management hostname or IP address. |
| `network` | String | [Network Configuration](#network) |
| `display_name` | String | Device's user-facing name. |
-| `port` | Integer | TCP port used to connect to the device. |
+| `port` | Integer | TCP port used to connect to the device. `22` by default. |
| `nos` | String | Network Operating System. Must be a supported platform. |
| `structured_output` | Boolean | Disabled output parsing to structured data. |
| `credential` | | [Device Credential Configuration](#credential) |
diff --git a/docs/docs/commands.mdx b/docs/docs/commands.mdx
index 96e82fc..e66e287 100644
--- a/docs/docs/commands.mdx
+++ b/docs/docs/commands.mdx
@@ -30,7 +30,7 @@ hyperglass comes with built in support for the following platforms:
- Huawei VRP
- VyOS
-:::warning VyOS & VRFs
+:::caution VyOS & VRFs
As of `vyos-1.3-rolling-202007050117` which is the latest release VyOS has been tested with hyperglass, VyOS does not support BGP or other dynamic routing protocols in a VRF. As such, the default BGP commands for VyOS **omit the VRF from the command**.
:::
diff --git a/docs/docs/platforms.mdx b/docs/docs/platforms.mdx
index 116b871..145f0ac 100644
--- a/docs/docs/platforms.mdx
+++ b/docs/docs/platforms.mdx
@@ -11,8 +11,12 @@ description: Platforms supported by hyperglass
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` |
@@ -21,7 +25,7 @@ The following platforms use [hyperglass-agent](agent/installation.mdx) for conne
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` |
@@ -31,6 +35,7 @@ The following platforms use [Netmiko](https://github.com/ktbyers/netmiko) or [Sc
| 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` |
@@ -65,6 +70,7 @@ The following platforms use [Netmiko](https://github.com/ktbyers/netmiko) or [Sc
| 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` |
@@ -97,3 +103,17 @@ The following platforms use [Netmiko](https://github.com/ktbyers/netmiko) or [Sc
| Ubuiquiti EdgeSwitch | `ubiquiti_edgeswitch` |
| Vyatta VyOS | `vyatta_vyos` |
| VyOS | `vyos` |
+
+## Caveats
+
+### BIRD
+
+If using BIRD, more specifically [`bird_ssh`](platforms.mdx), you'll more than likely need to provide hyperglass with credentials that have root privileges. This is because, to my knowledge, the socket `birdc` uses is created by root. Therefore, if hyperglass runs the `birdc` commands as an unprivileged user, they will fail with a permissions error.
+
+### FRRouting
+
+The user account you provide hyperglass in the `credential` field needs to be a member of the `frrvty` group. See [the FRR docs](http://docs.frrouting.org/en/latest/vtysh.html) for more detail.
+
+### VyOS
+
+As of `vyos-1.3-rolling-202007050117` which is the latest release VyOS has been tested with hyperglass, VyOS does not support BGP or other dynamic routing protocols in a VRF. As such, the default BGP commands for VyOS **omit the VRF from the command**.
diff --git a/hyperglass/cli/commands.py b/hyperglass/cli/commands.py
index af7fe9a..b558430 100644
--- a/hyperglass/cli/commands.py
+++ b/hyperglass/cli/commands.py
@@ -105,7 +105,7 @@ def start(build, direct, workers): # noqa: C901
try:
if build:
- build_complete = build_ui()
+ build_complete = build_ui(timeout=180)
if build_complete and not direct:
start(**kwargs)
diff --git a/hyperglass/constants.py b/hyperglass/constants.py
index 0b4bd60..9aefe7a 100644
--- a/hyperglass/constants.py
+++ b/hyperglass/constants.py
@@ -4,7 +4,7 @@
from datetime import datetime
__name__ = "hyperglass"
-__version__ = "1.0.0-beta.75"
+__version__ = "1.0.0-beta.76"
__author__ = "Matt Love"
__copyright__ = f"Copyright {datetime.now().year} Matthew Love"
__license__ = "BSD 3-Clause Clear License"
@@ -74,12 +74,14 @@ SCRAPE_HELPERS = {
DRIVER_MAP = {
"arista_eos": "scrapli",
+ "bird_ssh": "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",
}
diff --git a/hyperglass/execution/drivers/ssh_scrapli.py b/hyperglass/execution/drivers/ssh_scrapli.py
index fafb1d9..7ad2b2c 100644
--- a/hyperglass/execution/drivers/ssh_scrapli.py
+++ b/hyperglass/execution/drivers/ssh_scrapli.py
@@ -38,9 +38,11 @@ from .ssh import SSHConnection
SCRAPLI_DRIVER_MAP = {
"arista_eos": AsyncEOSDriver,
+ "bird_ssh": AsyncGenericDriver,
"cisco_ios": AsyncIOSXEDriver,
"cisco_nxos": AsyncNXOSDriver,
"cisco_xr": AsyncIOSXRDriver,
+ "frr_ssh": AsyncGenericDriver,
"juniper": AsyncJunosDriver,
"tnsr": AsyncGenericDriver,
}
diff --git a/hyperglass/models/commands/__init__.py b/hyperglass/models/commands/__init__.py
index 9ca62df..17f408e 100644
--- a/hyperglass/models/commands/__init__.py
+++ b/hyperglass/models/commands/__init__.py
@@ -1,6 +1,8 @@
"""Validate command configuration variables."""
# Local
+from .frr import FRRCommands
+from .bird import BIRDCommands
from .tnsr import TNSRCommands
from .vyos import VyosCommands
from ..main import HyperglassModelExtra
@@ -17,9 +19,11 @@ from .mikrotik_switchos import MikrotikSwitchOS
_NOS_MAP = {
"arista": AristaCommands,
+ "bird_ssh": BIRDCommands,
"cisco_ios": CiscoIOSCommands,
"cisco_nxos": CiscoNXOSCommands,
"cisco_xr": CiscoXRCommands,
+ "frr_ssh": FRRCommands,
"huawei": HuaweiCommands,
"juniper": JuniperCommands,
"mikrotik_routeros": MikrotikRouterOS,
@@ -33,12 +37,14 @@ _NOS_MAP = {
class Commands(HyperglassModelExtra):
"""Base class for command definitions."""
- juniper: CommandGroup = JuniperCommands()
arista: CommandGroup = AristaCommands()
+ bird_ssh: CommandGroup = BIRDCommands()
cisco_ios: CommandGroup = CiscoIOSCommands()
- cisco_xr: CommandGroup = CiscoXRCommands()
cisco_nxos: CommandGroup = CiscoNXOSCommands()
+ cisco_xr: CommandGroup = CiscoXRCommands()
+ frr_ssh: CommandGroup = FRRCommands()
huawei: CommandGroup = HuaweiCommands()
+ juniper: CommandGroup = JuniperCommands()
mikrotik_routeros: CommandGroup = MikrotikRouterOS()
mikrotik_switchos: CommandGroup = MikrotikSwitchOS()
nokia_sros: CommandGroup = NokiaSROSCommands()
diff --git a/hyperglass/models/commands/bird.py b/hyperglass/models/commands/bird.py
new file mode 100644
index 0000000..9a86f0a
--- /dev/null
+++ b/hyperglass/models/commands/bird.py
@@ -0,0 +1,56 @@
+"""BIRD Routing Daemon Command Model."""
+
+# Third Party
+from pydantic import StrictStr
+
+# Local
+from .common import CommandSet, CommandGroup
+
+
+class _IPv4(CommandSet):
+ """Default commands for ipv4 commands."""
+
+ bgp_community: str = 'birdc "show route all where {target} ~ bgp_community"'
+ bgp_aspath: str = 'birdc "show route all where bgp_path ~ {target}"'
+ bgp_route: str = 'birdc "show route all where {target} ~ net"'
+ ping: str = "ping -4 -c 5 -I {source} {target}"
+ traceroute: str = "traceroute -4 -w 1 -q 1 -s {source} {target}"
+
+
+class _IPv6(CommandSet):
+ """Default commands for ipv6 commands."""
+
+ bgp_community: str = 'birdc "show route all where {target} ~ bgp_community"'
+ bgp_aspath: str = 'birdc "show route all where bgp_path ~ {target}"'
+ bgp_route: str = 'birdc "show route all where {target} ~ net"'
+ ping: StrictStr = "ping -6 -c 5 -I {source} {target}"
+ traceroute: StrictStr = "traceroute -6 -w 1 -q 1 -s {source} {target}"
+
+
+class _VPNIPv4(CommandSet):
+ """Default commands for dual afi commands."""
+
+ bgp_community: str = 'birdc "show route all table {vrf} where {target} ~ bgp_community"'
+ bgp_aspath: str = 'birdc "show route all table {vrf} where bgp_path ~ {target}"'
+ bgp_route: str = 'birdc "show route all table {vrf} where {target} ~ net"'
+ ping: StrictStr = "ping -4 -c 5 -I {source} {target}"
+ traceroute: StrictStr = "traceroute -4 -w 1 -q 1 -s {source} {target}"
+
+
+class _VPNIPv6(CommandSet):
+ """Default commands for dual afi commands."""
+
+ bgp_community: str = 'birdc "show route all table {vrf} where {target} ~ bgp_community"'
+ bgp_aspath: str = 'birdc "show route all table {vrf} where bgp_path ~ {target}"'
+ bgp_route: str = 'birdc "show route all table {vrf} where {target} ~ net"'
+ ping: StrictStr = "ping -6 -c 5 -I {source} {target}"
+ traceroute: StrictStr = "traceroute -6 -w 1 -q 1 -s {source} {target}"
+
+
+class BIRDCommands(CommandGroup):
+ """Validation model for default BIRD commands."""
+
+ ipv4_default: _IPv4 = _IPv4()
+ ipv6_default: _IPv6 = _IPv6()
+ ipv4_vpn: _VPNIPv4 = _VPNIPv4()
+ ipv6_vpn: _VPNIPv6 = _VPNIPv6()
diff --git a/hyperglass/models/commands/frr.py b/hyperglass/models/commands/frr.py
new file mode 100644
index 0000000..2dde708
--- /dev/null
+++ b/hyperglass/models/commands/frr.py
@@ -0,0 +1,56 @@
+"""FRRouting Command Model."""
+
+# Third Party
+from pydantic import StrictStr
+
+# Local
+from .common import CommandSet, CommandGroup
+
+
+class _IPv4(CommandSet):
+ """Default commands for ipv4 commands."""
+
+ bgp_community: StrictStr = 'vtysh -c "show bgp ipv4 unicast community {target}"'
+ bgp_aspath: StrictStr = 'vtysh -c "show bgp ipv4 unicast regexp {target}"'
+ bgp_route: StrictStr = 'vtysh -c "show bgp ipv4 unicast {target}"'
+ ping: StrictStr = "ping -4 -c 5 -I {source} {target}"
+ traceroute: StrictStr = "traceroute -4 -w 1 -q 1 -s {source} {target}"
+
+
+class _IPv6(CommandSet):
+ """Default commands for ipv6 commands."""
+
+ bgp_community: StrictStr = 'vtysh -c "show bgp ipv6 unicast community {target}"'
+ bgp_aspath: StrictStr = 'vtysh -c "show bgp ipv6 unicast regexp {target}"'
+ bgp_route: StrictStr = 'vtysh -c "show bgp ipv6 unicast {target}"'
+ ping: StrictStr = "ping -6 -c 5 -I {source} {target}"
+ traceroute: StrictStr = "traceroute -6 -w 1 -q 1 -s {source} {target}"
+
+
+class _VPNIPv4(CommandSet):
+ """Default commands for dual afi commands."""
+
+ bgp_community: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv4 unicast community {target}"'
+ bgp_aspath: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv4 unicast regexp {target}"'
+ bgp_route: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv4 unicast {target}"'
+ ping: StrictStr = "ping -4 -c 5 -I {source} {target}"
+ traceroute: StrictStr = "traceroute -4 -w 1 -q 1 -s {source} {target}"
+
+
+class _VPNIPv6(CommandSet):
+ """Default commands for dual afi commands."""
+
+ bgp_community: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv6 unicast community {target}"'
+ bgp_aspath: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv6 unicast regexp {target}"'
+ bgp_route: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv6 unicast {target}"'
+ ping: StrictStr = "ping -6 -c 5 -I {source} {target}"
+ traceroute: StrictStr = "traceroute -6 -w 1 -q 1 -s {source} {target}"
+
+
+class FRRCommands(CommandGroup):
+ """Validation model for default FRRouting commands."""
+
+ ipv4_default: _IPv4 = _IPv4()
+ ipv6_default: _IPv6 = _IPv6()
+ ipv4_vpn: _VPNIPv4 = _VPNIPv4()
+ ipv6_vpn: _VPNIPv6 = _VPNIPv6()
diff --git a/hyperglass/models/config/devices.py b/hyperglass/models/config/devices.py
index 0379951..ec2b592 100644
--- a/hyperglass/models/config/devices.py
+++ b/hyperglass/models/config/devices.py
@@ -50,7 +50,7 @@ class Device(HyperglassModel):
credential: Credential
proxy: Optional[Proxy]
display_name: StrictStr
- port: StrictInt
+ port: StrictInt = 22
ssl: Optional[Ssl]
nos: StrictStr
commands: Optional[StrictStr]
diff --git a/hyperglass/ui/components/path/chart.tsx b/hyperglass/ui/components/path/chart.tsx
index a0b150b..ca20700 100644
--- a/hyperglass/ui/components/path/chart.tsx
+++ b/hyperglass/ui/components/path/chart.tsx
@@ -1,14 +1,12 @@
-import { useMemo } from 'react';
import { Box, Flex, SkeletonText, Badge, VStack } from '@chakra-ui/react';
import ReactFlow from 'react-flow-renderer';
import { Background, ReactFlowProvider } from 'react-flow-renderer';
import { Handle, Position } from 'react-flow-renderer';
-import { useConfig, useColorValue, useColorToken, useBreakpointValue } from '~/context';
+import { useConfig, useColorValue, useColorToken } from '~/context';
import { useASNDetail } from '~/hooks';
import { Controls } from './controls';
-import { buildElements } from './util';
+import { useElements } from './useElements';
-import type { ReactFlowProps } from 'react-flow-renderer';
import type { TChart, TNode, TNodeData } from './types';
export const Chart: React.FC = (props: TChart) => {
@@ -17,19 +15,17 @@ export const Chart: React.FC = (props: TChart) => {
const dots = useColorToken('colors', 'blackAlpha.500', 'whiteAlpha.400');
- const flowProps = useBreakpointValue>({
- base: { defaultPosition: [0, 300], defaultZoom: 0 },
- lg: { defaultPosition: [100, 300], defaultZoom: 0.7 },
- }) ?? { defaultPosition: [100, 300], defaultZoom: 0.7 };
-
- const elements = useMemo(() => [...buildElements({ asn: primary_asn, name: org_name }, data)], [
- data,
- ]);
+ const elements = useElements({ asn: primary_asn, name: org_name }, data);
return (
-
+ setTimeout(() => inst.fitView(), 0)}
+ >
diff --git a/hyperglass/ui/components/path/path.tsx b/hyperglass/ui/components/path/path.tsx
index e4a6ea7..2f301a6 100644
--- a/hyperglass/ui/components/path/path.tsx
+++ b/hyperglass/ui/components/path/path.tsx
@@ -1,11 +1,11 @@
import {
Modal,
+ Skeleton,
ModalBody,
ModalHeader,
ModalOverlay,
ModalContent,
useDisclosure,
- Skeleton,
ModalCloseButton,
} from '@chakra-ui/react';
import { useColorValue, useBreakpointValue } from '~/context';
diff --git a/hyperglass/ui/components/path/useElements.ts b/hyperglass/ui/components/path/useElements.ts
new file mode 100644
index 0000000..e0916b4
--- /dev/null
+++ b/hyperglass/ui/components/path/useElements.ts
@@ -0,0 +1,115 @@
+import dagre from 'dagre';
+import { useMemo } from 'react';
+import isEqual from 'react-fast-compare';
+
+import type { FlowElement } from 'react-flow-renderer';
+import type { BasePath } from './types';
+
+const NODE_WIDTH = 200;
+const NODE_HEIGHT = 48;
+
+export function useElements(base: BasePath, data: TStructuredResponse): FlowElement[] {
+ return useMemo(() => {
+ return [...buildElements(base, data)];
+ }, [data.routes.length]);
+}
+
+/**
+ * Calculate the positions for each AS Path.
+ * @see https://github.com/MrBlenny/react-flow-chart/issues/61
+ */
+function* buildElements(base: BasePath, data: TStructuredResponse): Generator {
+ const { routes } = data;
+ // Eliminate empty AS paths & deduplicate non-empty AS paths. Length should be same as count minus empty paths.
+ const asPaths = routes.filter(r => r.as_path.length !== 0).map(r => [...new Set(r.as_path)]);
+
+ const totalPaths = asPaths.length - 1;
+
+ const g = new dagre.graphlib.Graph();
+ g.setGraph({ marginx: 20, marginy: 20 });
+ g.setDefaultEdgeLabel(() => ({}));
+
+ // Set the origin (i.e., the hyperglass user) at the base.
+ g.setNode(base.asn, { width: NODE_WIDTH, height: NODE_HEIGHT });
+
+ for (const [groupIdx, pathGroup] of asPaths.entries()) {
+ // For each ROUTE's AS Path:
+
+ // Find the route after this one.
+ const nextGroup = groupIdx < totalPaths ? asPaths[groupIdx + 1] : [];
+
+ // Connect the first hop in the AS Path to the base (for dagre).
+ g.setEdge(base.asn, `${groupIdx}-${pathGroup[0]}`);
+
+ // Eliminate duplicate AS Paths.
+ if (!isEqual(pathGroup, nextGroup)) {
+ for (const [idx, asn] of pathGroup.entries()) {
+ // For each ASN in the ROUTE:
+
+ const node = `${groupIdx}-${asn}`;
+ const endIdx = pathGroup.length - 1;
+
+ // Add the AS as a node.
+ g.setNode(node, { width: NODE_WIDTH, height: NODE_HEIGHT });
+
+ // Connect the first hop in the AS Path to the base (for react-flow).
+ if (idx === 0) {
+ yield {
+ id: `e${base.asn}-${node}`,
+ source: base.asn,
+ target: node,
+ };
+ }
+ // Connect every intermediate hop to each other.
+ if (idx !== endIdx) {
+ const next = `${groupIdx}-${pathGroup[idx + 1]}`;
+ g.setEdge(node, next);
+ yield {
+ id: `e${node}-${next}`,
+ source: node,
+ target: next,
+ };
+ }
+ }
+ }
+ }
+
+ // Now that that nodes are added, create the layout.
+ dagre.layout(g, { rankdir: 'BT', align: 'UR' });
+
+ // Get the base ASN's positions.
+ const x = g.node(base.asn).x - NODE_WIDTH / 2;
+ const y = g.node(base.asn).y + NODE_HEIGHT * 6;
+
+ yield {
+ id: base.asn,
+ type: 'ASNode',
+ position: { x, y },
+ data: { asn: base.asn, name: base.name, hasChildren: true, hasParents: false },
+ };
+
+ for (const [groupIdx, pathGroup] of asPaths.entries()) {
+ const nextGroup = groupIdx < totalPaths ? asPaths[groupIdx + 1] : [];
+ if (!isEqual(pathGroup, nextGroup)) {
+ for (const [idx, asn] of pathGroup.entries()) {
+ const node = `${groupIdx}-${asn}`;
+ const endIdx = pathGroup.length - 1;
+ const x = g.node(node).x - NODE_WIDTH / 2;
+ const y = g.node(node).y - NODE_HEIGHT * (idx * 6);
+
+ // Get each ASN's positions.
+ yield {
+ id: node,
+ type: 'ASNode',
+ position: { x, y },
+ data: {
+ asn,
+ name: `AS${asn}`,
+ hasChildren: idx < endIdx,
+ hasParents: true,
+ },
+ };
+ }
+ }
+ }
+}
diff --git a/hyperglass/ui/components/path/util.ts b/hyperglass/ui/components/path/util.ts
deleted file mode 100644
index 67339a2..0000000
--- a/hyperglass/ui/components/path/util.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { arrangeIntoTree } from '~/util';
-
-import type { FlowElement, Elements } from 'react-flow-renderer';
-import type { PathPart } from '~/types';
-import type { BasePath } from './types';
-
-function treeToElement(part: PathPart, len: number, index: number): FlowElement[] {
- const x = index * 250;
- const y = -(len * 10);
- const elements = [
- {
- id: String(part.base),
- type: 'ASNode',
- position: { x, y },
- data: {
- asn: part.base,
- name: `AS${part.base}`,
- hasChildren: part.children.length !== 0,
- hasParents: true,
- },
- },
- ] as Elements;
-
- for (const child of part.children) {
- let xc = index;
- if (part.children.length !== 0) {
- elements.push({
- id: `e${part.base}-${child.base}`,
- source: String(part.base),
- target: String(child.base),
- });
- } else {
- xc = x;
- }
- elements.push(...treeToElement(child, part.children.length * 12 + len, xc));
- }
- return elements;
-}
-
-export function* buildElements(base: BasePath, data: TStructuredResponse): Generator {
- const { routes } = data;
- // Eliminate empty AS paths & deduplicate non-empty AS paths. Length should be same as count minus empty paths.
- const asPaths = routes.filter(r => r.as_path.length !== 0).map(r => [...new Set(r.as_path)]);
- const asTree = arrangeIntoTree(asPaths);
- const numHops = asPaths.flat().length;
- const childPaths = asTree.map((a, i) => {
- return treeToElement(a, asTree.length, i);
- });
-
- // Add the first hop at the base.
- yield {
- id: base.asn,
- type: 'ASNode',
- position: { x: 150, y: numHops * 10 },
- data: { asn: base.asn, name: base.name, hasChildren: true, hasParents: false },
- };
-
- for (const path of childPaths) {
- // path = Each unique path from origin
- const first = path[0];
- yield { id: `e${base.asn}-${first.id}`, source: base.asn, target: first.id };
- // Add link from base to each first hop.
- yield { id: `e${base.asn}-${first.id}`, source: base.asn, target: first.id };
- for (const hop of path) {
- yield hop;
- }
- }
-}
diff --git a/hyperglass/ui/package.json b/hyperglass/ui/package.json
index 82c4f6d..7dea06d 100644
--- a/hyperglass/ui/package.json
+++ b/hyperglass/ui/package.json
@@ -26,6 +26,7 @@
"@hookstate/persistence": "^3.0.0",
"@meronex/icons": "^4.0.0",
"color2k": "^1.1.1",
+ "dagre": "^0.8.5",
"dayjs": "^1.8.25",
"framer-motion": "^3.2.2-rc.1",
"lodash": "^4.17.15",
@@ -47,6 +48,7 @@
},
"devDependencies": {
"@hookstate/devtools": "^3.0.0",
+ "@types/dagre": "^0.7.44",
"@types/node": "^14.14.17",
"@types/react": "^17.0.0",
"@types/react-select": "^3.0.28",
diff --git a/hyperglass/ui/yarn.lock b/hyperglass/ui/yarn.lock
index 344fee9..6f7a449 100644
--- a/hyperglass/ui/yarn.lock
+++ b/hyperglass/ui/yarn.lock
@@ -1055,6 +1055,11 @@
dependencies:
tslib "^2.0.0"
+"@types/dagre@^0.7.44":
+ version "0.7.44"
+ resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.44.tgz#8f4b796b118ca29c132da7068fbc0d0351ee5851"
+ integrity sha512-N6HD+79w77ZVAaVO7JJDW5yJ9LAxM62FpgNGO9xEde+KVYjDRyhIMzfiErXpr1g0JPon9kwlBzoBK6s4fOww9Q==
+
"@types/eslint-visitor-keys@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@@ -2735,6 +2740,14 @@ d@1, d@^1.0.1:
es5-ext "^0.10.50"
type "^1.0.1"
+dagre@^0.8.5:
+ version "0.8.5"
+ resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee"
+ integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==
+ dependencies:
+ graphlib "^2.1.8"
+ lodash "^4.17.15"
+
damerau-levenshtein@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791"
@@ -3971,6 +3984,13 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
+graphlib@^2.1.8:
+ version "2.1.8"
+ resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
+ integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
+ dependencies:
+ lodash "^4.17.15"
+
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
diff --git a/pyproject.toml b/pyproject.toml
index 9190005..37a0520 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,7 +23,7 @@ license = "BSD-3-Clause-Clear"
name = "hyperglass"
readme = "README.md"
repository = "https://github.com/checktheroads/hyperglass"
-version = "1.0.0-beta.75"
+version = "1.0.0-beta.76"
[tool.poetry.scripts]
hyperglass = "hyperglass.console:CLI"