mirror of
https://github.com/thatmattlove/hyperglass.git
synced 2026-01-17 08:48:05 +00:00
all the things
This commit is contained in:
parent
9e94429bb6
commit
eca889e866
42 changed files with 2010 additions and 968 deletions
|
|
@ -12,8 +12,8 @@
|
|||
|
||||
From `hyperglass/hyperglass/configuration/configuration.toml` `[branding]` table.
|
||||
|
||||
# Site Parameters
|
||||
#### site_title
|
||||
# `[branding]` - Site Parameters
|
||||
#### site_name
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------- |
|
||||
|
|
@ -21,6 +21,42 @@ From `hyperglass/hyperglass/configuration/configuration.toml` `[branding]` table
|
|||
|
||||
HTML `<title>` element that is shown in a browser's title bar.
|
||||
|
||||
## `[branding.footer]` - Footer Configuration
|
||||
#### enable
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
Enables or disables entire footer element.
|
||||
|
||||
The footer text itself can be customized by adding a [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) document to `hyperglass/hyperglass/render/templates/info/details/footer.md`. The example file, `footer.md.example`, can be copied to `footer.md` and modified. All Markdown files in this directory are excluded from change control and will not be overwritten when hyperglass is updated.
|
||||
|
||||
!!! note "Syntax"
|
||||
The custom content Markdown files *must* have TOML Front Matter, even if there are no attributes used.
|
||||
|
||||
## `[branding.credit]` - Credit Configuration
|
||||
#### enable
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
Enables or disables text below the footer element, which links to the hyperglass repo:
|
||||
|
||||
> Powered by Hyperglass. Source code licensed BSD 3-Clause Clear.
|
||||
|
||||
## `[branding.peering_db]` - PeeringDB Configuration
|
||||
#### enable
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
Enables or disables the PeeringDB link in the upper right corner. If `true`, the [primary_asn](#primary_asn) will be automatically used to create the URL to your ASN's PeeringDB entry.
|
||||
|
||||
## `[branding.text]` - Site-Wide Text Customizations
|
||||
|
||||
#### title_mode
|
||||
|
||||
| Type | Default Value |
|
||||
|
|
@ -47,91 +83,113 @@ Controls the title section on the main page.
|
|||
|
||||
See [primary_asn](#primary_asn) parameter.
|
||||
|
||||
#### enable_footer
|
||||
#### query_type
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
| Type | Default Value |
|
||||
| ------ | -------------------- |
|
||||
| String | `"Query Type"` |
|
||||
|
||||
Enables or disables entire footer element, which contains text defined in `hyperglass/hyperglass/render/templates/footer.md`.
|
||||
Placeholder text that appears in the Query Type dropdown.
|
||||
|
||||
#### enable_credit
|
||||
#### results
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
| Type | Default Value |
|
||||
| ------ | -------------------- |
|
||||
| String | `"Results"` |
|
||||
|
||||
Enables or disables hoverable icon on the left side of the footer, which links to the hyperglass repo.
|
||||
Title text used for the results message box which contains the results of the query.
|
||||
|
||||
#### show_peeringdb
|
||||
#### location
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
| Type | Default Value |
|
||||
| ------ | ---------------------- |
|
||||
| String | `"Select Location..."` |
|
||||
|
||||
Enables or disables the PeeringDB link in the upper right corner. If `True`, the [primary_asn](#primary_asn) will be automatically used to create the URL to your ASN's PeeringDB entry.
|
||||
Placeholder text that appears in the Location dropdown.
|
||||
|
||||
# Colors
|
||||
#### query_placeholder
|
||||
|
||||
#### color_btn_submit
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------------------- |
|
||||
| String | `"IP, Prefix, Community, or AS Path"` |
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
|
||||
Placeholder text that appears in the main search box.
|
||||
|
||||
Sets color of the submit button.
|
||||
#### bgp_route
|
||||
|
||||
#### color_tag_loctitle
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"BGP Route"` |
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
|
||||
Dropdown text used for the BGP Route query type.
|
||||
|
||||
Sets color of the title portion of the location tag which appears at the top of the results box on the left side.
|
||||
#### bgp_community
|
||||
|
||||
#### color_tag_cmdtitle
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------- |
|
||||
| String | `"BGP Community"` |
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
|
||||
Dropdown text used for the BGP Community query type.
|
||||
|
||||
Sets color of the title portion of the command tag which appears at the top of the results box on the right side.
|
||||
#### bgp_aspath
|
||||
|
||||
#### color_tag_cmd
|
||||
| Type | Default Value |
|
||||
| ------ | --------------- |
|
||||
| String | `"BGP AS Path"` |
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#ff5e5b"` | <span class="bd-color" style="background-color: #ff5e5b;"></span> |
|
||||
Dropdown text used for the BGP AS Path query type.
|
||||
|
||||
Sets color of the command name portion of the command tag which appears at the top of the results box on the right side.
|
||||
#### ping
|
||||
|
||||
#### color_tag_loc
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"Ping"` |
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
|
||||
Dropdown text used for the Ping query type.
|
||||
|
||||
Sets color of the location name portion of the location tag which appears at the top of the results box on the left side.
|
||||
#### traceroute
|
||||
|
||||
#### color_bg
|
||||
| Type | Default Value |
|
||||
| ------ | -------------- |
|
||||
| String | `"Traceroute"` |
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#fbfffe"` | <span class="bd-color" style="background-color: #fbfffe;"></span> |
|
||||
Dropdown text used for the Traceroute query type.
|
||||
|
||||
Sets the background color of the main page.
|
||||
### `[branding.text.404]` - 404 Error Page Text Customization
|
||||
|
||||
#### color_progressbar
|
||||
The 404 error page will be displayed if a user attempts to visit any non-existent URI, e.g. `http://lg.domain.tld/this_isnt_real`
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
|
||||
#### title
|
||||
|
||||
Sets color of the progress bar that displays while the back-end application processes the request.
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"Error"` |
|
||||
|
||||
# Logo
|
||||
#### subtitle
|
||||
|
||||
#### logo_path
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------ |
|
||||
| String | `"Page Not Found"` |
|
||||
|
||||
### `[branding.text.500]` - 500 Error Page Text Customization
|
||||
|
||||
The 500 error page will be displayed if there is a backend problem or if an exception is raised. If you get this page, you should probably enable debug mode to find out why.
|
||||
|
||||
#### title
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"Error"` |
|
||||
|
||||
#### subtitle
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------ |
|
||||
| String | `"Something Went Wrong"` |
|
||||
|
||||
## `[branding.logo]` - Logo & Favicon Configuration
|
||||
|
||||
#### path
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------------------- |
|
||||
|
|
@ -139,7 +197,10 @@ Sets color of the progress bar that displays while the back-end application proc
|
|||
|
||||
Sets the path to the logo file, which will be displayed if [title_mode](#title_mode) is set to `"logo_only"`. This file can be any browser-compatible format, such as JPEG, PNG, or SVG.
|
||||
|
||||
#### logo_width
|
||||
!!! note "Custom Files"
|
||||
The `hyperglass/hyperglass/static/custom/` directory is excluded from change control, and will not be overwritten when hyperglass is updated. Custom image files should be placed here.
|
||||
|
||||
#### width
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
|
|
@ -147,31 +208,127 @@ Sets the path to the logo file, which will be displayed if [title_mode](#title_m
|
|||
|
||||
Sets the width of the logo defined in the [logo_path](#logo_path) parameter. This is helpful if your logo is a dimension that doesn't quite work with the default width.
|
||||
|
||||
# UI Text
|
||||
|
||||
#### placeholder_prefix
|
||||
#### favicons
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------------------- |
|
||||
| String | `"Prefix, IP, Community, or AS_PATH"` |
|
||||
| String | `"static/images/favicon/"` |
|
||||
|
||||
Sets the placeholder text that appears in the main search box.
|
||||
Sets the path to the favicons directory (must have a trailing `/`). For full browser and platform comatability, it is recommended to use [RealFaviconGenerator](https://realfavicongenerator.net/) and place all the generated files in `static/custom/images/favicon/` (and update the `favicons` parameter).
|
||||
|
||||
#### text_results
|
||||
## `[branding.color]` - Color Customization
|
||||
|
||||
#### background
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#fbfffe"` | <span class="bd-color" style="background-color: #fbfffe;"></span> |
|
||||
|
||||
Sets the background color of the main page.
|
||||
|
||||
|
||||
#### button_submit
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
|
||||
|
||||
Sets color of the submit button.
|
||||
|
||||
#### danger
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#ff3860"` | <span class="bd-color" style="background-color: #ff3860;"></span> |
|
||||
|
||||
Sets color of the Bulma "danger" class, which is used for some user-facing error, and as the background color for the 404, 500 and Rate Limit error pages.
|
||||
|
||||
#### progress_bar
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
|
||||
|
||||
Sets color of the progress bar that displays while the back-end application processes the request.
|
||||
|
||||
### `[branding.color.tag]` - Tag Color Customization
|
||||
|
||||
Bulma tags are used to show attributes for the active query being run.
|
||||
|
||||
#### type_title
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
|
||||
|
||||
Sets color of the title portion of the query type tag which appears at the top of the results box on the right side.
|
||||
|
||||
#### type
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#ff5e5b"` | <span class="bd-color" style="background-color: #ff5e5b;"></span> |
|
||||
|
||||
Sets color of the type portion of the query type tag which appears at the top of the results box on the right side.
|
||||
|
||||
#### location_title
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
|
||||
|
||||
Sets color of the title portion of the location tag which appears at the top of the results box on the left side.
|
||||
|
||||
#### location
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
|
||||
|
||||
Sets color of the location name portion of the location tag which appears at the top of the results box on the left side.
|
||||
|
||||
## `[branding.font]` - Font Customization
|
||||
|
||||
Hyperglass makes use of two font families - a primary family and a monospace family. The primary family is used for all paragraph, title/subtitle, and non-code/preformatted text, and the monospace font is used for any code/preformatted blocks as well as the query results.
|
||||
|
||||
The values are passed as a Jinja2 variable to generate `hyperglass/hyperglass/static/sass/hyperglass.scss`, which will be compiled from Sass to CSS.
|
||||
|
||||
### `[branding.font.primary]` - Primary Font Customization
|
||||
|
||||
#### name
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"Results"` |
|
||||
| String | `"Nunito"` |
|
||||
|
||||
Sets the header text of the results box.
|
||||
Sets the web font name for the primary font.
|
||||
|
||||
#### text_location
|
||||
#### url
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------------------------------------------------------- |
|
||||
| String | `"https://fonts.googleapis.com/css?family=Nunito:400,600,700"` |
|
||||
|
||||
Sets the web font URL for the primary font.
|
||||
|
||||
### `[branding.font.mono]` - Monospace Font Customization
|
||||
|
||||
#### name
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"Location"` |
|
||||
| String | `"Fira Mono"` |
|
||||
|
||||
Sets the web font name for the monospace/code/preformatted text font.
|
||||
|
||||
#### url
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------------------------------------------- |
|
||||
| String | `"https://fonts.googleapis.com/css?family=Fira+Mono"` |
|
||||
|
||||
Sets the web font URL for the monospace/code/preformatted text font.
|
||||
|
||||
Sets the placeholder text of the location selector.
|
||||
|
||||
#### text_cache
|
||||
|
||||
|
|
@ -196,110 +353,3 @@ Sets the title text for the site-wide rate limit page. Users are redirected to t
|
|||
| String | `"You have accessed this site more than {rate_limit_site} times in the last minute."` |
|
||||
|
||||
Sets the subtitle text for the site-wide rate limit page. Users are redirected to this page when they have accessed the site more than the [specified](/configuration/general/#rate_limit_site) limit. `{rate_limit_site}` will be formatted with the value of [rate_limit_site](/configuration/general/#rate_limit_site).
|
||||
|
||||
#### text_500_title
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------- |
|
||||
| String | `"Error"` |
|
||||
|
||||
Sets the title text for the full general error page.
|
||||
|
||||
#### text_500_subtitle
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `"Something went wrong."` |
|
||||
|
||||
Sets the subtitle text for the full general error page.
|
||||
|
||||
#### text_500_button
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------- |
|
||||
| String | `"Home"` |
|
||||
|
||||
Sets the button text for the full general error page.
|
||||
|
||||
#### text_help_bgp_route
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `"Performs BGP table lookup based on IPv4/IPv6 prefix."` |
|
||||
|
||||
Sets the BGP Route query help text, displayed when the **?** icon is hovered.
|
||||
|
||||
#### text_help_bgp_community
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `'Performs BGP table lookup based on <a href="https://tools.ietf.org/html/rfc4360">Extended</a> or <a href="https://tools.ietf.org/html/rfc8195">Large</a> community value.'` |
|
||||
|
||||
Sets the BGP Community query help text, displayed when the **?** icon is hovered.
|
||||
|
||||
!!! note
|
||||
Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string.
|
||||
|
||||
#### text_help_bgp_aspath
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `'Performs BGP table lookup based on <code>AS_PATH</code> regular expression.<br>For commonly used BGP regular expressions, <a href="https://hyperglass.readthedocs.io/en/latest/Extras/common_as_path_regex/">click here</a>.'` |
|
||||
|
||||
Sets the BGP AS Path query help text, displayed when the **?** icon is hovered.
|
||||
|
||||
!!! note
|
||||
Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string.
|
||||
|
||||
#### text_help_ping
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `"Sends 5 ICMP echo requests to the target."` |
|
||||
|
||||
Sets the Ping query help text, displayed when the **?** icon is hovered.
|
||||
|
||||
#### text_help_traceroute
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `'Performs UDP Based traceroute to the target.<br>For information about how to interpret traceroute results, <a href="https://www.nanog.org/meetings/nanog45/presentations/Sunday/RAS_traceroute_N45.pdf">click here</a>.'` |
|
||||
|
||||
Sets the Traceroute query help text, displayed when the **?** icon is hovered.
|
||||
|
||||
!!! note
|
||||
Since there are double quotes (`" "`) in the `<a>` HTML tags, single quotes (`' '`) are required for the TOML string.
|
||||
|
||||
# Fonts
|
||||
|
||||
#### primary_font_url
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `"https://fonts.googleapis.com/css?family=Nunito:400,600,700"` |
|
||||
|
||||
Sets the web font URL for the primary font. This font is used for all titles, subtitles, and non-code/preformatted text. The value is passed as a Jinja2 variable to the head block in the base template.
|
||||
|
||||
#### primary_font_name
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `"Nunito"` |
|
||||
|
||||
Sets the web font name for the primary font. This font is used for all titles, subtitles, and non-code/preformatted text. The value is passed as a Jinja2 variable to generate `hyperglass/hyperglass/static/sass/hyperglass.scss`, which ultimately get passed to CSS.
|
||||
|
||||
#### mono_font_url
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `"https://fonts.googleapis.com/css?family=Fira+Mono"` |
|
||||
|
||||
Sets the web font URL for the monospace/code/preformatted text font. This font is used for all query output text, as well as the command title and command name tag. The value is passed as a Jinja2 variable to the head block in the base template.
|
||||
|
||||
#### mono_font_name
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `"Fira Mono"` |
|
||||
|
||||
Sets the web font URL for the monospace/code/preformatted text font. This font is used for all query output text, as well as the command title and command name tag. The value is passed as a Jinja2 variable to generate `hyperglass/hyperglass/static/sass/hyperglass.scss`, which ultimately get passed to CSS.
|
||||
|
|
|
|||
58
docs/configuration/commands.md
Normal file
58
docs/configuration/commands.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
Commands are defined in `hyperglass/hyperglass/configuration/commands.toml`. A table for each NOS (Network Operating System) contains three nested tables: `dual`, `ipv4`, and `ipv6`.
|
||||
|
||||
| Table | Function | Commands |
|
||||
| --------- | ----------------------------- | ------------------------------- |
|
||||
| **dual** | Protocol agnostic commands | `bgp_community` `bgp_aspath` |
|
||||
| **ipv4** | IPv4-specific commands | `bgp_route` `ping` `traceroute` |
|
||||
| **ipv6** | IPv6-specific commands | `bgp_route` `ping` `traceroute` |
|
||||
|
||||
#### Variables
|
||||
|
||||
The following variables can be used in the command definitions.
|
||||
|
||||
- `{target}` Maps to search box input.
|
||||
- `{src_addr_ipv4}` Maps to [src_addr_ipv4](configuration/devices.md/#src_addr_ipv4)
|
||||
- `{src_addr_ipv6}` Maps to [src_addr_ipv6](configuration/devices.md/#src_addr_ipv6)
|
||||
|
||||
#### Example
|
||||
|
||||
```toml
|
||||
[[cisco_ios]]
|
||||
[cisco_ios.dual]
|
||||
bgp_community = "show bgp all community {target}"
|
||||
bgp_aspath = 'show bgp all quote-regexp "{target}"'
|
||||
[cisco_ios.ipv4]
|
||||
bgp_route = "show bgp ipv4 unicast {target} | exclude pathid:|Epoch"
|
||||
ping = "ping {target} repeat 5 source {src_addr_ipv4}"
|
||||
traceroute = "traceroute {target} timeout 1 probe 2 source {src_addr_ipv4}"
|
||||
[cisco_ios.ipv6]
|
||||
bgp_route = "show bgp ipv6 unicast {target} | exclude pathid:|Epoch"
|
||||
ping = "ping ipv6 {target} repeat 5 source {src_addr_ipv6}"
|
||||
traceroute = "traceroute ipv6 {target} timeout 1 probe 2 source {src_addr_ipv6}"
|
||||
|
||||
[[cisco_xr]]
|
||||
[cisco_xr.dual]
|
||||
bgp_community = 'show bgp all unicast community {target} | utility egrep -v "\(BGP |Table |Non-stop\)"'
|
||||
bgp_aspath = 'show bgp all unicast regexp {target} | utility egrep -v "\(BGP |Table |Non-stop\)"'
|
||||
[cisco_xr.ipv4]
|
||||
bgp_route = 'show bgp ipv4 unicast {target} | util egrep "\(BGP routing table entry|Path \#|aggregated by|Origin |Community:|validity| from \)"'
|
||||
ping = "ping ipv4 {target} count 5 source {src_addr_ipv4}"
|
||||
traceroute = "traceroute ipv4 {target} timeout 1 probe 2 source {src_addr_ipv4}"
|
||||
[cisco_xr.ipv6]
|
||||
bgp_route = 'show bgp ipv6 unicast {target} | util egrep "\(BGP routing table entry|Path \#|aggregated by|Origin |Community:|validity| from \)"'
|
||||
ping = "ping ipv6 {target} count 5 source {src_addr_ipv6}"
|
||||
traceroute = "traceroute ipv6 {target} timeout 1 probe 2 source {src_addr_ipv6}"
|
||||
|
||||
[[juniper]]
|
||||
[juniper.dual]
|
||||
bgp_community = "show route protocol bgp community {target}"
|
||||
bgp_aspath = "show route protocol bgp aspath-regex {target}"
|
||||
[juniper.ipv4]
|
||||
bgp_route = "show route protocol bgp table inet.0 {target} detail"
|
||||
ping = "ping inet {target} count 5 source {src_addr_ipv4}"
|
||||
traceroute = "traceroute inet {target} wait 1 source {src_addr_ipv4}"
|
||||
[juniper.ipv6]
|
||||
bgp_route = "show route protocol bgp table inet6.0 {target} detail"
|
||||
ping = "ping inet6 {target} count 5 source {src_addr_ipv6}"
|
||||
traceroute = "traceroute inet6 {target} wait 1 source {src_addr_ipv6}"
|
||||
```
|
||||
256
docs/configuration/features.md
Normal file
256
docs/configuration/features.md
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
From `hyperglass/hyperglass/configuration/configuration.toml` `[features]`table.
|
||||
|
||||
`[features]`
|
||||
|
||||
## Rate Limiting
|
||||
##### `[features.rate_limit.query]`
|
||||
|
||||
#### Query
|
||||
|
||||
Configuration paramters for rate limiting the number of queries per visitor. For information on how this works, please see the [rate limiting documentation](/ratelimiting/#query).
|
||||
|
||||
##### `rate`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Integer | `5` |
|
||||
|
||||
Sets the number of queries **per minute** allowed from the remote IP address of the request.
|
||||
|
||||
##### `period`
|
||||
|
||||
| Type | Default Value |
|
||||
| -------| ------------- |
|
||||
| String | `"minute"` |
|
||||
|
||||
Sets the time period to which `rate` applies.
|
||||
|
||||
##### `message`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------------------------------------------------------------------- |
|
||||
| String | `"Query limit of {rate} per minute reached. Please wait one {period} and try again."` |
|
||||
|
||||
Message presented to the user when the [query limit](#rate_limit_query) is reached. `{rate_limit_query}` will be formatted as the [`rate_limit_query`](#rate_limit_query) parameter.
|
||||
|
||||
#### Site
|
||||
`[features.rate_limit.site]`
|
||||
|
||||
Configuration parameters for rate limiting the number of site visits per visitor. For information on how this works, please see the [rate limiting documentation](/ratelimiting/#site).
|
||||
|
||||
##### `rate`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Integer | `60` |
|
||||
|
||||
Sets the number of site visits allowed from the remote IP address of the request during the configured [period](#period) below.
|
||||
|
||||
##### `period`
|
||||
|
||||
| Type | Default Value |
|
||||
| -------| ------------- |
|
||||
| String | `"minute"` |
|
||||
|
||||
Sets the time period to which `rate` applies.
|
||||
|
||||
##### `title`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------- |
|
||||
| String | `"Limit Reached"` |
|
||||
|
||||
Title text on Rate Limit error page.
|
||||
|
||||
##### `subtitle`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ---------------------------------------------------------------------------- |
|
||||
| String | `"You have accessed this site more than {rate} times in the last {period}."` |
|
||||
|
||||
Subtitle text on Rate Limit error page.
|
||||
|
||||
## Caching
|
||||
`[features.cache]`
|
||||
|
||||
For information on how this works, please see the [caching documentation](/caching).
|
||||
|
||||
##### `timeout`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Integer | `120` |
|
||||
|
||||
Sets the number of **seconds** to cache the back-end response.
|
||||
|
||||
##### `directory`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------------------------------- |
|
||||
| String | `"hyperglass/hyperglass/.flask_cache"` |
|
||||
|
||||
Sets the directory where the back-end responses are cached. `hyperglass/hyperglass/.flask_cache` is excluded from change control.
|
||||
|
||||
!!! note "Permissions"
|
||||
The user hyperglass runs as must have permissions to this directory.
|
||||
|
||||
##### `show_text`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
If `true`, a message will be displayed at the bottom of the results box:
|
||||
|
||||
> Results will be cached for {seconds / 60} minutes.
|
||||
|
||||
##### `text`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------------------------------------------- |
|
||||
| String | `"Results will be cached for {seconds / 60} minutes"` |
|
||||
|
||||
Sets the caching message text if `show_text` is `true`.
|
||||
|
||||
## Maximum Prefix Length
|
||||
##### `[features.max_prefix]`
|
||||
|
||||
##### `enable`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `false` |
|
||||
|
||||
Enables or disables a maximum allowed prefix size for BGP Route queries. If enabled, the prefix length of BGP Route queries must be shorter than the `max_prefix_length_ipv4` and `max_prefix_length_ipv6` parameters. For example, a BGP Route query for `192.0.2.0/25` would result in the following error message:
|
||||
|
||||
<img src="/max_prefix_error.png" style="width: 70%"></img>
|
||||
|
||||
##### `ipv4`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Integer | `24` |
|
||||
|
||||
If `enable` is `true`, sets the maxiumum prefix length allowed for IPv4 BGP Route queries.
|
||||
|
||||
##### `ipv6`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Integer | `64` |
|
||||
|
||||
If `enable` is `true`, sets the maxiumum prefix length allowed for IPv6 BGP Route queries.
|
||||
|
||||
## BGP Route
|
||||
##### `[features.bgp_route]`
|
||||
|
||||
##### `enable`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
Enables or disables the BGP Route query type.
|
||||
|
||||
## BGP Community
|
||||
##### `[features.bgp_community]`
|
||||
|
||||
##### `enable`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
Enables or disables the BGP Community query type.
|
||||
|
||||
#### Regex
|
||||
##### `[features.bgp_community.regex]`
|
||||
|
||||
Override the default regex patterns for validating BGP Community input.
|
||||
|
||||
##### `decimal`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------- |
|
||||
| String | `"^[0-9]{1,10}$"` |
|
||||
|
||||
Decimal/32 bit community format.
|
||||
|
||||
##### `extended_as`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------------------------- |
|
||||
| String | `"^([0-9]{0,5})\:([0-9]{1,5})$"` |
|
||||
|
||||
Extended community format
|
||||
|
||||
##### `large`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------------------------------------- |
|
||||
| String | `"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$"` |
|
||||
|
||||
Large community format
|
||||
|
||||
## BGP AS Path
|
||||
##### `[features.bgp_aspath]`
|
||||
|
||||
##### `enable`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
Enables or disables the BGP AS Path query type.
|
||||
|
||||
#### Regex
|
||||
##### `[features.bgp_aspath.regex]`
|
||||
|
||||
##### `mode`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"asplain"` |
|
||||
|
||||
Sets the AS Path type used **network-wide**. Options are `asplain`, `asdot`. For more information on what these options mean, [click here](https://tools.ietf.org/html/rfc5396).
|
||||
|
||||
!!! warning "AS_PATH Format"
|
||||
This pattern will be used to validate AS_PATH queries to your routers, so it should match how your routers are actually configured.
|
||||
|
||||
##### `asplain`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------------------------------------- |
|
||||
| String | `"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$"` |
|
||||
|
||||
Regex pattern used to validate `asplain` formatted AS numbers in an AS_PATH. Only used if `mode` is set to `asplain.`
|
||||
|
||||
##### `asdot`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------------------------------------------------------- |
|
||||
| String | `"^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$"` |
|
||||
|
||||
Regex pattern used to validate `asdot` formatted AS numbers in an AS_PATH. Only used if `mode` is set to `asdot.`
|
||||
|
||||
## Ping
|
||||
##### `[features.ping]`
|
||||
|
||||
##### `enable`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
Enables or disables the Ping query type.
|
||||
|
||||
## Traceroute
|
||||
##### `[features.traceroute]`
|
||||
|
||||
##### `enable`
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `true` |
|
||||
|
||||
Enables or disables the Traceroute query type.
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
From `hyperglass/hyperglass/configuration/config.toml`:
|
||||
|
||||
### primary_asn
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"65000"` |
|
||||
|
||||
Your network's _primary_ ASN. Number only, e.g. `65000`, **not** `AS65000`.
|
||||
|
||||
### debug
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `False` |
|
||||
|
||||
Enables Flask debugging. May be used to enable other module debugs in the future.
|
||||
|
||||
### google_analytics
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | None |
|
||||
|
||||
Google Analytics ID number. For more information on how to set up Google Analytics, see [here](https://support.google.com/analytics/answer/1008080?hl=en).
|
||||
|
||||
### message_error
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------------- |
|
||||
| String | `"{input} is invalid."` |
|
||||
|
||||
Message presented to the user when invalid input is detected. `{input}` will be formatted as the input received from the main search field. For each command, input is validated via regular expression in the following patterns:
|
||||
|
||||
| Command | Pattern |
|
||||
| ------------- | -------------------------------------------- |
|
||||
| BGP Route | Valid IPv4 or IPv6 Address |
|
||||
| BGP Community | Valid new-format, 32 bit, or large community |
|
||||
| BGP AS Path | Any pattern |
|
||||
| Ping | Valid IPv4 or IPv6 Address |
|
||||
| Traceroute | Valid IPv4 or IPv6 Address |
|
||||
|
||||
!!! note
|
||||
The BGP AS Path command currently allows `(.*)` to be submitted to the end device. Obviously, the device itself will return an error for garbage input, but ideally this would be "locked down" further. If you have an idea for a regex pattern to validate an `AS_PATH` regex, please submit a PR.
|
||||
|
||||
### message_blacklist
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | --------------------------- |
|
||||
| String | `"{input} is not allowed."` |
|
||||
|
||||
Message presented to the user when an IPv4 or IPv6 address matches the `blacklist.toml` array. `{input}` will be formatted as the input received from the main search field. For information on how this works, please see the [blacklist documentation](/configuration/blacklist).
|
||||
|
||||
### message_rate_limit_query
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------------------------------------------------------------------------------------- |
|
||||
| String | `"Query limit of {rate_limit_query} per minute reached. Please wait one minute and try again."` |
|
||||
|
||||
Message presented to the user when the [query limit](#rate_limit_query) is reached. `{rate_limit_query}` will be formatted as the [`rate_limit_query`](#rate_limit_query) parameter. For information on how this works, please see the [rate limiting documentation](/ratelimiting/query).
|
||||
|
||||
### enable_bgp_route
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
|
||||
Enables or disables the BGP Route query type.
|
||||
|
||||
### enable_bgp_community
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
|
||||
Enables or disables the BGP Community query type.
|
||||
|
||||
### enable_bgp_aspath
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
|
||||
Enables or disables the BGP AS Path query type.
|
||||
|
||||
### enable_ping
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
|
||||
Enables or disables the Ping query type.
|
||||
|
||||
### enable_traceroute
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
|
||||
Enables or disables the Traceroute query type.
|
||||
|
||||
### rate_limit_query
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"5"` |
|
||||
|
||||
Sets the number of queries **per minute** allowed by `remote_address` of the request. For information on how this works, please see the [rate limiting documentation](/ratelimiting/query).
|
||||
|
||||
### rate_limit_site
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"120"` |
|
||||
|
||||
Sets the number of site loads **per minute** allowed by `remote_address` of the request. For information on how this works, please see the [rate limiting documentation](/ratelimiting/site).
|
||||
|
||||
### cache_timeout
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Integer | `120` |
|
||||
|
||||
Sets the number of **seconds** to cache the back-end response. For information on how this works, please see the [caching documentation](/caching).
|
||||
|
||||
### cache_directory
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------------------------------- |
|
||||
| String | `"hyperglass/hyperglass/.flask_cache"` |
|
||||
|
||||
Sets the directory where the back-end responses are cached. For information on how this works, please see the [caching documentation](/caching).
|
||||
|
||||
### enable_max_prefix
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `false` |
|
||||
|
||||
Enables or disables a maximum allowed prefix size for BGP Route queries. If enabled, the prefix length of BGP Route queries must be shorter than the `max_prefix_length_ipv4` and `max_prefix_length_ipv6` parameters. For example, a BGP Route query for `192.0.2.0/25` would result in the following error message:
|
||||
|
||||
<img src="/max_prefix_error.png" style="width: 70%"></img>
|
||||
|
||||
### max_prefix_length_ipv4
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Integer | `24` |
|
||||
|
||||
If `enable_max_prefix` is enabled, the maxiumum prefix length allowed for IPv4 BGP Route queries.
|
||||
|
||||
### max_prefix_length_ipv6
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Integer | `64` |
|
||||
|
||||
If `enable_max_prefix` is enabled, the maxiumum prefix length allowed for IPv6 BGP Route queries.
|
||||
|
|
@ -4,28 +4,55 @@ Hyperglass configuration files are stored in `hyperglass/hyperglass/configuratio
|
|||
|
||||
```console
|
||||
hyperglass/configuration/
|
||||
├── blacklist.toml
|
||||
├── commands.toml
|
||||
├── configuration.toml
|
||||
├── devices.toml
|
||||
└── requires_ipv6_cidr.toml
|
||||
└── devices.toml
|
||||
```
|
||||
|
||||
## Blacklist
|
||||
## Site Parameters
|
||||
|
||||
Blacklisted querys are defined in `hyperglass/hyperglass/configuration/blacklist.toml`
|
||||
Global hyperglass parameters
|
||||
|
||||
#### debug
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `false` |
|
||||
|
||||
Enables hyperglass & Flask debugging.
|
||||
|
||||
!!! warning "Logging"
|
||||
Enabling debug mode will produce a large amount of log output, as every configuration parameter and backend transaction is logged to stdout.
|
||||
|
||||
#### requires_ipv6_cidr
|
||||
|
||||
| Type | Default Value |
|
||||
| ----- | ----------------------------- |
|
||||
| Array | `["cisco_ios", "cisco_nxos"]` |
|
||||
|
||||
Some platforms (namely Cisco IOS) are unable to perform a BGP lookup by IPv6 host address (e.g. 2001:db8::1), but must perform the lookup by prefix (e.g. 2001:db8::/48). `requires_ipv6_cidr` is a list (TOML array) of network operating systems that require this (in Netmiko format).
|
||||
|
||||
If a user attempts to query a device requiring IPv6 lookups in CIDR format with an IPv6 host address, the following message will be displayed:
|
||||
|
||||
<img src="/requires_ipv6_cidr.png" style="width: 70%"></img>
|
||||
|
||||
#### blacklist
|
||||
|
||||
| Type | Default Value |
|
||||
| ----- | ------------- |
|
||||
| Array | See Example |
|
||||
|
||||
The blacklist is a simple TOML array (list) of host IPs or prefixes that you do not want end users to be able to query. For example, if you have one or more hosts/subnets you wish to prevent users from looking up (or any contained host or prefix), add them to the list.
|
||||
|
||||
#### Example
|
||||
##### Example
|
||||
|
||||
```toml
|
||||
blacklist = [
|
||||
'198.18.0.0/15',
|
||||
'2001:db8::/32',
|
||||
'10.0.0.0/8',
|
||||
'192.168.0.0/16',
|
||||
'172.16.0.0/12'
|
||||
"198.18.0.0/15",
|
||||
"2001:db8::/32",
|
||||
"10.0.0.0/8",
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.0/12"
|
||||
]
|
||||
```
|
||||
|
||||
|
|
@ -33,76 +60,20 @@ When users attempt to query a matching host/prefix, they will receive the follow
|
|||
|
||||
<img src="/blacklist_error.png" style="width: 70%"></img>
|
||||
|
||||
## Commands
|
||||
## `[general]` - Site Parameters
|
||||
|
||||
Commands are defined in `hyperglass/hyperglass/configuration/commands.toml`. A table for each NOS (Network Operating System) contains three nested tables: `dual`, `ipv4`, and `ipv6`.
|
||||
#### primary_asn
|
||||
|
||||
| Table | Function | Commands |
|
||||
| --------- | ----------------------------- | ------------------------------- |
|
||||
| **dual** | Protocol agnostic commands | `bgp_community` `bgp_aspath` |
|
||||
| **ipv4** | IPv4-specific commands | `bgp_route` `ping` `traceroute` |
|
||||
| **ipv6** | IPv6-specific commands | `bgp_route` `ping` `traceroute` |
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"65000"` |
|
||||
|
||||
#### Variables
|
||||
Your network's _primary_ ASN. Number only, e.g. `65000`, **not** `AS65000`.
|
||||
|
||||
The following variables can be used in the command definitions.
|
||||
#### google_analytics
|
||||
|
||||
- `{target}` Maps to search box input.
|
||||
- `{src_addr_ipv4}` Maps to [src_addr_ipv4](configuration/devices.md/#src_addr_ipv4)
|
||||
- `{src_addr_ipv6}` Maps to [src_addr_ipv6](configuration/devices.md/#src_addr_ipv6)
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `""` |
|
||||
|
||||
#### Example
|
||||
|
||||
```toml
|
||||
[[cisco_ios]]
|
||||
[cisco_ios.dual]
|
||||
bgp_community = "show bgp all community {target}"
|
||||
bgp_aspath = 'show bgp all quote-regexp "{target}"'
|
||||
[cisco_ios.ipv4]
|
||||
bgp_route = "show bgp ipv4 unicast {target} | exclude pathid:|Epoch"
|
||||
ping = "ping {target} repeat 5 source {src_addr_ipv4}"
|
||||
traceroute = "traceroute {target} timeout 1 probe 2 source {src_addr_ipv4}"
|
||||
[cisco_ios.ipv6]
|
||||
bgp_route = "show bgp ipv6 unicast {target} | exclude pathid:|Epoch"
|
||||
ping = "ping ipv6 {target} repeat 5 source {src_addr_ipv6}"
|
||||
traceroute = "traceroute ipv6 {target} timeout 1 probe 2 source {src_addr_ipv6}"
|
||||
|
||||
[[cisco_xr]]
|
||||
[cisco_xr.dual]
|
||||
bgp_community = 'show bgp all unicast community {target} | utility egrep -v "\(BGP |Table |Non-stop\)"'
|
||||
bgp_aspath = 'show bgp all unicast regexp {target} | utility egrep -v "\(BGP |Table |Non-stop\)"'
|
||||
[cisco_xr.ipv4]
|
||||
bgp_route = 'show bgp ipv4 unicast {target} | util egrep "\(BGP routing table entry|Path \#|aggregated by|Origin |Community:|validity| from \)"'
|
||||
ping = "ping ipv4 {target} count 5 source {src_addr_ipv4}"
|
||||
traceroute = "traceroute ipv4 {target} timeout 1 probe 2 source {src_addr_ipv4}"
|
||||
[cisco_xr.ipv6]
|
||||
bgp_route = 'show bgp ipv6 unicast {target} | util egrep "\(BGP routing table entry|Path \#|aggregated by|Origin |Community:|validity| from \)"'
|
||||
ping = "ping ipv6 {target} count 5 source {src_addr_ipv6}"
|
||||
traceroute = "traceroute ipv6 {target} timeout 1 probe 2 source {src_addr_ipv6}"
|
||||
|
||||
[[juniper]]
|
||||
[juniper.dual]
|
||||
bgp_community = "show route protocol bgp community {target}"
|
||||
bgp_aspath = "show route protocol bgp aspath-regex {target}"
|
||||
[juniper.ipv4]
|
||||
bgp_route = "show route protocol bgp table inet.0 {target} detail"
|
||||
ping = "ping inet {target} count 5 source {src_addr_ipv4}"
|
||||
traceroute = "traceroute inet {target} wait 1 source {src_addr_ipv4}"
|
||||
[juniper.ipv6]
|
||||
bgp_route = "show route protocol bgp table inet6.0 {target} detail"
|
||||
ping = "ping inet6 {target} count 5 source {src_addr_ipv6}"
|
||||
traceroute = "traceroute inet6 {target} wait 1 source {src_addr_ipv6}"
|
||||
```
|
||||
|
||||
## IPv6 CIDR Format Required
|
||||
|
||||
Some platforms (namely Cisco IOS) are unable to perform a BGP lookup by IPv6 host address (e.g. 2001:db8::1), but must perform the lookup by prefix (e.g. 2001:db8::/48). `requires_ipv6_cidr.toml` is a list (TOML array) of network operating systems that require this (in Netmiko format).
|
||||
|
||||
#### Example
|
||||
|
||||
```toml
|
||||
requires_ipv6_cidr = [
|
||||
"cisco_ios",
|
||||
"cisco_nxos"
|
||||
]
|
||||
```
|
||||
Google Analytics ID number. For more information on how to set up Google Analytics, see [here](https://support.google.com/analytics/answer/1008080?hl=en).
|
||||
|
|
|
|||
0
docs/monitoring.md
Normal file
0
docs/monitoring.md
Normal file
BIN
docs/traceroute_nanog.pdf
Normal file
BIN
docs/traceroute_nanog.pdf
Normal file
Binary file not shown.
|
|
@ -3,9 +3,14 @@
|
|||
Accepts filtered & validated input from execute.py, constructs SSH command for Netmiko library or \
|
||||
API call parameters for hyperglass-frr
|
||||
"""
|
||||
# Module Imports
|
||||
# Standard Imports
|
||||
import json
|
||||
import inspect
|
||||
import logging
|
||||
|
||||
# Module Imports
|
||||
import logzero
|
||||
from logzero import logger
|
||||
from netaddr import IPNetwork, IPAddress # pylint: disable=unused-import
|
||||
|
||||
# Dear PyLint, the netaddr library is a special snowflake. You might not see `IPAddress` get used, \
|
||||
|
|
@ -17,7 +22,12 @@ from hyperglass import configuration
|
|||
|
||||
# Configuration Imports
|
||||
codes = configuration.codes()
|
||||
config = configuration.general()
|
||||
|
||||
# Logzero Configuration
|
||||
if configuration.debug_state():
|
||||
logzero.loglevel(logging.DEBUG)
|
||||
else:
|
||||
logzero.loglevel(logging.INFO)
|
||||
|
||||
|
||||
def current_function():
|
||||
|
|
@ -45,79 +55,90 @@ class Construct:
|
|||
src = self.d_src_addr_ipv4
|
||||
if ver == 6:
|
||||
src = self.d_src_addr_ipv6
|
||||
logger.debug(f"Source IPv{ver}: {src}")
|
||||
return src
|
||||
|
||||
def ping(self, transport, target):
|
||||
"""Constructs ping query parameters from pre-validated input"""
|
||||
cmd = current_function()
|
||||
query_type = current_function()
|
||||
logger.debug(f"Constructing {query_type} query for {target} via {transport}...")
|
||||
query = None
|
||||
ip_version = IPNetwork(target).ip.version
|
||||
afi = f"ipv{ip_version}"
|
||||
source = self.get_src(ip_version)
|
||||
if transport == "rest":
|
||||
query = json.dumps(
|
||||
{"cmd": cmd, "afi": afi, "source": source, "target": target}
|
||||
{"cmd": query_type, "afi": afi, "source": source, "target": target}
|
||||
)
|
||||
if transport == "scrape":
|
||||
conf_command = self.command[afi][cmd]
|
||||
conf_command = self.command[afi][query_type]
|
||||
fmt_command = conf_command.format(target=target, source=source)
|
||||
query = (self.d_address, self.d_type, fmt_command)
|
||||
logger.debug(f"Constructed query: {query}")
|
||||
return query
|
||||
|
||||
def traceroute(self, transport, target):
|
||||
"""Constructs traceroute query parameters from pre-validated input"""
|
||||
cmd = current_function()
|
||||
query_type = current_function()
|
||||
logger.debug(f"Constructing {query_type} query for {target} via {transport}...")
|
||||
query = None
|
||||
ip_version = IPNetwork(target).ip.version
|
||||
afi = f"ipv{ip_version}"
|
||||
source = self.get_src(ip_version)
|
||||
if transport == "rest":
|
||||
query = json.dumps(
|
||||
{"cmd": cmd, "afi": afi, "source": source, "target": target}
|
||||
{"cmd": query_type, "afi": afi, "source": source, "target": target}
|
||||
)
|
||||
|
||||
if transport == "scrape":
|
||||
conf_command = self.command[afi][cmd]
|
||||
conf_command = self.command[afi][query_type]
|
||||
fmt_command = conf_command.format(target=target, source=source)
|
||||
query = (self.d_address, self.d_type, fmt_command)
|
||||
logger.debug(f"Constructed query: {query}")
|
||||
return query
|
||||
|
||||
def bgp_route(self, transport, target):
|
||||
"""Constructs bgp_route query parameters from pre-validated input"""
|
||||
cmd = current_function()
|
||||
query_type = current_function()
|
||||
logger.debug(f"Constructing {query_type} query for {target} via {transport}...")
|
||||
query = None
|
||||
ip_version = IPNetwork(target).ip.version
|
||||
afi = f"ipv{ip_version}"
|
||||
if transport == "rest":
|
||||
query = json.dumps({"cmd": cmd, "afi": afi, "target": target})
|
||||
query = json.dumps({"cmd": query_type, "afi": afi, "target": target})
|
||||
if transport == "scrape":
|
||||
conf_command = self.command[afi][cmd]
|
||||
conf_command = self.command[afi][query_type]
|
||||
fmt_command = conf_command.format(target=target)
|
||||
query = (self.d_address, self.d_type, fmt_command)
|
||||
logger.debug(f"Constructed query: {query}")
|
||||
return query
|
||||
|
||||
def bgp_community(self, transport, target):
|
||||
"""Constructs bgp_community query parameters from pre-validated input"""
|
||||
cmd = current_function()
|
||||
query_type = current_function()
|
||||
logger.debug(f"Constructing {query_type} query for {target} via {transport}...")
|
||||
afi = "dual"
|
||||
query = None
|
||||
if transport == "rest":
|
||||
query = json.dumps({"cmd": cmd, "afi": afi, "target": target})
|
||||
query = json.dumps({"cmd": query_type, "afi": afi, "target": target})
|
||||
if transport == "scrape":
|
||||
conf_command = self.command[afi][cmd]
|
||||
conf_command = self.command[afi][query_type]
|
||||
fmt_command = conf_command.format(target=target)
|
||||
query = (self.d_address, self.d_type, fmt_command)
|
||||
logger.debug(f"Constructed query: {query}")
|
||||
return query
|
||||
|
||||
def bgp_aspath(self, transport, target):
|
||||
"""Constructs bgp_aspath query parameters from pre-validated input"""
|
||||
cmd = current_function()
|
||||
query_type = current_function()
|
||||
logger.debug(f"Constructing {query_type} query for {target} via {transport}...")
|
||||
afi = "dual"
|
||||
query = None
|
||||
if transport == "rest":
|
||||
query = json.dumps({"cmd": cmd, "afi": afi, "target": target})
|
||||
query = json.dumps({"cmd": query_type, "afi": afi, "target": target})
|
||||
if transport == "scrape":
|
||||
conf_command = self.command[afi][cmd]
|
||||
conf_command = self.command[afi][query_type]
|
||||
fmt_command = conf_command.format(target=target)
|
||||
query = (self.d_address, self.d_type, fmt_command)
|
||||
logger.debug(f"Constructed query: {query}")
|
||||
return query
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@ Accepts input from front end application, validates the input and returns errors
|
|||
invalid. Passes validated parameters to construct.py, which is used to build & run the Netmiko \
|
||||
connectoins or hyperglass-frr API calls, returns the output back to the front end.
|
||||
"""
|
||||
# Module Imports
|
||||
# Standard Imports
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
from pprint import pprint
|
||||
|
||||
# Module Imports
|
||||
import requests
|
||||
import requests.exceptions
|
||||
from logzero import logger
|
||||
import logzero
|
||||
from netmiko import (
|
||||
ConnectHandler,
|
||||
redispatch,
|
||||
|
|
@ -25,7 +30,14 @@ from hyperglass.command.construct import Construct
|
|||
from hyperglass.command.validate import Validate
|
||||
|
||||
codes = configuration.codes()
|
||||
config = configuration.general()
|
||||
config = configuration.params()
|
||||
# config = configuration.general()
|
||||
|
||||
# Logzero Configuration
|
||||
if configuration.debug_state():
|
||||
logzero.loglevel(logging.DEBUG)
|
||||
else:
|
||||
logzero.loglevel(logging.INFO)
|
||||
|
||||
|
||||
class Rest:
|
||||
|
|
@ -34,18 +46,22 @@ class Rest:
|
|||
# pylint: disable=too-few-public-methods
|
||||
# Dear PyLint, sometimes, people need to make their code scalable for future use. <3, -ML
|
||||
|
||||
def __init__(self, transport, device, cmd, target):
|
||||
def __init__(self, transport, device, query_type, target):
|
||||
self.transport = transport
|
||||
self.device = device
|
||||
self.cmd = cmd
|
||||
self.query_type = query_type
|
||||
self.target = target
|
||||
self.cred = configuration.credential(self.device["credential"])
|
||||
self.query = getattr(Construct(self.device), self.cmd)(
|
||||
self.query = getattr(Construct(self.device), self.query_type)(
|
||||
self.transport, self.target
|
||||
)
|
||||
|
||||
def frr(self):
|
||||
"""Sends HTTP POST to router running the hyperglass-frr API"""
|
||||
# Debug
|
||||
logger.debug(f"FRR host params:\n{pprint(self.device)}")
|
||||
logger.debug(f"Raw query parameters: {self.query}")
|
||||
# End Debug
|
||||
try:
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -53,14 +69,23 @@ class Rest:
|
|||
}
|
||||
json_query = json.dumps(self.query)
|
||||
frr_endpoint = f'http://{self.device["address"]}:{self.device["port"]}/frr'
|
||||
# Debug
|
||||
logger.debug(f"HTTP Headers:\n{pprint(headers)}")
|
||||
logger.debug(f"JSON query:\n{json_query}")
|
||||
logger.debug(f"FRR endpoint: {frr_endpoint}")
|
||||
# End Debug
|
||||
frr_response = requests.post(frr_endpoint, headers=headers, data=json_query)
|
||||
response = frr_response.text
|
||||
status = frr_response.status_code
|
||||
# Debug
|
||||
logger.debug(f"FRR response text:\n{response}")
|
||||
logger.debug(f"FRR status code: {status}")
|
||||
# End Debug
|
||||
except requests.exceptions.RequestException as requests_exception:
|
||||
logger.error(
|
||||
f'Error connecting to device {self.device["name"]}: {requests_exception}'
|
||||
)
|
||||
response = config["msg_error_general"]
|
||||
response = config["messages"]["general"]
|
||||
status = codes["danger"]
|
||||
return response, status
|
||||
|
||||
|
|
@ -71,16 +96,16 @@ class Netmiko:
|
|||
# pylint: disable=too-many-instance-attributes
|
||||
# Dear PyLint, I actually need all these. <3, -ML
|
||||
|
||||
def __init__(self, transport, device, cmd, target):
|
||||
def __init__(self, transport, device, query_type, target):
|
||||
self.device = device
|
||||
self.target = target
|
||||
self.cred = configuration.credential(self.device["credential"])
|
||||
self.params = getattr(Construct(device), cmd)(transport, target)
|
||||
self.router = self.params[0]
|
||||
self.params = getattr(Construct(device), query_type)(transport, target)
|
||||
self.location = self.params[0]
|
||||
self.nos = self.params[1]
|
||||
self.command = self.params[2]
|
||||
self.nm_host = {
|
||||
"host": self.router,
|
||||
"host": self.location,
|
||||
"device_type": self.nos,
|
||||
"username": self.cred["username"],
|
||||
"password": self.cred["password"],
|
||||
|
|
@ -89,17 +114,24 @@ class Netmiko:
|
|||
|
||||
def direct(self):
|
||||
"""Connects to the router via netmiko library, return the command output"""
|
||||
# Debug
|
||||
logger.debug(f"Netmiko host: {pprint(self.nm_host)}")
|
||||
logger.debug(f"Connecting to host via Netmiko library...")
|
||||
# End Debug
|
||||
try:
|
||||
nm_connect_direct = ConnectHandler(**self.nm_host)
|
||||
response = nm_connect_direct.send_command(self.command)
|
||||
status = codes["success"]
|
||||
logger.debug(
|
||||
f"Response for direction connection with command {self.command}:\n{response}"
|
||||
)
|
||||
except (
|
||||
NetMikoAuthenticationException,
|
||||
NetMikoTimeoutException,
|
||||
NetmikoAuthError,
|
||||
NetmikoTimeoutError,
|
||||
) as netmiko_exception:
|
||||
response = config["msg_error_general"]
|
||||
response = config["messages"]["general"]
|
||||
status = codes["danger"]
|
||||
logger.error(f"{netmiko_exception}, {status}")
|
||||
return response, status
|
||||
|
|
@ -120,29 +152,40 @@ class Netmiko:
|
|||
}
|
||||
nm_connect_proxied = ConnectHandler(**nm_proxy)
|
||||
nm_ssh_command = device_proxy["ssh_command"].format(**self.nm_host) + "\n"
|
||||
# Debug
|
||||
logger.debug(f"Netmiko proxy {proxy_name}:\n{pprint(nm_proxy)}")
|
||||
logger.debug(f"Proxy SSH command: {nm_ssh_command}")
|
||||
# End Debug
|
||||
nm_connect_proxied.write_channel(nm_ssh_command)
|
||||
time.sleep(1)
|
||||
proxy_output = nm_connect_proxied.read_channel()
|
||||
logger.debug(f"Proxy output:\n{proxy_output}")
|
||||
try:
|
||||
# Accept SSH key warnings
|
||||
if "Are you sure you want to continue connecting" in proxy_output:
|
||||
logger.debug(f"Received OpenSSH key warning")
|
||||
nm_connect_proxied.write_channel("yes" + "\n")
|
||||
nm_connect_proxied.write_channel(self.nm_host["password"] + "\n")
|
||||
# Send password on prompt
|
||||
elif "assword" in proxy_output:
|
||||
logger.debug(f"Received password prompt")
|
||||
nm_connect_proxied.write_channel(self.nm_host["password"] + "\n")
|
||||
proxy_output += nm_connect_proxied.read_channel()
|
||||
# Reclassify netmiko connection as configured device type
|
||||
logger.debug(
|
||||
f'Redispatching netmiko with device class {self.nm_host["device_type"]}'
|
||||
)
|
||||
redispatch(nm_connect_proxied, self.nm_host["device_type"])
|
||||
response = nm_connect_proxied.send_command(self.command)
|
||||
status = codes["success"]
|
||||
logger.debug(f"Netmiko proxied response:\n{response}")
|
||||
except (
|
||||
NetMikoAuthenticationException,
|
||||
NetMikoTimeoutException,
|
||||
NetmikoAuthError,
|
||||
NetmikoTimeoutError,
|
||||
) as netmiko_exception:
|
||||
response = config["msg_error_general"]
|
||||
response = config["messages"]["general"]
|
||||
status = codes["danger"]
|
||||
logger.error(
|
||||
f'{netmiko_exception}, {status},Proxy: {self.nm_host["proxy"]}'
|
||||
|
|
@ -158,23 +201,24 @@ class Execute:
|
|||
|
||||
def __init__(self, lg_data):
|
||||
self.input_data = lg_data
|
||||
self.input_router = lg_data["router"]
|
||||
self.input_cmd = lg_data["cmd"]
|
||||
self.input_target = lg_data["ipprefix"]
|
||||
self.device_config = configuration.device(self.input_router)
|
||||
self.input_location = lg_data["location"]
|
||||
self.input_type = lg_data["type"]
|
||||
self.input_target = lg_data["target"]
|
||||
|
||||
def parse(self, output):
|
||||
def parse(self, output, nos):
|
||||
"""Splits BGP output by AFI, returns only IPv4 & IPv6 output for protocol-agnostic \
|
||||
commands (Community & AS_PATH Lookups)"""
|
||||
nos = self.device_config["type"]
|
||||
logger.debug(f"Parsing output...")
|
||||
parsed = output
|
||||
if self.input_cmd in ["bgp_community", "bgp_aspath"]:
|
||||
if self.input_type in ["bgp_community", "bgp_aspath"]:
|
||||
if nos in ["cisco_ios"]:
|
||||
logger.debug(f"Parsing output for device type {nos}")
|
||||
delimiter = "For address family: "
|
||||
parsed_ipv4 = output.split(delimiter)[1]
|
||||
parsed_ipv6 = output.split(delimiter)[2]
|
||||
parsed = delimiter + parsed_ipv4 + delimiter + parsed_ipv6
|
||||
if nos in ["cisco_xr"]:
|
||||
logger.debug(f"Parsing output for device type {nos}")
|
||||
delimiter = "Address Family: "
|
||||
parsed_ipv4 = output.split(delimiter)[1]
|
||||
parsed_ipv6 = output.split(delimiter)[2]
|
||||
|
|
@ -186,34 +230,42 @@ class Execute:
|
|||
Initializes Execute.filter(), if input fails to pass filter, returns errors to front end. \
|
||||
Otherwise, executes queries.
|
||||
"""
|
||||
# Return error if no query type is specified
|
||||
if self.input_cmd == "Query Type":
|
||||
msg = config["msg_error_querytype"]
|
||||
status = codes["warning"]
|
||||
return msg, status, self.input_data
|
||||
validity, msg, status = getattr(Validate(self.device_config), self.input_cmd)(
|
||||
device_config = configuration.device(self.input_location)
|
||||
# Debug
|
||||
logger.debug(f"Received query for {self.input_data}")
|
||||
logger.debug(f"Matched device config:\n{pprint(device_config)}")
|
||||
# End Debug
|
||||
validity, msg, status = getattr(Validate(device_config), self.input_type)(
|
||||
self.input_target
|
||||
)
|
||||
if not validity:
|
||||
logger.debug(f"Invalid query")
|
||||
return msg, status, self.input_data
|
||||
connection = None
|
||||
output = config["msg_error_general"]
|
||||
output = config["messages"]["general"]
|
||||
info = self.input_data
|
||||
if self.device_config["type"] == "frr":
|
||||
connection = Rest(
|
||||
"rest", self.device_config, self.input_cmd, self.input_target
|
||||
)
|
||||
logger.debug(f"Validity: {validity}, Message: {msg}, Status: {status}")
|
||||
if device_config["type"] in configuration.rest_list():
|
||||
connection = Rest("rest", device_config, self.input_type, self.input_target)
|
||||
raw_output, status = connection.frr()
|
||||
output = self.parse(raw_output)
|
||||
if self.device_config["type"] in configuration.scrape_list():
|
||||
output = self.parse(raw_output, device_config["type"])
|
||||
return output, status, info
|
||||
if device_config["type"] in configuration.scrape_list():
|
||||
logger.debug(f"Initializing Netmiko...")
|
||||
connection = Netmiko(
|
||||
"scrape", self.device_config, self.input_cmd, self.input_target
|
||||
"scrape", device_config, self.input_type, self.input_target
|
||||
)
|
||||
if self.device_config["proxy"]:
|
||||
if device_config["proxy"]:
|
||||
raw_output, status = connection.proxied()
|
||||
else:
|
||||
raw_output, status = connection.direct()
|
||||
output = self.parse(raw_output)
|
||||
else:
|
||||
logger.error(f"{output}, {status}, {info}")
|
||||
output = self.parse(raw_output, device_config["type"])
|
||||
logger.debug(
|
||||
f'Parsed output for device type {device_config["type"]}:\n{output}'
|
||||
)
|
||||
return output, status, info
|
||||
if device_config["type"] not in configuration.supported_nos():
|
||||
logger.error(
|
||||
f"Device not supported, or no commands for device configured. {status}, {info}"
|
||||
)
|
||||
return output, status, info
|
||||
|
|
|
|||
|
|
@ -3,18 +3,33 @@
|
|||
Accepts raw input data from execute.py, passes it through specific filters based on query type, \
|
||||
returns validity boolean and specific error message.
|
||||
"""
|
||||
# Module Imports
|
||||
# Standard Imports
|
||||
import re
|
||||
import inspect
|
||||
import logging
|
||||
from pprint import pprint
|
||||
|
||||
# Module Imports
|
||||
import logzero
|
||||
from logzero import logger
|
||||
from netaddr.core import AddrFormatError
|
||||
from netaddr import IPNetwork, IPAddress, IPSet # pylint: disable=unused-import
|
||||
|
||||
# Dear PyLint, the netaddr library is a special snowflake. You might not see `IPAddress` get used, \
|
||||
# but when you use something like `IPNetwork("192.0.2.1/24").ip`, the returned value is \
|
||||
# IPAddress("192.0.2.1"), so I do actually need this import. <3, -ML
|
||||
|
||||
# Project Imports
|
||||
from hyperglass import configuration
|
||||
|
||||
# Configuration Imports
|
||||
config = configuration.general()
|
||||
config = configuration.params()
|
||||
|
||||
# Logzero Configuration
|
||||
if configuration.debug_state():
|
||||
logzero.loglevel(logging.DEBUG)
|
||||
else:
|
||||
logzero.loglevel(logging.INFO)
|
||||
|
||||
|
||||
class IPType:
|
||||
|
|
@ -61,8 +76,10 @@ class IPType:
|
|||
ip_version = IPNetwork(target).ip.version
|
||||
state = False
|
||||
if ip_version == 4 and re.match(self.ipv4_host, target):
|
||||
logger.debug(f"{target} is an IPv{ip_version} host.")
|
||||
state = True
|
||||
if ip_version == 6 and re.match(self.ipv6_host, target):
|
||||
logger.debug(f"{target} is an IPv{ip_version} host.")
|
||||
state = True
|
||||
return state
|
||||
|
||||
|
|
@ -89,9 +106,12 @@ def ip_validate(target):
|
|||
or valid_ip.is_loopback()
|
||||
):
|
||||
validity = False
|
||||
logger.debug(f"IP {valid_ip} is invalid")
|
||||
if valid_ip.is_unicast():
|
||||
validity = True
|
||||
logger.debug(f"IP {valid_ip} is valid")
|
||||
except AddrFormatError:
|
||||
logger.debug(f"IP {target} is invalid")
|
||||
validity = False
|
||||
return validity
|
||||
|
||||
|
|
@ -99,6 +119,7 @@ def ip_validate(target):
|
|||
def ip_blacklist(target):
|
||||
"""Check blacklist list for prefixes/IPs, return boolean based on list membership"""
|
||||
blacklist = IPSet(configuration.blacklist())
|
||||
logger.debug(f"Blacklist: {blacklist}")
|
||||
membership = False
|
||||
if target in blacklist:
|
||||
membership = True
|
||||
|
|
@ -124,44 +145,49 @@ def ip_attributes(target):
|
|||
return valid_attributes
|
||||
|
||||
|
||||
def ip_type_check(cmd, target, device):
|
||||
def ip_type_check(query_type, target, device):
|
||||
"""Checks multiple IP address related validation parameters"""
|
||||
prefix_attr = ip_attributes(target)
|
||||
logger.debug(f"IP Attributes:\n{pprint(prefix_attr)}")
|
||||
requires_ipv6_cidr = configuration.requires_ipv6_cidr(device["type"])
|
||||
validity = False
|
||||
msg = config["msg_error_notallowed"].format(i=target)
|
||||
msg = config["messages"]["not_allowed"].format(i=target)
|
||||
# If target is a member of the blacklist, return an error.
|
||||
if ip_blacklist(target):
|
||||
validity = False
|
||||
logger.debug(f"Failed blacklist check")
|
||||
return (validity, msg)
|
||||
# If enable_max_prefix feature enabled, require that BGP Route queries be smaller than\
|
||||
# configured size limit.
|
||||
if cmd == "bgp_route" and config["enable_max_prefix"]:
|
||||
max_length = config[f'max_prefix_length_{prefix_attr["afi"]}']
|
||||
if query_type == "bgp_route" and config["features"]["max_prefix"]["enable"]:
|
||||
max_length = config["features"]["max_prefix"][prefix_attr["afi"]]
|
||||
if prefix_attr["length"] > max_length:
|
||||
validity = False
|
||||
msg = config["msg_max_prefix"].format(
|
||||
msg = config["features"]["max_prefix"]["message"].format(
|
||||
m=max_length, i=prefix_attr["network"]
|
||||
)
|
||||
logger.debug(f"Failed max prefix length check")
|
||||
return (validity, msg)
|
||||
# If device NOS is listed in requires_ipv6_cidr.toml, and query is an IPv6 host address, \
|
||||
# return an error.
|
||||
if (
|
||||
cmd == "bgp_route"
|
||||
query_type == "bgp_route"
|
||||
and prefix_attr["version"] == 6
|
||||
and requires_ipv6_cidr
|
||||
and IPType().is_host(target)
|
||||
):
|
||||
msg = config["msg_error_ipv6cidr"].format(d=device["display_name"])
|
||||
msg = config["messages"]["requires_ipv6_cidr"].format(d=device["display_name"])
|
||||
validity = False
|
||||
logger.debug(f"Failed requires IPv6 CIDR check")
|
||||
return (validity, msg)
|
||||
# If query type is ping or traceroute, and query target is in CIDR format, return an error.
|
||||
if cmd in ["ping", "traceroute"] and IPType().is_cidr(target):
|
||||
msg = config["msg_error_directed_cidr"].format(cmd=cmd.capitalize())
|
||||
if query_type in ["ping", "traceroute"] and IPType().is_cidr(target):
|
||||
msg = config["messages"]["directed_cidr"].format(q=query_type.capitalize())
|
||||
validity = False
|
||||
logger.debug(f"Failed CIDR format for ping/traceroute check")
|
||||
return (validity, msg)
|
||||
validity = True
|
||||
msg = f"{target} is a valid {cmd} query."
|
||||
msg = f"{target} is a valid {query_type} query."
|
||||
return (validity, msg)
|
||||
|
||||
|
||||
|
|
@ -183,9 +209,10 @@ class Validate:
|
|||
|
||||
def ping(self, target):
|
||||
"""Ping Query: Input Validation & Error Handling"""
|
||||
cmd = current_function()
|
||||
query_type = current_function()
|
||||
logger.debug(f"Validating {query_type} query for target {target}...")
|
||||
validity = False
|
||||
msg = config["msg_error_invalidip"].format(i=target)
|
||||
msg = config["messages"]["invalid_ip"].format(i=target)
|
||||
status = self.codes["warning"]
|
||||
# Perform basic validation of an IP address, return error if not a valid IP.
|
||||
if not ip_validate(target):
|
||||
|
|
@ -193,19 +220,21 @@ class Validate:
|
|||
logger.error(f"{msg}, {status}")
|
||||
return (validity, msg, status)
|
||||
# Perform further validation of a valid IP address, return an error upon failure.
|
||||
valid_query, msg = ip_type_check(cmd, target, self.device)
|
||||
valid_query, msg = ip_type_check(query_type, target, self.device)
|
||||
if valid_query:
|
||||
validity = True
|
||||
msg = f"{target} is a valid {cmd} query."
|
||||
msg = f"{target} is a valid {query_type} query."
|
||||
status = self.codes["success"]
|
||||
logger.debug(f"{msg}, {status}")
|
||||
return (validity, msg, status)
|
||||
return (validity, msg, status)
|
||||
|
||||
def traceroute(self, target):
|
||||
"""Traceroute Query: Input Validation & Error Handling"""
|
||||
cmd = current_function()
|
||||
query_type = current_function()
|
||||
logger.debug(f"Validating {query_type} query for target {target}...")
|
||||
validity = False
|
||||
msg = config["msg_error_invalidip"].format(i=target)
|
||||
msg = config["messages"]["invalid_ip"].format(i=target)
|
||||
status = self.codes["warning"]
|
||||
# Perform basic validation of an IP address, return error if not a valid IP.
|
||||
if not ip_validate(target):
|
||||
|
|
@ -213,19 +242,21 @@ class Validate:
|
|||
logger.error(f"{msg}, {status}")
|
||||
return (validity, msg, status)
|
||||
# Perform further validation of a valid IP address, return an error upon failure.
|
||||
valid_query, msg = ip_type_check(cmd, target, self.device)
|
||||
valid_query, msg = ip_type_check(query_type, target, self.device)
|
||||
if valid_query:
|
||||
validity = True
|
||||
msg = f"{target} is a valid {cmd} query."
|
||||
msg = f"{target} is a valid {query_type} query."
|
||||
status = self.codes["success"]
|
||||
logger.debug(f"{msg}, {status}")
|
||||
return (validity, msg, status)
|
||||
return (validity, msg, status)
|
||||
|
||||
def bgp_route(self, target):
|
||||
"""BGP Route Query: Input Validation & Error Handling"""
|
||||
cmd = current_function()
|
||||
query_type = current_function()
|
||||
logger.debug(f"Validating {query_type} query for target {target}...")
|
||||
validity = False
|
||||
msg = config["msg_error_invalidip"].format(i=target)
|
||||
msg = config["messages"]["invalid_ip"].format(i=target)
|
||||
status = self.codes["warning"]
|
||||
# Perform basic validation of an IP address, return error if not a valid IP.
|
||||
if not ip_validate(target):
|
||||
|
|
@ -233,49 +264,56 @@ class Validate:
|
|||
logger.error(f"{msg}, {status}")
|
||||
return (validity, msg, status)
|
||||
# Perform further validation of a valid IP address, return an error upon failure.
|
||||
valid_query, msg = ip_type_check(cmd, target, self.device)
|
||||
valid_query, msg = ip_type_check(query_type, target, self.device)
|
||||
if valid_query:
|
||||
validity = True
|
||||
msg = f"{target} is a valid {cmd} query."
|
||||
msg = f"{target} is a valid {query_type} query."
|
||||
status = self.codes["success"]
|
||||
logger.debug(f"{msg}, {status}")
|
||||
return (validity, msg, status)
|
||||
return (validity, msg, status)
|
||||
|
||||
def bgp_community(self, target):
|
||||
"""BGP Community Query: Input Validation & Error Handling"""
|
||||
query_type = current_function()
|
||||
logger.debug(f"Validating {query_type} query for target {target}...")
|
||||
validity = False
|
||||
msg = config["msg_error_invaliddual"].format(i=target, qt="BGP Community")
|
||||
msg = config["messages"]["invalid_dual"].format(i=target, qt="BGP Community")
|
||||
status = self.codes["danger"]
|
||||
# Validate input communities against configured or default regex pattern
|
||||
# Extended Communities, new-format
|
||||
if re.match(config["re_bgp_community_new"], target):
|
||||
if re.match(config["features"][query_type]["regex"]["extended_as"], target):
|
||||
validity = True
|
||||
msg = f"{target} matched new-format community."
|
||||
msg = f"{target} matched extended AS format community."
|
||||
status = self.codes["success"]
|
||||
# Extended Communities, 32 bit format
|
||||
if re.match(config["re_bgp_community_32bit"], target):
|
||||
if re.match(config["features"][query_type]["regex"]["decimal"], target):
|
||||
validity = True
|
||||
msg = f"{target} matched 32 bit community."
|
||||
msg = f"{target} matched decimal format community."
|
||||
status = self.codes["success"]
|
||||
# RFC 8092 Large Community Support
|
||||
if re.match(config["re_bgp_community_large"], target):
|
||||
if re.match(config["features"][query_type]["regex"]["large"], target):
|
||||
validity = True
|
||||
msg = f"{target} matched large community."
|
||||
status = self.codes["success"]
|
||||
if not validity:
|
||||
logger.error(f"{msg}, {status}")
|
||||
logger.debug(f"{msg}, {status}")
|
||||
return (validity, msg, status)
|
||||
|
||||
def bgp_aspath(self, target):
|
||||
"""BGP AS Path Query: Input Validation & Error Handling"""
|
||||
query_type = current_function()
|
||||
logger.debug(f"Validating {query_type} query for target {target}...")
|
||||
validity = False
|
||||
msg = config["msg_error_invaliddual"].format(i=target, qt="AS Path")
|
||||
msg = config["messages"]["invalid_dual"].format(i=target, qt="AS Path")
|
||||
status = self.codes["danger"]
|
||||
# Validate input AS_PATH regex pattern against configured or default regex pattern
|
||||
if re.match(config["re_bgp_aspath"], target):
|
||||
if re.match(config["features"][query_type]["regex"]["pattern"], target):
|
||||
validity = True
|
||||
msg = f"{target} matched AS_PATH regex."
|
||||
status = self.codes["success"]
|
||||
if not validity:
|
||||
logger.error(f"{msg}, {status}")
|
||||
logger.debug(f"{msg}, {status}")
|
||||
return (validity, msg, status)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ Imports configuration varibles from configuration files and returns default valu
|
|||
# Standard Imports
|
||||
import os
|
||||
import math
|
||||
import logging
|
||||
|
||||
# Module Imports
|
||||
import toml
|
||||
import logzero
|
||||
from logzero import logger
|
||||
|
||||
# Project Imports
|
||||
import hyperglass
|
||||
|
|
@ -17,20 +20,33 @@ working_dir = os.path.dirname(os.path.abspath(__file__))
|
|||
hyperglass_root = os.path.dirname(hyperglass.__file__)
|
||||
|
||||
# TOML Imports
|
||||
configuration = toml.load(os.path.join(working_dir, "configuration.toml"))
|
||||
config = toml.load(os.path.join(working_dir, "configuration.toml"))
|
||||
devices = toml.load(os.path.join(working_dir, "devices.toml"))
|
||||
|
||||
|
||||
def debug_state():
|
||||
"""Returns string for logzero log level"""
|
||||
state = config.get("debug", False)
|
||||
return state
|
||||
|
||||
|
||||
# Logzero Configuration
|
||||
if debug_state():
|
||||
logzero.loglevel(logging.DEBUG)
|
||||
else:
|
||||
logzero.loglevel(logging.INFO)
|
||||
|
||||
|
||||
def blacklist():
|
||||
"""Returns list of subnets/IPs defined in blacklist.toml"""
|
||||
blacklist_config = toml.load(os.path.join(working_dir, "blacklist.toml"))
|
||||
blacklist_config = config["blacklist"]
|
||||
return blacklist_config["blacklist"]
|
||||
|
||||
|
||||
def requires_ipv6_cidr(nos):
|
||||
"""Returns boolean for input NOS association with the NOS list defined in \
|
||||
requires_ipv6_cidr.toml"""
|
||||
nos_list = configuration["requires_ipv6_cidr"]
|
||||
nos_list = config["requires_ipv6_cidr"]
|
||||
return bool(nos in nos_list)
|
||||
|
||||
|
||||
|
|
@ -48,7 +64,17 @@ def networks():
|
|||
return asn_dict
|
||||
|
||||
|
||||
def networks_list():
|
||||
def locations():
|
||||
"""Returns list of all location identifiers"""
|
||||
loc_list = []
|
||||
routers_list = devices["router"]
|
||||
for router_config in routers_list.values():
|
||||
loc = router_config["location"]
|
||||
loc_list.append(loc)
|
||||
return loc_list
|
||||
|
||||
|
||||
def locations_list():
|
||||
"""Returns a dictionary of ASNs as keys, list of associated locations, router hostnames, and \
|
||||
router display names as keys. Used by Flask to populate the /routers/<asn> route, which is \
|
||||
ingested by a JS Ajax call to populate the list of locations associated with the selected \
|
||||
|
|
@ -99,6 +125,12 @@ def codes_reason():
|
|||
return code_desc_dict
|
||||
|
||||
|
||||
def rest_list():
|
||||
"""Returns list of supported hyperglass API types"""
|
||||
rest = ["frr"]
|
||||
return rest
|
||||
|
||||
|
||||
def scrape_list():
|
||||
"""Returns list of configured network operating systems"""
|
||||
config_commands = toml.load(os.path.join(working_dir, "commands.toml"))
|
||||
|
|
@ -108,6 +140,14 @@ def scrape_list():
|
|||
return scrape
|
||||
|
||||
|
||||
def supported_nos():
|
||||
"""Combines scrape_list & rest_list for full list of supported network operating systems"""
|
||||
scrape = scrape_list()
|
||||
rest = rest_list()
|
||||
supported = scrape + rest
|
||||
return supported
|
||||
|
||||
|
||||
def command(nos):
|
||||
"""Associates input NOS with matched commands defined in commands.toml"""
|
||||
config_commands = toml.load(os.path.join(working_dir, "commands.toml"))
|
||||
|
|
@ -158,165 +198,254 @@ def proxy(prx):
|
|||
)
|
||||
|
||||
|
||||
def general():
|
||||
"""Exports general config variables and sets default values if undefined"""
|
||||
gen = configuration["general"]
|
||||
re_bgp_aspath_mode = gen["bgp_aspath"].get("mode", "asplain")
|
||||
if re_bgp_aspath_mode == "asplain":
|
||||
re_bgp_aspath_default = r"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$"
|
||||
if re_bgp_aspath_mode == "asdot":
|
||||
re_bgp_aspath_default = (
|
||||
r"^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$"
|
||||
)
|
||||
return dict(
|
||||
primary_asn=gen.get("primary_asn", "65000"),
|
||||
org_name=gen.get("org_name", "The Company"),
|
||||
debug=gen.get("debug", False),
|
||||
google_analytics=gen.get("google_analytics", ""),
|
||||
msg_error_querytype=gen.get(
|
||||
"msg_error_querytype", "You must select a query type."
|
||||
),
|
||||
msg_error_notallowed=gen.get(
|
||||
"msg_error_notallowed", "<b>{i}</b> is not allowed."
|
||||
),
|
||||
msg_error_ipv6cidr=gen.get(
|
||||
"msg_error_ipv6cidr",
|
||||
"<b>{d}</b> requires IPv6 BGP lookups to be in CIDR notation.",
|
||||
),
|
||||
msg_error_invalidip=gen.get(
|
||||
"msg_error_invalidip", "<b>{i}</b> is not a valid IP address."
|
||||
),
|
||||
msg_error_invaliddual=gen.get(
|
||||
"msg_error_invaliddual", "<b>{i}</b> is an invalid {qt}."
|
||||
),
|
||||
msg_error_general=gen.get("msg_error_general", "A general error occurred."),
|
||||
msg_error_directed_cidr=gen.get(
|
||||
"msg_error_directed_cidr", "<b>{cmd}</b> queries can not be in CIDR format."
|
||||
),
|
||||
msg_max_prefix=gen.get(
|
||||
"msg_max_prefix",
|
||||
"Prefix length must be smaller than /{m}. <b>{i}</b> is too specific.",
|
||||
),
|
||||
rate_limit_query=gen.get("rate_limit_query", "5"),
|
||||
message_rate_limit_query=gen.get(
|
||||
"message_rate_limit_query",
|
||||
(
|
||||
f'Query limit of {gen.get("rate_limit_query", "5")} per minute reached. '
|
||||
"Please wait one minute and try again."
|
||||
),
|
||||
),
|
||||
enable_bgp_route=gen.get("enable_bgp_route", True),
|
||||
enable_bgp_community=gen.get("enable_bgp_community", True),
|
||||
enable_bgp_aspath=gen.get("enable_bgp_aspath", True),
|
||||
enable_ping=gen.get("enable_ping", True),
|
||||
enable_traceroute=gen.get("enable_traceroute", True),
|
||||
rate_limit_site=gen.get("rate_limit_site", "120"),
|
||||
cache_timeout=gen.get("cache_timeout", 120),
|
||||
cache_directory=gen.get(
|
||||
"cache_directory", os.path.join(hyperglass_root, ".flask_cache")
|
||||
),
|
||||
enable_max_prefix=gen.get("enable_max_prefix", False),
|
||||
max_prefix_length_ipv4=gen.get("max_prefix_length_ipv4", 24),
|
||||
max_prefix_length_ipv6=gen.get("max_prefix_length_ipv6", 64),
|
||||
re_bgp_community_new=gen.get(
|
||||
"re_bgp_community_new", r"^([0-9]{0,5})\:([0-9]{1,5})$"
|
||||
),
|
||||
re_bgp_community_32bit=gen.get("re_bgp_community_32bit", r"^[0-9]{1,10}$"),
|
||||
re_bgp_community_large=gen.get(
|
||||
"re_bgp_community_large", r"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$"
|
||||
),
|
||||
re_bgp_aspath=gen["bgp_aspath"][re_bgp_aspath_mode].get(
|
||||
"regex", re_bgp_aspath_default
|
||||
),
|
||||
def params():
|
||||
"""Builds combined nested dictionary of all parameters defined in configuration.toml, and if \
|
||||
undefined, uses a default value"""
|
||||
# pylint: disable=too-many-statements
|
||||
# Dear PyLint, this function is intended to be long AF, because hyperglass is inteded to be \
|
||||
# customizable AF. It would also be silly AF to break this into multiple functions, and you'd \
|
||||
# probably still complain. <3 -ML
|
||||
general = {}
|
||||
branding = {}
|
||||
features = {}
|
||||
messages = {}
|
||||
general["primary_asn"] = config["general"].get("primary_asn", "65000")
|
||||
general["org_name"] = config["general"].get("org_name", "The Company")
|
||||
general["google_analytics"] = config["general"].get("google_analytics", "")
|
||||
features["rate_limit"] = config["features"]["rate_limit"]
|
||||
features["rate_limit"]["query"] = config["features"]["rate_limit"]["query"]
|
||||
features["rate_limit"]["query"]["rate"] = config["features"]["rate_limit"][
|
||||
"query"
|
||||
].get("rate", 5)
|
||||
features["rate_limit"]["query"]["period"] = config["features"]["rate_limit"].get(
|
||||
"period", "minute"
|
||||
)
|
||||
|
||||
|
||||
def branding():
|
||||
"""Exports branding config variables and sets default values if undefined"""
|
||||
brand = configuration["branding"]
|
||||
gen = general()
|
||||
return dict(
|
||||
site_title=brand.get("site_title", "hyperglass"),
|
||||
title=brand.get("title", "hyperglass"),
|
||||
subtitle=brand.get("subtitle", f'AS{gen["primary_asn"]}'),
|
||||
title_mode=brand.get("title_mode", "logo_only"),
|
||||
enable_footer=brand.get("enable_footer", True),
|
||||
enable_credit=brand.get("enable_credit", True),
|
||||
color_btn_submit=brand.get("color_btn_submit", "#40798c"),
|
||||
color_tag_loctitle=brand.get("color_tag_loctitle", "#330036"),
|
||||
color_tag_cmdtitle=brand.get("color_tag_cmdtitle", "#330036"),
|
||||
color_tag_cmd=brand.get("color_tag_cmd", "#ff5e5b"),
|
||||
color_tag_loc=brand.get("color_tag_loc", "#40798c"),
|
||||
color_progressbar=brand.get("color_progressbar", "#40798c"),
|
||||
color_bg=brand.get("color_bg", "#fbfffe"),
|
||||
color_danger=brand.get("color_danger", "#ff3860"),
|
||||
logo_path=brand.get(
|
||||
"logo_path",
|
||||
os.path.join(hyperglass_root, "static/images/hyperglass-dark.png"),
|
||||
),
|
||||
logo_width=brand.get("logo_width", "384"),
|
||||
favicon_dir=brand.get("favicon_path", "static/images/favicon/"),
|
||||
placeholder_prefix=brand.get(
|
||||
"placeholder_prefix", "IP, Prefix, Community, or AS_PATH"
|
||||
),
|
||||
show_peeringdb=brand.get("show_peeringdb", True),
|
||||
text_results=brand.get("text_results", "Results"),
|
||||
text_location=brand.get("text_location", "Select Location..."),
|
||||
text_cache=brand.get(
|
||||
"text_cache",
|
||||
f'Results will be cached for {math.ceil(gen["cache_timeout"] / 60)} minutes.',
|
||||
),
|
||||
primary_font_name=brand.get("primary_font_name", "Nunito"),
|
||||
primary_font_url=brand.get(
|
||||
"primary_font_url",
|
||||
"https://fonts.googleapis.com/css?family=Nunito:400,600,700",
|
||||
),
|
||||
mono_font_name=brand.get("mono_font_name", "Fira Mono"),
|
||||
mono_font_url=brand.get(
|
||||
"mono_font_url", "https://fonts.googleapis.com/css?family=Fira+Mono"
|
||||
),
|
||||
text_limiter_title=brand.get("text_limiter_title", "Limit Reached"),
|
||||
text_limiter_subtitle=brand.get(
|
||||
"text_limiter_subtitle",
|
||||
(
|
||||
f'You have accessed this site more than {gen["rate_limit_site"]} '
|
||||
"times in the last minute."
|
||||
),
|
||||
),
|
||||
text_500_title=brand.get("text_500_title", "Error"),
|
||||
text_500_subtitle=brand.get("text_500_subtitle", "Something went wrong."),
|
||||
text_500_button=brand.get("text_500_button", "Home"),
|
||||
text_help_bgp_route=brand.get(
|
||||
"text_help_bgp_route",
|
||||
"Performs BGP table lookup based on IPv4/IPv6 prefix.",
|
||||
),
|
||||
text_help_bgp_community=brand.get(
|
||||
"text_help_bgp_community",
|
||||
(
|
||||
'Performs BGP table lookup based on <a href="https://tools.ietf.org/html/rfc4360">'
|
||||
'Extended</a> or <a href="https://tools.ietf.org/html/rfc8195">Large</a> '
|
||||
"community value.<br>"
|
||||
'<a href="#" onclick="bgpHelpCommunity()">BGP Communities</a>'
|
||||
),
|
||||
),
|
||||
text_help_bgp_aspath=brand.get(
|
||||
"text_help_bgp_aspath",
|
||||
(
|
||||
"Performs BGP table lookup based on <code>AS_PATH</code> regular expression."
|
||||
'<br>For commonly used BGP regular expressions, <a href="https://hyperglass.'
|
||||
'readthedocs.io/en/latest/Extras/common_as_path_regex/">click here</a>.<br>'
|
||||
'<a href="#" onclick="bgpHelpASPath()">Allowed BGP AS Path Expressions</a>'
|
||||
),
|
||||
),
|
||||
text_help_ping=brand.get(
|
||||
"text_help_ping", "Sends 5 ICMP echo requests to the target."
|
||||
),
|
||||
text_help_traceroute=brand.get(
|
||||
"text_help_traceroute",
|
||||
(
|
||||
"Performs UDP Based traceroute to the target.<br>For information about how to"
|
||||
'interpret traceroute results, <a href="https://www.nanog.org/meetings/nanog45/'
|
||||
'presentations/Sunday/RAS_traceroute_N45.pdf">click here</a>.'
|
||||
),
|
||||
),
|
||||
features["rate_limit"]["query"]["title"] = config["features"]["rate_limit"][
|
||||
"query"
|
||||
].get("title", "Query Limit Reached")
|
||||
features["rate_limit"]["query"]["message"] = config["features"]["rate_limit"][
|
||||
"query"
|
||||
].get(
|
||||
"message",
|
||||
f"""Query limit of {features["rate_limit"]["query"]["rate"]} per \
|
||||
{features["rate_limit"]["query"]["period"]} reached. Please wait one minute and try \
|
||||
again.""",
|
||||
)
|
||||
features["rate_limit"]["query"]["button"] = config["features"]["rate_limit"][
|
||||
"query"
|
||||
].get("button", "Try Again")
|
||||
|
||||
features["rate_limit"]["message"] = config["features"]["rate_limit"].get(
|
||||
"message",
|
||||
f"""Query limit of {features["rate_limit"]["query"]} per minute reached. \
|
||||
Please wait one minute and try again.""",
|
||||
)
|
||||
features["rate_limit"]["site"] = config["features"]["rate_limit"]["site"]
|
||||
features["rate_limit"]["site"]["rate"] = config["features"]["rate_limit"].get(
|
||||
"rate", 60
|
||||
)
|
||||
features["rate_limit"]["site"]["period"] = config["features"]["rate_limit"].get(
|
||||
"period", "minute"
|
||||
)
|
||||
features["rate_limit"]["site"]["title"] = config["features"]["rate_limit"][
|
||||
"site"
|
||||
].get("title", "Limit Reached")
|
||||
features["rate_limit"]["site"]["subtitle"] = config["features"]["rate_limit"][
|
||||
"site"
|
||||
].get(
|
||||
"subtitle",
|
||||
f'You have accessed this site more than {features["rate_limit"]["site"]["rate"]} '
|
||||
f'times in the last {features["rate_limit"]["site"]["period"]}.',
|
||||
)
|
||||
features["cache"] = config["features"]["cache"]
|
||||
features["cache"]["timeout"] = config["features"]["cache"].get("timeout", 120)
|
||||
features["cache"]["directory"] = config["features"]["cache"].get(
|
||||
"directory", os.path.join(hyperglass_root, ".flask_cache")
|
||||
)
|
||||
features["cache"]["show_text"] = config["features"]["cache"].get("show_text", True)
|
||||
features["cache"]["text"] = config["features"]["cache"].get(
|
||||
"text",
|
||||
f'Results will be cached for {math.ceil(features["cache"]["timeout"] / 60)} minutes.',
|
||||
)
|
||||
features["bgp_route"] = config["features"]["bgp_route"]
|
||||
features["bgp_route"]["enable"] = config["features"]["bgp_route"].get(
|
||||
"enable", True
|
||||
)
|
||||
features["bgp_community"] = config["features"]["bgp_community"]
|
||||
features["bgp_community"]["enable"] = config["features"]["bgp_community"].get(
|
||||
"enable", True
|
||||
)
|
||||
features["bgp_community"]["regex"] = config["features"]["bgp_community"]["regex"]
|
||||
features["bgp_community"]["regex"]["decimal"] = config["features"]["bgp_community"][
|
||||
"regex"
|
||||
].get("decimal", r"^[0-9]{1,10}$")
|
||||
features["bgp_community"]["regex"]["extended_as"] = config["features"][
|
||||
"bgp_community"
|
||||
]["regex"].get("extended_as", r"^([0-9]{0,5})\:([0-9]{1,5})$")
|
||||
features["bgp_community"]["regex"]["large"] = config["features"]["bgp_community"][
|
||||
"regex"
|
||||
].get("large", r"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$")
|
||||
features["bgp_aspath"] = config["features"]["bgp_aspath"]
|
||||
features["bgp_aspath"]["enable"] = config["features"]["bgp_aspath"].get(
|
||||
"enable", True
|
||||
)
|
||||
features["bgp_aspath"]["regex"] = config["features"]["bgp_aspath"]["regex"]
|
||||
features["bgp_aspath"]["regex"]["mode"] = config["features"]["bgp_aspath"][
|
||||
"regex"
|
||||
].get("mode", "asplain")
|
||||
features["bgp_aspath"]["regex"]["asplain"] = config["features"]["bgp_aspath"][
|
||||
"regex"
|
||||
].get("asplain", r"^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$")
|
||||
features["bgp_aspath"]["regex"]["asdot"] = config["features"]["bgp_aspath"][
|
||||
"regex"
|
||||
].get("asdot", r"^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$")
|
||||
features["bgp_aspath"]["regex"]["pattern"] = config["features"]["bgp_aspath"][
|
||||
"regex"
|
||||
].get(features["bgp_aspath"]["regex"]["mode"], None)
|
||||
features["ping"] = config["features"]["ping"]
|
||||
features["ping"]["enable"] = config["features"]["ping"].get("enable", True)
|
||||
features["traceroute"] = config["features"]["traceroute"]
|
||||
features["traceroute"]["enable"] = config["features"]["traceroute"].get(
|
||||
"enable", True
|
||||
)
|
||||
features["max_prefix"] = config["features"]["max_prefix"]
|
||||
features["max_prefix"]["enable"] = config["features"]["max_prefix"].get(
|
||||
"enable", False
|
||||
)
|
||||
features["max_prefix"]["ipv4"] = config["features"]["max_prefix"].get("ipv4", 24)
|
||||
features["max_prefix"]["ipv6"] = config["features"]["max_prefix"].get("ipv6", 64)
|
||||
features["max_prefix"]["message"] = config["features"]["max_prefix"].get(
|
||||
"message",
|
||||
"Prefix length must be smaller than /{m}. <b>{i}</b> is too specific.",
|
||||
)
|
||||
messages["no_query_type"] = config["messages"].get(
|
||||
"no_query_type", "Query Type must be specified."
|
||||
)
|
||||
messages["no_location"] = config["messages"].get(
|
||||
"no_location", "A location must be selected."
|
||||
)
|
||||
messages["no_input"] = config["messages"].get(
|
||||
"no_input", "A target must be specified"
|
||||
)
|
||||
messages["not_allowed"] = config["messages"].get(
|
||||
"not_allowed", "<b>{i}</b> is not allowed."
|
||||
)
|
||||
messages["requires_ipv6_cidr"] = config["messages"].get(
|
||||
"requires_ipv6_cidr",
|
||||
"<b>{d}</b> requires IPv6 BGP lookups to be in CIDR notation.",
|
||||
)
|
||||
messages["invalid_ip"] = config["messages"].get(
|
||||
"invalid_ip", "<b>{i}</b> is not a valid IP address."
|
||||
)
|
||||
messages["invalid_dual"] = config["messages"].get(
|
||||
"invalid_dual", "<b>{i}</b> is an invalid {qt}."
|
||||
)
|
||||
messages["general"] = config["messages"].get("general", "An error occurred.")
|
||||
messages["directed_cidr"] = config["messages"].get(
|
||||
"directed_cidr", "<b>{q}</b> queries can not be in CIDR format."
|
||||
)
|
||||
branding["site_name"] = config["branding"].get("site_name", "hyperglass")
|
||||
branding["footer"] = config["branding"]["footer"]
|
||||
branding["footer"]["enable"] = config["branding"]["footer"].get("enable", True)
|
||||
branding["credit"] = config["branding"]["credit"]
|
||||
branding["credit"]["enable"] = config["branding"]["credit"].get("enable", True)
|
||||
branding["peering_db"] = config["branding"]["peering_db"]
|
||||
branding["peering_db"]["enable"] = config["branding"]["peering_db"].get(
|
||||
"enable", True
|
||||
)
|
||||
branding["text"] = config["branding"]["text"]
|
||||
branding["text"]["query_type"] = config["branding"]["text"].get(
|
||||
"query_type", "Query Type"
|
||||
)
|
||||
branding["text"]["title_mode"] = config["branding"]["text"].get(
|
||||
"title_mode", "logo_only"
|
||||
)
|
||||
branding["text"]["title"] = config["branding"]["text"].get("title", "hyperglass")
|
||||
branding["text"]["subtitle"] = config["branding"]["text"].get(
|
||||
"subtitle", f'AS{general["primary_asn"]}'
|
||||
)
|
||||
branding["text"]["results"] = config["branding"]["text"].get("results", "Results")
|
||||
branding["text"]["location"] = config["branding"]["text"].get(
|
||||
"location", "Select Location..."
|
||||
)
|
||||
branding["text"]["query_placeholder"] = config["branding"]["text"].get(
|
||||
"query_placeholder", "IP, Prefix, Community, or AS Path"
|
||||
)
|
||||
branding["text"]["bgp_route"] = config["branding"]["text"].get(
|
||||
"bgp_route", "BGP Route"
|
||||
)
|
||||
branding["text"]["bgp_community"] = config["branding"]["text"].get(
|
||||
"bgp_community", "BGP Community"
|
||||
)
|
||||
branding["text"]["bgp_aspath"] = config["branding"]["text"].get(
|
||||
"bgp_aspath", "BGP AS Path"
|
||||
)
|
||||
branding["text"]["ping"] = config["branding"]["text"].get("ping", "Ping")
|
||||
branding["text"]["traceroute"] = config["branding"]["text"].get(
|
||||
"traceroute", "Traceroute"
|
||||
)
|
||||
branding["text"]["404"]["title"] = config["branding"]["text"]["404"].get(
|
||||
"title", "Error"
|
||||
)
|
||||
branding["text"]["404"]["subtitle"] = config["branding"]["text"]["404"].get(
|
||||
"subtitle", "Page Not Found"
|
||||
)
|
||||
branding["text"]["500"]["title"] = config["branding"]["text"]["500"].get(
|
||||
"title", "Error"
|
||||
)
|
||||
branding["text"]["500"]["subtitle"] = config["branding"]["text"]["500"].get(
|
||||
"subtitle", "Something Went Wrong"
|
||||
)
|
||||
branding["text"]["500"]["button"] = config["branding"]["text"]["500"].get(
|
||||
"button", "Home"
|
||||
)
|
||||
branding["logo"] = config["branding"]["logo"]
|
||||
branding["logo"]["path"] = config["branding"]["logo"].get(
|
||||
"path", "static/images/hyperglass-dark.png"
|
||||
)
|
||||
branding["logo"]["width"] = config["branding"]["logo"].get("width", 384)
|
||||
branding["logo"]["favicons"] = config["branding"]["logo"].get(
|
||||
"favicons", "static/images/favicon/"
|
||||
)
|
||||
branding["color"] = config["branding"]["color"]
|
||||
branding["color"]["background"] = config["branding"]["color"].get(
|
||||
"background", "#fbfffe"
|
||||
)
|
||||
branding["color"]["button_submit"] = config["branding"]["color"].get(
|
||||
"button_submit", "#40798c"
|
||||
)
|
||||
branding["color"]["danger"] = config["branding"]["color"].get("danger", "#ff3860")
|
||||
branding["color"]["progress_bar"] = config["branding"]["color"].get(
|
||||
"progress_bar", "#40798c"
|
||||
)
|
||||
branding["color"]["tag"]["type"] = config["branding"]["color"]["tag"].get(
|
||||
"type", "#ff5e5b"
|
||||
)
|
||||
branding["color"]["tag"]["type_title"] = config["branding"]["color"]["tag"].get(
|
||||
"type_title", "#330036"
|
||||
)
|
||||
branding["color"]["tag"]["location"] = config["branding"]["color"]["tag"].get(
|
||||
"location", "#40798c"
|
||||
)
|
||||
branding["color"]["tag"]["location_title"] = config["branding"]["color"]["tag"].get(
|
||||
"location_title", "#330036"
|
||||
)
|
||||
branding["font"] = config["branding"]["font"]
|
||||
branding["font"]["primary"] = config["branding"]["font"]["primary"]
|
||||
branding["font"]["primary"]["name"] = config["branding"]["font"]["primary"].get(
|
||||
"name", "Nunito"
|
||||
)
|
||||
branding["font"]["primary"]["url"] = config["branding"]["font"]["primary"].get(
|
||||
"url", "https://fonts.googleapis.com/css?family=Nunito:400,600,700"
|
||||
)
|
||||
branding["font"]["mono"] = config["branding"]["font"]["mono"]
|
||||
branding["font"]["mono"]["name"] = config["branding"]["font"]["mono"].get(
|
||||
"name", "Fira Mono"
|
||||
)
|
||||
branding["font"]["mono"]["url"] = config["branding"]["font"]["mono"].get(
|
||||
"url", "https://fonts.googleapis.com/css?family=Fira+Mono"
|
||||
)
|
||||
params_dict = dict(
|
||||
general=general, branding=branding, features=features, messages=messages
|
||||
)
|
||||
return params_dict
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
# Define networks that you don't want users to be able to query. Any IP inside the subnet will return an error message.
|
||||
blacklist = [
|
||||
"100.64.0.0/10",
|
||||
"198.18.0.0/15",
|
||||
"10.0.0.0/8",
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.0/12"
|
||||
]
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
requires_ipv6_cidr = [
|
||||
"cisco_ios",
|
||||
"cisco_nxos"
|
||||
]
|
||||
|
|
@ -2,8 +2,13 @@
|
|||
"""
|
||||
Main Hyperglass Front End
|
||||
"""
|
||||
# Module Imports
|
||||
# Standard Imports
|
||||
import json
|
||||
import logging
|
||||
from pprint import pprint
|
||||
|
||||
# Module Imports
|
||||
import logzero
|
||||
from logzero import logger
|
||||
from flask import Flask, request, Response
|
||||
from flask_caching import Cache
|
||||
|
|
@ -12,31 +17,49 @@ from flask_limiter.util import get_ipaddr
|
|||
from prometheus_client import generate_latest, Counter
|
||||
|
||||
# Project Imports
|
||||
import hyperglass.configuration as configuration
|
||||
from hyperglass.command import execute
|
||||
from hyperglass import configuration
|
||||
from hyperglass import render
|
||||
|
||||
# Main Flask definition
|
||||
app = Flask(__name__, static_url_path="/static")
|
||||
|
||||
# Logzero Configuration
|
||||
if configuration.debug_state():
|
||||
logzero.loglevel(logging.DEBUG)
|
||||
else:
|
||||
logzero.loglevel(logging.INFO)
|
||||
|
||||
# Initialize general configuration parameters for reuse
|
||||
general = configuration.general()
|
||||
# brand = configuration.branding()
|
||||
config = configuration.params()
|
||||
codes = configuration.codes()
|
||||
codes_reason = configuration.codes_reason()
|
||||
logger.debug(f"Configuration Parameters:\n {pprint(config)}")
|
||||
|
||||
# Flask-Limiter Config
|
||||
rate_limit_query = f'{general["rate_limit_query"]} per minute'
|
||||
rate_limit_site = f'{general["rate_limit_site"]} per minute'
|
||||
query_rate = config["features"]["rate_limit"]["query"]["rate"]
|
||||
query_period = config["features"]["rate_limit"]["query"]["period"]
|
||||
site_rate = config["features"]["rate_limit"]["site"]["rate"]
|
||||
site_period = config["features"]["rate_limit"]["site"]["period"]
|
||||
rate_limit_query = f"{query_rate} per {query_period}"
|
||||
rate_limit_site = f"{site_rate} per {site_period}"
|
||||
limiter = Limiter(app, key_func=get_ipaddr, default_limits=[rate_limit_site])
|
||||
logger.debug(f"Query rate limit: {rate_limit_query}")
|
||||
logger.debug(f"Site rate limit: {rate_limit_site}")
|
||||
|
||||
# Flask-Caching Config
|
||||
cache_directory = config["features"]["cache"]["directory"]
|
||||
cache_timeout = config["features"]["cache"]["timeout"]
|
||||
cache = Cache(
|
||||
app,
|
||||
config={
|
||||
"CACHE_TYPE": "filesystem",
|
||||
"CACHE_DIR": general["cache_directory"],
|
||||
"CACHE_DEFAULT_TIMEOUT": general["cache_timeout"],
|
||||
"CACHE_DIR": cache_directory,
|
||||
"CACHE_DEFAULT_TIMEOUT": cache_timeout,
|
||||
},
|
||||
)
|
||||
logger.debug(f"Cache directory: {cache_directory}, Cache timeout: {cache_timeout}")
|
||||
|
||||
# Prometheus Config
|
||||
count_data = Counter(
|
||||
|
|
@ -56,22 +79,34 @@ count_ratelimit = Counter(
|
|||
|
||||
@app.route("/metrics")
|
||||
def metrics():
|
||||
CONTENT_TYPE_LATEST = str("text/plain; version=0.0.4; charset=utf-8")
|
||||
return Response(generate_latest(), mimetype=CONTENT_TYPE_LATEST)
|
||||
"""Prometheus metrics"""
|
||||
content_type_latest = str("text/plain; version=0.0.4; charset=utf-8")
|
||||
return Response(generate_latest(), mimetype=content_type_latest)
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def handle_404(e):
|
||||
"""Renders full error page for too many site queries"""
|
||||
html = render.html("404")
|
||||
count_ratelimit.labels(e, get_ipaddr()).inc()
|
||||
logger.error(e)
|
||||
return html, 404
|
||||
|
||||
|
||||
@app.errorhandler(429)
|
||||
def error429(e):
|
||||
def handle_429(e):
|
||||
"""Renders full error page for too many site queries"""
|
||||
html = render.html("429")
|
||||
count_ratelimit.labels(e, get_ipaddr()).inc()
|
||||
logger.error(f"{e}")
|
||||
logger.error(e)
|
||||
return html, 429
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
def general_error():
|
||||
def handle_500(e):
|
||||
"""General Error Page"""
|
||||
count_errors.labels(500, e, get_ipaddr(), None, None, None).inc()
|
||||
logger.error(e)
|
||||
html = render.html("500")
|
||||
return html, 500
|
||||
|
||||
|
|
@ -98,15 +133,16 @@ def site():
|
|||
def test_route():
|
||||
"""Test route for various tests"""
|
||||
html = render.html("500")
|
||||
return html
|
||||
return html, 500
|
||||
|
||||
|
||||
@app.route("/routers/<asn>", methods=["GET"])
|
||||
def get_routers(asn):
|
||||
"""Flask GET route provides a JSON list of all routers for the selected network/ASN"""
|
||||
networks_list = configuration.networks_list()
|
||||
networks_list_json = json.dumps(networks_list[asn])
|
||||
return networks_list_json
|
||||
@app.route("/locations/<asn>", methods=["GET"])
|
||||
def get_locations(asn):
|
||||
"""Flask GET route provides a JSON list of all locations for the selected network/ASN"""
|
||||
locations_list = configuration.locations_list()
|
||||
locations_list_json = json.dumps(locations_list[asn])
|
||||
logger.debug(f"Locations list:\n{locations_list}")
|
||||
return locations_list_json
|
||||
|
||||
|
||||
@app.route("/lg", methods=["POST"])
|
||||
|
|
@ -117,28 +153,51 @@ def hyperglass_main():
|
|||
the backend application to perform the filtering/lookups"""
|
||||
# Get JSON data from Ajax POST
|
||||
lg_data = request.get_json()
|
||||
logger.debug(f"Unvalidated input: {lg_data}")
|
||||
# Return error if no target is specified
|
||||
if not lg_data["target"]:
|
||||
logger.debug("No input specified")
|
||||
return Response(config["messages"]["no_input"], codes["danger"])
|
||||
# Return error if no location is selected
|
||||
if lg_data["location"] not in configuration.locations():
|
||||
logger.debug("No selection specified")
|
||||
return Response(config["messages"]["no_location"], codes["danger"])
|
||||
# Return error if no query type is selected
|
||||
if lg_data["type"] not in [
|
||||
"bgp_route",
|
||||
"bgp_community",
|
||||
"bgp_aspath",
|
||||
"ping",
|
||||
"traceroute",
|
||||
]:
|
||||
logger.debug("No query specified")
|
||||
return Response(config["messages"]["no_query_type"], codes["danger"])
|
||||
client_addr = request.remote_addr
|
||||
count_data.labels(
|
||||
client_addr, lg_data["cmd"], lg_data["router"], lg_data["ipprefix"]
|
||||
client_addr, lg_data["type"], lg_data["location"], lg_data["target"]
|
||||
).inc()
|
||||
logger.debug(f"Client Address: {client_addr}")
|
||||
# Stringify the form response containing serialized JSON for the request, use as key for k/v
|
||||
# cache store so each command output value is unique
|
||||
cache_key = str(lg_data)
|
||||
# Check if cached entry exists
|
||||
if cache.get(cache_key) is None:
|
||||
try:
|
||||
logger.debug(f"Sending query {cache_key} to execute module...")
|
||||
cache_value = execute.Execute(lg_data).response()
|
||||
logger.info(f"Cache Value: {cache_value}")
|
||||
logger.debug(f"Validated response...")
|
||||
value_code = cache_value[1]
|
||||
value_entry = cache_value[0:2]
|
||||
value_params = cache_value[2:]
|
||||
logger.info(f"No cache match for: {cache_key}")
|
||||
logger.debug(
|
||||
f"Status Code: {value_code}, Output: {cache_value[1]}, Info: {cache_value[2]}"
|
||||
)
|
||||
# If it doesn't, create a cache entry
|
||||
cache.set(cache_key, value_entry)
|
||||
logger.info(f"Added cache entry: {value_params}")
|
||||
logger.debug(f"Added cache entry for query: {cache_key}")
|
||||
# If 200, return output
|
||||
response = cache.get(cache_key)
|
||||
if value_code == 200:
|
||||
logger.debug(f"Returning {value_code} response")
|
||||
return Response(response[0], response[1])
|
||||
# If 400 error, return error message and code
|
||||
# Note: 200 & 400 errors are separated mainly for potential future use
|
||||
|
|
@ -147,26 +206,17 @@ def hyperglass_main():
|
|||
response[1],
|
||||
codes_reason[response[1]],
|
||||
client_addr,
|
||||
lg_data["cmd"],
|
||||
lg_data["router"],
|
||||
lg_data["ipprefix"],
|
||||
lg_data["type"],
|
||||
lg_data["location"],
|
||||
lg_data["target"],
|
||||
).inc()
|
||||
logger.debug(f"Returning {value_code} response")
|
||||
return Response(response[0], response[1])
|
||||
if value_code == 500:
|
||||
count_errors.labels(
|
||||
response[1],
|
||||
codes_reason[response[1]],
|
||||
client_addr,
|
||||
lg_data["cmd"],
|
||||
lg_data["router"],
|
||||
lg_data["ipprefix"],
|
||||
).inc()
|
||||
return Response(general["msg_error_general"], 500)
|
||||
except:
|
||||
logger.error(f"Unable to add output to cache: {cache_key}")
|
||||
raise
|
||||
# If it does, return the cached entry
|
||||
else:
|
||||
logger.info(f"Cache match for: {cache_key}, returning cached entry...")
|
||||
logger.debug(f"Cache match for: {cache_key}, returning cached entry")
|
||||
response = cache.get(cache_key)
|
||||
return Response(response[0], response[1])
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ Renders Jinja2 & Sass templates for use by the front end application
|
|||
"""
|
||||
# Standard Imports
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
# Module Imports
|
||||
import sass
|
||||
import toml
|
||||
import jinja2
|
||||
import logzero
|
||||
from logzero import logger
|
||||
from markdown2 import Markdown
|
||||
from flask import render_template
|
||||
|
|
@ -24,40 +26,47 @@ hyperglass_root = os.path.dirname(hyperglass.__file__)
|
|||
file_loader = jinja2.FileSystemLoader(working_directory)
|
||||
env = jinja2.Environment(loader=file_loader)
|
||||
|
||||
# Logzero Configuration
|
||||
if configuration.debug_state():
|
||||
logzero.loglevel(logging.DEBUG)
|
||||
else:
|
||||
logzero.loglevel(logging.INFO)
|
||||
|
||||
# Configuration Imports
|
||||
branding = configuration.branding()
|
||||
general = configuration.general()
|
||||
config = configuration.params()
|
||||
# branding = configuration.branding()
|
||||
# general = configuration.general()
|
||||
networks = configuration.networks()
|
||||
|
||||
defaults = {
|
||||
default_details = {
|
||||
"footer": """
|
||||
+++
|
||||
+++
|
||||
By using {{ site_title }}, you agree to be bound by the following terms of use: All queries \
|
||||
executed on this page are logged for analysis and troubleshooting. Users are prohibited from \
|
||||
automating queries, or attempting to process queries in bulk. This service is provided on a best \
|
||||
effort basis, and {{ org_name }} makes no availability or performance warranties or guarantees \
|
||||
whatsoever.
|
||||
By using {{ branding["site_name"] }}, you agree to be bound by the following terms of use: All \
|
||||
queries executed on this page are logged for analysis and troubleshooting. Users are prohibited \
|
||||
from automating queries, or attempting to process queries in bulk. This service is provided on a \
|
||||
best effort basis, and {{ general["org_name"] }} makes no availability or performance warranties or \
|
||||
guarantees whatsoever.
|
||||
""",
|
||||
"bgp_aspath": r"""
|
||||
+++
|
||||
title = "Supported AS Path Patterns"
|
||||
+++
|
||||
{{ site_title }} accepts the following `AS_PATH` regular expression patterns:
|
||||
{{ branding["site_name"] }} accepts the following `AS_PATH` regular expression patterns:
|
||||
|
||||
| Expression | Match |
|
||||
| :----------------------- | ----------------------------------------------------: |
|
||||
| `_65000$` | Originated by AS65000 |
|
||||
| `^65000\_` | Received from AS65000 |
|
||||
| `_65000_` | Via AS65000 |
|
||||
| `_65000_65001_` | Via AS65000 and AS65001 |
|
||||
| `_65000(_.+_)65001$` | Anything from AS65001 that passed through AS65000 |
|
||||
| Expression | Match |
|
||||
| :------------------- | :-------------------------------------------- |
|
||||
| `_65000$` | Originated by 65000 |
|
||||
| `^65000_` | Received from 65000 |
|
||||
| `_65000_` | Via 65000 |
|
||||
| `_65000_65001_` | Via 65000 and 65001 |
|
||||
| `_65000(_.+_)65001$` | Anything from 65001 that passed through 65000 |
|
||||
""",
|
||||
"bgp_community": """
|
||||
+++
|
||||
title = "BGP Communities"
|
||||
+++
|
||||
{{ site_title }} makes use of the following BGP communities:
|
||||
{{ branding["site_name"] }} makes use of the following BGP communities:
|
||||
|
||||
| Community | Description |
|
||||
| :-------- | :---------- |
|
||||
|
|
@ -67,8 +76,44 @@ title = "BGP Communities"
|
|||
""",
|
||||
}
|
||||
|
||||
default_info = {
|
||||
"bgp_route": """
|
||||
+++
|
||||
+++
|
||||
Performs BGP table lookup based on IPv4/IPv6 prefix.
|
||||
""",
|
||||
"bgp_community": """
|
||||
+++
|
||||
link = '<a href="#" onclick="bgpHelpCommunity()">{{ general["org_name"] }} BGP Communities</a>'
|
||||
+++
|
||||
Performs BGP table lookup based on [Extended](https://tools.ietf.org/html/rfc4360) or \
|
||||
[Large](https://tools.ietf.org/html/rfc8195) community value.
|
||||
|
||||
def content(file_name):
|
||||
{{ info["bgp_community"]["link"] }}
|
||||
""",
|
||||
"bgp_aspath": """
|
||||
+++
|
||||
link = '<a href="#" onclick="bgpHelpASPath()">Supported BGP AS Path Expressions</a>'
|
||||
+++
|
||||
Performs BGP table lookup based on `AS_PATH` regular expression.
|
||||
|
||||
{{ info["bgp_aspath"]["link"] }}
|
||||
""",
|
||||
"ping": """
|
||||
+++
|
||||
+++
|
||||
Sends 5 ICMP echo requests to the target.
|
||||
""",
|
||||
"traceroute": """
|
||||
+++
|
||||
+++
|
||||
Performs UDP Based traceroute to the target.<br>For information about how to interpret traceroute \
|
||||
results, [click here](https://hyperglass.readthedocs.io/nanog_traceroute.pdf).
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
def info(file_name):
|
||||
"""Converts Markdown documents to HTML, renders Jinja2 variables, renders TOML frontmatter \
|
||||
variables, returns dictionary of variables and HTML content"""
|
||||
html_classes = {"table": "table"}
|
||||
|
|
@ -80,48 +125,89 @@ def content(file_name):
|
|||
"html-classes": html_classes,
|
||||
}
|
||||
)
|
||||
delim = "+++"
|
||||
file = os.path.join(working_directory, f"templates/content/{file_name}.md")
|
||||
frontmatter_dict = None
|
||||
file = os.path.join(working_directory, f"templates/info/{file_name}.md")
|
||||
frontmatter_dict = {}
|
||||
if os.path.exists(file):
|
||||
with open(file, "r") as file_raw:
|
||||
file_read = file_raw.read()
|
||||
_, frontmatter, content_md = file_read.split(delim)
|
||||
frontmatter_dict = {file_name: toml.loads(frontmatter)}
|
||||
content_md_template = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
content_md
|
||||
_, frontmatter, content = file_read.split("+++")
|
||||
frontmatter_dict[file_name] = toml.loads(frontmatter)
|
||||
md_template_fm = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
frontmatter
|
||||
)
|
||||
md_template_content = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
content
|
||||
)
|
||||
else:
|
||||
content_read = defaults[file_name]
|
||||
_, frontmatter, content_md = content_read.split(delim)
|
||||
frontmatter_dict = {file_name: toml.loads(frontmatter)}
|
||||
content_md_template = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
content_md
|
||||
_, frontmatter, content = default_info[file_name].split("+++")
|
||||
md_template_fm = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
frontmatter
|
||||
)
|
||||
content_rendered = content_md_template.render(
|
||||
**general, **branding, **frontmatter_dict
|
||||
)
|
||||
content_html = markdown.convert(content_rendered)
|
||||
frontmatter_dict[file_name]["content"] = content_html
|
||||
md_template_content = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
content
|
||||
)
|
||||
frontmatter_rendered = md_template_fm.render(**config)
|
||||
frontmatter_dict[file_name] = toml.loads(frontmatter_rendered)
|
||||
content_rendered = md_template_content.render(**config, info=frontmatter_dict)
|
||||
frontmatter_dict[file_name]["content"] = markdown.convert(content_rendered)
|
||||
return frontmatter_dict
|
||||
|
||||
|
||||
def html(t):
|
||||
def details(file_name):
|
||||
"""Converts Markdown documents to HTML, renders Jinja2 variables, renders TOML frontmatter \
|
||||
variables, returns dictionary of variables and HTML content"""
|
||||
html_classes = {"table": "table"}
|
||||
markdown = Markdown(
|
||||
extras={
|
||||
"break-on-newline": True,
|
||||
"code-friendly": True,
|
||||
"tables": True,
|
||||
"html-classes": html_classes,
|
||||
}
|
||||
)
|
||||
file = os.path.join(working_directory, f"templates/info/details/{file_name}.md")
|
||||
frontmatter_dict = {}
|
||||
if os.path.exists(file):
|
||||
with open(file, "r") as file_raw:
|
||||
file_read = file_raw.read()
|
||||
_, frontmatter, content = file_read.split("+++")
|
||||
md_template_fm = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
frontmatter
|
||||
)
|
||||
md_template_content = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
content
|
||||
)
|
||||
else:
|
||||
_, frontmatter, content = default_details[file_name].split("+++")
|
||||
frontmatter_dict[file_name] = toml.loads(frontmatter)
|
||||
md_template_fm = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
frontmatter
|
||||
)
|
||||
md_template_content = jinja2.Environment(loader=jinja2.BaseLoader).from_string(
|
||||
content
|
||||
)
|
||||
frontmatter_rendered = md_template_fm.render(**config)
|
||||
frontmatter_dict[file_name] = toml.loads(frontmatter_rendered)
|
||||
content_rendered = md_template_content.render(**config, details=frontmatter_dict)
|
||||
frontmatter_dict[file_name]["content"] = markdown.convert(content_rendered)
|
||||
return frontmatter_dict
|
||||
|
||||
|
||||
def html(template_name):
|
||||
"""Renders Jinja2 HTML templates"""
|
||||
content_name_list = ["footer", "bgp_aspath", "bgp_community"]
|
||||
content_dict = {}
|
||||
for content_name in content_name_list:
|
||||
# content_file = os.path.join(working_directory, f"templates/content/{c}.md")
|
||||
content_data = content(content_name)
|
||||
content_dict.update(content_data)
|
||||
if t == "index":
|
||||
template = env.get_template("templates/index.html")
|
||||
elif t == "429":
|
||||
template = env.get_template("templates/429.html")
|
||||
elif t == "500":
|
||||
template = env.get_template("templates/500.html")
|
||||
details_name_list = ["footer", "bgp_aspath", "bgp_community"]
|
||||
details_dict = {}
|
||||
for details_name in details_name_list:
|
||||
details_data = details(details_name)
|
||||
details_dict.update(details_data)
|
||||
info_list = ["bgp_route", "bgp_aspath", "bgp_community", "ping", "traceroute"]
|
||||
info_dict = {}
|
||||
for info_name in info_list:
|
||||
info_data = info(info_name)
|
||||
info_dict.update(info_data)
|
||||
template = env.get_template(f"templates/{template_name}.html")
|
||||
return template.render(
|
||||
**general, **branding, **content_dict, device_networks=networks
|
||||
**config, info=info_dict, details=details_dict, networks=networks
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -133,7 +219,7 @@ def css():
|
|||
try:
|
||||
template_file = "templates/hyperglass.scss"
|
||||
template = env.get_template(template_file)
|
||||
rendered_output = template.render(**branding)
|
||||
rendered_output = template.render(**config)
|
||||
with open(scss_file, "w") as scss_output:
|
||||
scss_output.write(rendered_output)
|
||||
except:
|
||||
|
|
@ -144,7 +230,7 @@ def css():
|
|||
generated_sass = sass.compile(filename=scss_file)
|
||||
with open(css_file, "w") as css_output:
|
||||
css_output.write(generated_sass)
|
||||
logger.info(f"Compiled Sass file {scss_file} to CSS file {css_file}.")
|
||||
logger.debug(f"Compiled Sass file {scss_file} to CSS file {css_file}.")
|
||||
except:
|
||||
logger.error(f"Error compiling Sass in file {scss_file}.")
|
||||
raise
|
||||
|
|
|
|||
44
hyperglass/render/templates/404.html
Normal file
44
hyperglass/render/templates/404.html
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{% extends "templates/base.html" %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
</head>
|
||||
|
||||
{% block content %}
|
||||
|
||||
<body class="has-background-danger">
|
||||
<section class="section">
|
||||
<nav class="navbar has-background-danger">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
</section>
|
||||
<section>
|
||||
<div class="container has-text-centered">
|
||||
<h1 class="title is-size-1">
|
||||
{{ branding["text"]["404"]["title"] }}
|
||||
</h1>
|
||||
<h2 class="subtitle is-size-3">
|
||||
{{ branding["text"]["404"]["subtitle"] }}
|
||||
</h2>
|
||||
<br>
|
||||
</div>
|
||||
</section>
|
||||
{% if branding["footer"]["enable"] == true %}
|
||||
{% include "templates/footer.html" %}
|
||||
{% endif %}
|
||||
{% if branding["credit"]["enable"] == true %}
|
||||
{% include "templates/credit.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -25,19 +25,19 @@
|
|||
<section>
|
||||
<div class="container has-text-centered">
|
||||
<h1 class="title is-size-1">
|
||||
{{ text_limiter_title }}
|
||||
{{ features["rate_limit"]["site"]["title"] }}
|
||||
</h1>
|
||||
<h2 class="subtitle is-size-3">
|
||||
{{ text_limiter_subtitle }}
|
||||
{{ features["rate_limit"]["site"]["subtitle"] }}
|
||||
</h2>
|
||||
<br>
|
||||
<a href="/" class="button is-medium is-rounded is-inverted is-danger is-outlined">Try Again</a>
|
||||
<a href="/" class="button is-medium is-rounded is-inverted is-danger is-outlined">{{ features["rate_limit"]["site"]["button"] }}</a>
|
||||
</div>
|
||||
</section>
|
||||
{% if enable_footer == true %}
|
||||
{% if branding["footer"]["enable"] == true %}
|
||||
{% include "templates/footer.html" %}
|
||||
{% endif %}
|
||||
{% if enable_credit == true %}
|
||||
{% if branding["credit"]["enable"] == true %}
|
||||
{% include "templates/credit.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -25,19 +25,19 @@
|
|||
<section>
|
||||
<div class="container has-text-centered">
|
||||
<h1 class="title is-size-1">
|
||||
{{ text_500_title }}
|
||||
{{ branding["text"]["500"]["title"] }}
|
||||
</h1>
|
||||
<h2 class="subtitle is-size-3">
|
||||
{{ text_500_subtitle }}
|
||||
{{ branding["text"]["500"]["subtitle"] }}
|
||||
</h2>
|
||||
<br>
|
||||
<a href="/" class="button is-medium is-rounded is-inverted is-danger is-outlined">Home</a>
|
||||
<a href="/" class="button is-medium is-rounded is-inverted is-danger is-outlined">{{ branding["text"]["500"]["button"] }}</a>
|
||||
</div>
|
||||
</section>
|
||||
{% if enable_footer == true %}
|
||||
{% if branding["footer"]["enable"] == true %}
|
||||
{% include "templates/footer.html" %}
|
||||
{% endif %}
|
||||
{% if enable_credit == true %}
|
||||
{% if branding["credit"]["enable"] == true %}
|
||||
{% include "templates/credit.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@
|
|||
<html>
|
||||
<head>
|
||||
{% block head %}
|
||||
<title>{{ site_title }}</title>
|
||||
<title>{{ branding.site_name }}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ favicon_dir }}apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ favicon_dir }}favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ favicon_dir }}favicon-32x32.png">
|
||||
<link rel="manifest" href="{{ favicon_dir }}site.webmanifest">
|
||||
<link rel="mask-icon" href="{{ favicon_dir }}safari-pinned-tab.svg" color="{{ color_tag_cmd }}">
|
||||
<link rel="shortcut icon" href="{{ favicon_dir }}favicon.ico">
|
||||
<meta name="msapplication-TileColor" content="{{ color_tag_loctitle }}">
|
||||
<meta name="msapplication-config" content="{{ favicon_dir }}browserconfig.xml">
|
||||
<meta name="theme-color" content="{{ color_btn_submit }}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ branding["logo"]["favicons"] }}apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ branding["logo"]["favicons"] }}favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ branding["logo"]["favicons"] }}favicon-32x32.png">
|
||||
<link rel="manifest" href="{{ branding["logo"]["favicons"] }}site.webmanifest">
|
||||
<link rel="mask-icon" href="{{ branding["logo"]["favicons"] }}safari-pinned-tab.svg" color="{{ branding["color"]["tag"]["command"] }}">
|
||||
<link rel="shortcut icon" href="{{ branding["logo"]["favicons"] }}favicon.ico">
|
||||
<meta name="msapplication-TileColor" content="{{ branding["color"]["tag"]["location_title"] }}">
|
||||
<meta name="msapplication-config" content="{{ branding["logo"]["favicons"] }}browserconfig.xml">
|
||||
<meta name="theme-color" content="{{ branding["color"]["button_submit"] }}">
|
||||
<link href="static/css/icofont/icofont.min.css" rel="stylesheet" />
|
||||
<link href="static/css/hyperglass.css" rel="stylesheet" />
|
||||
{% endblock %}
|
||||
|
|
@ -24,10 +24,11 @@
|
|||
</body>
|
||||
{% block scripts %}
|
||||
<script src="static/js/jquery-3.4.0.min.js"></script>
|
||||
<script src="static/js/clipboard.min.js"></script>
|
||||
<script src="static/js/hyperglass.js"></script>
|
||||
{% if google_analytics|length > 0 %}
|
||||
{% if general.google_analytics|length > 0 %}
|
||||
<!--Google Analytics-->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ google_analytics }}"></script>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ general["google_analytics"] }}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
|
|
@ -36,7 +37,7 @@
|
|||
}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '{{ google_analytics }}');
|
||||
gtag('config', '{{ general["google_analytics"] }}');
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
<p class="title">{{ bgp_aspath["title"] }}</p>
|
||||
{{ bgp_aspath["content"] }}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<p class="title">{{ bgp_community["title"] }}</p>
|
||||
{{ bgp_community["content"] }}
|
||||
|
|
@ -1 +0,0 @@
|
|||
*.md
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
+++
|
||||
+++
|
||||
By using {{ site_title }}, you agree to be bound by the following terms of use: All queries executed on this page are logged for analysis and troubleshooting. Users are prohibited from automating queries, or attempting to process queries in bulk. This service is provided on a best effort basis, and {{ org_name }} makes no availability or performance warranties or guarantees whatsoever.
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="content is-small has-text-centered">
|
||||
<p>Powered by <a href="https://github.com/checktheroads/hyperglass">Hyperglass</a>. Source code licensed <a href="https://github.com/checktheroads/hyperglass/blob/master/LICENSE">BSD 3-Clause Clear.</a></p>
|
||||
<p>Powered by <a href="https://github.com/checktheroads/hyperglass">hyperglass</a>. Source code licensed <a href="https://github.com/checktheroads/hyperglass/blob/master/LICENSE">BSD 3-Clause Clear.</a></p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="content is-small has-text-centered">
|
||||
{{ footer["content"] }}
|
||||
{{ details["footer"]["content"] }}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
@charset "utf-8";
|
||||
|
||||
/* Fonts */
|
||||
@import url('{{ primary_font_url }}');
|
||||
@import url('{{ mono_font_url }}');
|
||||
@import url('{{ branding["font"]["primary"]["url"] }}');
|
||||
@import url('{{ branding["font"]["mono"]["url"] }}');
|
||||
|
||||
$family-sans-serif: "{{ primary_font_name }}", sans-serif;
|
||||
$family-monospace: "{{ mono_font_name }}", monospace;
|
||||
$family-sans-serif: "{{ branding["font"]["primary"]["name"] }}", sans-serif;
|
||||
$family-monospace: "{{ branding["font"]["mono"]["name"] }}", monospace;
|
||||
|
||||
/* Color Changes */
|
||||
$body-background-color: {{ color_bg }};
|
||||
$body-background-color: {{ branding["color"]["background"] }};
|
||||
$footer-background-color: transparent;
|
||||
$danger: {{ color_danger }};
|
||||
$danger: {{ branding["color"]["danger"] }};
|
||||
|
||||
/* Custom Colors */
|
||||
$lg-btn-submit: {{ color_btn_submit }};
|
||||
$lg-tag-loctitle: {{ color_tag_loctitle }};
|
||||
$lg-tag-cmdtitle: {{ color_tag_cmdtitle }};
|
||||
$lg-tag-cmd: {{ color_tag_cmd }};
|
||||
$lg-progressbar: {{ color_progressbar }};
|
||||
$lg-tag-loc: {{ color_tag_loc }};
|
||||
$lg-btn-submit: {{ branding["color"]["button_submit"] }};
|
||||
$lg-tag-loc_title: {{ branding["color"]["tag"]["location_title"] }};
|
||||
$lg-tag-type_title: {{ branding["color"]["tag"]["type_title"] }};
|
||||
$lg-tag-type: {{ branding["color"]["tag"]["type"] }};
|
||||
$lg-progressbar: {{ branding["color"]["progress_bar"] }};
|
||||
$lg-tag-loc: {{ branding["color"]["tag"]["location"] }};
|
||||
|
||||
/* Element Changes */
|
||||
$footer-padding: 3rem 1.5rem 3rem ;
|
||||
|
|
@ -31,5 +31,5 @@ $footer-padding: 3rem 1.5rem 3rem ;
|
|||
@import "grid/_all";
|
||||
@import "layout/_all";
|
||||
|
||||
/* Looking Glass Imports */
|
||||
/* Hyperglass Imports */
|
||||
@import "custom/custom_elements";
|
||||
|
|
|
|||
|
|
@ -11,35 +11,37 @@
|
|||
<div class="modal-content">
|
||||
<article class="message is-danger">
|
||||
<div class="message-header">
|
||||
<p>Query Limit Reached</p>
|
||||
<p>{{ features["rate_limit"]["query"]["title"] }}</p>
|
||||
</div>
|
||||
<div class="message-body">
|
||||
<p>{{ message_rate_limit_query }}</p>
|
||||
<p>{{ features["rate_limit"]["query"]["message"] }}</p>
|
||||
<br>
|
||||
<div class="buttons is-right">
|
||||
<a href="/" class="button is-danger is-rounded is-outlined">Try Again</a>
|
||||
<a href="/" class="button is-danger is-rounded is-outlined">{{ features["rate_limit"]["query"]["button"] }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{% if enable_bgp_aspath == true %}
|
||||
{% if features["bgp_aspath"]["enable"] == true %}
|
||||
<div class="modal" id="help_bgp_aspath">
|
||||
<div class="modal-background" onclick="closeModal()"></div>
|
||||
<div class="modal-content is-clipped">
|
||||
<div class="box">
|
||||
{% include "templates/bgp_aspath.html" %}
|
||||
<p class="title">{{ details["bgp_aspath"]["title"] }}</p>
|
||||
{{ details["bgp_aspath"]["content"] }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" onclick="closeModal()"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if enable_bgp_community == true %}
|
||||
{% if features["bgp_community"]["enable"] == true %}
|
||||
<div class="modal" id="help_bgp_community">
|
||||
<div class="modal-background" onclick="closeModal()"></div>
|
||||
<div class="modal-content">
|
||||
<div class="box">
|
||||
{% include "templates/bgp_community.html" %}
|
||||
<p class="title">{{ details["bgp_community"]["title"] }}</p>
|
||||
{{ details["bgp_community"]["content"] }}
|
||||
</div>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" onclick="closeModal()"></button>
|
||||
|
|
@ -49,10 +51,10 @@
|
|||
<div class="container is-fluid">
|
||||
<div class="navbar-brand">
|
||||
</div>
|
||||
{% if show_peeringdb == true %}
|
||||
{% if branding["peering_db"]["enable"] == true %}
|
||||
<div class="navbar-menu">
|
||||
<div class="navbar-end">
|
||||
<a class="navbar-item" href="https://as{{ primary_asn }}.peeringdb.com" target="_blank">
|
||||
<a class="navbar-item" href="https://as{{ general["primary_asn"] }}.peeringdb.com" target="_blank">
|
||||
<span>PeeringDB</span>
|
||||
<span class="icon">
|
||||
<i class="icofont-external"></i>
|
||||
|
|
@ -66,33 +68,33 @@
|
|||
</nav>
|
||||
<section class="section">
|
||||
<div class="container has-text-centered is-fluid">
|
||||
{% if title_mode == 'all' %}
|
||||
<img src="{{ logo_path }}" style="width: {{ logo_width }}px;">
|
||||
{% if branding["text"]["title_mode"] == 'all' %}
|
||||
<img src="{{ branding["logo"]["path"] }}" style="width: {{ branding["logo"]["width"] }}px;">
|
||||
<h1 class="title is-3" id="lg-title">
|
||||
{{ title }}
|
||||
{{ branding["text"]["title"] }}
|
||||
</h1>
|
||||
<h2 class="subtitle is-5" id="lg-subtitle">
|
||||
{{ subtitle }}
|
||||
{{ branding["text"]["subtitle"] }}
|
||||
</h2>
|
||||
<br>
|
||||
{% elif title_mode == 'text_only' %}
|
||||
{% elif branding["text"]["title_mode"] == 'text_only' %}
|
||||
<h1 class="title is-1" id="lg-title">
|
||||
{{ title }}
|
||||
{{ branding["text"]["title"] }}
|
||||
</h1>
|
||||
<h2 class="subtitle is-3" id="lg-subtitle">
|
||||
{{ subtitle }}
|
||||
{{ branding["text"]["subtitle"] }}
|
||||
</h2>
|
||||
<br>
|
||||
{% elif title_mode == 'logo_title' %}
|
||||
<img src="{{ logo_path }}" style="width: {{ logo_width }}px;">
|
||||
{% elif branding["text"]["title_mode"] == 'logo_title' %}
|
||||
<img src="{{ branding["logo"]["path"] }}" style="width: {{ branding["logo"]["width"] }}px;">
|
||||
<h1 class="title is-3" id="lg-title">
|
||||
{{ title }}
|
||||
{{ branding["text"]["title"] }}
|
||||
</h1>
|
||||
{% elif title_mode == 'logo_only' %}
|
||||
{% elif branding["text"]["title_mode"] == 'logo_only' %}
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<img src="{{ logo_path }}" style="width: {{ logo_width }}px;">
|
||||
<img src="{{ branding["logo"]["path"] }}" style="width: {{ branding["logo"]["width"] }}px;">
|
||||
<br>
|
||||
<br>
|
||||
{% endif %}
|
||||
|
|
@ -101,7 +103,7 @@
|
|||
<div class="container is-fluid">
|
||||
<div class="field has-addons has-addons-centered">
|
||||
<div class="control has-icons-left is-expanded">
|
||||
<input type="text" class="input is-medium is-rounded is-family-monospace" id="ipprefix" placeholder="{{ placeholder_prefix }}">
|
||||
<input type="text" class="input is-medium is-rounded is-family-monospace" id="target" placeholder="{{ branding["text"]["query_placeholder"] }}">
|
||||
<span class="icon is-small is-left"><i class="icofont-at"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -110,18 +112,17 @@
|
|||
<div class="control has-icons-left" id="network-control">
|
||||
<div class="select is-medium is-rounded">
|
||||
<select id="network" name="network" style="width: 256px">
|
||||
<!-- <option value="" disabled></option> -->
|
||||
{% for net in device_networks %}
|
||||
{% for net in networks %}
|
||||
<option value="{{ net }}">AS{{ net }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<span class="icon is-left"><i class="icofont-cloudapp"></i></span>
|
||||
</div>
|
||||
<div class="control has-icons-left" id="router-control">
|
||||
<div class="control has-icons-left" id="location-control">
|
||||
<div class="select is-medium is-rounded">
|
||||
<select id="router" style="width: 256px">
|
||||
<option id="text_location" selected disabled>{{ text_location }}</option>
|
||||
<select id="location" style="width: 256px">
|
||||
<option id="text_location" selected disabled>{{ branding["text"]["location"] }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<span class="icon is-left"><i class="icofont-location-arrow"></i></span>
|
||||
|
|
@ -135,41 +136,41 @@
|
|||
<div class="dropdown is-right" id="help-dropdown">
|
||||
<div class="dropdown-trigger">
|
||||
<button type="button" class="button is-rounded is-medium" aria-haspopup="true" aria-controls="dropdown-menu2" onclick="adjustHeight()">
|
||||
<span class="icon is-size-7 lg-icon-help">
|
||||
<i class="icofont-question" aria-hidden="true"></i>
|
||||
<span class="icon is-size-6 lg-icon-help">
|
||||
<i class="icofont-info-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu is-expanded" id="dropdown-menu2" role="menu">
|
||||
<div class="dropdown-content lg-help">
|
||||
{% if enable_bgp_route == true %}
|
||||
{% if features["bgp_route"]["enable"] == true %}
|
||||
<div class="dropdown-item">
|
||||
<strong>BGP Route</strong>
|
||||
<p>{{ text_help_bgp_route }}</p>
|
||||
<strong>{{ branding["text"]["bgp_route"] }}</strong>
|
||||
<p>{{ info["bgp_route"]["content"] }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if enable_bgp_community == true %}
|
||||
{% if features["bgp_community"]["enable"] == true %}
|
||||
<div class="dropdown-item">
|
||||
<strong>BGP Community</strong>
|
||||
<p>{{ text_help_bgp_community }}</p>
|
||||
<strong>{{ branding["text"]["bgp_community"] }}</strong>
|
||||
<p>{{ info["bgp_community"]["content"] }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if enable_bgp_aspath == true %}
|
||||
{% if features["bgp_aspath"]["enable"] == true %}
|
||||
<div class="dropdown-item">
|
||||
<strong>BGP AS Path</strong>
|
||||
<p>{{ text_help_bgp_aspath }}</p>
|
||||
<strong>{{ branding["text"]["bgp_aspath"] }}</strong>
|
||||
<p>{{ info["bgp_aspath"]["content"] }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if enable_ping == true %}
|
||||
{% if features["ping"]["enable"] == true %}
|
||||
<div class="dropdown-item">
|
||||
<strong>Ping</strong>
|
||||
<p>{{ text_help_ping }}</p>
|
||||
<strong>{{ branding["text"]["ping"] }}</strong>
|
||||
<p>{{ info["ping"]["content"] }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if enable_traceroute == true %}
|
||||
{% if features["traceroute"]["enable"] == true %}
|
||||
<div class="dropdown-item">
|
||||
<strong>Traceroute</strong>
|
||||
<p>{{ text_help_traceroute }}</p>
|
||||
<strong>{{ branding["text"]["traceroute"] }}</strong>
|
||||
<p>{{ info["traceroute"]["content"] }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -178,40 +179,40 @@
|
|||
</div>
|
||||
<div class="control">
|
||||
<div class="select is-medium is-rounded">
|
||||
<select id="cmd">
|
||||
<select id="type">
|
||||
<option selected disabled>
|
||||
Query Type
|
||||
{{ branding["text"]["query_type"] }}
|
||||
</option>
|
||||
{% if enable_bgp_route == true %}
|
||||
<option name="cmd" id="cmd_bgp_route" value="bgp_route">
|
||||
BGP Route
|
||||
{% if features["bgp_route"]["enable"] == true %}
|
||||
<option name="type" id="type_bgp_route" value="bgp_route">
|
||||
{{ branding["text"]["bgp_route"] }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% if enable_bgp_community == true %}
|
||||
<option name="cmd" id="cmd_bgp_community" value="bgp_community">
|
||||
BGP Community
|
||||
{% if features["bgp_community"]["enable"] == true %}
|
||||
<option name="type" id="type_bgp_community" value="bgp_community">
|
||||
{{ branding["text"]["bgp_community"] }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% if enable_bgp_aspath == true %}
|
||||
<option name="cmd" id="cmd_bgp_aspath" value="bgp_aspath">
|
||||
BGP AS Path
|
||||
{% if features["bgp_aspath"]["enable"] == true %}
|
||||
<option name="type" id="type_bgp_aspath" value="bgp_aspath">
|
||||
{{ branding["text"]["bgp_aspath"] }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% if enable_ping == true %}
|
||||
<option name="cmd" id="cmd_ping" value="ping">
|
||||
Ping
|
||||
{% if features["ping"]["enable"] == true %}
|
||||
<option name="type" id="type_ping" value="ping">
|
||||
{{ branding["text"]["ping"] }}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% if enable_traceroute == true %}
|
||||
<option name="cmd" id="cmd_traceroute" value="traceroute">
|
||||
Traceroute
|
||||
{% if features["traceroute"]["enable"] == true %}
|
||||
<option name="type" id="type_traceroute" value="traceroute">
|
||||
{{ branding["text"]["traceroute"] }}
|
||||
</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button lg-btn-submit is-medium is-rounded" type="submit" name="cmd">
|
||||
<button class="button lg-btn-submit is-medium is-rounded" type="submit" name="type">
|
||||
<span class="icon">
|
||||
<i class="icofont-search-1"></i>
|
||||
</span>
|
||||
|
|
@ -220,7 +221,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-one-third" id="ipprefix_error">
|
||||
<div class="column is-one-third" id="target_error">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -229,7 +230,12 @@
|
|||
<section class="section">
|
||||
<div class="container is-fluid">
|
||||
<div class="box" id="resultsbox">
|
||||
<p class="title" id="results">{{ text_results }}</p>
|
||||
<a class="button is-pulled-right" id="btn-copy" data-clipboard-target="#output">
|
||||
<span class="icon is-small">
|
||||
<i id="copy-icon" class="icofont-ui-copy"></i>
|
||||
</span>
|
||||
</a>
|
||||
<p class="title" id="results">{{ branding["text"]["results"] }}</p>
|
||||
<p id="queryInfo">
|
||||
</p>
|
||||
<p id="progress">
|
||||
|
|
@ -239,19 +245,19 @@
|
|||
<br>
|
||||
<p class="query-output" id="output">
|
||||
</p>
|
||||
{% if text_cache|length > 1 %}
|
||||
{% if features["cache"]["show_text"] == true %}
|
||||
<hr>
|
||||
<p class="is-size-7">{{ text_cache }}</p>
|
||||
<p class="is-size-7">{{ features["cache"]["text"] }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% if enable_footer == true %}
|
||||
{% include "templates/footer.html" %}
|
||||
{% endif %}
|
||||
{% if enable_credit == true %}
|
||||
{% include "templates/credit.html" %}
|
||||
{% endif %}
|
||||
{% if branding["footer"]["enable"] == true %}
|
||||
{% include "templates/footer.html" %}
|
||||
{% endif %}
|
||||
{% if branding["credit"]["enable"] == true %}
|
||||
{% include "templates/credit.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
|
||||
|
|
|
|||
2
hyperglass/render/templates/info/.gitignore
vendored
Normal file
2
hyperglass/render/templates/info/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.DS_Store
|
||||
*.md
|
||||
6
hyperglass/render/templates/info/bgp_aspath.md.example
Normal file
6
hyperglass/render/templates/info/bgp_aspath.md.example
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
+++
|
||||
link = '<a href="#" onclick="bgpHelpASPath()">Supported BGP AS Path Expressions</a>'
|
||||
+++
|
||||
Performs BGP table lookup based on `AS_PATH` regular expression.
|
||||
|
||||
{{ info["bgp_aspath"]["link"] }}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
+++
|
||||
link = '<a href="#" onclick="bgpHelpCommunity()">{{ general["org_name"] }} BGP Communities</a>'
|
||||
+++
|
||||
Performs BGP table lookup based on [Extended](https://tools.ietf.org/html/rfc4360) or [Large](https://tools.ietf.org/html/rfc8195) community value.
|
||||
|
||||
{{ info["bgp_community"]["link"] }}
|
||||
3
hyperglass/render/templates/info/bgp_route.md.example
Normal file
3
hyperglass/render/templates/info/bgp_route.md.example
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
+++
|
||||
+++
|
||||
Performs BGP table lookup based on IPv4/IPv6 prefix.
|
||||
2
hyperglass/render/templates/info/details/.gitignore
vendored
Normal file
2
hyperglass/render/templates/info/details/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.DS_Store
|
||||
*.md
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
+++
|
||||
title = "Supported AS Path Patterns"
|
||||
+++
|
||||
{{ site_title }} accepts the following `AS_PATH` regular expression patterns:
|
||||
{{ branding.site_name }} accepts the following `AS_PATH` regular expression patterns:
|
||||
|
||||
| Expression | Match |
|
||||
| :----------------------- | ----------------------------------------------------: |
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
+++
|
||||
title = "BGP Communities"
|
||||
+++
|
||||
{{ site_title }} makes use of the following BGP communities:
|
||||
{{ branding.site_name }} makes use of the following BGP communities:
|
||||
|
||||
| Community | Description |
|
||||
| :-------- | :---------- |
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
+++
|
||||
+++
|
||||
By using {{ branding.site_name }}, you agree to be bound by the following terms of use: All queries executed on this page are logged for analysis and troubleshooting. Users are prohibited from automating queries, or attempting to process queries in bulk. This service is provided on a best effort basis, and {{ general.org_name }} makes no availability or performance warranties or guarantees whatsoever.
|
||||
3
hyperglass/render/templates/info/ping.md.example
Normal file
3
hyperglass/render/templates/info/ping.md.example
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
+++
|
||||
+++
|
||||
Sends 5 ICMP echo requests to the target.
|
||||
3
hyperglass/render/templates/info/traceroute.md.example
Normal file
3
hyperglass/render/templates/info/traceroute.md.example
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
+++
|
||||
+++
|
||||
Performs UDP Based traceroute to the target.<br>For information about how to interpret traceroute results, [click here](https://hyperglass.readthedocs.io/nanog_traceroute.pdf).
|
||||
7
hyperglass/static/js/clipboard.min.js
vendored
Executable file
7
hyperglass/static/js/clipboard.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
|
|
@ -1,9 +1,9 @@
|
|||
// Get the list of routers for the selected Network
|
||||
// Get the list of locations for the selected Network
|
||||
|
||||
var progress = ($('#progress'));
|
||||
var resultsbox = ($('#resultsbox'));
|
||||
var ipprefix_error = ($('#ipprefix_error'));
|
||||
var ipprefix_input = ($('#ipprefix'));
|
||||
var target_error = ($('#target_error'));
|
||||
var target_input = ($('#target'));
|
||||
adjustDropdowns();
|
||||
clearPage();
|
||||
|
||||
|
|
@ -14,6 +14,21 @@ dropdown.addEventListener('click', function(event) {
|
|||
dropdown.classList.toggle('is-active');
|
||||
});
|
||||
|
||||
var btn_copy = document.getElementById('btn-copy');
|
||||
var clipboard = new ClipboardJS(btn_copy);
|
||||
clipboard.on('success', function(e) {
|
||||
console.log(e);
|
||||
$('#btn-copy').addClass('is-success');
|
||||
$('#copy-icon').removeClass('icofont-ui-copy').addClass('icofont-check');
|
||||
setTimeout(function(){
|
||||
$('#btn-copy').removeClass('is-success');
|
||||
$('#copy-icon').removeClass('icofont-check').addClass('icofont-ui-copy');
|
||||
}, 1000)
|
||||
});
|
||||
clipboard.on('error', function(e) {
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
function bgpHelpASPath() {
|
||||
$("#help_bgp_aspath").addClass("is-active");
|
||||
}
|
||||
|
|
@ -40,30 +55,30 @@ function adjustDropdowns() {
|
|||
if (actual_width < 1024) {
|
||||
$('#lg-netlocdropdown').removeClass('has-addons').removeClass('has-addons-centered').addClass('is-grouped').addClass('is-grouped-centered').addClass('is-grouped-multiline');
|
||||
$('#network').css('width', actual_width * 0.85);
|
||||
$('#router').css('width', actual_width * 0.85);
|
||||
$('#location').css('width', actual_width * 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
function clearErrors() {
|
||||
progress.hide();
|
||||
ipprefix_error.hide();
|
||||
if (ipprefix_input.hasClass("is-warning")) {
|
||||
ipprefix_input.removeClass("is-warning");
|
||||
target_error.hide();
|
||||
if (target_input.hasClass("is-warning")) {
|
||||
target_input.removeClass("is-warning");
|
||||
};
|
||||
if (ipprefix_input.hasClass("is-danger")) {
|
||||
ipprefix_input.removeClass("is-danger");
|
||||
if (target_input.hasClass("is-danger")) {
|
||||
target_input.removeClass("is-danger");
|
||||
};
|
||||
}
|
||||
|
||||
function clearPage() {
|
||||
progress.hide();
|
||||
resultsbox.hide();
|
||||
ipprefix_error.hide();
|
||||
if (ipprefix_input.hasClass("is-warning")) {
|
||||
ipprefix_input.removeClass("is-warning");
|
||||
target_error.hide();
|
||||
if (target_input.hasClass("is-warning")) {
|
||||
target_input.removeClass("is-warning");
|
||||
};
|
||||
if (ipprefix_input.hasClass("is-danger")) {
|
||||
ipprefix_input.removeClass("is-danger");
|
||||
if (target_input.hasClass("is-danger")) {
|
||||
target_input.removeClass("is-danger");
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +90,7 @@ function prepResults() {
|
|||
$(document).ready(function() {
|
||||
var defaultasn = $("#network").val();
|
||||
$.ajax({
|
||||
url: `/routers/${defaultasn}`,
|
||||
url: `/locations/${defaultasn}`,
|
||||
context: document.body,
|
||||
type: 'get',
|
||||
success: function(data) {
|
||||
|
|
@ -91,9 +106,9 @@ $(document).ready(function() {
|
|||
|
||||
$('#network').on('change', () => {
|
||||
var asn = $("select[id=network").val()
|
||||
$('#router').children(":not(#text_location)").remove();
|
||||
$('#location').children(":not(#text_location)").remove();
|
||||
$.ajax({
|
||||
url: `/routers/${asn}`,
|
||||
url: `/locations/${asn}`,
|
||||
type: 'get',
|
||||
success: function(data) {
|
||||
cleanPage();
|
||||
|
|
@ -105,9 +120,9 @@ $('#network').on('change', () => {
|
|||
})
|
||||
})
|
||||
|
||||
function updateRouters(routers) {
|
||||
routers.forEach(function(r) {
|
||||
$('#router').append($("<option>").attr('value', r.location).text(r.display_name))
|
||||
function updateRouters(locations) {
|
||||
locations.forEach(function(r) {
|
||||
$('#location').append($("<option>").attr('value', r.location).text(r.display_name))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -118,14 +133,12 @@ $('#lgForm').on('submit', function() {
|
|||
|
||||
function submitForm() {
|
||||
clearErrors();
|
||||
// progress.hide();
|
||||
// ipprefix_error.hide();
|
||||
var cmd = $('#cmd option:selected').val();
|
||||
var cmdtitle = $('#cmd option:selected').text();
|
||||
var type = $('#type option:selected').val();
|
||||
var type_title = $('#type option:selected').text();
|
||||
var network = $('#network option:selected').val();
|
||||
var router = $('#router option:selected').val();
|
||||
var routername = $('#router option:selected').text();
|
||||
var ipprefix = $('#ipprefix').val();
|
||||
var location = $('#location option:selected').val();
|
||||
var location_name = $('#location option:selected').text();
|
||||
var target = $('#target').val();
|
||||
|
||||
$('#output').text("");
|
||||
$('#queryInfo').text("");
|
||||
|
|
@ -133,14 +146,14 @@ function submitForm() {
|
|||
<div class="field is-grouped is-grouped-multiline">
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag lg-tag-loctitle">AS${network}</span>
|
||||
<span class="tag lg-tag-loc">${routername}</span>
|
||||
<span class="tag lg-tag-loc-title">AS${network}</span>
|
||||
<span class="tag lg-tag-loc">${location_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag lg-tag-cmdtitle">${cmdtitle}</span>
|
||||
<span class="tag lg-tag-cmd">${ipprefix}</span>
|
||||
<span class="tag lg-tag-type-title">${type_title}</span>
|
||||
<span class="tag lg-tag-type">${target}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -150,9 +163,9 @@ function submitForm() {
|
|||
url: `/lg`,
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
router: router,
|
||||
cmd: cmd,
|
||||
ipprefix: ipprefix
|
||||
location: location,
|
||||
type: type,
|
||||
target: target
|
||||
}),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
context: document.body,
|
||||
|
|
@ -164,9 +177,9 @@ function submitForm() {
|
|||
},
|
||||
405: function(response, code) {
|
||||
clearPage();
|
||||
ipprefix_error.show()
|
||||
ipprefix_input.addClass('is-warning');
|
||||
ipprefix_error.html(`
|
||||
target_error.show()
|
||||
target_input.addClass('is-warning');
|
||||
target_error.html(`
|
||||
<br>
|
||||
<article class="message is-warning is-small" style="display: block;">
|
||||
<div class="message-header" style="display: block;">
|
||||
|
|
@ -180,9 +193,9 @@ function submitForm() {
|
|||
},
|
||||
415: function(response, code) {
|
||||
clearPage();
|
||||
ipprefix_error.show()
|
||||
ipprefix_input.addClass('is-danger');
|
||||
ipprefix_error.html(`
|
||||
target_error.show()
|
||||
target_input.addClass('is-danger');
|
||||
target_error.html(`
|
||||
<br>
|
||||
<article class="message is-danger is-small" style="display: block;">
|
||||
<div class="message-header" style="display: block;">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
// Custom Elements
|
||||
|
||||
.select:not(.is-multiple):not(.is-loading)::after
|
||||
border-color: $grey-light
|
||||
|
||||
html, body
|
||||
height: 100%
|
||||
|
||||
|
|
@ -52,22 +55,22 @@ a.navbar-item:hover
|
|||
border-color: transparent
|
||||
color: findColorInvert($lg-btn-submit)
|
||||
|
||||
.tag.lg-tag-loctitle
|
||||
background-color: $lg-tag-loctitle
|
||||
color: findColorInvert($lg-tag-loctitle)
|
||||
.tag.lg-tag-loc-title
|
||||
background-color: $lg-tag-loc_title
|
||||
color: findColorInvert($lg-tag-loc_title)
|
||||
font-family: $family-monospace
|
||||
|
||||
.tag.lg-tag-loc
|
||||
background-color: $lg-tag-loc
|
||||
color: findColorInvert($lg-tag-loc)
|
||||
|
||||
.tag.lg-tag-cmdtitle
|
||||
background-color: $lg-tag-cmdtitle
|
||||
color: findColorInvert($lg-tag-cmdtitle)
|
||||
.tag.lg-tag-type-title
|
||||
background-color: $lg-tag-type_title
|
||||
color: findColorInvert($lg-tag-type_title)
|
||||
|
||||
.tag.lg-tag-cmd
|
||||
background-color: $lg-tag-cmd
|
||||
color: findColorInvert($lg-tag-cmd)
|
||||
.tag.lg-tag-type
|
||||
background-color: $lg-tag-type
|
||||
color: findColorInvert($lg-tag-type)
|
||||
font-family: $family-monospace
|
||||
|
||||
.progress.lg-progressbar:indeterminate
|
||||
|
|
@ -91,16 +94,16 @@ a.navbar-item:hover
|
|||
width: 20rem
|
||||
|
||||
.icon.lg-icon-help
|
||||
color: $lg-btn-submit
|
||||
color: $grey-light
|
||||
|
||||
#ipprefix::-webkit-input-placeholder
|
||||
#target::-webkit-input-placeholder
|
||||
font-family: $family-sans-serif
|
||||
|
||||
#ipprefix:-ms-input-placeholder
|
||||
#target:-ms-input-placeholder
|
||||
font-family: $family-sans-serif
|
||||
|
||||
#ipprefix:-moz-placeholder
|
||||
#target:-moz-placeholder
|
||||
font-family: $family-sans-serif
|
||||
|
||||
#ipprefix::-moz-placeholder
|
||||
#target::-moz-placeholder
|
||||
font-family: $family-sans-serif
|
||||
|
|
|
|||
468
manage.py
468
manage.py
|
|
@ -12,22 +12,425 @@ import string
|
|||
|
||||
# Module Imports
|
||||
import click
|
||||
import json
|
||||
from passlib.hash import pbkdf2_sha256
|
||||
import requests
|
||||
|
||||
# Project Imports
|
||||
from hyperglass import hyperglass
|
||||
from hyperglass import render
|
||||
|
||||
# Initialize shutil copy function
|
||||
cp = shutil.copyfile
|
||||
|
||||
|
||||
def construct_test(test_query, location, test_target):
|
||||
"""Constructs JSON POST data for test_hyperglass function"""
|
||||
constructed_query = json.dumps(
|
||||
{"type": test_query, "location": location, "target": test_target}
|
||||
)
|
||||
return constructed_query
|
||||
|
||||
|
||||
@click.group()
|
||||
def hg():
|
||||
pass
|
||||
|
||||
|
||||
@hg.command()
|
||||
@hg.command("pre-check", help="Check hyperglass config & readiness")
|
||||
def pre_check():
|
||||
if sys.version_info < (3, 7):
|
||||
click.secho(
|
||||
f"Hyperglass requires Python 3.7 or higher. Curren version: Python {sys.version.split()[0]}",
|
||||
fg="red",
|
||||
bold=True,
|
||||
)
|
||||
if sys.version_info >= (3, 7):
|
||||
click.secho(
|
||||
f"✓ Python Version Check passed (Current version: Python {sys.version.split()[0]})",
|
||||
fg="green",
|
||||
bold=True,
|
||||
)
|
||||
try:
|
||||
from hyperglass import configuration
|
||||
|
||||
config = configuration.params()
|
||||
status = True
|
||||
while status:
|
||||
if config["general"]["primary_asn"] == "65000" or "":
|
||||
status = False
|
||||
reason = f'Primary ASN is not defined (Current: "{config["general"]["primary_asn"]}")'
|
||||
remediation = f"""
|
||||
To define the Primary ASN paramter, modify your `configuration.toml` and add the following \
|
||||
configuration:\n
|
||||
[general]
|
||||
primary_asn = "<Your Primary AS Number>"
|
||||
\nIf you do not define a Primary ASN, \"{config["general"]["primary_asn"]}\" will be used."""
|
||||
break
|
||||
if config["general"]["org_name"] == "The Company" or "":
|
||||
status = False
|
||||
reason = f'Org Name is not defined (Current: "{config["general"]["org_name"]}")'
|
||||
remediation = f"""
|
||||
To define an Org Name paramter, modify your `configuration.toml` and add the following \
|
||||
configuration:\n
|
||||
[general]
|
||||
org_name = "<Your Org Name>"
|
||||
\nIf you do not define an Org Name, \"{config["general"]["org_name"]}\" will be displayed."""
|
||||
break
|
||||
click.secho(reason, fg="red", bold=True)
|
||||
click.secho(remediation, fg="blue")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}", fg="red")
|
||||
|
||||
|
||||
@hg.command("test", help="Full test of all backend features")
|
||||
@click.option("-l", "--location", type=str, required=True, help="Location to query")
|
||||
@click.option(
|
||||
"-4",
|
||||
"--target-ipv4",
|
||||
"target_ipv4",
|
||||
type=str,
|
||||
default="1.1.1.0/24",
|
||||
required=False,
|
||||
show_default=True,
|
||||
help="IPv4 Target Address",
|
||||
)
|
||||
@click.option(
|
||||
"-6",
|
||||
"--target-ipv6",
|
||||
"target_ipv6",
|
||||
type=str,
|
||||
default="2606:4700:4700::/48",
|
||||
required=False,
|
||||
show_default=True,
|
||||
help="IPv6 Target Address",
|
||||
)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--community",
|
||||
"test_community",
|
||||
type=str,
|
||||
required=False,
|
||||
show_default=True,
|
||||
default="65000:1",
|
||||
help="BGP Community",
|
||||
)
|
||||
@click.option(
|
||||
"-a",
|
||||
"--aspath",
|
||||
"test_aspath",
|
||||
type=str,
|
||||
required=False,
|
||||
show_default=True,
|
||||
default="^65001$",
|
||||
help="BGP AS Path",
|
||||
)
|
||||
@click.option(
|
||||
"-r",
|
||||
"--requires-ipv6-cidr",
|
||||
"requires_ipv6_cidr",
|
||||
type=str,
|
||||
required=False,
|
||||
help="Location for testing IPv6 CIDR requirement",
|
||||
)
|
||||
@click.option(
|
||||
"-b",
|
||||
"--blacklist",
|
||||
"test_blacklist",
|
||||
type=str,
|
||||
default="100.64.0.1",
|
||||
required=False,
|
||||
show_default=True,
|
||||
help="Address to use for blacklist check",
|
||||
)
|
||||
@click.option(
|
||||
"-h",
|
||||
"--host",
|
||||
"test_host",
|
||||
type=str,
|
||||
default="localhost",
|
||||
required=False,
|
||||
show_default=True,
|
||||
help="Name or IP address of hyperglass server",
|
||||
)
|
||||
@click.option(
|
||||
"-p",
|
||||
"--port",
|
||||
"test_port",
|
||||
type=int,
|
||||
default=5000,
|
||||
required=False,
|
||||
show_default=True,
|
||||
help="Port hyperglass is running on",
|
||||
)
|
||||
def test_hyperglass(
|
||||
location,
|
||||
target_ipv4,
|
||||
target_ipv6,
|
||||
requires_ipv6_cidr,
|
||||
test_blacklist,
|
||||
test_community,
|
||||
test_aspath,
|
||||
test_host,
|
||||
test_port,
|
||||
):
|
||||
"""Fully tests hyperglass backend by making use of requests library to mimic the JS Ajax POST \
|
||||
performed by the front end."""
|
||||
test_target = None
|
||||
invalid_ip = "this_ain't_an_ip!"
|
||||
invalid_community = "192.0.2.1"
|
||||
invalid_aspath = ".*"
|
||||
ipv4_host = "1.1.1.1"
|
||||
ipv4_cidr = "1.1.1.0/24"
|
||||
ipv6_host = "2606:4700:4700::1111"
|
||||
ipv6_cidr = "2606:4700:4700::/48"
|
||||
test_headers = {"Content-Type": "application/json"}
|
||||
test_endpoint = f"http://{test_host}:{test_port}/lg"
|
||||
# No Query Type Test
|
||||
try:
|
||||
click.secho("Starting No Query Type test...", fg="black")
|
||||
test_query = construct_test("", location, target_ipv4)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ No Query Type test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ No Query Type test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# No Location Test
|
||||
try:
|
||||
click.secho("Starting No Location test...", fg="black")
|
||||
test_query = construct_test("bgp_route", "", target_ipv6)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ No Location test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ No Location test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# No Location Test
|
||||
try:
|
||||
click.secho("Starting No Target test...", fg="black")
|
||||
test_query = construct_test("bgp_route", location, "")
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ No Target test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ No Target test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Valid BGP IPv4 Route Test
|
||||
try:
|
||||
click.secho("Starting Valid BGP IPv4 Route test...", fg="black")
|
||||
test_query = construct_test("bgp_route", location, target_ipv4)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code == 200:
|
||||
click.secho("✓ Valid BGP IPv4 Route test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code == 200:
|
||||
click.secho("✗ Valid BGP IPv4 Route test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Valid BGP IPv6 Route Test
|
||||
try:
|
||||
click.secho("Starting Valid BGP IPv6 Route test...", fg="black")
|
||||
test_query = construct_test("bgp_route", location, target_ipv6)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code == 200:
|
||||
click.secho("✓ Valid BGP IPv6 Route test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code == 200:
|
||||
click.secho("✗ Valid BGP IPv6 Route test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Invalid BGP Route Test
|
||||
try:
|
||||
click.secho("Starting Invalid BGP IPv4 Route test...", fg="black")
|
||||
test_query = construct_test("bgp_route", location, invalid_ip)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ Invalid BGP IPv4 Route test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ Invalid BGP IPv4 Route test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Requires IPv6 CIDR Test
|
||||
if requires_ipv6_cidr:
|
||||
try:
|
||||
click.secho("Starting Requires IPv6 CIDR test...", fg="black")
|
||||
test_query = construct_test("bgp_route", requires_ipv6_cidr, ipv6_host)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ Requires IPv6 CIDR test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ Requires IPv6 CIDR test failed", fg="red", bold=True)
|
||||
click.secho(
|
||||
f"Status Code: {hg_response.status_code}", fg="red", bold=True
|
||||
)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Valid BGP Community Test
|
||||
try:
|
||||
click.secho("Starting Valid BGP Community test...", fg="black")
|
||||
test_query = construct_test("bgp_community", location, test_community)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code == 200:
|
||||
click.secho("✓ Valid BGP Community test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code == 200:
|
||||
click.secho("✗ Valid BGP Community test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Invalid BGP Community Test
|
||||
try:
|
||||
click.secho("Starting Invalid BGP Community test...", fg="black")
|
||||
test_query = construct_test("bgp_community", location, target_ipv4)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ Invalid BGP Community test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ Invalid BGP Community test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Valid BGP AS_PATH Test
|
||||
try:
|
||||
click.secho("Starting Valid BGP AS_PATH test...", fg="black")
|
||||
test_query = construct_test("bgp_aspath", location, test_aspath)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code == 200:
|
||||
click.secho("✓ Valid BGP AS_PATH test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code == 200:
|
||||
click.secho("✗ Valid BGP AS_PATH test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Invalid BGP AS_PATH Test
|
||||
try:
|
||||
click.secho("Starting invalid BGP AS_PATH test...", fg="black")
|
||||
test_query = construct_test("bgp_aspath", location, invalid_aspath)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ Invalid BGP AS_PATH test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ Invalid BGP AS_PATH test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Valid IPv4 Ping Test
|
||||
try:
|
||||
click.secho("Starting Valid IPv4 Ping test...", fg="black")
|
||||
test_query = construct_test("ping", location, ipv4_host)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code == 200:
|
||||
click.secho("✓ Valid IPv4 Ping test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code == 200:
|
||||
click.secho("✗ Valid IPv4 Ping test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Valid IPv6 Ping Test
|
||||
try:
|
||||
click.secho("Starting Valid IPv6 Ping test...", fg="black")
|
||||
test_query = construct_test("ping", location, ipv6_host)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code == 200:
|
||||
click.secho("✓ Valid IPv6 Ping test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code == 200:
|
||||
click.secho("✗ Valid IPv6 Ping test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Invalid IPv4 Ping Test
|
||||
try:
|
||||
click.secho("Starting Invalid IPv4 Ping test...", fg="black")
|
||||
test_query = construct_test("ping", location, ipv4_cidr)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ Invalid IPv4 Ping test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ Invalid IPv4 Ping test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Invalid IPv6 Ping Test
|
||||
try:
|
||||
click.secho("Starting Invalid IPv6 Ping test...", fg="black")
|
||||
test_query = construct_test("ping", location, ipv6_cidr)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ Invalid IPv6 Ping test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ Invalid IPv6 Ping test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
# Blacklist Test
|
||||
try:
|
||||
click.secho("Starting Blacklist test...", fg="black")
|
||||
test_query = construct_test("bgp_route", location, test_blacklist)
|
||||
hg_response = requests.post(
|
||||
test_endpoint, headers=test_headers, data=test_query
|
||||
)
|
||||
if hg_response.status_code in range(400, 500):
|
||||
click.secho("✓ Blacklist test passed", fg="green", bold=True)
|
||||
if not hg_response.status_code in range(400, 500):
|
||||
click.secho("✗ Blacklist test failed", fg="red", bold=True)
|
||||
click.secho(f"Status Code: {hg_response.status_code}", fg="red", bold=True)
|
||||
click.secho(hg_response.text, fg="red")
|
||||
except Exception as e:
|
||||
click.secho(f"Exception occurred:\n{e}")
|
||||
|
||||
|
||||
@hg.command("clear-cache", help="Clear Flask cache")
|
||||
def clearcache():
|
||||
"""Clears the Flask-Caching cache"""
|
||||
try:
|
||||
|
|
@ -38,8 +441,11 @@ def clearcache():
|
|||
raise
|
||||
|
||||
|
||||
@hg.command()
|
||||
def generatekey(string_length=16):
|
||||
@hg.command("generate-key", help="Generate API key & hash")
|
||||
@click.option(
|
||||
"-l", "--length", "string_length", type=int, default=16, show_default=True
|
||||
)
|
||||
def generatekey(string_length):
|
||||
"""Generates 16 character API Key for hyperglass-frr API, and a corresponding PBKDF2 SHA256 Hash"""
|
||||
ld = string.ascii_letters + string.digits
|
||||
api_key = "".join(random.choice(ld) for i in range(string_length))
|
||||
|
|
@ -55,42 +461,37 @@ Use this hash as the password for the device using the API module. For example,
|
|||
)
|
||||
|
||||
|
||||
@hg.command()
|
||||
def devserver():
|
||||
@hg.command("dev-server", help="Start Flask development server")
|
||||
# @click.option("--debug", type=bool, default="False", help="Enable Flask Debug Mode")
|
||||
@click.option("--host", type=str, default="0.0.0.0", help="Listening IP")
|
||||
@click.option("--port", type=int, default=5000, help="TCP Port")
|
||||
def flask_dev_server(host, port):
|
||||
"""Starts Flask development server for testing without WSGI/Reverse Proxy"""
|
||||
try:
|
||||
hyperglass.render.css()
|
||||
# hyperglass.metrics.start_http_server(9100)
|
||||
hyperglass.app.run(host="0.0.0.0", debug=True, port=5000)
|
||||
click.secho("✓ Started test server.", fg="green", bold=True)
|
||||
from hyperglass import hyperglass
|
||||
from hyperglass import configuration
|
||||
|
||||
debug_state = configuration.debug_state()
|
||||
render.css()
|
||||
click.secho(f"✓ Starting Flask development server", fg="green", bold=True)
|
||||
hyperglass.app.run(host=host, debug=debug_state, port=port)
|
||||
except:
|
||||
click.secho("✗ Failed to start test server.", fg="red", bold=True)
|
||||
raise
|
||||
|
||||
|
||||
@hg.command()
|
||||
def render():
|
||||
@hg.command("compile-sass", help="Compile Sass templates to CSS")
|
||||
def compile_sass():
|
||||
"""Renders Jinja2 and Sass templates to HTML & CSS files"""
|
||||
try:
|
||||
hyperglass.render.css()
|
||||
render.css()
|
||||
click.secho("✓ Successfully rendered CSS templates.", fg="green", bold=True)
|
||||
except:
|
||||
click.secho("✗ Failed to render CSS templates.", fg="red", bold=True)
|
||||
raise
|
||||
|
||||
|
||||
@hg.command()
|
||||
def content():
|
||||
"""Renders Jinja2 and Sass templates to HTML & CSS files"""
|
||||
try:
|
||||
hyperglass.render.markdown()
|
||||
click.secho("✓ Successfully rendered content templates.", fg="green", bold=True)
|
||||
except:
|
||||
click.secho("✗ Failed to render content templates.", fg="red", bold=True)
|
||||
raise
|
||||
|
||||
|
||||
@hg.command()
|
||||
@hg.command("migrate-configs", help="Copy TOML examples to usable config files")
|
||||
def migrateconfig():
|
||||
"""Copies example configuration files to usable config files"""
|
||||
try:
|
||||
|
|
@ -118,7 +519,7 @@ def migrateconfig():
|
|||
raise
|
||||
|
||||
|
||||
@hg.command()
|
||||
@hg.command("migrate-gunicorn", help="Copy Gunicorn example to usable config file")
|
||||
def migrategunicorn():
|
||||
"""Copies example Gunicorn config file to a usable config"""
|
||||
try:
|
||||
|
|
@ -148,9 +549,11 @@ def migrategunicorn():
|
|||
raise
|
||||
|
||||
|
||||
@hg.command()
|
||||
@click.option("--dir", default="/etc/systemd/system")
|
||||
def migratesystemd(dir):
|
||||
@hg.command("migrate-systemd", help="Copy Systemd example to OS")
|
||||
@click.option(
|
||||
"-d", "--directory", default="/etc/systemd/system", help="Destination Directory"
|
||||
)
|
||||
def migratesystemd(directory):
|
||||
"""Copies example systemd service file to /etc/systemd/system/"""
|
||||
try:
|
||||
click.secho("Migrating example systemd service...", fg="cyan")
|
||||
|
|
@ -158,7 +561,7 @@ def migratesystemd(dir):
|
|||
ex_file_base = "hyperglass.service.example"
|
||||
ex_file = os.path.join(hyperglass_root, ex_file_base)
|
||||
basefile, extension = os.path.splitext(ex_file_base)
|
||||
newfile = os.path.join(dir, basefile)
|
||||
newfile = os.path.join(directory, basefile)
|
||||
if os.path.exists(newfile):
|
||||
click.secho(f"{newfile} already exists", fg="blue")
|
||||
else:
|
||||
|
|
@ -178,7 +581,10 @@ def migratesystemd(dir):
|
|||
raise
|
||||
|
||||
|
||||
@hg.command()
|
||||
@hg.command(
|
||||
"update-permissions",
|
||||
help="Fix ownership & permissions of hyperglass project directory",
|
||||
)
|
||||
@click.option("--user", default="www-data")
|
||||
@click.option("--group", default="www-data")
|
||||
def fixpermissions(user, group):
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ nav:
|
|||
- 'Reverse Proxy & SSL': 'installation/reverseproxy.md'
|
||||
- Configuration:
|
||||
- 'Configuring Hyperglass': 'configuration/index.md'
|
||||
- 'General Parameters': 'configuration/general.md'
|
||||
- 'Branding': 'configuration/branding.md'
|
||||
- 'Devices': 'configuration/devices.md'
|
||||
- 'Branding': 'configuration/branding.md'
|
||||
- 'Features': 'configuration/features.md'
|
||||
- Caching: 'caching.md'
|
||||
- Rate Limiting: 'ratelimiting.md'
|
||||
- Monitoring: 'monitoring.md'
|
||||
- Development:
|
||||
- 'Introduction': 'development/index.md'
|
||||
- Extras:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue