initial
65
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
test.py
|
||||
.DS_Store
|
||||
*.sass-cache
|
||||
hyperglass/config/*.toml
|
||||
hyperglass/.flask_cache/
|
||||
#
|
||||
# Github Default from https://github.com/martinohanlon/flightlight/issues/1
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
assets/
|
||||
32
LICENSE
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
The Clear BSD License
|
||||
|
||||
Copyright (c) 2019 Matthew Love
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted (subject to the limitations in the disclaimer
|
||||
below) provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
||||
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
46
README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<img src="hyperglass/static/images/hyperglass-dark.png" width=300></img>
|
||||
|
||||
**Hyperglass** is a network looking glass application. A looking glass is typically implemented by network service providers as a way of providing customers, peers, and partners with a way to easily view elements of, or run tests from the provider's network.
|
||||
|
||||
<br>
|
||||
|
||||

|
||||

|
||||

|
||||
[](https://github.com/ambv/black)
|
||||
|
||||
|
||||
## Features
|
||||
- BGP Route, BGP Community, BGP AS_PATH, Ping, Traceroute
|
||||
- Full IPv6 support
|
||||
- [Netmiko](https://github.com/ktbyers/netmiko)-based connection handling
|
||||
- Customizable commands for each function by vendor
|
||||
- Clean, google-esq GUI based on the [Bumla](https://bulma.io) framework
|
||||
- Customizable colors, logo, web fonts, error messages, UI text
|
||||
- TOML-based config file for all customizable parameters (no databases!)
|
||||
- Configurable IP/Prefix "blacklist" to prevent lookup of internal/private prefixes
|
||||
- Configurable rate limiting, powered by [Flask-Limiter](https://github.com/alisaifee/flask-limiter)
|
||||
- Query response caching with configurable cache timeout, powered by [Flask-Caching](https://github.com/sh4nks/flask-caching)
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation can be found [here](https://hyperglass.readthedocs.io), or in the `docs/` directory.
|
||||
|
||||
## Preview
|
||||
|
||||
## Platform Support
|
||||
Theoretically, any vendor supported by Netmiko can be supported by Hyperglass. However, I am currently listing platforms I have personally tested and verified full functionality with:
|
||||
|
||||
### Routers
|
||||
- Cisco IOS-XR: `cisco_xr`
|
||||
- Cisco Classic IOS/IOS-XE: `cisco_ios`
|
||||
- Juniper JunOS: `junos`
|
||||
|
||||
### Proxies
|
||||
- Linux: `linux_ssh`
|
||||
|
||||
## Acknowledgements
|
||||
- This project originally started as a fork of vraulsan's [looking-glass](https://github.com/vraulsan/looking-glass) project. The guts of the Flask components still remain from that project, but almost everything else has been rewritten. Nevertheless, the inspiration for building hyperglass came from here.
|
||||
|
||||
## License
|
||||
[Clear BSD License](https://github.com/checktheroads/hyperglass/master/LICENSE)
|
||||
BIN
docs/blacklist_error.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
7
docs/caching.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Hyperglass supports caching the application's responses for a configurable period of time to reduce the number of lookups passed back to routers for repetitive/common lookups. By default, all application responses (including error messages), are cached for 2 minutes in the local file system.
|
||||
|
||||
Hyperglass uses [Flask-Caching](https://github.com/sh4nks/flask-caching), which is able to use a wide variety of cache storage backends (Filesystem, Redis, Memcached, etc.). By default, Hyperglass uses the Filesystem method, and stores the cached data in `hyperglass/hyperglass/.flask_cache`.
|
||||
|
||||
The cache list little more than a key value store. Hyperglass uses a stringified combination of router/location name, command used, and query type as a key, and the output as a value. If a lookup matching the exact key is found, the cached value will be returned. If not, a standard lookup is performed (and that data is subsequently cached).
|
||||
|
||||
# Insert Video Here
|
||||
18
docs/configuration.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Configuration
|
||||
|
||||
Hyperglass configuration files are stored `hyperglass/hyperglass/config`, in [TOML](https://github.com/toml-lang/toml) format.
|
||||
|
||||
Example configuration files are provided and end in `.example`. All example configuration files should be copied to their `.toml` name & extension. For example:
|
||||
|
||||
```console
|
||||
$ cd hyperglass/hyperglass/config
|
||||
$
|
||||
$ cp blacklist.toml.example blacklist.toml
|
||||
$ cp commands.toml.example commands.toml
|
||||
$ cp config.toml.example config.toml
|
||||
$ cp devices.toml.example devices.toml
|
||||
```
|
||||
|
||||
## `requires_ipv6_cidr.toml`
|
||||
|
||||
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).
|
||||
16
docs/configuration/authentication.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Authentication parameters are stored in the `devices.toml` file, at `hyperglass/hyperglass/config/devices.toml`. The array of tables simply stores the username and password for a device. SSH Key authentication is not yet supported.
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[credential.'default']
|
||||
username = "hyperglass"
|
||||
password = "secret_password"
|
||||
|
||||
[credential.'other_credential']
|
||||
username = "other_username"
|
||||
password = "other_secret_password"
|
||||
```
|
||||
|
||||
!!! warning "Security Warning"
|
||||
These values are stored in plain text. Make sure the accounts are restricted and that the configuration file is stored in a secure location.
|
||||
25
docs/configuration/blacklist.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Blacklisted querys are defined in `hyperglass/hyperglass/config/blacklist.toml`.
|
||||
|
||||
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 want to prevent users from looking up 198.18.0.0/15 or any contained host or prefix, you can add it to the blacklist:
|
||||
|
||||
```toml
|
||||
blacklist = [
|
||||
198.18.0.0/15
|
||||
]
|
||||
```
|
||||
|
||||
If you have multiple hosts/subnets you wish to blacklist, you can do so by adding a comma `,` after each entry (except the last):
|
||||
|
||||
```toml
|
||||
blacklist = [
|
||||
'198.18.0.0/15',
|
||||
'10.0.0.0/8',
|
||||
'192.168.0.0/16',
|
||||
'2001:db8::/32'
|
||||
'172.16.0.0/12'
|
||||
]
|
||||
```
|
||||
|
||||
When users attempt to query a matching host/prefix, they will receive the following error message by default:
|
||||
|
||||
<img src="/blacklist_error.png"></img>
|
||||
306
docs/configuration/branding.md
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
<style>
|
||||
.bd-color {
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,.1), inset 0 0 0 1px rgba(0,0,0,.1);
|
||||
display: inline-block;
|
||||
float: left;
|
||||
height: 16px;
|
||||
margin-right: 2px;
|
||||
width: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
From `hyperglass/hyperglass/config/config.toml`:
|
||||
|
||||
### site_title
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------- |
|
||||
| String | `"hyperglass"` |
|
||||
|
||||
HTML `<title>` element that is shown in a browser's title bar.
|
||||
|
||||
### title_mode
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"none"` |
|
||||
|
||||
Controls the title section on the main page.
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### `"none"`
|
||||
|
||||
Hides Title and Subtitle text, displays logo defined in [logo_path](#logo_path).
|
||||
|
||||
##### `"both"`
|
||||
|
||||
Displays both Title and Subtitle text defined in [title](#title) and [subtitle](#subtitle) parameters.
|
||||
|
||||
##### `"hide_subtitle"`
|
||||
|
||||
Displays only the Title text defined in the [title](#title) parameter.
|
||||
|
||||
### title
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------- |
|
||||
| String | `"hyperglass"` |
|
||||
|
||||
### subtitle
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | -------------------- |
|
||||
| String | `"AS" + primary_asn` |
|
||||
|
||||
See [primary_asn](#primary_asn) parameter.
|
||||
|
||||
### enable_footer
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
|
||||
Enables or disables entire footer element, which contains text defined in `hyperglass/hyperglass/templates/footer.md`.
|
||||
|
||||
### enable_credit
|
||||
|
||||
| Type | Default Value |
|
||||
| ------- | ------------- |
|
||||
| Boolean | `True` |
|
||||
|
||||
Enables or disables hoverable icon on the left side of the footer, which links to the hyperglass repo.
|
||||
|
||||
### color_btn_submit
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#40798c"` | <span class="bd-color" style="background-color: #40798c;"></span> |
|
||||
|
||||
Sets color of the submit button.
|
||||
|
||||
### color_tag_loctitle
|
||||
|
||||
| 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.
|
||||
|
||||
### color_tag_cmdtitle
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#330036"` | <span class="bd-color" style="background-color: #330036;"></span> |
|
||||
|
||||
Sets color of the title portion of the command tag which appears at the top of the results box on the right side.
|
||||
|
||||
### color_tag_cmd
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#ff5e5b"` | <span class="bd-color" style="background-color: #ff5e5b;"></span> |
|
||||
|
||||
Sets color of the command name portion of the command tag which appears at the top of the results box on the right side.
|
||||
|
||||
### color_tag_loc
|
||||
|
||||
| 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.
|
||||
|
||||
### color_hero
|
||||
|
||||
| Type | Default Value | Preview |
|
||||
| ------ | ------------- | ----------------------------------------------------------------- |
|
||||
| String | `"#fbfffe"` | <span class="bd-color" style="background-color: #fbfffe;"></span> |
|
||||
|
||||
Sets the background color of the main page. The main page is a Bulma [fullheight hero class](https://bulma.io/documentation/layout/hero/) layout. This parameter will set the color of the entire hero `<section>` class, including navbar, head, body, and footer subclasses.
|
||||
|
||||
### color_progressbar
|
||||
|
||||
| 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.
|
||||
|
||||
### logo_path
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------------------- |
|
||||
| String | `"static/images/hyperglass-dark.png"` |
|
||||
|
||||
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
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"384"` |
|
||||
|
||||
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.
|
||||
|
||||
### placeholder_prefix
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------------------- |
|
||||
| String | `"Prefix, IP, Community, or AS_PATH"` |
|
||||
|
||||
Sets the placeholder text that appears in the main search box.
|
||||
|
||||
### show_peeringdb
|
||||
|
||||
| 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.
|
||||
|
||||
### text_results
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"Results"` |
|
||||
|
||||
Sets the header text of the results box.
|
||||
|
||||
### text_location
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| String | `"Location"` |
|
||||
|
||||
Sets the placeholder text of the location selector.
|
||||
|
||||
### text_cache
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------------------------------------- |
|
||||
| String | `"Results will be cached for {cache_timeout} minutes."` |
|
||||
|
||||
Sets the text at the bottom of the results box that states the cache timeout. `{cache_timeout}` will be formatted with the value of [cache_timeout](/configuration/general/#cache_timeout).
|
||||
|
||||
### text_limiter_title
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------- |
|
||||
| String | `"Limit Reached"` |
|
||||
|
||||
Sets the title 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.
|
||||
|
||||
### text_limiter_subtitle
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------------------------------------------------------------------- |
|
||||
| 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_415_title
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ----------------- |
|
||||
| String | `"Error"` |
|
||||
|
||||
Sets the title text for the full general error page.
|
||||
|
||||
### text_415_subtitle
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------------------- |
|
||||
| String | `"Something went wrong."` |
|
||||
|
||||
Sets the subtitle text for the full general error page.
|
||||
|
||||
### text_415_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.
|
||||
|
||||
### 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.
|
||||
83
docs/configuration/commands.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
Commands are defined in `hyperglass/hyperglass/config/commands.toml`. Formatted as a nested array of tables, each table defines the commands that will be used to execute the queries on the routers.
|
||||
|
||||
Each table contains three nested tables:
|
||||
|
||||
##### dual
|
||||
|
||||
Commands that are IP protocol agnostic:
|
||||
|
||||
- `bgp_community`
|
||||
- `bgp_aspath`
|
||||
|
||||
##### ipv4
|
||||
|
||||
Commands that are IPv4-specific:
|
||||
|
||||
- `bgp_route`
|
||||
- `ping`
|
||||
- `traceroute`
|
||||
|
||||
##### ipv6
|
||||
|
||||
Commands that are IPv6-specific:
|
||||
|
||||
- `bgp_route`
|
||||
- `ping`
|
||||
- `traceroute`
|
||||
|
||||
#### Default Configuration
|
||||
|
||||
```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}"
|
||||
```
|
||||
|
||||
Every attempt has been made to filter out as much "noise" as possible from the command output.
|
||||
|
||||
##### `{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)
|
||||
160
docs/configuration/devices.md
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
Devices/routers are defined in `hyperglass/hyperglass/config/devices.toml`. `devices.toml` is effectively an array of hash tables/dictionaries/key value pairs:
|
||||
|
||||
```toml
|
||||
[[router]]
|
||||
address = "10.0.0.1"
|
||||
asn = "65000"
|
||||
src_addr_ipv4 = "192.0.2.1"
|
||||
src_addr_ipv6 = "2001:db8::1"
|
||||
credential = "default"
|
||||
location = "pop1"
|
||||
name = "router1.pop1"
|
||||
port = "22"
|
||||
type = "cisco_xr"
|
||||
proxy = "jumpbox1"
|
||||
|
||||
[[router]]
|
||||
address = "10.0.0.2"
|
||||
asn = "65000"
|
||||
src_addr_ipv4 = "192.0.2.2"
|
||||
src_addr_ipv6 = "2001:db8::2"
|
||||
credential = "default"
|
||||
location = "pop2"
|
||||
name = "router1.pop2"
|
||||
port = "22"
|
||||
type = "cisco_ios"
|
||||
proxy = "jumpbox2"
|
||||
|
||||
[[router]]
|
||||
address = "10.0.0.3"
|
||||
asn = "65000"
|
||||
src_addr_ipv4 = "192.0.2.3"
|
||||
src_addr_ipv6 = "2001:db8::3"
|
||||
credential = "default"
|
||||
location = "pop3"
|
||||
name = "router1.pop3"
|
||||
port = "22"
|
||||
type = "juniper"
|
||||
proxy = "jumpbox3"
|
||||
```
|
||||
|
||||
### Device Keys
|
||||
|
||||
#### address
|
||||
|
||||
IP address hyperglass will use to connect to the device.
|
||||
|
||||
#### asn
|
||||
|
||||
ASN this device is a member of.
|
||||
|
||||
#### src_addr_ipv4
|
||||
|
||||
Source IPv4 address used for `ping` and `traceroute` queries.
|
||||
|
||||
#### src_addr_ipv6
|
||||
|
||||
Source IPv6 address used for `ping` and `traceroute` queries.
|
||||
|
||||
#### credential
|
||||
|
||||
Name of credential (username & password) used to authenticate with the device. Credentials are defined as individual tables. See [here](/configuration/authentication.md) for more information on authentication.
|
||||
|
||||
#### location
|
||||
|
||||
Name of location/POP where this device resides.
|
||||
|
||||
#### name
|
||||
|
||||
Display name/hostname of device.
|
||||
|
||||
#### port
|
||||
|
||||
TCP port for SSH connection to device.
|
||||
|
||||
#### type
|
||||
|
||||
Device type/vendor name as recognized by [Netmiko](https://github.com/ktbyers/netmiko). See [supported device types](#supported-device-types) for a full list.
|
||||
|
||||
#### proxy
|
||||
|
||||
Name of SSH proxy/jumpbox, if any, used for connecting to the device. See [here](/configuration/proxy.md) for more information on proxying.
|
||||
|
||||
### Supported Device Types
|
||||
|
||||
Updated **2019-04-28** from [Netmiko](https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py#L76).
|
||||
|
||||
```console
|
||||
a10
|
||||
accedian
|
||||
alcatel_aos
|
||||
alcatel_sros
|
||||
apresia_aeos
|
||||
arista_eos
|
||||
aruba_os
|
||||
avaya_ers
|
||||
avaya_vsp
|
||||
brocade_fastiron
|
||||
brocade_netiron
|
||||
brocade_nos
|
||||
brocade_vdx
|
||||
brocade_vyos
|
||||
checkpoint_gaia
|
||||
calix_b6
|
||||
ciena_saos
|
||||
cisco_asa
|
||||
cisco_ios
|
||||
cisco_nxos
|
||||
cisco_s300
|
||||
cisco_tp
|
||||
cisco_wlc
|
||||
cisco_xe
|
||||
cisco_xr
|
||||
coriant
|
||||
dell_dnos9
|
||||
dell_force10
|
||||
dell_os6
|
||||
dell_os9
|
||||
dell_os10
|
||||
dell_powerconnect
|
||||
dell_isilon
|
||||
eltex
|
||||
enterasys
|
||||
extreme
|
||||
extreme_ers
|
||||
extreme_exos
|
||||
extreme_netiron
|
||||
extreme_nos
|
||||
extreme_slx
|
||||
extreme_vdx
|
||||
extreme_vsp
|
||||
extreme_wing
|
||||
f5_ltm
|
||||
f5_tmsh
|
||||
f5_linux
|
||||
fortinet
|
||||
generic_termserver
|
||||
hp_comware
|
||||
hp_procurve
|
||||
huawei
|
||||
huawei_vrpv8
|
||||
ipinfusion_ocnos
|
||||
juniper
|
||||
juniper_junos
|
||||
linux
|
||||
mellanox
|
||||
mrv_optiswitch
|
||||
netapp_cdot
|
||||
netscaler
|
||||
oneaccess_oneos
|
||||
ovs_linux
|
||||
paloalto_panos
|
||||
pluribus
|
||||
quanta_mesh
|
||||
rad_etx
|
||||
ruckus_fastiron
|
||||
ubiquiti_edge
|
||||
ubiquiti_edgeswitch
|
||||
vyatta_vyos
|
||||
vyos
|
||||
```
|
||||
132
docs/configuration/general.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
From `hyperglass/hyperglass/config/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).
|
||||
45
docs/configuration/proxy.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
Proxy servers are defined in `hyperglass/hyperglass/config/devices.toml`. Each proxy definition is a unique TOML table, for example:
|
||||
|
||||
```toml
|
||||
[proxy.'jumpbox1']
|
||||
address = "10.1.1.1"
|
||||
username = "hyperglass"
|
||||
password = "secret_password"
|
||||
type = "linux_ssh"
|
||||
ssh_command = "ssh -l {username} {host}"
|
||||
|
||||
[proxy.'jumpbox2']
|
||||
address = "10.1.1.2"
|
||||
username = "hyperglass"
|
||||
password = "secret_password"
|
||||
type = "linux_ssh"
|
||||
ssh_command = "ssh -l {username} {host}"
|
||||
```
|
||||
|
||||
When a proxy server is defined under the `[[router]]` heading in `devices.toml`, the defined proxy name is matched to a configured proxy as shown above. When the connection to the device is initiated, the hyperglass server will first initiate an SSH connection to the proxy, and then initiate a second connection to the target device (router) *from* the proxy server. This can be helpful if you want to secure access to your routers.
|
||||
|
||||
#### address
|
||||
|
||||
IP address hyperglass will use to connect to the device.
|
||||
|
||||
#### username
|
||||
|
||||
Username for SSH authentication to the proxy server/jumpbox. SSH Key authentication is not yet supported.
|
||||
|
||||
#### password
|
||||
|
||||
Plain text password for SSH authentication to the proxy server/jumpbox.
|
||||
|
||||
!!! warning "Security Warning"
|
||||
These values are stored in plain text. Make sure the accounts are restricted and that the configuration file is stored in a secure location.
|
||||
|
||||
#### type
|
||||
|
||||
Device type/vendor name as recognized by [Netmiko](https://github.com/ktbyers/netmiko). See [supported device types](#supported-device-types) for a full list.
|
||||
|
||||
!!! info "Compatibility"
|
||||
Hyperglass has only been tested with `linux_ssh` as of this writing.
|
||||
|
||||
#### ssh_command
|
||||
|
||||
Command used to initiate an SSH connection *from* the proxy server to the target device. `{username}` will map to the target device (router) username as defined in its associated credential mapping. `{host}` will map to the target device IP address as defined in `devices.toml`.
|
||||
127
docs/development/index.md
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# Hyperglass Development
|
||||
|
||||
Hyperglass is maintained as a [Github project](https://github.com/checktheroads/hyperglass) under the BSD 3-Clause Clear License. Hyperglass users are encouraged to submit Github issues for feature requests and bug reports.
|
||||
|
||||
## License
|
||||
|
||||
The intent behind the the [BSD 3-Clause Clear License](https://choosealicense.com/licenses/bsd-3-clause-clear/) is to ensure that anyone can use or modify Hyperglass in any way they wish, as long as credit and copyright notice is provied. If you have any questions about this, or wish to request any special permissions, please contact [matt@allroads.io](mailto:matt@allroads.io).
|
||||
|
||||
## Structure
|
||||
|
||||
Under the main `hyperglass/hyperglass/` directory, the following basic structure is in place:
|
||||
|
||||
```
|
||||
hyperglass/
|
||||
├── __init__.py
|
||||
├── app.py
|
||||
├── cmd_construct.py
|
||||
├── cmd_execute.py
|
||||
├── cmd_parser.py
|
||||
├── config/
|
||||
├── manage.py
|
||||
├── static/
|
||||
├── templates/
|
||||
├── templates.py
|
||||
└── vars.py
|
||||
```
|
||||
|
||||
### Directories
|
||||
|
||||
#### config
|
||||
|
||||
The `config/` directory contains all TOML config files used by Hyperglass:
|
||||
|
||||
```
|
||||
hyperglass/config/
|
||||
├── blacklist.toml
|
||||
├── commands.toml
|
||||
├── config.toml
|
||||
├── devices.toml
|
||||
└── requires_ipv6_cidr.toml
|
||||
```
|
||||
|
||||
#### static
|
||||
|
||||
The `static/` directory contains all static HTML/CSS/JS files used for serving the site:
|
||||
|
||||
```
|
||||
hyperglass/static/
|
||||
├── css
|
||||
│ ├── hyperglass.css
|
||||
│ └── icofont
|
||||
├── images
|
||||
│ ├── brand.svg
|
||||
│ ├── favicon
|
||||
│ ├── hyperglass-dark.png
|
||||
│ └── hyperglass-light.png
|
||||
├── js
|
||||
│ ├── hyperglass.js
|
||||
│ ├── jquery-3.4.0.min.js
|
||||
│ └── jquery-3.4.0.min.map
|
||||
└── sass
|
||||
├── base
|
||||
├── components
|
||||
├── custom
|
||||
├── elements
|
||||
├── grid
|
||||
├── hyperglass.scss
|
||||
├── layout
|
||||
└── utilities
|
||||
```
|
||||
|
||||
- `css/hyperglass.css` Final CSS file compiled from Sass file `hyperglass.scss`. Sass compiles all the `.sass` files located under `sass/` and combines them into a single CSS file.
|
||||
- `css/icofont/` Completely free alternative to FontAwesome - [Icofont](https://icofont.com/).
|
||||
- `js/hyerpglass.js` Basic Javascript helper to perform AJAX queries necessary to pull in dynamic information and render content.
|
||||
|
||||
#### templates
|
||||
|
||||
The `templates/` directory contains HTML and Sass Jinja2 templates:
|
||||
|
||||
```
|
||||
templates/
|
||||
├── 415.html
|
||||
├── 429.html
|
||||
├── base.html
|
||||
├── footer.html
|
||||
├── footer.md
|
||||
├── hyperglass.scss
|
||||
└── index.html
|
||||
```
|
||||
|
||||
- `415.html` General error page template.
|
||||
- `429.html` Site load rate limit page.
|
||||
- `base.html` Base template inherited by all other templates. Contains HTML `head`, JavaScript, etc.
|
||||
- `footer.html` Footer template containing footer text and hyperglass credit link.
|
||||
- `footer.md` Text that appears in the footer, if enabled. Markdown will be rendered as HTML.
|
||||
- `hyperglass.scss` Generates SCSS file for Bulma and local customizations.
|
||||
- `index.html` Main page template.
|
||||
|
||||
### Scripts
|
||||
|
||||
#### `app.py`
|
||||
|
||||
Main Flask application. Passes input to `cmd_execute.py`
|
||||
|
||||
#### `cmd_execute.py`
|
||||
|
||||
Matches router name to router IP, OS, and credentials. Passes data to `cmd_construct.py`, uses the results to execute the Netmiko action. Also performs error handling in the event of a [blacklist](/configuration/blacklist) match.
|
||||
|
||||
#### `cmd_construct.py`
|
||||
|
||||
Constructs full commands to run on routers from `hyperglass/hyperglass/config/commands.toml`. Also performs error handling in the event of input errors.
|
||||
|
||||
#### `cmd_parser.py`
|
||||
|
||||
Parses output before presentation to the user. For the time being, only BGP output from Cisco IOS is parsed. This is because for BGP Community and AS_PATH lookups, Cisco IOS returns results for *all* address families, including VPNv4. This script ensures that only IPv4 and IPv6 address family output is returned.
|
||||
|
||||
#### `manage.py`
|
||||
|
||||
Management script for perfoming one-off actions. For now, the only action implemented is a manual clearing of the Flask-cache cache.
|
||||
|
||||
#### `templates.py`
|
||||
|
||||
Renders HTML and Sass templates, compiles Sass to CSS.
|
||||
|
||||
#### `vars.py`
|
||||
|
||||
Imports configuration from TOML configuration files, defines default values, and exports each as a variable that can be called in other scripts.
|
||||
17
docs/extras/common_as_path_regex.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
| Expression | Match |
|
||||
| :----------------------- | ----------------------------------------------------: |
|
||||
| `.\*` | Anything |
|
||||
| `.+` | One Character |
|
||||
| `^$` | Local Routes |
|
||||
| `\_65000$` | Originated by `AS65000` |
|
||||
| `^65000\_` | Received from `AS65000` |
|
||||
| `_65000_` | Via `AS65000` |
|
||||
| `_65000_65001_` | Via `AS65000` and `AS65001` |
|
||||
| `_(65000_)+` | Multiple `AS65000` in path |
|
||||
| `^[0-9]+$` | AS_PATH length of 1 |
|
||||
| `^[0-9]+_[0-9]+$` | AS_PATH length of 2 |
|
||||
| `^[0-9]*_[0-9]+$` | AS_PATH length of 1 or 2 |
|
||||
| `^[0-9]*_[0-9]*$` | AS_PATH length of 0, 1, or 2 |
|
||||
| `^[0-9]+_[0-9]+_[0-9]+$` | AS_PATH length of 3 |
|
||||
| `_(65000\|65001)_` | Anything that has gone through `AS65000` or `AS65001` |
|
||||
| `_65000(_.+_)65001$` | Anything from `AS65001` that passed through `AS65001` |
|
||||
BIN
docs/hyperglass-dark.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
25
docs/index.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<img src="/hyperglass-dark.png" width=300></img>
|
||||
|
||||
# What is Hyperglass?
|
||||
|
||||
**Hyperglass** is an open source looking glass application to provide customers, peers, and partners of network operators with unattended visibility into the operator's network.
|
||||
|
||||
# Yet Another Looking Glass?
|
||||
|
||||
Many of the more popular open source looking glass applications are written in PHP or Perl, languages infrequently used by many network engineers today. With the widespread adoption of network operations tooling such as [Netmiko](https://github.com/ktbyers/netmiko), [Netbox](https://github.com/digitalocean/netbox), and [Napalm](https://github.com/napalm-automation/napalm), Python is most often the language of choice for network operators. Hyperglass is built completely on Python 3 and utilizes user-friendly configuration files to provide a highly customizable, easy to deploy looking glass app.
|
||||
|
||||
Hyperglass was created with the lofty goal of benefiting the internet community at-large, by providing an easier and more familiar way for operators to provide looking glass services to their customers, peers, and partners.
|
||||
|
||||
# Application Stack
|
||||
|
||||
| Function | Component |
|
||||
| -------------------------- | --------- |
|
||||
| Front End Framework | Bulma |
|
||||
| Front End Application | Flask |
|
||||
| Back End Application | Python 3 |
|
||||
| Device Connection Handling | Netmiko |
|
||||
| Configuration Format | TOML |
|
||||
|
||||
# Get Started
|
||||
|
||||
See the [installation guide](<>) to get started.
|
||||
3
docs/ratelimiting.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Hyperglass supports configurable rate limiting of both site loads and queries. By default, users are able to reach the site up to 120 times per minute, and submit up to 5 queries per minute. When the site limit is reached, users are directed to a separate error page until the timer expires. When the query limit is reached, an error message is displayed, and no further queries are allowed until the timer expires.
|
||||
|
||||
Hyperglass uses [Flask-Limiter](https://github.com/alisaifee/flask-limiter) to handle application rate limiting. In Flask, the `/lg` route, which is used for actual queries, is associated with the query rate limit, while the default `/` route is associated with the site rate limit. Both of these limits are configurabale in `hyperglass/hyperglass/configs/config.toml`. See [here](/configuration/general/#rate_limit_query) for more information.
|
||||
2
docs/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pygments
|
||||
pymdown-extensions
|
||||
6
hyperglass/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.DS_Store
|
||||
.sass-cache/
|
||||
.flask_cache/
|
||||
test.py
|
||||
__pycache__/
|
||||
parsing/
|
||||
0
hyperglass/__init__.py
Normal file
177
hyperglass/app.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# Module Imports
|
||||
import logging
|
||||
from flask import Flask, request, Response, jsonify, flash
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
from flask_caching import Cache
|
||||
import json
|
||||
import toml
|
||||
|
||||
# Local Imports
|
||||
import vars
|
||||
from cmd_execute import cmd_execute
|
||||
import templates
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Load TOML config file
|
||||
devices = toml.load(open("./config/devices.toml"))
|
||||
# Filter config file to list of routers & subsequent configurations
|
||||
routers_list = devices["router"]
|
||||
# Filter config file to array of operating systems that require IPv6 BGP lookups in CIDR format
|
||||
ipv6_cidr_list = toml.load(open("./config/requires_ipv6_cidr.toml"))[
|
||||
"requires_ipv6_cidr"
|
||||
]
|
||||
# Main Flask definition
|
||||
app = Flask(__name__, static_url_path="/static")
|
||||
|
||||
# Flask-Limiter Config
|
||||
rate_limit_query = vars.gen.rate_limit_query() + " per minute"
|
||||
rate_limit_site = vars.gen.rate_limit_site() + "per minute"
|
||||
limiter = Limiter(app, key_func=get_remote_address, default_limits=[rate_limit_site])
|
||||
|
||||
# Render Main Flask-Limiter Error Message
|
||||
@app.errorhandler(429)
|
||||
def error429(e):
|
||||
"""Renders full error page for too many site queries"""
|
||||
html = templates.html.renderTemplate("429")
|
||||
return html, 429
|
||||
|
||||
|
||||
def error415():
|
||||
"""Renders full error page for generic errors"""
|
||||
html = templates.html.renderTemplate("415")
|
||||
return html, 415
|
||||
|
||||
|
||||
def errorQuery():
|
||||
"""Renders modal error message"""
|
||||
return 429
|
||||
|
||||
|
||||
def errorGeneral(id):
|
||||
"""Renders notification error message with an ID number"""
|
||||
return "An unknown error occurred." + "\s" + id, 415
|
||||
|
||||
|
||||
# Flask-Caching Config
|
||||
cache = Cache(
|
||||
app,
|
||||
config={
|
||||
"CACHE_TYPE": "filesystem",
|
||||
"CACHE_DIR": vars.gen.cache_directory(),
|
||||
"CACHE_DEFAULT_TIMEOUT": vars.gen.cache_timeout(),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def clearCache():
|
||||
"""Function to clear the Flask-Caching cache"""
|
||||
with app.app_context():
|
||||
cache.clear()
|
||||
|
||||
|
||||
# Main / Flask route where html is rendered via Jinja2
|
||||
@app.route("/", methods=["GET"])
|
||||
@limiter.limit(rate_limit_site)
|
||||
def site():
|
||||
"""Main front-end web application"""
|
||||
html = templates.html.renderTemplate("index")
|
||||
return html
|
||||
|
||||
|
||||
# Test route for various tests
|
||||
@app.route("/test", methods=["GET"])
|
||||
def testRoute():
|
||||
html = templates.html.renderTemplate("test")
|
||||
return html
|
||||
|
||||
|
||||
# Flask GET route to provides a JSON list of all networks/ASNs from the config file
|
||||
@app.route("/networks", methods=["GET"])
|
||||
def get_networks():
|
||||
results = []
|
||||
results_dedup = set(results)
|
||||
for r in routers_list:
|
||||
if not r["asn"] in results_dedup:
|
||||
results_dedup.add(r["asn"])
|
||||
results.append(dict(network=r["asn"]))
|
||||
results_json = json.dumps(results, sort_keys=True)
|
||||
return results_json
|
||||
|
||||
|
||||
# Flask GET route provides a JSON list of all routers for the selected network/ASN
|
||||
@app.route("/routers/<asn>", methods=["GET"])
|
||||
def get_routers(asn):
|
||||
results = []
|
||||
# For any configured router matching the queried ASN, return only the address/hostname, location, and OS type of the matching routers
|
||||
for r in routers_list:
|
||||
if r["asn"] == asn:
|
||||
if r["type"] in ipv6_cidr_list:
|
||||
results.append(
|
||||
dict(
|
||||
location=r["location"],
|
||||
hostname=r["name"],
|
||||
type=r["type"],
|
||||
requiresIP6Cidr=True,
|
||||
)
|
||||
)
|
||||
else:
|
||||
results.append(
|
||||
dict(
|
||||
location=r["location"],
|
||||
hostname=r["name"],
|
||||
type=r["type"],
|
||||
requiresIP6Cidr=False,
|
||||
)
|
||||
)
|
||||
results_json = json.dumps(results)
|
||||
return results_json
|
||||
|
||||
|
||||
# Flask POST route ingests data from the JS form submit, passes it to the backend looking glass application to perform the filtering/lookups
|
||||
@app.route("/lg", methods=["POST"])
|
||||
# Invoke Flask-Limiter with configured rate limit
|
||||
@limiter.limit(rate_limit_query)
|
||||
def lg():
|
||||
"""Main backend application initiator"""
|
||||
lg_data = request.get_json()
|
||||
# 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:
|
||||
cache_value = cmd_execute(lg_data)
|
||||
log.debug(cache_value[1:])
|
||||
value_output = cache_value[0]
|
||||
value_code = cache_value[1]
|
||||
value_params = cache_value[2:]
|
||||
log.debug("No cache match for: ", cache_key, "\nAdding cache entry...")
|
||||
# If it doesn't, create a cache entry
|
||||
try:
|
||||
cache.set(cache_key, value_output)
|
||||
log.debug("\nAdded cache entry: ", *value_params)
|
||||
except:
|
||||
raise RuntimeError("Unable to add output to cache.", 415, *value_params)
|
||||
# If 200, return output
|
||||
if value_code == 200:
|
||||
return cache.get(cache_key)
|
||||
# If 400 error, return error message and code
|
||||
elif value_code in [405, 415]:
|
||||
return Response(cache.get(cache_key), value_code)
|
||||
# If it does, return the cached entry
|
||||
else:
|
||||
log.debug("Cache match for: ", cache_key, "\nReturning cached entry...")
|
||||
try:
|
||||
return cache.get(cache_key)
|
||||
except:
|
||||
id = 4152
|
||||
raise RuntimeError(
|
||||
id + ":\s" + "Unable to return cached output.", 415, *value_params
|
||||
)
|
||||
# Upon exception, render generic error
|
||||
return Response(errorGeneral(id))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
templates.css.renderTemplate()
|
||||
app.run(host="0.0.0.0", debug=vars.gen.debug(), port=5000)
|
||||
232
hyperglass/cmd_construct.py
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
import sys
|
||||
import logging
|
||||
import toml
|
||||
import re
|
||||
from netaddr import *
|
||||
|
||||
# Local imports
|
||||
import vars
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
# Load TOML config file
|
||||
devices = toml.load(open("./config/devices.toml"))
|
||||
|
||||
# Load TOML commands file
|
||||
commands = toml.load(open("./config/commands.toml"))
|
||||
|
||||
# Filter config to router list
|
||||
routers_list = devices["router"]
|
||||
|
||||
# Receives JSON from Flask, constucts the command that will be passed to the router
|
||||
# Also handles input validation & error handling
|
||||
def cmd_construct(router, cmd, ipprefix):
|
||||
inputParams = router, cmd, ipprefix
|
||||
try:
|
||||
# Loop through routers config file, match input router with configured routers, set variables
|
||||
for r in routers_list:
|
||||
try:
|
||||
if router == r["address"]:
|
||||
type = r["type"]
|
||||
src_addr_ipv4 = r["src_addr_ipv4"]
|
||||
src_addr_ipv6 = r["src_addr_ipv6"]
|
||||
# Loop through commands config file, set variables for matched commands
|
||||
for nos in commands:
|
||||
if type == nos:
|
||||
nos = commands[type]
|
||||
nos_commands = nos[0]
|
||||
# Dual stack commands (agnostic of IPv4/IPv6)
|
||||
dual_commands = nos_commands["dual"]
|
||||
# IPv4 Specific Commands
|
||||
ipv4_commands = nos_commands["ipv4"]
|
||||
# IPv6 Specific Commands
|
||||
ipv6_commands = nos_commands["ipv6"]
|
||||
if cmd == "Query Type":
|
||||
msg = "You must select a query type."
|
||||
code = 415
|
||||
log.error(msg, code, *inputParams)
|
||||
return (msg, code, *inputParams)
|
||||
# BGP Community Query
|
||||
elif cmd in ["bgp_community"]:
|
||||
# Extended Communities, new-format
|
||||
if re.match("^([0-9]{0,5})\:([0-9]{1,5})$", ipprefix):
|
||||
for a, c in dual_commands.items():
|
||||
if a == cmd:
|
||||
command = c.format(target=ipprefix)
|
||||
msg = "{i} matched new-format community.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 200
|
||||
log.info(msg, code, router, type, command)
|
||||
return (msg, code, router, type, command)
|
||||
# Extended Communities, 32 bit format
|
||||
elif re.match("^[0-9]{1,10}$", ipprefix):
|
||||
for a, c in dual_commands.items():
|
||||
if a == cmd:
|
||||
command = c.format(target=ipprefix)
|
||||
msg = "{i} matched 32 bit community.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 200
|
||||
log.info(msg, code, router, type, command)
|
||||
return (msg, code, router, type, command)
|
||||
# RFC 8092 Large Community Support
|
||||
elif re.match(
|
||||
"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$",
|
||||
ipprefix,
|
||||
):
|
||||
for a, c in dual_commands.items():
|
||||
if a == cmd:
|
||||
command = c.format(target=ipprefix)
|
||||
msg = "{i} matched large community.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 200
|
||||
log.info(msg, code, router, type, command)
|
||||
return (msg, code, router, type, command)
|
||||
else:
|
||||
msg = "{i} is an invalid BGP Community Format.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 415
|
||||
log.error(msg, code, *inputParams)
|
||||
return (msg, code, *inputParams)
|
||||
# BGP AS_PATH Query
|
||||
elif cmd in ["bgp_aspath"]:
|
||||
if re.match(".*", ipprefix):
|
||||
for a, c in dual_commands.items():
|
||||
if a == cmd:
|
||||
command = c.format(target=ipprefix)
|
||||
msg = "{i} matched AS_PATH regex.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 200
|
||||
log.info(msg, code, router, type, command)
|
||||
return (msg, code, router, type, command)
|
||||
else:
|
||||
msg = "{i} is an invalid AS_PATH regex.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 415
|
||||
log.error(msg, code, *inputParams)
|
||||
return (msg, code, *inputParams)
|
||||
# BGP Route Query
|
||||
elif cmd in ["bgp_route"]:
|
||||
try:
|
||||
# Use netaddr library to verify if input is a valid IPv4 address or prefix
|
||||
if IPNetwork(ipprefix).ip.version == 4:
|
||||
for a, c in ipv4_commands.items():
|
||||
if a == cmd:
|
||||
command = c.format(target=ipprefix)
|
||||
msg = "{i} is a valid IPv4 Adddress.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 200
|
||||
log.info(
|
||||
msg, code, router, type, command
|
||||
)
|
||||
return (
|
||||
msg,
|
||||
code,
|
||||
router,
|
||||
type,
|
||||
command,
|
||||
)
|
||||
# Use netaddr library to verify if input is a valid IPv6 address or prefix
|
||||
elif IPNetwork(ipprefix).ip.version == 6:
|
||||
for a, c in ipv6_commands.items():
|
||||
if a == cmd:
|
||||
command = c.format(
|
||||
target=ipprefix
|
||||
)
|
||||
msg = "{i} is a valid IPv6 Adddress.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 200
|
||||
log.info(
|
||||
msg,
|
||||
code,
|
||||
router,
|
||||
type,
|
||||
command,
|
||||
)
|
||||
return (
|
||||
msg,
|
||||
code,
|
||||
router,
|
||||
type,
|
||||
command,
|
||||
)
|
||||
# Exception from netaddr library will return a user-facing error
|
||||
except:
|
||||
msg = "{i} is an invalid IP Address.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 415
|
||||
log.error(msg, code, *inputParams)
|
||||
return (msg, code, *inputParams)
|
||||
# Ping/Traceroute
|
||||
elif cmd in ["ping", "traceroute"]:
|
||||
try:
|
||||
if IPNetwork(ipprefix).ip.version == 4:
|
||||
for a, c in ipv4_commands.items():
|
||||
if a == cmd:
|
||||
command = c.format(
|
||||
target=ipprefix,
|
||||
src_addr_ipv4=src_addr_ipv4,
|
||||
)
|
||||
msg = "{i} is a valid IPv4 Adddress.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 200
|
||||
log.info(
|
||||
msg, code, router, type, command
|
||||
)
|
||||
return (
|
||||
msg,
|
||||
code,
|
||||
router,
|
||||
type,
|
||||
command,
|
||||
)
|
||||
elif IPNetwork(ipprefix).ip.version == 6:
|
||||
for a, c in ipv6_commands.items():
|
||||
if a == cmd:
|
||||
command = c.format(
|
||||
target=ipprefix,
|
||||
src_addr_ipv6=src_addr_ipv6,
|
||||
)
|
||||
msg = "{i} is a valid IPv6 Adddress.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 200
|
||||
log.info(
|
||||
msg, code, router, type, command
|
||||
)
|
||||
return (
|
||||
msg,
|
||||
code,
|
||||
router,
|
||||
type,
|
||||
command,
|
||||
)
|
||||
except:
|
||||
msg = "{i} is an invalid IP Address.".format(
|
||||
i=ipprefix
|
||||
)
|
||||
code = 415
|
||||
log.error(msg, code, *inputParams)
|
||||
return (msg, code, *inputParams)
|
||||
else:
|
||||
msg = "Command {i} not found.".format(i=cmd)
|
||||
code = 415
|
||||
log.error(msg, code, *inputParams)
|
||||
return (msg, code, *inputParams)
|
||||
except:
|
||||
error_msg = log.error(
|
||||
"Input router IP {router} does not match the configured router IP of {ip}".format(
|
||||
router=router, ip=r["address"]
|
||||
)
|
||||
)
|
||||
raise ValueError(error_msg)
|
||||
except:
|
||||
raise
|
||||
173
hyperglass/cmd_execute.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import sys
|
||||
import logging
|
||||
import toml
|
||||
import time
|
||||
from netmiko import ConnectHandler
|
||||
from netmiko import redispatch
|
||||
from netaddr import *
|
||||
from cmd_construct import cmd_construct
|
||||
import vars
|
||||
import cmd_parser as parser
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
# Load TOML config file
|
||||
devices = toml.load(open("./config/devices.toml"))
|
||||
# Filter config to router list
|
||||
routers_list = devices["router"]
|
||||
# Filter config to credential list
|
||||
credentials_list = devices["credential"]
|
||||
# Filter config to proxy servers
|
||||
proxies_list = devices["proxy"]
|
||||
|
||||
blacklist_config = toml.load(open("./config/blacklist.toml"))
|
||||
blacklist = IPSet(blacklist_config["blacklist"])
|
||||
|
||||
general_error = "Error connecting to device."
|
||||
|
||||
|
||||
def cmd_execute(lg_data):
|
||||
# Check POST data from JS, if location matches a configured router's
|
||||
# location, use the router's configured IP address to connect
|
||||
for r in routers_list:
|
||||
if r["location"] == lg_data["router"]:
|
||||
lg_router_address = r["address"]
|
||||
|
||||
# Check blacklist.toml array for prefixes/IPs and return an error upon a match
|
||||
if lg_data["cmd"] in ["bgp_route", "ping", "traceroute"]:
|
||||
try:
|
||||
if IPNetwork(lg_data["ipprefix"]).ip in blacklist:
|
||||
msg = "{i} is not allowed.".format(i=lg_data["ipprefix"])
|
||||
code = 405
|
||||
log.error(msg, code, lg_data)
|
||||
return (msg, code, lg_data)
|
||||
# If netaddr library throws an exception, return a user-facing error.
|
||||
except:
|
||||
msg = "{i} is not a valid IP Address.".format(i=lg_data["ipprefix"])
|
||||
code = 415
|
||||
log.error(msg, code, lg_data)
|
||||
return (msg, code, lg_data)
|
||||
# Send "clean" request to cmd_construct to build the command that will be sent to the router
|
||||
msg, status, router, type, command = cmd_construct(
|
||||
lg_router_address, lg_data["cmd"], lg_data["ipprefix"]
|
||||
)
|
||||
# Loop through proxy config, match configured proxy name for each router with a configured proxy
|
||||
# Return configured proxy parameters for netmiko
|
||||
def matchProxy(search_proxy):
|
||||
if configured_proxy in proxies_list:
|
||||
proxy_address = proxies_list[search_proxy]["address"]
|
||||
proxy_username = proxies_list[search_proxy]["username"]
|
||||
proxy_password = proxies_list[search_proxy]["password"]
|
||||
proxy_type = proxies_list[search_proxy]["type"]
|
||||
proxy_ssh_command = proxies_list[search_proxy]["ssh_command"]
|
||||
return (
|
||||
proxy_address,
|
||||
proxy_username,
|
||||
proxy_password,
|
||||
proxy_type,
|
||||
proxy_ssh_command,
|
||||
)
|
||||
else:
|
||||
msg = "Router does not have a proxy configured."
|
||||
code = 415
|
||||
log.error(msg, code, lg_data)
|
||||
return (msg, code, lg_data)
|
||||
|
||||
# Matches router with configured credential
|
||||
def findCred(router):
|
||||
for r in routers_list:
|
||||
if r["address"] == router:
|
||||
configured_credential = r["credential"]
|
||||
return configured_credential
|
||||
|
||||
# Matches configured credential with real username/password
|
||||
def returnCred(configured_credential):
|
||||
if configured_credential in credentials_list:
|
||||
matched_username = credentials_list[configured_credential]["username"]
|
||||
matched_password = credentials_list[configured_credential]["password"]
|
||||
return matched_username, matched_password
|
||||
else:
|
||||
msg = "Credential {i} does not exist".format(i=configured_credential)
|
||||
code = 415
|
||||
log.error(msg, code, lg_data)
|
||||
return (general_error, code, lg_data)
|
||||
|
||||
# Connect to the router via netmiko library, return the command output
|
||||
def getOutputDirect():
|
||||
try:
|
||||
nm_connect_direct = ConnectHandler(**nm_host)
|
||||
nm_output_direct = nm_connect_direct.send_command(command)
|
||||
return nm_output_direct
|
||||
except:
|
||||
msg = "Unable to reach target {l}".format(l=lg_data["router"])
|
||||
code = 415
|
||||
log.error(msg, code, lg_data)
|
||||
return (general_error, code, lg_data)
|
||||
|
||||
# Connect to the proxy server via netmiko library, then log into the router
|
||||
# via standard SSH
|
||||
def getOutputProxy(router_proxy):
|
||||
nm_proxy = {
|
||||
"host": matchProxy(router_proxy)[0],
|
||||
"username": matchProxy(router_proxy)[1],
|
||||
"password": matchProxy(router_proxy)[2],
|
||||
"device_type": matchProxy(router_proxy)[3],
|
||||
}
|
||||
nm_connect_proxied = ConnectHandler(**nm_proxy)
|
||||
nm_ssh_command = matchProxy(router_proxy)[4].format(**nm_host) + "\n"
|
||||
nm_connect_proxied.write_channel(nm_ssh_command)
|
||||
time.sleep(1)
|
||||
proxy_output = nm_connect_proxied.read_channel()
|
||||
try:
|
||||
# Accept SSH key warnings
|
||||
if "Are you sure you want to continue connecting" in proxy_output:
|
||||
nm_connect_proxied.write_channel("yes" + "\n")
|
||||
time.sleep(1)
|
||||
nm_connect_proxied.write_channel(nm_host["password"] + "\n")
|
||||
# Send password on prompt
|
||||
elif "assword" in proxy_output:
|
||||
nm_connect_proxied.write_channel(nm_host["password"] + "\n")
|
||||
time.sleep(1)
|
||||
proxy_output += nm_connect_proxied.read_channel()
|
||||
# Reclassify netmiko connection as configured device type
|
||||
redispatch(nm_connect_proxied, nm_host["device_type"])
|
||||
|
||||
host_output = nm_connect_proxied.send_command(command)
|
||||
if host_output:
|
||||
return host_output
|
||||
except:
|
||||
msg = "Proxy server {p} unable to reach target {d}".format(
|
||||
p=nm_proxy["host"], d=nm_host["host"]
|
||||
)
|
||||
code = 415
|
||||
log.error(msg, code, lg_data)
|
||||
return (general_error, code, lg_data)
|
||||
|
||||
nm_host = {
|
||||
"host": router,
|
||||
"device_type": type,
|
||||
"username": returnCred(findCred(router))[0],
|
||||
"password": returnCred(findCred(router))[1],
|
||||
}
|
||||
|
||||
# Loop through router list, determine if proxy exists
|
||||
for r in routers_list:
|
||||
if r["address"] == router:
|
||||
configured_proxy = r["proxy"]
|
||||
if len(configured_proxy) == 0:
|
||||
connection_proxied = False
|
||||
else:
|
||||
connection_proxied = True
|
||||
if status == 200:
|
||||
try:
|
||||
if connection_proxied is True:
|
||||
output_proxied = getOutputProxy(configured_proxy)
|
||||
parsed_output = parser.parse(output_proxied, type, lg_data["cmd"])
|
||||
return parsed_output, status, router, type, command
|
||||
elif connection_proxied is False:
|
||||
output_direct = getOutputDirect()
|
||||
parsed_output = parser.parse(output_direct, type, lg_data["cmd"])
|
||||
return parsed_output, status, router, type, command
|
||||
except:
|
||||
raise
|
||||
else:
|
||||
return msg, status, router, type, command
|
||||
17
hyperglass/cmd_parser.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
def parse(output, type, cmd):
|
||||
"""Splits Cisco IOS BGP output by AFI, returns only IPv4 & IPv6 output for protocol-agnostic commands (Community & AS_PATH Lookups)"""
|
||||
try:
|
||||
if cmd in ["bgp_community", "bgp_aspath"] and type in ["cisco_ios"]:
|
||||
delimiter = "For address family: "
|
||||
parsed_ipv4 = output.split(delimiter)[1]
|
||||
parsed_ipv6 = output.split(delimiter)[2]
|
||||
return delimiter + parsed_ipv4 + delimiter + parsed_ipv6
|
||||
else:
|
||||
return output
|
||||
if cmd in ["bgp_community", "bgp_aspath"] and type in ["cisco_xr"]:
|
||||
delimiter = "Address Family: "
|
||||
parsed_ipv4 = output.split(delimiter)[1]
|
||||
parsed_ipv6 = output.split(delimiter)[2]
|
||||
return delimiter + parsed_ipv4 + delimiter + parsed_ipv6
|
||||
except:
|
||||
raise
|
||||
2
hyperglass/config/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.DS_Store
|
||||
*.toml
|
||||
8
hyperglass/config/blacklist.toml.example
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# 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/12',
|
||||
'198.18.0.0/15',
|
||||
'10.0.0.0/8',
|
||||
'192.168.0.0/16',
|
||||
'172.16.0.0/12'
|
||||
]
|
||||
41
hyperglass/config/commands.toml.example
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Cisco IOS/IOS-XE
|
||||
[[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 IOS-XR
|
||||
[[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]]
|
||||
[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}"
|
||||
55
hyperglass/config/config.toml.example
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# General site-wide parameters
|
||||
[[general]]
|
||||
primary_asn = ""
|
||||
debug = true
|
||||
google_analytics = ""
|
||||
message_error = ""
|
||||
message_blacklist = ""
|
||||
message_rate_limit_query = ""
|
||||
enable_bgp_route = true
|
||||
enable_bgp_community = true
|
||||
enable_bgp_aspath = true
|
||||
enable_ping = true
|
||||
enable_traceroute = true
|
||||
rate_limit_query = ""
|
||||
rate_limit_site = ""
|
||||
cache_timeout = 120
|
||||
cache_directory = ""
|
||||
|
||||
# Branding/Site Customization Parameters
|
||||
[[branding]]
|
||||
site_title = ""
|
||||
title_mode = ""
|
||||
title = ""
|
||||
subtitle = ""
|
||||
enable_footer = true
|
||||
enable_credit = true
|
||||
color_bg = ""
|
||||
color_btn_submit = ""
|
||||
color_tag_loctitle = ""
|
||||
color_tag_cmdtitle = ""
|
||||
color_tag_cmd = ""
|
||||
color_tag_loc = ""
|
||||
color_hero = ""
|
||||
color_progressbar = ""
|
||||
logo_path = ""
|
||||
logo_width = ""
|
||||
placeholder_prefix = ""
|
||||
show_peeringdb = true
|
||||
text_results = ""
|
||||
text_location = ""
|
||||
text_cache = ""
|
||||
text_limiter_title = ""
|
||||
text_limiter_subtitle = ""
|
||||
text_415_title = ""
|
||||
text_415_subtitle = ""
|
||||
text_415_button = ""
|
||||
text_help_bgp_route = ""
|
||||
text_help_bgp_community = ""
|
||||
text_help_bgp_aspath = ""
|
||||
text_help_ping = ""
|
||||
text_help_traceroute = ""
|
||||
primary_font_url = ""
|
||||
primary_font_name = ""
|
||||
mono_font_url = ""
|
||||
mono_font_name = ""
|
||||
54
hyperglass/config/devices.toml.example
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Routers
|
||||
[[router]]
|
||||
address = "10.0.0.1"
|
||||
asn = "65000"
|
||||
src_addr_ipv4 = "192.0.2.1"
|
||||
src_addr_ipv6 = "2001:db8::1"
|
||||
credential = "default"
|
||||
location = "pop01"
|
||||
name = "router01"
|
||||
port = "22"
|
||||
type = "cisco_xr"
|
||||
proxy = ""
|
||||
|
||||
[[router]]
|
||||
address = "10.0.0.2"
|
||||
asn = "65000"
|
||||
src_addr_ipv4 = "192.0.2.2"
|
||||
src_addr_ipv6 = "2001:db8::2"
|
||||
credential = "default"
|
||||
location = "pop02"
|
||||
name = "router02"
|
||||
port = "22"
|
||||
type = "cisco_ios"
|
||||
proxy = "server01"
|
||||
|
||||
[[router]]
|
||||
address = "10.0.0.3"
|
||||
asn = "65000"
|
||||
src_addr_ipv4 = "192.0.2.3"
|
||||
src_addr_ipv6 = "2001:db8::3"
|
||||
credential = "other"
|
||||
location = "pop03"
|
||||
name = "router03"
|
||||
port = "22"
|
||||
type = "juniper"
|
||||
proxy = ""
|
||||
|
||||
|
||||
# Router Credentials
|
||||
[credential.'default']
|
||||
username = "username"
|
||||
password = "password"
|
||||
|
||||
[credential.'other']
|
||||
username = "otheruser"
|
||||
password = "otherpass"
|
||||
|
||||
# SSH Proxy Servers
|
||||
[proxy.'server01']
|
||||
address = "10.0.1.1"
|
||||
username = "username"
|
||||
password = "password"
|
||||
type = "linux_ssh"
|
||||
ssh_command = "ssh -l {username} {host}"
|
||||
4
hyperglass/config/requires_ipv6_cidr.toml.example
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
requires_ipv6_cidr = [
|
||||
"cisco_ios",
|
||||
"cisco_nxos"
|
||||
]
|
||||
20
hyperglass/manage.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import os
|
||||
import sys
|
||||
import app
|
||||
|
||||
|
||||
def clearcache():
|
||||
try:
|
||||
app.clearCache()
|
||||
except:
|
||||
raise
|
||||
|
||||
|
||||
for arg in sys.argv:
|
||||
try:
|
||||
if arg == "clearcache":
|
||||
clearcache()
|
||||
print("Successfully cleared cache.")
|
||||
except:
|
||||
print("Failed to clear cache.")
|
||||
raise
|
||||
4
hyperglass/static/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
6991
hyperglass/static/css/hyperglass.css
Normal file
18942
hyperglass/static/css/icofont/demo.html
Normal file
BIN
hyperglass/static/css/icofont/fonts/icofont.eot
Normal file
2105
hyperglass/static/css/icofont/fonts/icofont.svg
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
hyperglass/static/css/icofont/fonts/icofont.ttf
Normal file
BIN
hyperglass/static/css/icofont/fonts/icofont.woff
Normal file
BIN
hyperglass/static/css/icofont/fonts/icofont.woff2
Normal file
10757
hyperglass/static/css/icofont/icofont.css
Normal file
7
hyperglass/static/css/icofont/icofont.min.css
vendored
Normal file
18942
hyperglass/static/icofont/demo.html
Normal file
BIN
hyperglass/static/icofont/fonts/icofont.eot
Normal file
2105
hyperglass/static/icofont/fonts/icofont.svg
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
hyperglass/static/icofont/fonts/icofont.ttf
Normal file
BIN
hyperglass/static/icofont/fonts/icofont.woff
Normal file
BIN
hyperglass/static/icofont/fonts/icofont.woff2
Normal file
10757
hyperglass/static/icofont/icofont.css
Normal file
7
hyperglass/static/icofont/icofont.min.css
vendored
Normal file
1
hyperglass/static/images/brand.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="search" class="svg-inline--fa fa-search fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"></path></svg>
|
||||
|
After Width: | Height: | Size: 577 B |
BIN
hyperglass/static/images/favicon/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 8 KiB |
BIN
hyperglass/static/images/favicon/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
hyperglass/static/images/favicon/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
9
hyperglass/static/images/favicon/browserconfig.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="static/img/mstile-150x150.png"/>
|
||||
<TileColor>#330036</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
hyperglass/static/images/favicon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 849 B |
BIN
hyperglass/static/images/favicon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
hyperglass/static/images/favicon/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
hyperglass/static/images/favicon/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
64
hyperglass/static/images/favicon/safari-pinned-tab.svg
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1014.000000pt" height="1014.000000pt" viewBox="0 0 1014.000000 1014.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,1014.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M4809 10126 c-2 -2 -51 -7 -109 -10 -92 -6 -211 -18 -305 -30 -16 -3
|
||||
-52 -8 -80 -11 -27 -4 -138 -24 -245 -46 -516 -102 -1052 -302 -1491 -556 -48
|
||||
-27 -93 -53 -100 -56 -30 -14 -217 -138 -333 -220 -432 -307 -811 -676 -1130
|
||||
-1102 -213 -283 -391 -582 -540 -905 -145 -311 -255 -631 -331 -956 -19 -82
|
||||
-37 -167 -41 -189 -3 -22 -7 -42 -9 -45 -2 -3 -6 -24 -9 -46 -4 -22 -8 -51
|
||||
-11 -64 -3 -14 -14 -92 -24 -175 -18 -134 -24 -193 -37 -380 -5 -75 -5 -429 1
|
||||
-520 11 -199 29 -383 45 -470 5 -27 11 -69 14 -92 16 -128 97 -477 156 -668
|
||||
133 -435 341 -881 591 -1267 40 -62 76 -115 79 -118 3 -3 20 -27 39 -55 44
|
||||
-64 226 -300 267 -345 17 -19 67 -75 111 -125 143 -160 349 -359 508 -490 33
|
||||
-28 62 -53 65 -56 3 -3 59 -46 125 -96 552 -416 1168 -712 1831 -877 99 -25
|
||||
193 -47 209 -50 17 -3 68 -12 115 -21 47 -8 99 -17 115 -20 17 -2 62 -8 100
|
||||
-14 39 -5 102 -13 140 -16 39 -4 81 -8 95 -10 152 -23 651 -23 890 -1 83 8
|
||||
186 19 201 21 9 2 40 6 69 10 29 4 60 8 69 10 9 1 36 6 61 9 25 4 99 18 165
|
||||
32 66 13 129 26 140 28 211 43 664 194 900 301 125 57 372 182 458 233 49 28
|
||||
91 52 93 52 10 0 306 199 410 275 457 338 859 756 1177 1221 178 262 372 614
|
||||
472 859 20 50 43 104 50 120 76 172 208 608 251 830 2 11 6 31 9 45 16 76 42
|
||||
229 50 299 4 30 8 61 10 70 1 9 6 50 9 91 4 41 9 91 11 110 21 173 21 731 1
|
||||
860 -3 14 -7 59 -11 100 -4 41 -8 86 -10 100 -2 14 -6 41 -9 60 -2 19 -8 58
|
||||
-11 85 -6 41 -66 351 -80 405 -39 163 -145 506 -188 610 -6 14 -11 27 -12 30
|
||||
-26 78 -121 289 -205 455 -197 391 -422 723 -735 1085 -78 91 -413 416 -522
|
||||
507 -272 227 -507 389 -805 556 -126 70 -359 186 -436 216 -27 11 -69 29 -93
|
||||
39 -255 114 -781 267 -1059 307 -24 4 -47 8 -50 10 -4 2 -35 6 -71 10 -35 3
|
||||
-72 8 -81 11 -18 5 -50 9 -168 19 -41 4 -91 9 -110 12 -41 5 -646 14 -651 9z
|
||||
m527 -1010 c67 -4 133 -9 145 -11 13 -2 45 -6 72 -9 26 -3 68 -9 95 -12 187
|
||||
-23 542 -109 759 -185 215 -76 581 -243 675 -310 14 -11 28 -19 31 -19 21 0
|
||||
304 -191 441 -298 108 -83 126 -99 236 -196 185 -166 420 -427 563 -626 9 -14
|
||||
37 -53 62 -87 180 -251 389 -667 504 -1008 72 -215 146 -522 165 -690 2 -16 7
|
||||
-50 10 -75 22 -146 31 -304 30 -540 -1 -214 -7 -354 -19 -420 -2 -14 -7 -52
|
||||
-11 -85 -3 -33 -8 -69 -10 -80 -2 -10 -6 -37 -9 -60 -35 -241 -152 -645 -260
|
||||
-900 -59 -138 -178 -378 -233 -469 -23 -37 -42 -70 -42 -71 0 -14 -267 -396
|
||||
-290 -415 -3 -3 -15 -17 -26 -32 -72 -93 -203 -236 -334 -364 -508 -494 -1148
|
||||
-850 -1839 -1023 -168 -42 -380 -81 -521 -95 -25 -3 -76 -8 -115 -13 -142 -15
|
||||
-691 -8 -792 11 -10 2 -43 7 -74 10 -31 4 -65 8 -75 10 -11 2 -41 7 -68 10
|
||||
-27 4 -52 9 -55 10 -3 2 -27 7 -52 10 -81 11 -389 93 -529 141 -413 142 -798
|
||||
346 -1145 609 -327 248 -663 597 -888 926 -376 548 -621 1187 -692 1805 -3 28
|
||||
-8 64 -10 80 -25 188 -25 664 0 840 3 17 7 53 10 80 29 264 110 602 211 880
|
||||
292 808 844 1513 1560 1993 194 130 481 286 654 356 36 15 74 31 85 36 110 52
|
||||
496 172 662 205 133 27 269 49 374 61 30 3 65 8 79 10 14 2 86 7 160 10 74 4
|
||||
136 8 138 9 4 3 244 -3 368 -9z"/>
|
||||
<path d="M3445 8398 c-84 -32 -397 -246 -566 -388 -223 -187 -442 -415 -652
|
||||
-680 -123 -155 -312 -465 -420 -690 -74 -154 -190 -440 -202 -500 -1 -3 -15
|
||||
-50 -32 -105 -30 -96 -77 -280 -87 -337 -3 -15 -11 -63 -20 -105 -15 -79 -20
|
||||
-108 -31 -198 -4 -27 -8 -60 -10 -73 -13 -79 -20 -239 -20 -457 0 -280 5 -309
|
||||
70 -377 55 -60 102 -79 191 -79 88 -1 173 52 217 135 20 38 22 56 23 261 2
|
||||
262 11 413 34 555 5 30 12 73 15 94 3 21 7 48 10 60 3 12 7 33 10 49 12 64 17
|
||||
88 46 197 124 482 347 931 658 1325 151 191 360 403 534 543 40 31 74 59 77
|
||||
63 12 13 222 159 300 208 103 65 136 94 162 145 60 117 16 265 -97 332 -53 31
|
||||
-158 42 -210 22z"/>
|
||||
<path d="M1691 4003 c-91 -33 -161 -138 -161 -241 0 -74 135 -389 275 -641
|
||||
107 -195 164 -243 285 -244 138 -1 246 98 254 232 4 63 -12 111 -73 216 -68
|
||||
117 -166 320 -226 467 -50 124 -86 172 -153 204 -50 24 -147 27 -201 7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
19
hyperglass/static/images/favicon/site.webmanifest
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "hyperglass",
|
||||
"short_name": "hyperglass",
|
||||
"icons": [
|
||||
{
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#330036",
|
||||
"background_color": "#330036",
|
||||
"display": "standalone"
|
||||
}
|
||||
BIN
hyperglass/static/images/hyperglass-dark.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
hyperglass/static/images/hyperglass-light.png
Normal file
|
After Width: | Height: | Size: 255 KiB |
258
hyperglass/static/js/hyperglass.js
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
// Get the list of routers for the selected Network
|
||||
|
||||
var progress = ($('#progress'));
|
||||
var resultsbox = ($('#resultsbox'));
|
||||
resultsbox.hide();
|
||||
progress.hide();
|
||||
listNetworks ();
|
||||
clientIP ();
|
||||
|
||||
function clientIP () {
|
||||
$.getJSON("https://jsonip.com?callback=?", function(data) {
|
||||
clientip = data.ip
|
||||
});
|
||||
};
|
||||
|
||||
function listNetworks () {
|
||||
let networklist = $('#network');
|
||||
networklist.empty();
|
||||
networklist.prop('selectedIndex', 0);
|
||||
const url = '/networks';
|
||||
$.getJSON(url, function (data) {
|
||||
$.each(data, function (key, entry) {
|
||||
networklist.append($('<option></option>').attr('value', entry.network).text('AS'+entry.network));
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Update the list of routers for the *default* selected network
|
||||
$( document ).ready(function(){
|
||||
var defaultasn = $ ( "#network" ).val();
|
||||
$.ajax({
|
||||
url: `/routers/${defaultasn}`,
|
||||
context: document.body,
|
||||
type: 'get',
|
||||
success: function (data) {
|
||||
selectedRouters = JSON.parse(data)
|
||||
console.log(selectedRouters)
|
||||
updateRouters(selectedRouters);
|
||||
},
|
||||
error: function (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
$('#network').on('change', () => {
|
||||
var asn = $("select[id=network").val()
|
||||
$.ajax({
|
||||
url: `/routers/${asn}`,
|
||||
type: 'get',
|
||||
success: function (data) {
|
||||
updateRouters(JSON.parse(data));
|
||||
|
||||
},
|
||||
error: function (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function updateRouters (routers) {
|
||||
routers.forEach(function (r) {
|
||||
$('#router').append($("<option>").attr('value', r.location).attr('type', r.type).text(r.location))
|
||||
})
|
||||
}
|
||||
|
||||
// Submit Form Action
|
||||
$('#lgForm').on('submit', function () {
|
||||
|
||||
// Regex to match any IPv4 host address or CIDR prefix
|
||||
var ipv4_any = new RegExp('^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/(3[0-2]|2[0-9]|1[0-9]|[0-9]))?$');
|
||||
// Regex to match any IPv6 host address or CIDR prefix
|
||||
var ipv6_any = new RegExp('^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\/((1(1[0-9]|2[0-8]))|([0-9][0-9])|([0-9])))?$');
|
||||
// Regex to match an IPv4 CIDR prefix only (excludes a host address)
|
||||
var ipv4_cidr = new RegExp('^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/(3[0-2]|2[0-9]|1[0-9]|[0-9])?$');
|
||||
// Regex to match an IPv6 CIDR prefix only (excludes a host address)
|
||||
var ipv6_cidr = new RegExp('^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/((1(1[0-9]|2[0-8]))|([0-9][0-9])|([0-9]))?$');
|
||||
var ipv6_host = new RegExp('^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))?$')
|
||||
var cmd = $('#cmd option:selected').val();
|
||||
var routerType = $('#router option:selected').attr('type');
|
||||
var ipprefix = $('#ipprefix').val();
|
||||
var router = $('#router option:selected').val();
|
||||
// Filters selectedRouters JSON object to only the selected router, returns all attributes passed from Flask's `get_routers`
|
||||
var routersJson = selectedRouters.filter(r => r.location === router);
|
||||
// Filters above to value of `requiresIP6Cidr` as passed from Flask's `get_routers`
|
||||
var requiresIP6Cidr = routersJson[0].requiresIP6Cidr
|
||||
|
||||
// If BGP lookup, and lookup is an IPv6 address *without* CIDR prefix (e.g. 2001:db8::1, NOT 2001:db8::/48), and requiresIP6Cidr
|
||||
// is true, show an error.
|
||||
$('#ipprefix_error').hide()
|
||||
$('#ipprefix').removeClass('is-danger')
|
||||
if (cmd == 'bgp_route' && ipv6_host.test(ipprefix) == true && requiresIP6Cidr == true) {
|
||||
console.log('matched requires ipv6 cidr')
|
||||
$('#ipprefix_error').show()
|
||||
$('#ipprefix').addClass('is-danger')
|
||||
$('#ipprefix_error').html(`
|
||||
<br>
|
||||
<article class="message is-danger is-small" style="display: block;">
|
||||
<div class="message-header" style="display: block;">
|
||||
Invalid Input
|
||||
</div>
|
||||
<div id="error" style="display: block;" class="message-body">
|
||||
This router requires IPv6 BGP lookups to be and exact match in CIDR notation.
|
||||
</div>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
// If ping, and lookup is an IPv4 address *with* CIDR prefix (e.g. 192.0.2.0/24, NOT 192.0.2.1), show an error.
|
||||
else if (ipv4_cidr.test(ipprefix) == true && cmd == 'ping') {
|
||||
$('#ipprefix_error').show()
|
||||
$('#ipprefix').addClass('is-danger')
|
||||
$('#ipprefix_error').html(`
|
||||
<br>
|
||||
<article class="message is-danger is-small" style="display: block;">
|
||||
<div class="message-header" style="display: block;">
|
||||
Invalid Input
|
||||
</div>
|
||||
<div id="error" style="display: block;" class="message-body">
|
||||
<code>ping</code> does not allow network masks.
|
||||
</div>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
// If traceroute, and lookup is an IPv4 address *with* CIDR prefix (e.g. 192.0.2.0/24, NOT 192.0.2.1), show an error.
|
||||
else if (ipv4_cidr.test(ipprefix) == true && cmd == 'traceroute') {
|
||||
$('#ipprefix_error').show()
|
||||
$('#ipprefix').addClass('is-danger')
|
||||
$('#ipprefix_error').html(`
|
||||
<br>
|
||||
<article class="message is-danger is-small" style="display: block;">
|
||||
<div class="message-header" style="display: block;">
|
||||
Invalid Input
|
||||
</div>
|
||||
<div id="error" style="display: block;" class="message-body">
|
||||
<code>traceroute</code> does not allow network masks.
|
||||
</div>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
// If ping, and lookup is an IPv6 address *with* CIDR prefix (e.g. 2001:db8::/48, NOT 2001:db8::1), show an error.
|
||||
else if (ipv6_cidr.test(ipprefix) == true && cmd == 'ping') {
|
||||
$('#ipprefix_error').show()
|
||||
$('#ipprefix').addClass('is-danger')
|
||||
$('#ipprefix_error').html(`
|
||||
<br>
|
||||
<article class="message is-danger is-small" style="display: block;">
|
||||
<div class="message-header" style="display: block;">
|
||||
Invalid Input
|
||||
</div>
|
||||
<div id="error" style="display: block;" class="message-body">
|
||||
<code>ping</code> does not allow network masks.
|
||||
</div>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
// If traceroute, and lookup is an IPv6 address *with* CIDR prefix (e.g. 2001:db8::/48, NOT 2001:db8::1), show an error.
|
||||
else if (ipv6_cidr.test(ipprefix) == true && cmd == 'traceroute') {
|
||||
$('#ipprefix_error').show()
|
||||
$('#ipprefix').addClass('is-danger')
|
||||
$('#ipprefix_error').html(`
|
||||
<br>
|
||||
<article class="message is-danger is-small" style="display: block;">
|
||||
<div class="message-header" style="display: block;">
|
||||
Invalid Input
|
||||
</div>
|
||||
<div id="error" style="display: block;" class="message-body">
|
||||
<code>traceroute</code> does not allow network masks.
|
||||
</div>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
else submitForm();
|
||||
});
|
||||
|
||||
var submitForm = function() {
|
||||
progress.hide();
|
||||
var cmd = $('#cmd option:selected').val();
|
||||
var cmdtitle = cmd.replace('_', ': ');
|
||||
var network = $('#network option:selected').val();
|
||||
var router = $('#router option:selected').val();
|
||||
var routername = $('#router option:selected').text();
|
||||
var ipprefix = $('#ipprefix').val();
|
||||
var routerType = $('#router option:selected').attr('type');
|
||||
|
||||
$('#output').text("")
|
||||
$('#queryInfo').text("")
|
||||
|
||||
$('#queryInfo').html(`
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/lg', true);
|
||||
resultsbox.show()
|
||||
progress.show()
|
||||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
|
||||
xhr.send(JSON.stringify({router: router, cmd: cmd, ipprefix: ipprefix}))
|
||||
console.log(JSON.stringify({router: router, cmd: cmd, ipprefix: ipprefix}));
|
||||
|
||||
xhr_timer = window.setInterval(function() {
|
||||
if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||
progress.hide();
|
||||
window.clearTimeout(xhr_timer);
|
||||
}
|
||||
var output = document.getElementById('output')
|
||||
if (xhr.status == 415){
|
||||
console.log(XMLHttpRequest.status, 'error')
|
||||
var output = document.getElementById('output')
|
||||
$('#ipprefix').addClass('is-danger')
|
||||
output.innerHTML =
|
||||
'<br>' +
|
||||
'<div class="notification is-danger" id="output">' +
|
||||
xhr.responseText +
|
||||
'</div>'
|
||||
}
|
||||
if (xhr.status == 405){
|
||||
console.log(XMLHttpRequest.status, 'error')
|
||||
var output = document.getElementById('output')
|
||||
$('#ipprefix').addClass('is-warning')
|
||||
output.innerHTML =
|
||||
'<br>' +
|
||||
'<div class="notification is-warning" id="output">' +
|
||||
xhr.responseText +
|
||||
'</div>'
|
||||
}
|
||||
else if (xhr.status == 200){
|
||||
console.log(xhr.status, 'success')
|
||||
output.innerHTML =
|
||||
'<br>' +
|
||||
'<div class="content">' +
|
||||
'<p class="query-output" id="output">' +
|
||||
xhr.responseText +
|
||||
'</p>' +
|
||||
'</div>'
|
||||
}
|
||||
else if (xhr.status == 429){
|
||||
console.log(xhr.status, 'rate limit reached');
|
||||
$("#ratelimit").addClass("is-active");
|
||||
}
|
||||
}, 500);
|
||||
|
||||
xhr.addEventListener("error", function(e) {
|
||||
console.log("error: " + e);
|
||||
});
|
||||
}
|
||||
2
hyperglass/static/js/jquery-3.4.0.min.js
vendored
Normal file
1
hyperglass/static/js/jquery-3.4.0.min.map
Normal file
5
hyperglass/static/sass/base/_all.sass
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
@charset "utf-8"
|
||||
|
||||
@import "minireset.sass"
|
||||
@import "generic.sass"
|
||||
@import "helpers.sass"
|
||||
130
hyperglass/static/sass/base/generic.sass
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
$body-background-color: $white !default
|
||||
$body-size: 16px !default
|
||||
$body-rendering: optimizeLegibility !default
|
||||
$body-family: $family-primary !default
|
||||
$body-color: $text !default
|
||||
$body-weight: $weight-normal !default
|
||||
$body-line-height: 1.5 !default
|
||||
|
||||
$code-family: $family-code !default
|
||||
$code-padding: 0.25em 0.5em 0.25em !default
|
||||
$code-weight: normal !default
|
||||
$code-size: 0.875em !default
|
||||
|
||||
$hr-background-color: $background !default
|
||||
$hr-height: 2px !default
|
||||
$hr-margin: 1.5rem 0 !default
|
||||
|
||||
$strong-color: $text-strong !default
|
||||
$strong-weight: $weight-bold !default
|
||||
|
||||
html
|
||||
background-color: $body-background-color
|
||||
font-size: $body-size
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
-webkit-font-smoothing: antialiased
|
||||
min-width: 300px
|
||||
overflow-x: hidden
|
||||
overflow-y: scroll
|
||||
text-rendering: $body-rendering
|
||||
text-size-adjust: 100%
|
||||
|
||||
article,
|
||||
aside,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
section
|
||||
display: block
|
||||
|
||||
body,
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea
|
||||
font-family: $body-family
|
||||
|
||||
code,
|
||||
pre
|
||||
-moz-osx-font-smoothing: auto
|
||||
-webkit-font-smoothing: auto
|
||||
font-family: $code-family
|
||||
|
||||
body
|
||||
color: $body-color
|
||||
font-size: 1rem
|
||||
font-weight: $body-weight
|
||||
line-height: $body-line-height
|
||||
|
||||
// Inline
|
||||
|
||||
a
|
||||
color: $link
|
||||
cursor: pointer
|
||||
text-decoration: none
|
||||
strong
|
||||
color: currentColor
|
||||
&:hover
|
||||
color: $link-hover
|
||||
|
||||
code
|
||||
background-color: $code-background
|
||||
color: $code
|
||||
font-size: $code-size
|
||||
font-weight: $code-weight
|
||||
padding: $code-padding
|
||||
|
||||
hr
|
||||
background-color: $hr-background-color
|
||||
border: none
|
||||
display: block
|
||||
height: $hr-height
|
||||
margin: $hr-margin
|
||||
|
||||
img
|
||||
height: auto
|
||||
max-width: 100%
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"]
|
||||
vertical-align: baseline
|
||||
|
||||
small
|
||||
font-size: 0.875em
|
||||
|
||||
span
|
||||
font-style: inherit
|
||||
font-weight: inherit
|
||||
|
||||
strong
|
||||
color: $strong-color
|
||||
font-weight: $strong-weight
|
||||
|
||||
// Block
|
||||
|
||||
fieldset
|
||||
border: none
|
||||
|
||||
pre
|
||||
+overflow-touch
|
||||
background-color: $pre-background
|
||||
color: $pre
|
||||
font-size: 0.875em
|
||||
overflow-x: auto
|
||||
padding: 1.25rem 1.5rem
|
||||
white-space: pre
|
||||
word-wrap: normal
|
||||
code
|
||||
background-color: transparent
|
||||
color: currentColor
|
||||
font-size: 1em
|
||||
padding: 0
|
||||
|
||||
table
|
||||
td,
|
||||
th
|
||||
text-align: left
|
||||
vertical-align: top
|
||||
th
|
||||
color: $text-strong
|
||||
276
hyperglass/static/sass/base/helpers.sass
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
// Float
|
||||
|
||||
.is-clearfix
|
||||
+clearfix
|
||||
|
||||
.is-pulled-left
|
||||
float: left !important
|
||||
|
||||
.is-pulled-right
|
||||
float: right !important
|
||||
|
||||
// Overflow
|
||||
|
||||
.is-clipped
|
||||
overflow: hidden !important
|
||||
|
||||
// Overlay
|
||||
|
||||
.is-overlay
|
||||
@extend %overlay
|
||||
|
||||
// Typography
|
||||
|
||||
=typography-size($target:'')
|
||||
@each $size in $sizes
|
||||
$i: index($sizes, $size)
|
||||
.is-size-#{$i}#{if($target == '', '', '-' + $target)}
|
||||
font-size: $size !important
|
||||
|
||||
+typography-size()
|
||||
|
||||
+mobile
|
||||
+typography-size('mobile')
|
||||
|
||||
+tablet
|
||||
+typography-size('tablet')
|
||||
|
||||
+touch
|
||||
+typography-size('touch')
|
||||
|
||||
+desktop
|
||||
+typography-size('desktop')
|
||||
|
||||
+widescreen
|
||||
+typography-size('widescreen')
|
||||
|
||||
+fullhd
|
||||
+typography-size('fullhd')
|
||||
|
||||
$alignments: ('centered': 'center', 'justified': 'justify', 'left': 'left', 'right': 'right')
|
||||
|
||||
@each $alignment, $text-align in $alignments
|
||||
.has-text-#{$alignment}
|
||||
text-align: #{$text-align} !important
|
||||
|
||||
@each $alignment, $text-align in $alignments
|
||||
+mobile
|
||||
.has-text-#{$alignment}-mobile
|
||||
text-align: #{$text-align} !important
|
||||
+tablet
|
||||
.has-text-#{$alignment}-tablet
|
||||
text-align: #{$text-align} !important
|
||||
+tablet-only
|
||||
.has-text-#{$alignment}-tablet-only
|
||||
text-align: #{$text-align} !important
|
||||
+touch
|
||||
.has-text-#{$alignment}-touch
|
||||
text-align: #{$text-align} !important
|
||||
+desktop
|
||||
.has-text-#{$alignment}-desktop
|
||||
text-align: #{$text-align} !important
|
||||
+desktop-only
|
||||
.has-text-#{$alignment}-desktop-only
|
||||
text-align: #{$text-align} !important
|
||||
+widescreen
|
||||
.has-text-#{$alignment}-widescreen
|
||||
text-align: #{$text-align} !important
|
||||
+widescreen-only
|
||||
.has-text-#{$alignment}-widescreen-only
|
||||
text-align: #{$text-align} !important
|
||||
+fullhd
|
||||
.has-text-#{$alignment}-fullhd
|
||||
text-align: #{$text-align} !important
|
||||
|
||||
.is-capitalized
|
||||
text-transform: capitalize !important
|
||||
|
||||
.is-lowercase
|
||||
text-transform: lowercase !important
|
||||
|
||||
.is-uppercase
|
||||
text-transform: uppercase !important
|
||||
|
||||
.is-italic
|
||||
font-style: italic !important
|
||||
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
.has-text-#{$name}
|
||||
color: $color !important
|
||||
a.has-text-#{$name}
|
||||
&:hover,
|
||||
&:focus
|
||||
color: darken($color, 10%) !important
|
||||
.has-background-#{$name}
|
||||
background-color: $color !important
|
||||
|
||||
@each $name, $shade in $shades
|
||||
.has-text-#{$name}
|
||||
color: $shade !important
|
||||
.has-background-#{$name}
|
||||
background-color: $shade !important
|
||||
|
||||
.has-text-weight-light
|
||||
font-weight: $weight-light !important
|
||||
.has-text-weight-normal
|
||||
font-weight: $weight-normal !important
|
||||
.has-text-weight-semibold
|
||||
font-weight: $weight-semibold !important
|
||||
.has-text-weight-bold
|
||||
font-weight: $weight-bold !important
|
||||
|
||||
.is-family-primary
|
||||
font-family: $family-primary !important
|
||||
|
||||
.is-family-secondary
|
||||
font-family: $family-secondary !important
|
||||
|
||||
.is-family-sans-serif
|
||||
font-family: $family-sans-serif !important
|
||||
|
||||
.is-family-monospace
|
||||
font-family: $family-monospace !important
|
||||
|
||||
.is-family-code
|
||||
font-family: $family-code !important
|
||||
|
||||
// Visibility
|
||||
|
||||
$displays: 'block' 'flex' 'inline' 'inline-block' 'inline-flex'
|
||||
|
||||
@each $display in $displays
|
||||
.is-#{$display}
|
||||
display: #{$display} !important
|
||||
+mobile
|
||||
.is-#{$display}-mobile
|
||||
display: #{$display} !important
|
||||
+tablet
|
||||
.is-#{$display}-tablet
|
||||
display: #{$display} !important
|
||||
+tablet-only
|
||||
.is-#{$display}-tablet-only
|
||||
display: #{$display} !important
|
||||
+touch
|
||||
.is-#{$display}-touch
|
||||
display: #{$display} !important
|
||||
+desktop
|
||||
.is-#{$display}-desktop
|
||||
display: #{$display} !important
|
||||
+desktop-only
|
||||
.is-#{$display}-desktop-only
|
||||
display: #{$display} !important
|
||||
+widescreen
|
||||
.is-#{$display}-widescreen
|
||||
display: #{$display} !important
|
||||
+widescreen-only
|
||||
.is-#{$display}-widescreen-only
|
||||
display: #{$display} !important
|
||||
+fullhd
|
||||
.is-#{$display}-fullhd
|
||||
display: #{$display} !important
|
||||
|
||||
.is-hidden
|
||||
display: none !important
|
||||
|
||||
.is-sr-only
|
||||
border: none !important
|
||||
clip: rect(0, 0, 0, 0) !important
|
||||
height: 0.01em !important
|
||||
overflow: hidden !important
|
||||
padding: 0 !important
|
||||
position: absolute !important
|
||||
white-space: nowrap !important
|
||||
width: 0.01em !important
|
||||
|
||||
+mobile
|
||||
.is-hidden-mobile
|
||||
display: none !important
|
||||
|
||||
+tablet
|
||||
.is-hidden-tablet
|
||||
display: none !important
|
||||
|
||||
+tablet-only
|
||||
.is-hidden-tablet-only
|
||||
display: none !important
|
||||
|
||||
+touch
|
||||
.is-hidden-touch
|
||||
display: none !important
|
||||
|
||||
+desktop
|
||||
.is-hidden-desktop
|
||||
display: none !important
|
||||
|
||||
+desktop-only
|
||||
.is-hidden-desktop-only
|
||||
display: none !important
|
||||
|
||||
+widescreen
|
||||
.is-hidden-widescreen
|
||||
display: none !important
|
||||
|
||||
+widescreen-only
|
||||
.is-hidden-widescreen-only
|
||||
display: none !important
|
||||
|
||||
+fullhd
|
||||
.is-hidden-fullhd
|
||||
display: none !important
|
||||
|
||||
.is-invisible
|
||||
visibility: hidden !important
|
||||
|
||||
+mobile
|
||||
.is-invisible-mobile
|
||||
visibility: hidden !important
|
||||
|
||||
+tablet
|
||||
.is-invisible-tablet
|
||||
visibility: hidden !important
|
||||
|
||||
+tablet-only
|
||||
.is-invisible-tablet-only
|
||||
visibility: hidden !important
|
||||
|
||||
+touch
|
||||
.is-invisible-touch
|
||||
visibility: hidden !important
|
||||
|
||||
+desktop
|
||||
.is-invisible-desktop
|
||||
visibility: hidden !important
|
||||
|
||||
+desktop-only
|
||||
.is-invisible-desktop-only
|
||||
visibility: hidden !important
|
||||
|
||||
+widescreen
|
||||
.is-invisible-widescreen
|
||||
visibility: hidden !important
|
||||
|
||||
+widescreen-only
|
||||
.is-invisible-widescreen-only
|
||||
visibility: hidden !important
|
||||
|
||||
+fullhd
|
||||
.is-invisible-fullhd
|
||||
visibility: hidden !important
|
||||
|
||||
// Other
|
||||
|
||||
.is-marginless
|
||||
margin: 0 !important
|
||||
|
||||
.is-paddingless
|
||||
padding: 0 !important
|
||||
|
||||
.is-radiusless
|
||||
border-radius: 0 !important
|
||||
|
||||
.is-shadowless
|
||||
box-shadow: none !important
|
||||
|
||||
.is-unselectable
|
||||
@extend %unselectable
|
||||
84
hyperglass/static/sass/base/minireset.sass
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*! minireset.css v0.0.4 | MIT License | github.com/jgthms/minireset.css */
|
||||
// Blocks
|
||||
html,
|
||||
body,
|
||||
p,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
blockquote,
|
||||
figure,
|
||||
fieldset,
|
||||
legend,
|
||||
textarea,
|
||||
pre,
|
||||
iframe,
|
||||
hr,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
// Headings
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6
|
||||
font-size: 100%
|
||||
font-weight: normal
|
||||
|
||||
// List
|
||||
ul
|
||||
list-style: none
|
||||
|
||||
// Form
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea
|
||||
margin: 0
|
||||
|
||||
// Box sizing
|
||||
html
|
||||
box-sizing: border-box
|
||||
|
||||
*
|
||||
&,
|
||||
&::before,
|
||||
&::after
|
||||
box-sizing: inherit
|
||||
|
||||
// Media
|
||||
img,
|
||||
embed,
|
||||
iframe,
|
||||
object,
|
||||
video
|
||||
height: auto
|
||||
max-width: 100%
|
||||
|
||||
audio
|
||||
max-width: 100%
|
||||
|
||||
// Iframe
|
||||
iframe
|
||||
border: 0
|
||||
|
||||
// Table
|
||||
table
|
||||
border-collapse: collapse
|
||||
border-spacing: 0
|
||||
|
||||
td,
|
||||
th
|
||||
padding: 0
|
||||
text-align: left
|
||||
15
hyperglass/static/sass/components/_all.sass
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
@charset "utf-8"
|
||||
|
||||
@import "breadcrumb.sass"
|
||||
@import "card.sass"
|
||||
@import "dropdown.sass"
|
||||
@import "level.sass"
|
||||
@import "list.sass"
|
||||
@import "media.sass"
|
||||
@import "menu.sass"
|
||||
@import "message.sass"
|
||||
@import "modal.sass"
|
||||
@import "navbar.sass"
|
||||
@import "pagination.sass"
|
||||
@import "panel.sass"
|
||||
@import "tabs.sass"
|
||||
75
hyperglass/static/sass/components/breadcrumb.sass
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
$breadcrumb-item-color: $link !default
|
||||
$breadcrumb-item-hover-color: $link-hover !default
|
||||
$breadcrumb-item-active-color: $text-strong !default
|
||||
|
||||
$breadcrumb-item-padding-vertical: 0 !default
|
||||
$breadcrumb-item-padding-horizontal: 0.75em !default
|
||||
|
||||
$breadcrumb-item-separator-color: $grey-light !default
|
||||
|
||||
.breadcrumb
|
||||
@extend %block
|
||||
@extend %unselectable
|
||||
font-size: $size-normal
|
||||
white-space: nowrap
|
||||
a
|
||||
align-items: center
|
||||
color: $breadcrumb-item-color
|
||||
display: flex
|
||||
justify-content: center
|
||||
padding: $breadcrumb-item-padding-vertical $breadcrumb-item-padding-horizontal
|
||||
&:hover
|
||||
color: $breadcrumb-item-hover-color
|
||||
li
|
||||
align-items: center
|
||||
display: flex
|
||||
&:first-child a
|
||||
padding-left: 0
|
||||
&.is-active
|
||||
a
|
||||
color: $breadcrumb-item-active-color
|
||||
cursor: default
|
||||
pointer-events: none
|
||||
& + li::before
|
||||
color: $breadcrumb-item-separator-color
|
||||
content: "\0002f"
|
||||
ul,
|
||||
ol
|
||||
align-items: flex-start
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
justify-content: flex-start
|
||||
.icon
|
||||
&:first-child
|
||||
margin-right: 0.5em
|
||||
&:last-child
|
||||
margin-left: 0.5em
|
||||
// Alignment
|
||||
&.is-centered
|
||||
ol,
|
||||
ul
|
||||
justify-content: center
|
||||
&.is-right
|
||||
ol,
|
||||
ul
|
||||
justify-content: flex-end
|
||||
// Sizes
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
// Styles
|
||||
&.has-arrow-separator
|
||||
li + li::before
|
||||
content: "\02192"
|
||||
&.has-bullet-separator
|
||||
li + li::before
|
||||
content: "\02022"
|
||||
&.has-dot-separator
|
||||
li + li::before
|
||||
content: "\000b7"
|
||||
&.has-succeeds-separator
|
||||
li + li::before
|
||||
content: "\0227B"
|
||||
74
hyperglass/static/sass/components/card.sass
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
$card-color: $text !default
|
||||
$card-background-color: $white !default
|
||||
$card-shadow: 0 2px 3px rgba($black, 0.1), 0 0 0 1px rgba($black, 0.1) !default
|
||||
|
||||
$card-header-background-color: transparent !default
|
||||
$card-header-color: $text-strong !default
|
||||
$card-header-shadow: 0 1px 2px rgba($black, 0.1) !default
|
||||
$card-header-weight: $weight-bold !default
|
||||
|
||||
$card-content-background-color: transparent !default
|
||||
|
||||
$card-footer-background-color: transparent !default
|
||||
$card-footer-border-top: 1px solid $border !default
|
||||
|
||||
.card
|
||||
background-color: $card-background-color
|
||||
box-shadow: $card-shadow
|
||||
color: $card-color
|
||||
max-width: 100%
|
||||
position: relative
|
||||
|
||||
.card-header
|
||||
background-color: $card-header-background-color
|
||||
align-items: stretch
|
||||
box-shadow: $card-header-shadow
|
||||
display: flex
|
||||
|
||||
.card-header-title
|
||||
align-items: center
|
||||
color: $card-header-color
|
||||
display: flex
|
||||
flex-grow: 1
|
||||
font-weight: $card-header-weight
|
||||
padding: 0.75rem
|
||||
&.is-centered
|
||||
justify-content: center
|
||||
|
||||
.card-header-icon
|
||||
align-items: center
|
||||
cursor: pointer
|
||||
display: flex
|
||||
justify-content: center
|
||||
padding: 0.75rem
|
||||
|
||||
.card-image
|
||||
display: block
|
||||
position: relative
|
||||
|
||||
.card-content
|
||||
background-color: $card-content-background-color
|
||||
padding: 1.5rem
|
||||
|
||||
.card-footer
|
||||
background-color: $card-footer-background-color
|
||||
border-top: $card-footer-border-top
|
||||
align-items: stretch
|
||||
display: flex
|
||||
|
||||
.card-footer-item
|
||||
align-items: center
|
||||
display: flex
|
||||
flex-basis: 0
|
||||
flex-grow: 1
|
||||
flex-shrink: 0
|
||||
justify-content: center
|
||||
padding: 0.75rem
|
||||
&:not(:last-child)
|
||||
border-right: $card-footer-border-top
|
||||
|
||||
// Combinations
|
||||
|
||||
.card
|
||||
.media:not(:last-child)
|
||||
margin-bottom: 0.75rem
|
||||
77
hyperglass/static/sass/components/dropdown.sass
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
$dropdown-content-background-color: $white !default
|
||||
$dropdown-content-arrow: $link !default
|
||||
$dropdown-content-offset: 4px !default
|
||||
$dropdown-content-radius: $radius !default
|
||||
$dropdown-content-shadow: 0 2px 3px rgba($black, 0.1), 0 0 0 1px rgba($black, 0.1) !default
|
||||
$dropdown-content-z: 20 !default
|
||||
|
||||
$dropdown-item-color: $grey-dark !default
|
||||
$dropdown-item-hover-color: $black !default
|
||||
$dropdown-item-hover-background-color: $background !default
|
||||
$dropdown-item-active-color: $link-invert !default
|
||||
$dropdown-item-active-background-color: $link !default
|
||||
|
||||
$dropdown-divider-background-color: $border !default
|
||||
|
||||
.dropdown
|
||||
display: inline-flex
|
||||
position: relative
|
||||
vertical-align: top
|
||||
&.is-active,
|
||||
&.is-hoverable:hover
|
||||
.dropdown-menu
|
||||
display: block
|
||||
&.is-right
|
||||
.dropdown-menu
|
||||
left: auto
|
||||
right: 0
|
||||
&.is-up
|
||||
.dropdown-menu
|
||||
bottom: 100%
|
||||
padding-bottom: $dropdown-content-offset
|
||||
padding-top: initial
|
||||
top: auto
|
||||
|
||||
.dropdown-menu
|
||||
display: none
|
||||
left: 0
|
||||
min-width: 12rem
|
||||
padding-top: $dropdown-content-offset
|
||||
position: absolute
|
||||
top: 100%
|
||||
z-index: $dropdown-content-z
|
||||
|
||||
.dropdown-content
|
||||
background-color: $dropdown-content-background-color
|
||||
border-radius: $dropdown-content-radius
|
||||
box-shadow: $dropdown-content-shadow
|
||||
padding-bottom: 0.5rem
|
||||
padding-top: 0.5rem
|
||||
|
||||
.dropdown-item
|
||||
color: $dropdown-item-color
|
||||
display: block
|
||||
font-size: 0.875rem
|
||||
line-height: 1.5
|
||||
padding: 0.375rem 1rem
|
||||
position: relative
|
||||
|
||||
a.dropdown-item,
|
||||
button.dropdown-item
|
||||
padding-right: 3rem
|
||||
text-align: left
|
||||
white-space: nowrap
|
||||
width: 100%
|
||||
&:hover
|
||||
background-color: $dropdown-item-hover-background-color
|
||||
color: $dropdown-item-hover-color
|
||||
&.is-active
|
||||
background-color: $dropdown-item-active-background-color
|
||||
color: $dropdown-item-active-color
|
||||
|
||||
.dropdown-divider
|
||||
background-color: $dropdown-divider-background-color
|
||||
border: none
|
||||
display: block
|
||||
height: 1px
|
||||
margin: 0.5rem 0
|
||||
75
hyperglass/static/sass/components/level.sass
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
.level
|
||||
@extend %block
|
||||
align-items: center
|
||||
justify-content: space-between
|
||||
code
|
||||
border-radius: $radius
|
||||
img
|
||||
display: inline-block
|
||||
vertical-align: top
|
||||
// Modifiers
|
||||
&.is-mobile
|
||||
display: flex
|
||||
.level-left,
|
||||
.level-right
|
||||
display: flex
|
||||
.level-left + .level-right
|
||||
margin-top: 0
|
||||
.level-item
|
||||
&:not(:last-child)
|
||||
margin-bottom: 0
|
||||
margin-right: 0.75rem
|
||||
&:not(.is-narrow)
|
||||
flex-grow: 1
|
||||
// Responsiveness
|
||||
+tablet
|
||||
display: flex
|
||||
& > .level-item
|
||||
&:not(.is-narrow)
|
||||
flex-grow: 1
|
||||
|
||||
.level-item
|
||||
align-items: center
|
||||
display: flex
|
||||
flex-basis: auto
|
||||
flex-grow: 0
|
||||
flex-shrink: 0
|
||||
justify-content: center
|
||||
.title,
|
||||
.subtitle
|
||||
margin-bottom: 0
|
||||
// Responsiveness
|
||||
+mobile
|
||||
&:not(:last-child)
|
||||
margin-bottom: 0.75rem
|
||||
|
||||
.level-left,
|
||||
.level-right
|
||||
flex-basis: auto
|
||||
flex-grow: 0
|
||||
flex-shrink: 0
|
||||
.level-item
|
||||
// Modifiers
|
||||
&.is-flexible
|
||||
flex-grow: 1
|
||||
// Responsiveness
|
||||
+tablet
|
||||
&:not(:last-child)
|
||||
margin-right: 0.75rem
|
||||
|
||||
.level-left
|
||||
align-items: center
|
||||
justify-content: flex-start
|
||||
// Responsiveness
|
||||
+mobile
|
||||
& + .level-right
|
||||
margin-top: 1.5rem
|
||||
+tablet
|
||||
display: flex
|
||||
|
||||
.level-right
|
||||
align-items: center
|
||||
justify-content: flex-end
|
||||
// Responsiveness
|
||||
+tablet
|
||||
display: flex
|
||||
39
hyperglass/static/sass/components/list.sass
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
$list-background-color: $white !default
|
||||
$list-shadow: 0 2px 3px rgba($black, 0.1), 0 0 0 1px rgba($black, 0.1) !default
|
||||
$list-radius: $radius !default
|
||||
|
||||
$list-item-border: 1px solid $border !default
|
||||
$list-item-color: $text !default
|
||||
$list-item-active-background-color: $link !default
|
||||
$list-item-active-color: $link-invert !default
|
||||
$list-item-hover-background-color: $background !default
|
||||
|
||||
.list
|
||||
@extend %block
|
||||
background-color: $list-background-color
|
||||
border-radius: $list-radius
|
||||
box-shadow: $list-shadow
|
||||
// &.is-hoverable > .list-item:hover:not(.is-active)
|
||||
// background-color: $list-item-hover-background-color
|
||||
// cursor: pointer
|
||||
|
||||
.list-item
|
||||
display: block
|
||||
padding: 0.5em 1em
|
||||
&:not(a)
|
||||
color: $list-item-color
|
||||
&:first-child
|
||||
border-top-left-radius: $list-radius
|
||||
border-top-right-radius: $list-radius
|
||||
&:last-child
|
||||
border-top-left-radius: $list-radius
|
||||
border-top-right-radius: $list-radius
|
||||
&:not(:last-child)
|
||||
border-bottom: $list-item-border
|
||||
&.is-active
|
||||
background-color: $list-item-active-background-color
|
||||
color: $list-item-active-color
|
||||
|
||||
a.list-item
|
||||
background-color: $list-item-hover-background-color
|
||||
cursor: pointer
|
||||
48
hyperglass/static/sass/components/media.sass
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
.media
|
||||
align-items: flex-start
|
||||
display: flex
|
||||
text-align: left
|
||||
.content:not(:last-child)
|
||||
margin-bottom: 0.75rem
|
||||
.media
|
||||
border-top: 1px solid rgba($border, 0.5)
|
||||
display: flex
|
||||
padding-top: 0.75rem
|
||||
.content:not(:last-child),
|
||||
.control:not(:last-child)
|
||||
margin-bottom: 0.5rem
|
||||
.media
|
||||
padding-top: 0.5rem
|
||||
& + .media
|
||||
margin-top: 0.5rem
|
||||
& + .media
|
||||
border-top: 1px solid rgba($border, 0.5)
|
||||
margin-top: 1rem
|
||||
padding-top: 1rem
|
||||
// Sizes
|
||||
&.is-large
|
||||
& + .media
|
||||
margin-top: 1.5rem
|
||||
padding-top: 1.5rem
|
||||
|
||||
.media-left,
|
||||
.media-right
|
||||
flex-basis: auto
|
||||
flex-grow: 0
|
||||
flex-shrink: 0
|
||||
|
||||
.media-left
|
||||
margin-right: 1rem
|
||||
|
||||
.media-right
|
||||
margin-left: 1rem
|
||||
|
||||
.media-content
|
||||
flex-basis: auto
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
text-align: left
|
||||
|
||||
+mobile
|
||||
.media-content
|
||||
overflow-x: auto
|
||||
50
hyperglass/static/sass/components/menu.sass
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
$menu-item-color: $text !default
|
||||
$menu-item-radius: $radius-small !default
|
||||
$menu-item-hover-color: $text-strong !default
|
||||
$menu-item-hover-background-color: $background !default
|
||||
$menu-item-active-color: $link-invert !default
|
||||
$menu-item-active-background-color: $link !default
|
||||
|
||||
$menu-list-border-left: 1px solid $border !default
|
||||
|
||||
$menu-label-color: $text-light !default
|
||||
|
||||
.menu
|
||||
font-size: $size-normal
|
||||
// Sizes
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
|
||||
.menu-list
|
||||
line-height: 1.25
|
||||
a
|
||||
border-radius: $menu-item-radius
|
||||
color: $menu-item-color
|
||||
display: block
|
||||
padding: 0.5em 0.75em
|
||||
&:hover
|
||||
background-color: $menu-item-hover-background-color
|
||||
color: $menu-item-hover-color
|
||||
// Modifiers
|
||||
&.is-active
|
||||
background-color: $menu-item-active-background-color
|
||||
color: $menu-item-active-color
|
||||
li
|
||||
ul
|
||||
border-left: $menu-list-border-left
|
||||
margin: 0.75em
|
||||
padding-left: 0.75em
|
||||
|
||||
.menu-label
|
||||
color: $menu-label-color
|
||||
font-size: 0.75em
|
||||
letter-spacing: 0.1em
|
||||
text-transform: uppercase
|
||||
&:not(:first-child)
|
||||
margin-top: 1em
|
||||
&:not(:last-child)
|
||||
margin-bottom: 1em
|
||||
86
hyperglass/static/sass/components/message.sass
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
$message-background-color: $background !default
|
||||
$message-radius: $radius !default
|
||||
|
||||
$message-header-background-color: $text !default
|
||||
$message-header-color: $text-invert !default
|
||||
$message-header-weight: $weight-bold !default
|
||||
$message-header-padding: 0.75em 1em !default
|
||||
$message-header-radius: $radius !default
|
||||
|
||||
$message-body-border-color: $border !default
|
||||
$message-body-border-width: 0 0 0 4px !default
|
||||
$message-body-color: $text !default
|
||||
$message-body-padding: 1.25em 1.5em !default
|
||||
$message-body-radius: $radius !default
|
||||
|
||||
$message-body-pre-background-color: $white !default
|
||||
$message-body-pre-code-background-color: transparent !default
|
||||
|
||||
$message-header-body-border-width: 0 !default
|
||||
|
||||
.message
|
||||
@extend %block
|
||||
background-color: $message-background-color
|
||||
border-radius: $message-radius
|
||||
font-size: $size-normal
|
||||
strong
|
||||
color: currentColor
|
||||
a:not(.button):not(.tag):not(.dropdown-item)
|
||||
color: currentColor
|
||||
text-decoration: underline
|
||||
// Sizes
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
// Colors
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
$color-invert: nth($pair, 2)
|
||||
$color-lightning: max((100% - lightness($color)) - 2%, 0%)
|
||||
$color-luminance: colorLuminance($color)
|
||||
$darken-percentage: $color-luminance * 70%
|
||||
$desaturate-percentage: $color-luminance * 30%
|
||||
&.is-#{$name}
|
||||
background-color: lighten($color, $color-lightning)
|
||||
.message-header
|
||||
background-color: $color
|
||||
color: $color-invert
|
||||
.message-body
|
||||
border-color: $color
|
||||
color: desaturate(darken($color, $darken-percentage), $desaturate-percentage)
|
||||
|
||||
.message-header
|
||||
align-items: center
|
||||
background-color: $message-header-background-color
|
||||
border-radius: $message-header-radius $message-header-radius 0 0
|
||||
color: $message-header-color
|
||||
display: flex
|
||||
font-weight: $message-header-weight
|
||||
justify-content: space-between
|
||||
line-height: 1.25
|
||||
padding: $message-header-padding
|
||||
position: relative
|
||||
.delete
|
||||
flex-grow: 0
|
||||
flex-shrink: 0
|
||||
margin-left: 0.75em
|
||||
& + .message-body
|
||||
border-width: $message-header-body-border-width
|
||||
border-top-left-radius: 0
|
||||
border-top-right-radius: 0
|
||||
|
||||
.message-body
|
||||
border-color: $message-body-border-color
|
||||
border-radius: $message-body-radius
|
||||
border-style: solid
|
||||
border-width: $message-body-border-width
|
||||
color: $message-body-color
|
||||
padding: $message-body-padding
|
||||
code,
|
||||
pre
|
||||
background-color: $message-body-pre-background-color
|
||||
pre code
|
||||
background-color: $message-body-pre-code-background-color
|
||||
113
hyperglass/static/sass/components/modal.sass
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
$modal-z: 40 !default
|
||||
|
||||
$modal-background-background-color: rgba($black, 0.86) !default
|
||||
|
||||
$modal-content-width: 640px !default
|
||||
$modal-content-margin-mobile: 20px !default
|
||||
$modal-content-spacing-mobile: 160px !default
|
||||
$modal-content-spacing-tablet: 40px !default
|
||||
|
||||
$modal-close-dimensions: 40px !default
|
||||
$modal-close-right: 20px !default
|
||||
$modal-close-top: 20px !default
|
||||
|
||||
$modal-card-spacing: 40px !default
|
||||
|
||||
$modal-card-head-background-color: $background !default
|
||||
$modal-card-head-border-bottom: 1px solid $border !default
|
||||
$modal-card-head-padding: 20px !default
|
||||
$modal-card-head-radius: $radius-large !default
|
||||
|
||||
$modal-card-title-color: $text-strong !default
|
||||
$modal-card-title-line-height: 1 !default
|
||||
$modal-card-title-size: $size-4 !default
|
||||
|
||||
$modal-card-foot-radius: $radius-large !default
|
||||
$modal-card-foot-border-top: 1px solid $border !default
|
||||
|
||||
$modal-card-body-background-color: $white !default
|
||||
$modal-card-body-padding: 20px !default
|
||||
|
||||
.modal
|
||||
@extend %overlay
|
||||
align-items: center
|
||||
display: none
|
||||
flex-direction: column
|
||||
justify-content: center
|
||||
overflow: hidden
|
||||
position: fixed
|
||||
z-index: $modal-z
|
||||
// Modifiers
|
||||
&.is-active
|
||||
display: flex
|
||||
|
||||
.modal-background
|
||||
@extend %overlay
|
||||
background-color: $modal-background-background-color
|
||||
|
||||
.modal-content,
|
||||
.modal-card
|
||||
margin: 0 $modal-content-margin-mobile
|
||||
max-height: calc(100vh - #{$modal-content-spacing-mobile})
|
||||
overflow: auto
|
||||
position: relative
|
||||
width: 100%
|
||||
// Responsiveness
|
||||
+tablet
|
||||
margin: 0 auto
|
||||
max-height: calc(100vh - #{$modal-content-spacing-tablet})
|
||||
width: $modal-content-width
|
||||
|
||||
.modal-close
|
||||
@extend %delete
|
||||
background: none
|
||||
height: $modal-close-dimensions
|
||||
position: fixed
|
||||
right: $modal-close-right
|
||||
top: $modal-close-top
|
||||
width: $modal-close-dimensions
|
||||
|
||||
.modal-card
|
||||
display: flex
|
||||
flex-direction: column
|
||||
max-height: calc(100vh - #{$modal-card-spacing})
|
||||
overflow: hidden
|
||||
-ms-overflow-y: visible
|
||||
|
||||
.modal-card-head,
|
||||
.modal-card-foot
|
||||
align-items: center
|
||||
background-color: $modal-card-head-background-color
|
||||
display: flex
|
||||
flex-shrink: 0
|
||||
justify-content: flex-start
|
||||
padding: $modal-card-head-padding
|
||||
position: relative
|
||||
|
||||
.modal-card-head
|
||||
border-bottom: $modal-card-head-border-bottom
|
||||
border-top-left-radius: $modal-card-head-radius
|
||||
border-top-right-radius: $modal-card-head-radius
|
||||
|
||||
.modal-card-title
|
||||
color: $modal-card-title-color
|
||||
flex-grow: 1
|
||||
flex-shrink: 0
|
||||
font-size: $modal-card-title-size
|
||||
line-height: $modal-card-title-line-height
|
||||
|
||||
.modal-card-foot
|
||||
border-bottom-left-radius: $modal-card-foot-radius
|
||||
border-bottom-right-radius: $modal-card-foot-radius
|
||||
border-top: $modal-card-foot-border-top
|
||||
.button
|
||||
&:not(:last-child)
|
||||
margin-right: 10px
|
||||
|
||||
.modal-card-body
|
||||
+overflow-touch
|
||||
background-color: $modal-card-body-background-color
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
overflow: auto
|
||||
padding: $modal-card-body-padding
|
||||
428
hyperglass/static/sass/components/navbar.sass
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
$navbar-background-color: $white !default
|
||||
$navbar-box-shadow-size: 0 2px 0 0 !default
|
||||
$navbar-box-shadow-color: $background !default
|
||||
$navbar-height: 3.25rem !default
|
||||
$navbar-padding-vertical: 1rem !default
|
||||
$navbar-padding-horizontal: 2rem !default
|
||||
$navbar-z: 30 !default
|
||||
$navbar-fixed-z: 30 !default
|
||||
|
||||
$navbar-item-color: $grey-dark !default
|
||||
$navbar-item-hover-color: $link !default
|
||||
$navbar-item-hover-background-color: $white-bis !default
|
||||
$navbar-item-active-color: $black !default
|
||||
$navbar-item-active-background-color: transparent !default
|
||||
$navbar-item-img-max-height: 1.75rem !default
|
||||
|
||||
$navbar-burger-color: $navbar-item-color !default
|
||||
|
||||
$navbar-tab-hover-background-color: transparent !default
|
||||
$navbar-tab-hover-border-bottom-color: $link !default
|
||||
$navbar-tab-active-color: $link !default
|
||||
$navbar-tab-active-background-color: transparent !default
|
||||
$navbar-tab-active-border-bottom-color: $link !default
|
||||
$navbar-tab-active-border-bottom-style: solid !default
|
||||
$navbar-tab-active-border-bottom-width: 3px !default
|
||||
|
||||
$navbar-dropdown-background-color: $white !default
|
||||
$navbar-dropdown-border-top: 2px solid $border !default
|
||||
$navbar-dropdown-offset: -4px !default
|
||||
$navbar-dropdown-arrow: $link !default
|
||||
$navbar-dropdown-radius: $radius-large !default
|
||||
$navbar-dropdown-z: 20 !default
|
||||
|
||||
$navbar-dropdown-boxed-radius: $radius-large !default
|
||||
$navbar-dropdown-boxed-shadow: 0 8px 8px rgba($black, 0.1), 0 0 0 1px rgba($black, 0.1) !default
|
||||
|
||||
$navbar-dropdown-item-hover-color: $black !default
|
||||
$navbar-dropdown-item-hover-background-color: $background !default
|
||||
$navbar-dropdown-item-active-color: $link !default
|
||||
$navbar-dropdown-item-active-background-color: $background !default
|
||||
|
||||
$navbar-divider-background-color: $background !default
|
||||
$navbar-divider-height: 2px !default
|
||||
|
||||
$navbar-bottom-box-shadow-size: 0 -2px 0 0 !default
|
||||
|
||||
$navbar-breakpoint: $desktop !default
|
||||
|
||||
=navbar-fixed
|
||||
left: 0
|
||||
position: fixed
|
||||
right: 0
|
||||
z-index: $navbar-fixed-z
|
||||
|
||||
.navbar
|
||||
background-color: $navbar-background-color
|
||||
min-height: $navbar-height
|
||||
position: relative
|
||||
z-index: $navbar-z
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
$color-invert: nth($pair, 2)
|
||||
&.is-#{$name}
|
||||
background-color: $color
|
||||
color: $color-invert
|
||||
.navbar-brand
|
||||
& > .navbar-item,
|
||||
.navbar-link
|
||||
color: $color-invert
|
||||
& > a.navbar-item,
|
||||
.navbar-link
|
||||
&:hover,
|
||||
&.is-active
|
||||
background-color: darken($color, 5%)
|
||||
color: $color-invert
|
||||
.navbar-link
|
||||
&::after
|
||||
border-color: $color-invert
|
||||
.navbar-burger
|
||||
color: $color-invert
|
||||
+from($navbar-breakpoint)
|
||||
.navbar-start,
|
||||
.navbar-end
|
||||
& > .navbar-item,
|
||||
.navbar-link
|
||||
color: $color-invert
|
||||
& > a.navbar-item,
|
||||
.navbar-link
|
||||
&:hover,
|
||||
&.is-active
|
||||
background-color: darken($color, 5%)
|
||||
color: $color-invert
|
||||
.navbar-link
|
||||
&::after
|
||||
border-color: $color-invert
|
||||
.navbar-item.has-dropdown:hover .navbar-link,
|
||||
.navbar-item.has-dropdown.is-active .navbar-link
|
||||
background-color: darken($color, 5%)
|
||||
color: $color-invert
|
||||
.navbar-dropdown
|
||||
a.navbar-item
|
||||
&.is-active
|
||||
background-color: $color
|
||||
color: $color-invert
|
||||
& > .container
|
||||
align-items: stretch
|
||||
display: flex
|
||||
min-height: $navbar-height
|
||||
width: 100%
|
||||
&.has-shadow
|
||||
box-shadow: $navbar-box-shadow-size $navbar-box-shadow-color
|
||||
&.is-fixed-bottom,
|
||||
&.is-fixed-top
|
||||
+navbar-fixed
|
||||
&.is-fixed-bottom
|
||||
bottom: 0
|
||||
&.has-shadow
|
||||
box-shadow: $navbar-bottom-box-shadow-size $navbar-box-shadow-color
|
||||
&.is-fixed-top
|
||||
top: 0
|
||||
|
||||
html,
|
||||
body
|
||||
&.has-navbar-fixed-top
|
||||
padding-top: $navbar-height
|
||||
&.has-navbar-fixed-bottom
|
||||
padding-bottom: $navbar-height
|
||||
|
||||
.navbar-brand,
|
||||
.navbar-tabs
|
||||
align-items: stretch
|
||||
display: flex
|
||||
flex-shrink: 0
|
||||
min-height: $navbar-height
|
||||
|
||||
.navbar-brand
|
||||
a.navbar-item
|
||||
&:hover
|
||||
background-color: transparent
|
||||
|
||||
.navbar-tabs
|
||||
+overflow-touch
|
||||
max-width: 100vw
|
||||
overflow-x: auto
|
||||
overflow-y: hidden
|
||||
|
||||
.navbar-burger
|
||||
color: $navbar-burger-color
|
||||
+hamburger($navbar-height)
|
||||
margin-left: auto
|
||||
|
||||
.navbar-menu
|
||||
display: none
|
||||
|
||||
.navbar-item,
|
||||
.navbar-link
|
||||
color: $navbar-item-color
|
||||
display: block
|
||||
line-height: 1.5
|
||||
padding: 0.5rem 0.75rem
|
||||
position: relative
|
||||
.icon
|
||||
&:only-child
|
||||
margin-left: -0.25rem
|
||||
margin-right: -0.25rem
|
||||
|
||||
a.navbar-item,
|
||||
.navbar-link
|
||||
cursor: pointer
|
||||
&:hover,
|
||||
&.is-active
|
||||
background-color: $navbar-item-hover-background-color
|
||||
color: $navbar-item-hover-color
|
||||
|
||||
.navbar-item
|
||||
display: block
|
||||
flex-grow: 0
|
||||
flex-shrink: 0
|
||||
img
|
||||
max-height: $navbar-item-img-max-height
|
||||
&.has-dropdown
|
||||
padding: 0
|
||||
&.is-expanded
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
&.is-tab
|
||||
border-bottom: 1px solid transparent
|
||||
min-height: $navbar-height
|
||||
padding-bottom: calc(0.5rem - 1px)
|
||||
&:hover
|
||||
background-color: $navbar-tab-hover-background-color
|
||||
border-bottom-color: $navbar-tab-hover-border-bottom-color
|
||||
&.is-active
|
||||
background-color: $navbar-tab-active-background-color
|
||||
border-bottom-color: $navbar-tab-active-border-bottom-color
|
||||
border-bottom-style: $navbar-tab-active-border-bottom-style
|
||||
border-bottom-width: $navbar-tab-active-border-bottom-width
|
||||
color: $navbar-tab-active-color
|
||||
padding-bottom: calc(0.5rem - #{$navbar-tab-active-border-bottom-width})
|
||||
|
||||
.navbar-content
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
|
||||
.navbar-link:not(.is-arrowless)
|
||||
padding-right: 2.5em
|
||||
&::after
|
||||
@extend %arrow
|
||||
border-color: $navbar-dropdown-arrow
|
||||
margin-top: -0.375em
|
||||
right: 1.125em
|
||||
|
||||
.navbar-dropdown
|
||||
font-size: 0.875rem
|
||||
padding-bottom: 0.5rem
|
||||
padding-top: 0.5rem
|
||||
.navbar-item
|
||||
padding-left: 1.5rem
|
||||
padding-right: 1.5rem
|
||||
|
||||
.navbar-divider
|
||||
background-color: $navbar-divider-background-color
|
||||
border: none
|
||||
display: none
|
||||
height: $navbar-divider-height
|
||||
margin: 0.5rem 0
|
||||
|
||||
+until($navbar-breakpoint)
|
||||
.navbar > .container
|
||||
display: block
|
||||
.navbar-brand,
|
||||
.navbar-tabs
|
||||
.navbar-item
|
||||
align-items: center
|
||||
display: flex
|
||||
.navbar-link
|
||||
&::after
|
||||
display: none
|
||||
.navbar-menu
|
||||
background-color: $navbar-background-color
|
||||
box-shadow: 0 8px 16px rgba($black, 0.1)
|
||||
padding: 0.5rem 0
|
||||
&.is-active
|
||||
display: block
|
||||
// Fixed navbar
|
||||
.navbar
|
||||
&.is-fixed-bottom-touch,
|
||||
&.is-fixed-top-touch
|
||||
+navbar-fixed
|
||||
&.is-fixed-bottom-touch
|
||||
bottom: 0
|
||||
&.has-shadow
|
||||
box-shadow: 0 -2px 3px rgba($black, 0.1)
|
||||
&.is-fixed-top-touch
|
||||
top: 0
|
||||
&.is-fixed-top,
|
||||
&.is-fixed-top-touch
|
||||
.navbar-menu
|
||||
+overflow-touch
|
||||
max-height: calc(100vh - #{$navbar-height})
|
||||
overflow: auto
|
||||
html,
|
||||
body
|
||||
&.has-navbar-fixed-top-touch
|
||||
padding-top: $navbar-height
|
||||
&.has-navbar-fixed-bottom-touch
|
||||
padding-bottom: $navbar-height
|
||||
|
||||
+from($navbar-breakpoint)
|
||||
.navbar,
|
||||
.navbar-menu,
|
||||
.navbar-start,
|
||||
.navbar-end
|
||||
align-items: stretch
|
||||
display: flex
|
||||
.navbar
|
||||
min-height: $navbar-height
|
||||
&.is-spaced
|
||||
padding: $navbar-padding-vertical $navbar-padding-horizontal
|
||||
.navbar-start,
|
||||
.navbar-end
|
||||
align-items: center
|
||||
a.navbar-item,
|
||||
.navbar-link
|
||||
border-radius: $radius
|
||||
&.is-transparent
|
||||
a.navbar-item,
|
||||
.navbar-link
|
||||
&:hover,
|
||||
&.is-active
|
||||
background-color: transparent !important
|
||||
.navbar-item.has-dropdown
|
||||
&.is-active,
|
||||
&.is-hoverable:hover
|
||||
.navbar-link
|
||||
background-color: transparent !important
|
||||
.navbar-dropdown
|
||||
a.navbar-item
|
||||
&:hover
|
||||
background-color: $navbar-dropdown-item-hover-background-color
|
||||
color: $navbar-dropdown-item-hover-color
|
||||
&.is-active
|
||||
background-color: $navbar-dropdown-item-active-background-color
|
||||
color: $navbar-dropdown-item-active-color
|
||||
.navbar-burger
|
||||
display: none
|
||||
.navbar-item,
|
||||
.navbar-link
|
||||
align-items: center
|
||||
display: flex
|
||||
.navbar-item
|
||||
display: flex
|
||||
&.has-dropdown
|
||||
align-items: stretch
|
||||
&.has-dropdown-up
|
||||
.navbar-link::after
|
||||
transform: rotate(135deg) translate(0.25em, -0.25em)
|
||||
.navbar-dropdown
|
||||
border-bottom: $navbar-dropdown-border-top
|
||||
border-radius: $navbar-dropdown-radius $navbar-dropdown-radius 0 0
|
||||
border-top: none
|
||||
bottom: 100%
|
||||
box-shadow: 0 -8px 8px rgba($black, 0.1)
|
||||
top: auto
|
||||
&.is-active,
|
||||
&.is-hoverable:hover
|
||||
.navbar-dropdown
|
||||
display: block
|
||||
.navbar.is-spaced &,
|
||||
&.is-boxed
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
transform: translateY(0)
|
||||
.navbar-menu
|
||||
flex-grow: 1
|
||||
flex-shrink: 0
|
||||
.navbar-start
|
||||
justify-content: flex-start
|
||||
margin-right: auto
|
||||
.navbar-end
|
||||
justify-content: flex-end
|
||||
margin-left: auto
|
||||
.navbar-dropdown
|
||||
background-color: $navbar-dropdown-background-color
|
||||
border-bottom-left-radius: $navbar-dropdown-radius
|
||||
border-bottom-right-radius: $navbar-dropdown-radius
|
||||
border-top: $navbar-dropdown-border-top
|
||||
box-shadow: 0 8px 8px rgba($black, 0.1)
|
||||
display: none
|
||||
font-size: 0.875rem
|
||||
left: 0
|
||||
min-width: 100%
|
||||
position: absolute
|
||||
top: 100%
|
||||
z-index: $navbar-dropdown-z
|
||||
.navbar-item
|
||||
padding: 0.375rem 1rem
|
||||
white-space: nowrap
|
||||
a.navbar-item
|
||||
padding-right: 3rem
|
||||
&:hover
|
||||
background-color: $navbar-dropdown-item-hover-background-color
|
||||
color: $navbar-dropdown-item-hover-color
|
||||
&.is-active
|
||||
background-color: $navbar-dropdown-item-active-background-color
|
||||
color: $navbar-dropdown-item-active-color
|
||||
.navbar.is-spaced &,
|
||||
&.is-boxed
|
||||
border-radius: $navbar-dropdown-boxed-radius
|
||||
border-top: none
|
||||
box-shadow: $navbar-dropdown-boxed-shadow
|
||||
display: block
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
top: calc(100% + (#{$navbar-dropdown-offset}))
|
||||
transform: translateY(-5px)
|
||||
transition-duration: $speed
|
||||
transition-property: opacity, transform
|
||||
&.is-right
|
||||
left: auto
|
||||
right: 0
|
||||
.navbar-divider
|
||||
display: block
|
||||
.navbar > .container,
|
||||
.container > .navbar
|
||||
.navbar-brand
|
||||
margin-left: -.75rem
|
||||
.navbar-menu
|
||||
margin-right: -.75rem
|
||||
// Fixed navbar
|
||||
.navbar
|
||||
&.is-fixed-bottom-desktop,
|
||||
&.is-fixed-top-desktop
|
||||
+navbar-fixed
|
||||
&.is-fixed-bottom-desktop
|
||||
bottom: 0
|
||||
&.has-shadow
|
||||
box-shadow: 0 -2px 3px rgba($black, 0.1)
|
||||
&.is-fixed-top-desktop
|
||||
top: 0
|
||||
html,
|
||||
body
|
||||
&.has-navbar-fixed-top-desktop
|
||||
padding-top: $navbar-height
|
||||
&.has-navbar-fixed-bottom-desktop
|
||||
padding-bottom: $navbar-height
|
||||
&.has-spaced-navbar-fixed-top
|
||||
padding-top: $navbar-height + ($navbar-padding-vertical * 2)
|
||||
&.has-spaced-navbar-fixed-bottom
|
||||
padding-bottom: $navbar-height + ($navbar-padding-vertical * 2)
|
||||
// Hover/Active states
|
||||
a.navbar-item,
|
||||
.navbar-link
|
||||
&.is-active
|
||||
color: $navbar-item-active-color
|
||||
&.is-active:not(:hover)
|
||||
background-color: $navbar-item-active-background-color
|
||||
.navbar-item.has-dropdown
|
||||
&:hover,
|
||||
&.is-active
|
||||
.navbar-link
|
||||
background-color: $navbar-item-hover-background-color
|
||||
|
||||
// Combination
|
||||
|
||||
.hero
|
||||
&.is-fullheight-with-navbar
|
||||
min-height: calc(100vh - #{$navbar-height})
|
||||
144
hyperglass/static/sass/components/pagination.sass
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
$pagination-color: $grey-darker !default
|
||||
$pagination-border-color: $grey-lighter !default
|
||||
$pagination-margin: -0.25rem !default
|
||||
$pagination-min-width: $control-height !default
|
||||
|
||||
$pagination-hover-color: $link-hover !default
|
||||
$pagination-hover-border-color: $link-hover-border !default
|
||||
|
||||
$pagination-focus-color: $link-focus !default
|
||||
$pagination-focus-border-color: $link-focus-border !default
|
||||
|
||||
$pagination-active-color: $link-active !default
|
||||
$pagination-active-border-color: $link-active-border !default
|
||||
|
||||
$pagination-disabled-color: $grey !default
|
||||
$pagination-disabled-background-color: $grey-lighter !default
|
||||
$pagination-disabled-border-color: $grey-lighter !default
|
||||
|
||||
$pagination-current-color: $link-invert !default
|
||||
$pagination-current-background-color: $link !default
|
||||
$pagination-current-border-color: $link !default
|
||||
|
||||
$pagination-ellipsis-color: $grey-light !default
|
||||
|
||||
$pagination-shadow-inset: inset 0 1px 2px rgba($black, 0.2)
|
||||
|
||||
.pagination
|
||||
font-size: $size-normal
|
||||
margin: $pagination-margin
|
||||
// Sizes
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
&.is-rounded
|
||||
.pagination-previous,
|
||||
.pagination-next
|
||||
padding-left: 1em
|
||||
padding-right: 1em
|
||||
border-radius: $radius-rounded
|
||||
.pagination-link
|
||||
border-radius: $radius-rounded
|
||||
|
||||
.pagination,
|
||||
.pagination-list
|
||||
align-items: center
|
||||
display: flex
|
||||
justify-content: center
|
||||
text-align: center
|
||||
|
||||
.pagination-previous,
|
||||
.pagination-next,
|
||||
.pagination-link,
|
||||
.pagination-ellipsis
|
||||
@extend %control
|
||||
@extend %unselectable
|
||||
font-size: 1em
|
||||
padding-left: 0.5em
|
||||
padding-right: 0.5em
|
||||
justify-content: center
|
||||
margin: 0.25rem
|
||||
text-align: center
|
||||
|
||||
.pagination-previous,
|
||||
.pagination-next,
|
||||
.pagination-link
|
||||
border-color: $pagination-border-color
|
||||
color: $pagination-color
|
||||
min-width: $pagination-min-width
|
||||
&:hover
|
||||
border-color: $pagination-hover-border-color
|
||||
color: $pagination-hover-color
|
||||
&:focus
|
||||
border-color: $pagination-focus-border-color
|
||||
&:active
|
||||
box-shadow: $pagination-shadow-inset
|
||||
&[disabled]
|
||||
background-color: $pagination-disabled-background-color
|
||||
border-color: $pagination-disabled-border-color
|
||||
box-shadow: none
|
||||
color: $pagination-disabled-color
|
||||
opacity: 0.5
|
||||
|
||||
.pagination-previous,
|
||||
.pagination-next
|
||||
padding-left: 0.75em
|
||||
padding-right: 0.75em
|
||||
white-space: nowrap
|
||||
|
||||
.pagination-link
|
||||
&.is-current
|
||||
background-color: $pagination-current-background-color
|
||||
border-color: $pagination-current-border-color
|
||||
color: $pagination-current-color
|
||||
|
||||
.pagination-ellipsis
|
||||
color: $pagination-ellipsis-color
|
||||
pointer-events: none
|
||||
|
||||
.pagination-list
|
||||
flex-wrap: wrap
|
||||
|
||||
+mobile
|
||||
.pagination
|
||||
flex-wrap: wrap
|
||||
.pagination-previous,
|
||||
.pagination-next
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
.pagination-list
|
||||
li
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
|
||||
+tablet
|
||||
.pagination-list
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
justify-content: flex-start
|
||||
order: 1
|
||||
.pagination-previous
|
||||
order: 2
|
||||
.pagination-next
|
||||
order: 3
|
||||
.pagination
|
||||
justify-content: space-between
|
||||
&.is-centered
|
||||
.pagination-previous
|
||||
order: 1
|
||||
.pagination-list
|
||||
justify-content: center
|
||||
order: 2
|
||||
.pagination-next
|
||||
order: 3
|
||||
&.is-right
|
||||
.pagination-previous
|
||||
order: 1
|
||||
.pagination-next
|
||||
order: 2
|
||||
.pagination-list
|
||||
justify-content: flex-end
|
||||
order: 3
|
||||
101
hyperglass/static/sass/components/panel.sass
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
$panel-item-border: 1px solid $border !default
|
||||
|
||||
$panel-heading-background-color: $background !default
|
||||
$panel-heading-color: $text-strong !default
|
||||
$panel-heading-line-height: 1.25 !default
|
||||
$panel-heading-padding: 0.5em 0.75em !default
|
||||
$panel-heading-radius: $radius !default
|
||||
$panel-heading-size: 1.25em !default
|
||||
$panel-heading-weight: $weight-light !default
|
||||
|
||||
$panel-tab-border-bottom: 1px solid $border !default
|
||||
$panel-tab-active-border-bottom-color: $link-active-border !default
|
||||
$panel-tab-active-color: $link-active !default
|
||||
|
||||
$panel-list-item-color: $text !default
|
||||
$panel-list-item-hover-color: $link !default
|
||||
|
||||
$panel-block-color: $text-strong !default
|
||||
$panel-block-hover-background-color: $background !default
|
||||
$panel-block-active-border-left-color: $link !default
|
||||
$panel-block-active-color: $link-active !default
|
||||
$panel-block-active-icon-color: $link !default
|
||||
|
||||
$panel-icon-color: $text-light !default
|
||||
|
||||
.panel
|
||||
font-size: $size-normal
|
||||
&:not(:last-child)
|
||||
margin-bottom: 1.5rem
|
||||
|
||||
.panel-heading,
|
||||
.panel-tabs,
|
||||
.panel-block
|
||||
border-bottom: $panel-item-border
|
||||
border-left: $panel-item-border
|
||||
border-right: $panel-item-border
|
||||
&:first-child
|
||||
border-top: $panel-item-border
|
||||
|
||||
.panel-heading
|
||||
background-color: $panel-heading-background-color
|
||||
border-radius: $panel-heading-radius $panel-heading-radius 0 0
|
||||
color: $panel-heading-color
|
||||
font-size: $panel-heading-size
|
||||
font-weight: $panel-heading-weight
|
||||
line-height: $panel-heading-line-height
|
||||
padding: $panel-heading-padding
|
||||
|
||||
.panel-tabs
|
||||
align-items: flex-end
|
||||
display: flex
|
||||
font-size: 0.875em
|
||||
justify-content: center
|
||||
a
|
||||
border-bottom: $panel-tab-border-bottom
|
||||
margin-bottom: -1px
|
||||
padding: 0.5em
|
||||
// Modifiers
|
||||
&.is-active
|
||||
border-bottom-color: $panel-tab-active-border-bottom-color
|
||||
color: $panel-tab-active-color
|
||||
|
||||
.panel-list
|
||||
a
|
||||
color: $panel-list-item-color
|
||||
&:hover
|
||||
color: $panel-list-item-hover-color
|
||||
|
||||
.panel-block
|
||||
align-items: center
|
||||
color: $panel-block-color
|
||||
display: flex
|
||||
justify-content: flex-start
|
||||
padding: 0.5em 0.75em
|
||||
input[type="checkbox"]
|
||||
margin-right: 0.75em
|
||||
& > .control
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
width: 100%
|
||||
&.is-wrapped
|
||||
flex-wrap: wrap
|
||||
&.is-active
|
||||
border-left-color: $panel-block-active-border-left-color
|
||||
color: $panel-block-active-color
|
||||
.panel-icon
|
||||
color: $panel-block-active-icon-color
|
||||
|
||||
a.panel-block,
|
||||
label.panel-block
|
||||
cursor: pointer
|
||||
&:hover
|
||||
background-color: $panel-block-hover-background-color
|
||||
|
||||
.panel-icon
|
||||
+fa(14px, 1em)
|
||||
color: $panel-icon-color
|
||||
margin-right: 0.75em
|
||||
.fa
|
||||
font-size: inherit
|
||||
line-height: inherit
|
||||
151
hyperglass/static/sass/components/tabs.sass
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
$tabs-border-bottom-color: $border !default
|
||||
$tabs-border-bottom-style: solid !default
|
||||
$tabs-border-bottom-width: 1px !default
|
||||
$tabs-link-color: $text !default
|
||||
$tabs-link-hover-border-bottom-color: $text-strong !default
|
||||
$tabs-link-hover-color: $text-strong !default
|
||||
$tabs-link-active-border-bottom-color: $link !default
|
||||
$tabs-link-active-color: $link !default
|
||||
$tabs-link-padding: 0.5em 1em !default
|
||||
|
||||
$tabs-boxed-link-radius: $radius !default
|
||||
$tabs-boxed-link-hover-background-color: $background !default
|
||||
$tabs-boxed-link-hover-border-bottom-color: $border !default
|
||||
|
||||
$tabs-boxed-link-active-background-color: $white !default
|
||||
$tabs-boxed-link-active-border-color: $border !default
|
||||
$tabs-boxed-link-active-border-bottom-color: transparent !default
|
||||
|
||||
$tabs-toggle-link-border-color: $border !default
|
||||
$tabs-toggle-link-border-style: solid !default
|
||||
$tabs-toggle-link-border-width: 1px !default
|
||||
$tabs-toggle-link-hover-background-color: $background !default
|
||||
$tabs-toggle-link-hover-border-color: $border-hover !default
|
||||
$tabs-toggle-link-radius: $radius !default
|
||||
$tabs-toggle-link-active-background-color: $link !default
|
||||
$tabs-toggle-link-active-border-color: $link !default
|
||||
$tabs-toggle-link-active-color: $link-invert !default
|
||||
|
||||
.tabs
|
||||
@extend %block
|
||||
+overflow-touch
|
||||
@extend %unselectable
|
||||
align-items: stretch
|
||||
display: flex
|
||||
font-size: $size-normal
|
||||
justify-content: space-between
|
||||
overflow: hidden
|
||||
overflow-x: auto
|
||||
white-space: nowrap
|
||||
a
|
||||
align-items: center
|
||||
border-bottom-color: $tabs-border-bottom-color
|
||||
border-bottom-style: $tabs-border-bottom-style
|
||||
border-bottom-width: $tabs-border-bottom-width
|
||||
color: $tabs-link-color
|
||||
display: flex
|
||||
justify-content: center
|
||||
margin-bottom: -#{$tabs-border-bottom-width}
|
||||
padding: $tabs-link-padding
|
||||
vertical-align: top
|
||||
&:hover
|
||||
border-bottom-color: $tabs-link-hover-border-bottom-color
|
||||
color: $tabs-link-hover-color
|
||||
li
|
||||
display: block
|
||||
&.is-active
|
||||
a
|
||||
border-bottom-color: $tabs-link-active-border-bottom-color
|
||||
color: $tabs-link-active-color
|
||||
ul
|
||||
align-items: center
|
||||
border-bottom-color: $tabs-border-bottom-color
|
||||
border-bottom-style: $tabs-border-bottom-style
|
||||
border-bottom-width: $tabs-border-bottom-width
|
||||
display: flex
|
||||
flex-grow: 1
|
||||
flex-shrink: 0
|
||||
justify-content: flex-start
|
||||
&.is-left
|
||||
padding-right: 0.75em
|
||||
&.is-center
|
||||
flex: none
|
||||
justify-content: center
|
||||
padding-left: 0.75em
|
||||
padding-right: 0.75em
|
||||
&.is-right
|
||||
justify-content: flex-end
|
||||
padding-left: 0.75em
|
||||
.icon
|
||||
&:first-child
|
||||
margin-right: 0.5em
|
||||
&:last-child
|
||||
margin-left: 0.5em
|
||||
// Alignment
|
||||
&.is-centered
|
||||
ul
|
||||
justify-content: center
|
||||
&.is-right
|
||||
ul
|
||||
justify-content: flex-end
|
||||
// Styles
|
||||
&.is-boxed
|
||||
a
|
||||
border: 1px solid transparent
|
||||
border-radius: $tabs-boxed-link-radius $tabs-boxed-link-radius 0 0
|
||||
&:hover
|
||||
background-color: $tabs-boxed-link-hover-background-color
|
||||
border-bottom-color: $tabs-boxed-link-hover-border-bottom-color
|
||||
li
|
||||
&.is-active
|
||||
a
|
||||
background-color: $tabs-boxed-link-active-background-color
|
||||
border-color: $tabs-boxed-link-active-border-color
|
||||
border-bottom-color: $tabs-boxed-link-active-border-bottom-color !important
|
||||
&.is-fullwidth
|
||||
li
|
||||
flex-grow: 1
|
||||
flex-shrink: 0
|
||||
&.is-toggle
|
||||
a
|
||||
border-color: $tabs-toggle-link-border-color
|
||||
border-style: $tabs-toggle-link-border-style
|
||||
border-width: $tabs-toggle-link-border-width
|
||||
margin-bottom: 0
|
||||
position: relative
|
||||
&:hover
|
||||
background-color: $tabs-toggle-link-hover-background-color
|
||||
border-color: $tabs-toggle-link-hover-border-color
|
||||
z-index: 2
|
||||
li
|
||||
& + li
|
||||
margin-left: -#{$tabs-toggle-link-border-width}
|
||||
&:first-child a
|
||||
border-radius: $tabs-toggle-link-radius 0 0 $tabs-toggle-link-radius
|
||||
&:last-child a
|
||||
border-radius: 0 $tabs-toggle-link-radius $tabs-toggle-link-radius 0
|
||||
&.is-active
|
||||
a
|
||||
background-color: $tabs-toggle-link-active-background-color
|
||||
border-color: $tabs-toggle-link-active-border-color
|
||||
color: $tabs-toggle-link-active-color
|
||||
z-index: 1
|
||||
ul
|
||||
border-bottom: none
|
||||
&.is-toggle-rounded
|
||||
li
|
||||
&:first-child a
|
||||
border-bottom-left-radius: $radius-rounded
|
||||
border-top-left-radius: $radius-rounded
|
||||
padding-left: 1.25em
|
||||
&:last-child a
|
||||
border-bottom-right-radius: $radius-rounded
|
||||
border-top-right-radius: $radius-rounded
|
||||
padding-right: 1.25em
|
||||
// Sizes
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
60
hyperglass/static/sass/custom/custom_elements.sass
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Custom Elements
|
||||
|
||||
.hero.lg-hero-bg
|
||||
background-color: $lg-hero-bg
|
||||
color: findColorInvert($lg-hero-bg)
|
||||
|
||||
.hero-foot.lg-hero-bg
|
||||
background-color: $lg-hero-bg
|
||||
color: findColorInvert($lg-hero-bg)
|
||||
|
||||
.hero.lg-hero-bg .title, .subtitle
|
||||
color: findColorInvert($lg-hero-bg)
|
||||
|
||||
.hero.is-danger .title, .subtitle
|
||||
color: findColorInvert($danger)
|
||||
|
||||
.hero.lg-hero-bg .navbar-item
|
||||
color: findColorInvert($lg-hero-bg)
|
||||
|
||||
.button.lg-btn-submit
|
||||
background-color: $lg-btn-submit
|
||||
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-cmd
|
||||
background-color: $lg-tag-cmd
|
||||
color: findColorInvert($lg-tag-cmd)
|
||||
font-family: $family-monospace
|
||||
|
||||
.tag.lg-tag-cmdtitle
|
||||
background-color: $lg-tag-cmdtitle
|
||||
color: findColorInvert($lg-tag-cmdtitle)
|
||||
font-family: $family-monospace
|
||||
|
||||
.tag.lg-tag-loc
|
||||
background-color: $lg-tag-loc
|
||||
color: findColorInvert($lg-tag-loc)
|
||||
|
||||
.progress.lg-progressbar:indeterminate
|
||||
background-image: linear-gradient(to right,$lg-progressbar 30%,$grey-lighter 30%)
|
||||
|
||||
.message-body pre
|
||||
background-color: transparent!important
|
||||
|
||||
.query-output
|
||||
font-family: $family-monospace
|
||||
font-size: 0.875em
|
||||
overflow-x: auto
|
||||
white-space: pre
|
||||
word-wrap: normal
|
||||
|
||||
.dropdown-content.lg-help
|
||||
width: 24rem
|
||||
|
||||
.icon.lg-icon-help
|
||||
color: $lg-btn-submit
|
||||
16
hyperglass/static/sass/elements/_all.sass
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
@charset "utf-8"
|
||||
|
||||
@import "box.sass"
|
||||
@import "button.sass"
|
||||
@import "container.sass"
|
||||
@import "content.sass"
|
||||
@import "form.sass"
|
||||
@import "icon.sass"
|
||||
@import "image.sass"
|
||||
@import "notification.sass"
|
||||
@import "progress.sass"
|
||||
@import "table.sass"
|
||||
@import "tag.sass"
|
||||
@import "title.sass"
|
||||
|
||||
@import "other.sass"
|
||||
24
hyperglass/static/sass/elements/box.sass
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
$box-color: $text !default
|
||||
$box-background-color: $white !default
|
||||
$box-radius: $radius-large !default
|
||||
$box-shadow: 0 2px 3px rgba($black, 0.1), 0 0 0 1px rgba($black, 0.1) !default
|
||||
$box-padding: 1.25rem !default
|
||||
|
||||
$box-link-hover-shadow: 0 2px 3px rgba($black, 0.1), 0 0 0 1px $link !default
|
||||
$box-link-active-shadow: inset 0 1px 2px rgba($black, 0.2), 0 0 0 1px $link !default
|
||||
|
||||
.box
|
||||
@extend %block
|
||||
background-color: $box-background-color
|
||||
border-radius: $box-radius
|
||||
box-shadow: $box-shadow
|
||||
color: $box-color
|
||||
display: block
|
||||
padding: $box-padding
|
||||
|
||||
a.box
|
||||
&:hover,
|
||||
&:focus
|
||||
box-shadow: $box-link-hover-shadow
|
||||
&:active
|
||||
box-shadow: $box-link-active-shadow
|
||||
275
hyperglass/static/sass/elements/button.sass
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
$button-color: $grey-darker !default
|
||||
$button-background-color: $white !default
|
||||
|
||||
$button-border-color: $grey-lighter !default
|
||||
$button-border-width: $control-border-width !default
|
||||
|
||||
$button-padding-vertical: calc(0.375em - #{$button-border-width}) !default
|
||||
$button-padding-horizontal: 0.75em !default
|
||||
|
||||
$button-hover-color: $link-hover !default
|
||||
$button-hover-border-color: $link-hover-border !default
|
||||
|
||||
$button-focus-color: $link-focus !default
|
||||
$button-focus-border-color: $link-focus-border !default
|
||||
$button-focus-box-shadow-size: 0 0 0 0.125em !default
|
||||
$button-focus-box-shadow-color: rgba($link, 0.25) !default
|
||||
|
||||
$button-active-color: $link-active !default
|
||||
$button-active-border-color: $link-active-border !default
|
||||
|
||||
$button-text-color: $text !default
|
||||
$button-text-hover-background-color: $background !default
|
||||
$button-text-hover-color: $text-strong !default
|
||||
|
||||
$button-disabled-background-color: $white !default
|
||||
$button-disabled-border-color: $grey-lighter !default
|
||||
$button-disabled-shadow: none !default
|
||||
$button-disabled-opacity: 0.5 !default
|
||||
|
||||
$button-static-color: $grey !default
|
||||
$button-static-background-color: $white-ter !default
|
||||
$button-static-border-color: $grey-lighter !default
|
||||
|
||||
// The button sizes use mixins so they can be used at different breakpoints
|
||||
=button-small
|
||||
border-radius: $radius-small
|
||||
font-size: $size-small
|
||||
=button-normal
|
||||
font-size: $size-normal
|
||||
=button-medium
|
||||
font-size: $size-medium
|
||||
=button-large
|
||||
font-size: $size-large
|
||||
|
||||
.button
|
||||
@extend %control
|
||||
@extend %unselectable
|
||||
background-color: $button-background-color
|
||||
border-color: $button-border-color
|
||||
border-width: $button-border-width
|
||||
color: $button-color
|
||||
cursor: pointer
|
||||
justify-content: center
|
||||
padding-bottom: $button-padding-vertical
|
||||
padding-left: $button-padding-horizontal
|
||||
padding-right: $button-padding-horizontal
|
||||
padding-top: $button-padding-vertical
|
||||
text-align: center
|
||||
white-space: nowrap
|
||||
strong
|
||||
color: inherit
|
||||
.icon
|
||||
&,
|
||||
&.is-small,
|
||||
&.is-medium,
|
||||
&.is-large
|
||||
height: 1.5em
|
||||
width: 1.5em
|
||||
&:first-child:not(:last-child)
|
||||
margin-left: calc(-0.375em - #{$button-border-width})
|
||||
margin-right: 0.1875em
|
||||
&:last-child:not(:first-child)
|
||||
margin-left: 0.1875em
|
||||
margin-right: calc(-0.375em - #{$button-border-width})
|
||||
&:first-child:last-child
|
||||
margin-left: calc(-0.375em - #{$button-border-width})
|
||||
margin-right: calc(-0.375em - #{$button-border-width})
|
||||
// States
|
||||
&:hover,
|
||||
&.is-hovered
|
||||
border-color: $button-hover-border-color
|
||||
color: $button-hover-color
|
||||
&:focus,
|
||||
&.is-focused
|
||||
border-color: $button-focus-border-color
|
||||
color: $button-focus-color
|
||||
&:not(:active)
|
||||
box-shadow: $button-focus-box-shadow-size $button-focus-box-shadow-color
|
||||
&:active,
|
||||
&.is-active
|
||||
border-color: $button-active-border-color
|
||||
color: $button-active-color
|
||||
// Colors
|
||||
&.is-text
|
||||
background-color: transparent
|
||||
border-color: transparent
|
||||
color: $button-text-color
|
||||
text-decoration: underline
|
||||
&:hover,
|
||||
&.is-hovered,
|
||||
&:focus,
|
||||
&.is-focused
|
||||
background-color: $button-text-hover-background-color
|
||||
color: $button-text-hover-color
|
||||
&:active,
|
||||
&.is-active
|
||||
background-color: darken($button-text-hover-background-color, 5%)
|
||||
color: $button-text-hover-color
|
||||
&[disabled],
|
||||
fieldset[disabled] &
|
||||
background-color: transparent
|
||||
border-color: transparent
|
||||
box-shadow: none
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
$color-invert: nth($pair, 2)
|
||||
&.is-#{$name}
|
||||
background-color: $color
|
||||
border-color: transparent
|
||||
color: $color-invert
|
||||
&:hover,
|
||||
&.is-hovered
|
||||
background-color: darken($color, 2.5%)
|
||||
border-color: transparent
|
||||
color: $color-invert
|
||||
&:focus,
|
||||
&.is-focused
|
||||
border-color: transparent
|
||||
color: $color-invert
|
||||
&:not(:active)
|
||||
box-shadow: $button-focus-box-shadow-size rgba($color, 0.25)
|
||||
&:active,
|
||||
&.is-active
|
||||
background-color: darken($color, 5%)
|
||||
border-color: transparent
|
||||
color: $color-invert
|
||||
&[disabled],
|
||||
fieldset[disabled] &
|
||||
background-color: $color
|
||||
border-color: transparent
|
||||
box-shadow: none
|
||||
&.is-inverted
|
||||
background-color: $color-invert
|
||||
color: $color
|
||||
&:hover
|
||||
background-color: darken($color-invert, 5%)
|
||||
&[disabled],
|
||||
fieldset[disabled] &
|
||||
background-color: $color-invert
|
||||
border-color: transparent
|
||||
box-shadow: none
|
||||
color: $color
|
||||
&.is-loading
|
||||
&::after
|
||||
border-color: transparent transparent $color-invert $color-invert !important
|
||||
&.is-outlined
|
||||
background-color: transparent
|
||||
border-color: $color
|
||||
color: $color
|
||||
&:hover,
|
||||
&:focus
|
||||
background-color: $color
|
||||
border-color: $color
|
||||
color: $color-invert
|
||||
&.is-loading
|
||||
&::after
|
||||
border-color: transparent transparent $color $color !important
|
||||
&[disabled],
|
||||
fieldset[disabled] &
|
||||
background-color: transparent
|
||||
border-color: $color
|
||||
box-shadow: none
|
||||
color: $color
|
||||
&.is-inverted.is-outlined
|
||||
background-color: transparent
|
||||
border-color: $color-invert
|
||||
color: $color-invert
|
||||
&:hover,
|
||||
&:focus
|
||||
background-color: $color-invert
|
||||
color: $color
|
||||
&[disabled],
|
||||
fieldset[disabled] &
|
||||
background-color: transparent
|
||||
border-color: $color-invert
|
||||
box-shadow: none
|
||||
color: $color-invert
|
||||
// Sizes
|
||||
&.is-small
|
||||
+button-small
|
||||
&.is-normal
|
||||
+button-normal
|
||||
&.is-medium
|
||||
+button-medium
|
||||
&.is-large
|
||||
+button-large
|
||||
// Modifiers
|
||||
&[disabled],
|
||||
fieldset[disabled] &
|
||||
background-color: $button-disabled-background-color
|
||||
border-color: $button-disabled-border-color
|
||||
box-shadow: $button-disabled-shadow
|
||||
opacity: $button-disabled-opacity
|
||||
&.is-fullwidth
|
||||
display: flex
|
||||
width: 100%
|
||||
&.is-loading
|
||||
color: transparent !important
|
||||
pointer-events: none
|
||||
&::after
|
||||
@extend %loader
|
||||
+center(1em)
|
||||
position: absolute !important
|
||||
&.is-static
|
||||
background-color: $button-static-background-color
|
||||
border-color: $button-static-border-color
|
||||
color: $button-static-color
|
||||
box-shadow: none
|
||||
pointer-events: none
|
||||
&.is-rounded
|
||||
border-radius: $radius-rounded
|
||||
padding-left: 1em
|
||||
padding-right: 1em
|
||||
|
||||
.buttons
|
||||
align-items: center
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
justify-content: flex-start
|
||||
.button
|
||||
margin-bottom: 0.5rem
|
||||
&:not(:last-child):not(.is-fullwidth)
|
||||
margin-right: 0.5rem
|
||||
&:last-child
|
||||
margin-bottom: -0.5rem
|
||||
&:not(:last-child)
|
||||
margin-bottom: 1rem
|
||||
// Sizes
|
||||
&.are-small
|
||||
.button:not(.is-normal):not(.is-medium):not(.is-large)
|
||||
+button-small
|
||||
&.are-medium
|
||||
.button:not(.is-small):not(.is-normal):not(.is-large)
|
||||
+button-medium
|
||||
&.are-large
|
||||
.button:not(.is-small):not(.is-normal):not(.is-medium)
|
||||
+button-large
|
||||
&.has-addons
|
||||
.button
|
||||
&:not(:first-child)
|
||||
border-bottom-left-radius: 0
|
||||
border-top-left-radius: 0
|
||||
&:not(:last-child)
|
||||
border-bottom-right-radius: 0
|
||||
border-top-right-radius: 0
|
||||
margin-right: -1px
|
||||
&:last-child
|
||||
margin-right: 0
|
||||
&:hover,
|
||||
&.is-hovered
|
||||
z-index: 2
|
||||
&:focus,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&.is-active,
|
||||
&.is-selected
|
||||
z-index: 3
|
||||
&:hover
|
||||
z-index: 4
|
||||
&.is-expanded
|
||||
flex-grow: 1
|
||||
&.is-centered
|
||||
justify-content: center
|
||||
&.is-right
|
||||
justify-content: flex-end
|
||||
25
hyperglass/static/sass/elements/container.sass
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
.container
|
||||
margin: 0 auto
|
||||
position: relative
|
||||
+desktop
|
||||
max-width: $desktop - (2 * $gap)
|
||||
width: $desktop - (2 * $gap)
|
||||
&.is-fluid
|
||||
margin-left: $gap
|
||||
margin-right: $gap
|
||||
max-width: none
|
||||
width: auto
|
||||
+until-widescreen
|
||||
&.is-widescreen
|
||||
max-width: $widescreen - (2 * $gap)
|
||||
width: auto
|
||||
+until-fullhd
|
||||
&.is-fullhd
|
||||
max-width: $fullhd - (2 * $gap)
|
||||
width: auto
|
||||
+widescreen
|
||||
max-width: $widescreen - (2 * $gap)
|
||||
width: $widescreen - (2 * $gap)
|
||||
+fullhd
|
||||
max-width: $fullhd - (2 * $gap)
|
||||
width: $fullhd - (2 * $gap)
|
||||
151
hyperglass/static/sass/elements/content.sass
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
$content-heading-color: $text-strong !default
|
||||
$content-heading-weight: $weight-semibold !default
|
||||
$content-heading-line-height: 1.125 !default
|
||||
|
||||
$content-blockquote-background-color: $background !default
|
||||
$content-blockquote-border-left: 5px solid $border !default
|
||||
$content-blockquote-padding: 1.25em 1.5em !default
|
||||
|
||||
$content-pre-padding: 1.25em 1.5em !default
|
||||
|
||||
$content-table-cell-border: 1px solid $border !default
|
||||
$content-table-cell-border-width: 0 0 1px !default
|
||||
$content-table-cell-padding: 0.5em 0.75em !default
|
||||
$content-table-cell-heading-color: $text-strong !default
|
||||
$content-table-head-cell-border-width: 0 0 2px !default
|
||||
$content-table-head-cell-color: $text-strong !default
|
||||
$content-table-foot-cell-border-width: 2px 0 0 !default
|
||||
$content-table-foot-cell-color: $text-strong !default
|
||||
|
||||
.content
|
||||
@extend %block
|
||||
// Inline
|
||||
li + li
|
||||
margin-top: 0.25em
|
||||
// Block
|
||||
p,
|
||||
dl,
|
||||
ol,
|
||||
ul,
|
||||
blockquote,
|
||||
pre,
|
||||
table
|
||||
&:not(:last-child)
|
||||
margin-bottom: 1em
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6
|
||||
color: $content-heading-color
|
||||
font-weight: $content-heading-weight
|
||||
line-height: $content-heading-line-height
|
||||
h1
|
||||
font-size: 2em
|
||||
margin-bottom: 0.5em
|
||||
&:not(:first-child)
|
||||
margin-top: 1em
|
||||
h2
|
||||
font-size: 1.75em
|
||||
margin-bottom: 0.5714em
|
||||
&:not(:first-child)
|
||||
margin-top: 1.1428em
|
||||
h3
|
||||
font-size: 1.5em
|
||||
margin-bottom: 0.6666em
|
||||
&:not(:first-child)
|
||||
margin-top: 1.3333em
|
||||
h4
|
||||
font-size: 1.25em
|
||||
margin-bottom: 0.8em
|
||||
h5
|
||||
font-size: 1.125em
|
||||
margin-bottom: 0.8888em
|
||||
h6
|
||||
font-size: 1em
|
||||
margin-bottom: 1em
|
||||
blockquote
|
||||
background-color: $content-blockquote-background-color
|
||||
border-left: $content-blockquote-border-left
|
||||
padding: $content-blockquote-padding
|
||||
ol
|
||||
list-style-position: outside
|
||||
margin-left: 2em
|
||||
margin-top: 1em
|
||||
&:not([type])
|
||||
list-style-type: decimal
|
||||
&.is-lower-alpha
|
||||
list-style-type: lower-alpha
|
||||
&.is-lower-roman
|
||||
list-style-type: lower-roman
|
||||
&.is-upper-alpha
|
||||
list-style-type: upper-alpha
|
||||
&.is-upper-roman
|
||||
list-style-type: upper-roman
|
||||
ul
|
||||
list-style: disc outside
|
||||
margin-left: 2em
|
||||
margin-top: 1em
|
||||
ul
|
||||
list-style-type: circle
|
||||
margin-top: 0.5em
|
||||
ul
|
||||
list-style-type: square
|
||||
dd
|
||||
margin-left: 2em
|
||||
figure
|
||||
margin-left: 2em
|
||||
margin-right: 2em
|
||||
text-align: center
|
||||
&:not(:first-child)
|
||||
margin-top: 2em
|
||||
&:not(:last-child)
|
||||
margin-bottom: 2em
|
||||
img
|
||||
display: inline-block
|
||||
figcaption
|
||||
font-style: italic
|
||||
pre
|
||||
+overflow-touch
|
||||
overflow-x: auto
|
||||
padding: $content-pre-padding
|
||||
white-space: pre
|
||||
word-wrap: normal
|
||||
sup,
|
||||
sub
|
||||
font-size: 75%
|
||||
table
|
||||
width: 100%
|
||||
td,
|
||||
th
|
||||
border: $content-table-cell-border
|
||||
border-width: $content-table-cell-border-width
|
||||
padding: $content-table-cell-padding
|
||||
vertical-align: top
|
||||
th
|
||||
color: $content-table-cell-heading-color
|
||||
text-align: left
|
||||
thead
|
||||
td,
|
||||
th
|
||||
border-width: $content-table-head-cell-border-width
|
||||
color: $content-table-head-cell-color
|
||||
tfoot
|
||||
td,
|
||||
th
|
||||
border-width: $content-table-foot-cell-border-width
|
||||
color: $content-table-foot-cell-color
|
||||
tbody
|
||||
tr
|
||||
&:last-child
|
||||
td,
|
||||
th
|
||||
border-bottom-width: 0
|
||||
// Sizes
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
602
hyperglass/static/sass/elements/form.sass
Normal file
|
|
@ -0,0 +1,602 @@
|
|||
$input-color: $grey-darker !default
|
||||
$input-background-color: $white !default
|
||||
$input-border-color: $grey-lighter !default
|
||||
$input-height: $control-height !default
|
||||
$input-shadow: inset 0 1px 2px rgba($black, 0.1) !default
|
||||
$input-placeholder-color: rgba($input-color, 0.3) !default
|
||||
|
||||
$input-hover-color: $grey-darker !default
|
||||
$input-hover-border-color: $grey-light !default
|
||||
|
||||
$input-focus-color: $grey-darker !default
|
||||
$input-focus-border-color: $link !default
|
||||
$input-focus-box-shadow-size: 0 0 0 0.125em !default
|
||||
$input-focus-box-shadow-color: rgba($link, 0.25) !default
|
||||
|
||||
$input-disabled-color: $text-light !default
|
||||
$input-disabled-background-color: $background !default
|
||||
$input-disabled-border-color: $background !default
|
||||
$input-disabled-placeholder-color: rgba($input-disabled-color, 0.3) !default
|
||||
|
||||
$input-arrow: $link !default
|
||||
|
||||
$input-icon-color: $grey-lighter !default
|
||||
$input-icon-active-color: $grey !default
|
||||
|
||||
$input-radius: $radius !default
|
||||
|
||||
$file-border-color: $border !default
|
||||
$file-radius: $radius !default
|
||||
|
||||
$file-cta-background-color: $white-ter !default
|
||||
$file-cta-color: $grey-dark !default
|
||||
$file-cta-hover-color: $grey-darker !default
|
||||
$file-cta-active-color: $grey-darker !default
|
||||
|
||||
$file-name-border-color: $border !default
|
||||
$file-name-border-style: solid !default
|
||||
$file-name-border-width: 1px 1px 1px 0 !default
|
||||
$file-name-max-width: 16em !default
|
||||
|
||||
$label-color: $grey-darker !default
|
||||
$label-weight: $weight-bold !default
|
||||
|
||||
$help-size: $size-small !default
|
||||
|
||||
=input
|
||||
@extend %control
|
||||
background-color: $input-background-color
|
||||
border-color: $input-border-color
|
||||
color: $input-color
|
||||
+placeholder
|
||||
color: $input-placeholder-color
|
||||
&:hover,
|
||||
&.is-hovered
|
||||
border-color: $input-hover-border-color
|
||||
&:focus,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&.is-active
|
||||
border-color: $input-focus-border-color
|
||||
box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color
|
||||
&[disabled],
|
||||
fieldset[disabled] &
|
||||
background-color: $input-disabled-background-color
|
||||
border-color: $input-disabled-border-color
|
||||
box-shadow: none
|
||||
color: $input-disabled-color
|
||||
+placeholder
|
||||
color: $input-disabled-placeholder-color
|
||||
|
||||
.input,
|
||||
.textarea
|
||||
+input
|
||||
box-shadow: $input-shadow
|
||||
max-width: 100%
|
||||
width: 100%
|
||||
&[readonly]
|
||||
box-shadow: none
|
||||
// Colors
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
&.is-#{$name}
|
||||
border-color: $color
|
||||
&:focus,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&.is-active
|
||||
box-shadow: $input-focus-box-shadow-size rgba($color, 0.25)
|
||||
// Sizes
|
||||
&.is-small
|
||||
+control-small
|
||||
&.is-medium
|
||||
+control-medium
|
||||
&.is-large
|
||||
+control-large
|
||||
// Modifiers
|
||||
&.is-fullwidth
|
||||
display: block
|
||||
width: 100%
|
||||
&.is-inline
|
||||
display: inline
|
||||
width: auto
|
||||
|
||||
.input
|
||||
&.is-rounded
|
||||
border-radius: $radius-rounded
|
||||
padding-left: 1em
|
||||
padding-right: 1em
|
||||
&.is-static
|
||||
background-color: transparent
|
||||
border-color: transparent
|
||||
box-shadow: none
|
||||
padding-left: 0
|
||||
padding-right: 0
|
||||
|
||||
.textarea
|
||||
display: block
|
||||
max-width: 100%
|
||||
min-width: 100%
|
||||
padding: 0.625em
|
||||
resize: vertical
|
||||
&:not([rows])
|
||||
max-height: 600px
|
||||
min-height: 120px
|
||||
&[rows]
|
||||
height: initial
|
||||
// Modifiers
|
||||
&.has-fixed-size
|
||||
resize: none
|
||||
|
||||
.checkbox,
|
||||
.radio
|
||||
cursor: pointer
|
||||
display: inline-block
|
||||
line-height: 1.25
|
||||
position: relative
|
||||
input
|
||||
cursor: pointer
|
||||
&:hover
|
||||
color: $input-hover-color
|
||||
&[disabled],
|
||||
fieldset[disabled] &
|
||||
color: $input-disabled-color
|
||||
cursor: not-allowed
|
||||
|
||||
.radio
|
||||
& + .radio
|
||||
margin-left: 0.5em
|
||||
|
||||
.select
|
||||
display: inline-block
|
||||
max-width: 100%
|
||||
position: relative
|
||||
vertical-align: top
|
||||
&:not(.is-multiple)
|
||||
height: $input-height
|
||||
&:not(.is-multiple):not(.is-loading)
|
||||
&::after
|
||||
@extend %arrow
|
||||
border-color: $input-arrow
|
||||
right: 1.125em
|
||||
z-index: 4
|
||||
&.is-rounded
|
||||
select
|
||||
border-radius: $radius-rounded
|
||||
padding-left: 1em
|
||||
select
|
||||
+input
|
||||
cursor: pointer
|
||||
display: block
|
||||
font-size: 1em
|
||||
max-width: 100%
|
||||
outline: none
|
||||
&::-ms-expand
|
||||
display: none
|
||||
&[disabled]:hover,
|
||||
fieldset[disabled] &:hover
|
||||
border-color: $input-disabled-border-color
|
||||
&:not([multiple])
|
||||
padding-right: 2.5em
|
||||
&[multiple]
|
||||
height: auto
|
||||
padding: 0
|
||||
option
|
||||
padding: 0.5em 1em
|
||||
// States
|
||||
&:not(.is-multiple):not(.is-loading):hover
|
||||
&::after
|
||||
border-color: $input-hover-color
|
||||
// Colors
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
&.is-#{$name}
|
||||
&:not(:hover)::after
|
||||
border-color: $color
|
||||
select
|
||||
border-color: $color
|
||||
&:hover,
|
||||
&.is-hovered
|
||||
border-color: darken($color, 5%)
|
||||
&:focus,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&.is-active
|
||||
box-shadow: $input-focus-box-shadow-size rgba($color, 0.25)
|
||||
// Sizes
|
||||
&.is-small
|
||||
+control-small
|
||||
&.is-medium
|
||||
+control-medium
|
||||
&.is-large
|
||||
+control-large
|
||||
// Modifiers
|
||||
&.is-disabled
|
||||
&::after
|
||||
border-color: $input-disabled-color
|
||||
&.is-fullwidth
|
||||
width: 100%
|
||||
select
|
||||
width: 100%
|
||||
&.is-loading
|
||||
&::after
|
||||
@extend %loader
|
||||
margin-top: 0
|
||||
position: absolute
|
||||
right: 0.625em
|
||||
top: 0.625em
|
||||
transform: none
|
||||
&.is-small:after
|
||||
font-size: $size-small
|
||||
&.is-medium:after
|
||||
font-size: $size-medium
|
||||
&.is-large:after
|
||||
font-size: $size-large
|
||||
|
||||
.file
|
||||
@extend %unselectable
|
||||
align-items: stretch
|
||||
display: flex
|
||||
justify-content: flex-start
|
||||
position: relative
|
||||
// Colors
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
$color-invert: nth($pair, 2)
|
||||
&.is-#{$name}
|
||||
.file-cta
|
||||
background-color: $color
|
||||
border-color: transparent
|
||||
color: $color-invert
|
||||
&:hover,
|
||||
&.is-hovered
|
||||
.file-cta
|
||||
background-color: darken($color, 2.5%)
|
||||
border-color: transparent
|
||||
color: $color-invert
|
||||
&:focus,
|
||||
&.is-focused
|
||||
.file-cta
|
||||
border-color: transparent
|
||||
box-shadow: 0 0 0.5em rgba($color, 0.25)
|
||||
color: $color-invert
|
||||
&:active,
|
||||
&.is-active
|
||||
.file-cta
|
||||
background-color: darken($color, 5%)
|
||||
border-color: transparent
|
||||
color: $color-invert
|
||||
// Sizes
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
.file-icon
|
||||
.fa
|
||||
font-size: 21px
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
.file-icon
|
||||
.fa
|
||||
font-size: 28px
|
||||
// Modifiers
|
||||
&.has-name
|
||||
.file-cta
|
||||
border-bottom-right-radius: 0
|
||||
border-top-right-radius: 0
|
||||
.file-name
|
||||
border-bottom-left-radius: 0
|
||||
border-top-left-radius: 0
|
||||
&.is-empty
|
||||
.file-cta
|
||||
border-radius: $file-radius
|
||||
.file-name
|
||||
display: none
|
||||
&.is-boxed
|
||||
.file-label
|
||||
flex-direction: column
|
||||
.file-cta
|
||||
flex-direction: column
|
||||
height: auto
|
||||
padding: 1em 3em
|
||||
.file-name
|
||||
border-width: 0 1px 1px
|
||||
.file-icon
|
||||
height: 1.5em
|
||||
width: 1.5em
|
||||
.fa
|
||||
font-size: 21px
|
||||
&.is-small
|
||||
.file-icon .fa
|
||||
font-size: 14px
|
||||
&.is-medium
|
||||
.file-icon .fa
|
||||
font-size: 28px
|
||||
&.is-large
|
||||
.file-icon .fa
|
||||
font-size: 35px
|
||||
&.has-name
|
||||
.file-cta
|
||||
border-radius: $file-radius $file-radius 0 0
|
||||
.file-name
|
||||
border-radius: 0 0 $file-radius $file-radius
|
||||
border-width: 0 1px 1px
|
||||
&.is-centered
|
||||
justify-content: center
|
||||
&.is-fullwidth
|
||||
.file-label
|
||||
width: 100%
|
||||
.file-name
|
||||
flex-grow: 1
|
||||
max-width: none
|
||||
&.is-right
|
||||
justify-content: flex-end
|
||||
.file-cta
|
||||
border-radius: 0 $file-radius $file-radius 0
|
||||
.file-name
|
||||
border-radius: $file-radius 0 0 $file-radius
|
||||
border-width: 1px 0 1px 1px
|
||||
order: -1
|
||||
|
||||
.file-label
|
||||
align-items: stretch
|
||||
display: flex
|
||||
cursor: pointer
|
||||
justify-content: flex-start
|
||||
overflow: hidden
|
||||
position: relative
|
||||
&:hover
|
||||
.file-cta
|
||||
background-color: darken($file-cta-background-color, 2.5%)
|
||||
color: $file-cta-hover-color
|
||||
.file-name
|
||||
border-color: darken($file-name-border-color, 2.5%)
|
||||
&:active
|
||||
.file-cta
|
||||
background-color: darken($file-cta-background-color, 5%)
|
||||
color: $file-cta-active-color
|
||||
.file-name
|
||||
border-color: darken($file-name-border-color, 5%)
|
||||
|
||||
.file-input
|
||||
height: 100%
|
||||
left: 0
|
||||
opacity: 0
|
||||
outline: none
|
||||
position: absolute
|
||||
top: 0
|
||||
width: 100%
|
||||
|
||||
.file-cta,
|
||||
.file-name
|
||||
@extend %control
|
||||
border-color: $file-border-color
|
||||
border-radius: $file-radius
|
||||
font-size: 1em
|
||||
padding-left: 1em
|
||||
padding-right: 1em
|
||||
white-space: nowrap
|
||||
|
||||
.file-cta
|
||||
background-color: $file-cta-background-color
|
||||
color: $file-cta-color
|
||||
|
||||
.file-name
|
||||
border-color: $file-name-border-color
|
||||
border-style: $file-name-border-style
|
||||
border-width: $file-name-border-width
|
||||
display: block
|
||||
max-width: $file-name-max-width
|
||||
overflow: hidden
|
||||
text-align: left
|
||||
text-overflow: ellipsis
|
||||
|
||||
.file-icon
|
||||
align-items: center
|
||||
display: flex
|
||||
height: 1em
|
||||
justify-content: center
|
||||
margin-right: 0.5em
|
||||
width: 1em
|
||||
.fa
|
||||
font-size: 14px
|
||||
|
||||
.label
|
||||
color: $label-color
|
||||
display: block
|
||||
font-size: $size-normal
|
||||
font-weight: $label-weight
|
||||
&:not(:last-child)
|
||||
margin-bottom: 0.5em
|
||||
// Sizes
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
|
||||
.help
|
||||
display: block
|
||||
font-size: $help-size
|
||||
margin-top: 0.25rem
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
&.is-#{$name}
|
||||
color: $color
|
||||
|
||||
// Containers
|
||||
|
||||
.field
|
||||
&:not(:last-child)
|
||||
margin-bottom: 0.75rem
|
||||
// Modifiers
|
||||
&.has-addons
|
||||
display: flex
|
||||
justify-content: flex-start
|
||||
.control
|
||||
&:not(:last-child)
|
||||
margin-right: -1px
|
||||
&:not(:first-child):not(:last-child)
|
||||
.button,
|
||||
.input,
|
||||
.select select
|
||||
border-radius: 0
|
||||
&:first-child:not(:only-child)
|
||||
.button,
|
||||
.input,
|
||||
.select select
|
||||
border-bottom-right-radius: 0
|
||||
border-top-right-radius: 0
|
||||
&:last-child:not(:only-child)
|
||||
.button,
|
||||
.input,
|
||||
.select select
|
||||
border-bottom-left-radius: 0
|
||||
border-top-left-radius: 0
|
||||
.button,
|
||||
.input,
|
||||
.select select
|
||||
&:not([disabled])
|
||||
&:hover,
|
||||
&.is-hovered
|
||||
z-index: 2
|
||||
&:focus,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&.is-active
|
||||
z-index: 3
|
||||
&:hover
|
||||
z-index: 4
|
||||
&.is-expanded
|
||||
flex-grow: 1
|
||||
&.has-addons-centered
|
||||
justify-content: center
|
||||
&.has-addons-right
|
||||
justify-content: flex-end
|
||||
&.has-addons-fullwidth
|
||||
.control
|
||||
flex-grow: 1
|
||||
flex-shrink: 0
|
||||
&.is-grouped
|
||||
display: flex
|
||||
justify-content: flex-start
|
||||
& > .control
|
||||
flex-shrink: 0
|
||||
&:not(:last-child)
|
||||
margin-bottom: 0
|
||||
margin-right: 0.75rem
|
||||
&.is-expanded
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
&.is-grouped-centered
|
||||
justify-content: center
|
||||
&.is-grouped-right
|
||||
justify-content: flex-end
|
||||
&.is-grouped-multiline
|
||||
flex-wrap: wrap
|
||||
& > .control
|
||||
&:last-child,
|
||||
&:not(:last-child)
|
||||
margin-bottom: 0.75rem
|
||||
&:last-child
|
||||
margin-bottom: -0.75rem
|
||||
&:not(:last-child)
|
||||
margin-bottom: 0
|
||||
&.is-horizontal
|
||||
+tablet
|
||||
display: flex
|
||||
|
||||
.field-label
|
||||
.label
|
||||
font-size: inherit
|
||||
+mobile
|
||||
margin-bottom: 0.5rem
|
||||
+tablet
|
||||
flex-basis: 0
|
||||
flex-grow: 1
|
||||
flex-shrink: 0
|
||||
margin-right: 1.5rem
|
||||
text-align: right
|
||||
&.is-small
|
||||
font-size: $size-small
|
||||
padding-top: 0.375em
|
||||
&.is-normal
|
||||
padding-top: 0.375em
|
||||
&.is-medium
|
||||
font-size: $size-medium
|
||||
padding-top: 0.375em
|
||||
&.is-large
|
||||
font-size: $size-large
|
||||
padding-top: 0.375em
|
||||
|
||||
.field-body
|
||||
.field .field
|
||||
margin-bottom: 0
|
||||
+tablet
|
||||
display: flex
|
||||
flex-basis: 0
|
||||
flex-grow: 5
|
||||
flex-shrink: 1
|
||||
.field
|
||||
margin-bottom: 0
|
||||
& > .field
|
||||
flex-shrink: 1
|
||||
&:not(.is-narrow)
|
||||
flex-grow: 1
|
||||
&:not(:last-child)
|
||||
margin-right: 0.75rem
|
||||
|
||||
.control
|
||||
box-sizing: border-box
|
||||
clear: both //fixes the icon floating out of the input when help text is floated right
|
||||
font-size: $size-normal
|
||||
position: relative
|
||||
text-align: left
|
||||
// Modifiers
|
||||
&.has-icons-left,
|
||||
&.has-icons-right
|
||||
.input,
|
||||
.select
|
||||
&:focus
|
||||
& ~ .icon
|
||||
color: $input-icon-active-color
|
||||
&.is-small ~ .icon
|
||||
font-size: $size-small
|
||||
&.is-medium ~ .icon
|
||||
font-size: $size-medium
|
||||
&.is-large ~ .icon
|
||||
font-size: $size-large
|
||||
.icon
|
||||
color: $input-icon-color
|
||||
height: $input-height
|
||||
pointer-events: none
|
||||
position: absolute
|
||||
top: 0
|
||||
width: $input-height
|
||||
z-index: 4
|
||||
&.has-icons-left
|
||||
.input,
|
||||
.select select
|
||||
padding-left: $input-height
|
||||
.icon.is-left
|
||||
left: 0
|
||||
&.has-icons-right
|
||||
.input,
|
||||
.select select
|
||||
padding-right: $input-height
|
||||
.icon.is-right
|
||||
right: 0
|
||||
&.is-loading
|
||||
&::after
|
||||
@extend %loader
|
||||
position: absolute !important
|
||||
right: 0.625em
|
||||
top: 0.625em
|
||||
z-index: 4
|
||||
&.is-small:after
|
||||
font-size: $size-small
|
||||
&.is-medium:after
|
||||
font-size: $size-medium
|
||||
&.is-large:after
|
||||
font-size: $size-large
|
||||
21
hyperglass/static/sass/elements/icon.sass
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
$icon-dimensions: 1.5rem !default
|
||||
$icon-dimensions-small: 1rem !default
|
||||
$icon-dimensions-medium: 2rem !default
|
||||
$icon-dimensions-large: 3rem !default
|
||||
|
||||
.icon
|
||||
align-items: center
|
||||
display: inline-flex
|
||||
justify-content: center
|
||||
height: $icon-dimensions
|
||||
width: $icon-dimensions
|
||||
// Sizes
|
||||
&.is-small
|
||||
height: $icon-dimensions-small
|
||||
width: $icon-dimensions-small
|
||||
&.is-medium
|
||||
height: $icon-dimensions-medium
|
||||
width: $icon-dimensions-medium
|
||||
&.is-large
|
||||
height: $icon-dimensions-large
|
||||
width: $icon-dimensions-large
|
||||
69
hyperglass/static/sass/elements/image.sass
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
$dimensions: 16 24 32 48 64 96 128 !default
|
||||
|
||||
.image
|
||||
display: block
|
||||
position: relative
|
||||
img
|
||||
display: block
|
||||
height: auto
|
||||
width: 100%
|
||||
&.is-rounded
|
||||
border-radius: $radius-rounded
|
||||
// Ratio
|
||||
&.is-square,
|
||||
&.is-1by1,
|
||||
&.is-5by4,
|
||||
&.is-4by3,
|
||||
&.is-3by2,
|
||||
&.is-5by3,
|
||||
&.is-16by9,
|
||||
&.is-2by1,
|
||||
&.is-3by1,
|
||||
&.is-4by5,
|
||||
&.is-3by4,
|
||||
&.is-2by3,
|
||||
&.is-3by5,
|
||||
&.is-9by16,
|
||||
&.is-1by2,
|
||||
&.is-1by3
|
||||
img,
|
||||
.has-ratio
|
||||
@extend %overlay
|
||||
height: 100%
|
||||
width: 100%
|
||||
&.is-square,
|
||||
&.is-1by1
|
||||
padding-top: 100%
|
||||
&.is-5by4
|
||||
padding-top: 80%
|
||||
&.is-4by3
|
||||
padding-top: 75%
|
||||
&.is-3by2
|
||||
padding-top: 66.6666%
|
||||
&.is-5by3
|
||||
padding-top: 60%
|
||||
&.is-16by9
|
||||
padding-top: 56.25%
|
||||
&.is-2by1
|
||||
padding-top: 50%
|
||||
&.is-3by1
|
||||
padding-top: 33.3333%
|
||||
&.is-4by5
|
||||
padding-top: 125%
|
||||
&.is-3by4
|
||||
padding-top: 133.3333%
|
||||
&.is-2by3
|
||||
padding-top: 150%
|
||||
&.is-3by5
|
||||
padding-top: 166.6666%
|
||||
&.is-9by16
|
||||
padding-top: 177.7777%
|
||||
&.is-1by2
|
||||
padding-top: 200%
|
||||
&.is-1by3
|
||||
padding-top: 300%
|
||||
// Sizes
|
||||
@each $dimension in $dimensions
|
||||
&.is-#{$dimension}x#{$dimension}
|
||||
height: $dimension * 1px
|
||||
width: $dimension * 1px
|
||||
35
hyperglass/static/sass/elements/notification.sass
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
$notification-background-color: $background !default
|
||||
$notification-radius: $radius !default
|
||||
$notification-padding: 1.25rem 2.5rem 1.25rem 1.5rem !default
|
||||
|
||||
.notification
|
||||
@extend %block
|
||||
background-color: $notification-background-color
|
||||
border-radius: $notification-radius
|
||||
padding: $notification-padding
|
||||
position: relative
|
||||
a:not(.button):not(.dropdown-item)
|
||||
color: currentColor
|
||||
text-decoration: underline
|
||||
strong
|
||||
color: currentColor
|
||||
code,
|
||||
pre
|
||||
background: $white
|
||||
pre code
|
||||
background: transparent
|
||||
& > .delete
|
||||
position: absolute
|
||||
right: 0.5rem
|
||||
top: 0.5rem
|
||||
.title,
|
||||
.subtitle,
|
||||
.content
|
||||
color: currentColor
|
||||
// Colors
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
$color-invert: nth($pair, 2)
|
||||
&.is-#{$name}
|
||||
background-color: $color
|
||||
color: $color-invert
|
||||
39
hyperglass/static/sass/elements/other.sass
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
.block
|
||||
@extend %block
|
||||
|
||||
.delete
|
||||
@extend %delete
|
||||
|
||||
.heading
|
||||
display: block
|
||||
font-size: 11px
|
||||
letter-spacing: 1px
|
||||
margin-bottom: 5px
|
||||
text-transform: uppercase
|
||||
|
||||
.highlight
|
||||
@extend %block
|
||||
font-weight: $weight-normal
|
||||
max-width: 100%
|
||||
overflow: hidden
|
||||
padding: 0
|
||||
pre
|
||||
overflow: auto
|
||||
max-width: 100%
|
||||
|
||||
.loader
|
||||
@extend %loader
|
||||
|
||||
.number
|
||||
align-items: center
|
||||
background-color: $background
|
||||
border-radius: $radius-rounded
|
||||
display: inline-flex
|
||||
font-size: $size-medium
|
||||
height: 2em
|
||||
justify-content: center
|
||||
margin-right: 1.5rem
|
||||
min-width: 2.5em
|
||||
padding: 0.25rem 0.5rem
|
||||
text-align: center
|
||||
vertical-align: top
|
||||
65
hyperglass/static/sass/elements/progress.sass
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
$progress-bar-background-color: $border !default
|
||||
$progress-value-background-color: $text !default
|
||||
|
||||
$progress-indeterminate-duration: 1.5s !default
|
||||
|
||||
.progress
|
||||
@extend %block
|
||||
-moz-appearance: none
|
||||
-webkit-appearance: none
|
||||
border: none
|
||||
border-radius: $radius-rounded
|
||||
display: block
|
||||
height: $size-normal
|
||||
overflow: hidden
|
||||
padding: 0
|
||||
width: 100%
|
||||
&::-webkit-progress-bar
|
||||
background-color: $progress-bar-background-color
|
||||
&::-webkit-progress-value
|
||||
background-color: $progress-value-background-color
|
||||
&::-moz-progress-bar
|
||||
background-color: $progress-value-background-color
|
||||
&::-ms-fill
|
||||
background-color: $progress-value-background-color
|
||||
border: none
|
||||
&:indeterminate
|
||||
animation-duration: $progress-indeterminate-duration
|
||||
animation-iteration-count: infinite
|
||||
animation-name: moveIndeterminate
|
||||
animation-timing-function: linear
|
||||
background-color: $progress-bar-background-color
|
||||
background-image: linear-gradient(to right, $text 30%, $progress-bar-background-color 30%)
|
||||
background-position: top left
|
||||
background-repeat: no-repeat
|
||||
background-size: 150% 150%
|
||||
&::-webkit-progress-bar
|
||||
background-color: transparent
|
||||
&::-moz-progress-bar
|
||||
background-color: transparent
|
||||
// Colors
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
&.is-#{$name}
|
||||
&::-webkit-progress-value
|
||||
background-color: $color
|
||||
&::-moz-progress-bar
|
||||
background-color: $color
|
||||
&::-ms-fill
|
||||
background-color: $color
|
||||
&:indeterminate
|
||||
background-image: linear-gradient(to right, $color 30%, $progress-bar-background-color 30%)
|
||||
|
||||
// Sizes
|
||||
&.is-small
|
||||
height: $size-small
|
||||
&.is-medium
|
||||
height: $size-medium
|
||||
&.is-large
|
||||
height: $size-large
|
||||
|
||||
@keyframes moveIndeterminate
|
||||
from
|
||||
background-position: 200% 0
|
||||
to
|
||||
background-position: -200% 0
|
||||
126
hyperglass/static/sass/elements/table.sass
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
$table-color: $grey-darker !default
|
||||
$table-background-color: $white !default
|
||||
|
||||
$table-cell-border: 1px solid $grey-lighter !default
|
||||
$table-cell-border-width: 0 0 1px !default
|
||||
$table-cell-padding: 0.5em 0.75em !default
|
||||
$table-cell-heading-color: $text-strong !default
|
||||
|
||||
$table-head-cell-border-width: 0 0 2px !default
|
||||
$table-head-cell-color: $text-strong !default
|
||||
$table-foot-cell-border-width: 2px 0 0 !default
|
||||
$table-foot-cell-color: $text-strong !default
|
||||
|
||||
$table-head-background-color: transparent !default
|
||||
$table-body-background-color: transparent !default
|
||||
$table-foot-background-color: transparent !default
|
||||
|
||||
$table-row-hover-background-color: $white-bis !default
|
||||
|
||||
$table-row-active-background-color: $primary !default
|
||||
$table-row-active-color: $primary-invert !default
|
||||
|
||||
$table-striped-row-even-background-color: $white-bis !default
|
||||
$table-striped-row-even-hover-background-color: $white-ter !default
|
||||
|
||||
.table
|
||||
@extend %block
|
||||
background-color: $table-background-color
|
||||
color: $table-color
|
||||
td,
|
||||
th
|
||||
border: $table-cell-border
|
||||
border-width: $table-cell-border-width
|
||||
padding: $table-cell-padding
|
||||
vertical-align: top
|
||||
// Colors
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
$color-invert: nth($pair, 2)
|
||||
&.is-#{$name}
|
||||
background-color: $color
|
||||
border-color: $color
|
||||
color: $color-invert
|
||||
// Modifiers
|
||||
&.is-narrow
|
||||
white-space: nowrap
|
||||
width: 1%
|
||||
&.is-selected
|
||||
background-color: $table-row-active-background-color
|
||||
color: $table-row-active-color
|
||||
a,
|
||||
strong
|
||||
color: currentColor
|
||||
th
|
||||
color: $table-cell-heading-color
|
||||
text-align: left
|
||||
tr
|
||||
&.is-selected
|
||||
background-color: $table-row-active-background-color
|
||||
color: $table-row-active-color
|
||||
a,
|
||||
strong
|
||||
color: currentColor
|
||||
td,
|
||||
th
|
||||
border-color: $table-row-active-color
|
||||
color: currentColor
|
||||
thead
|
||||
background-color: $table-head-background-color
|
||||
td,
|
||||
th
|
||||
border-width: $table-head-cell-border-width
|
||||
color: $table-head-cell-color
|
||||
tfoot
|
||||
background-color: $table-foot-background-color
|
||||
td,
|
||||
th
|
||||
border-width: $table-foot-cell-border-width
|
||||
color: $table-foot-cell-color
|
||||
tbody
|
||||
background-color: $table-body-background-color
|
||||
tr
|
||||
&:last-child
|
||||
td,
|
||||
th
|
||||
border-bottom-width: 0
|
||||
// Modifiers
|
||||
&.is-bordered
|
||||
td,
|
||||
th
|
||||
border-width: 1px
|
||||
tr
|
||||
&:last-child
|
||||
td,
|
||||
th
|
||||
border-bottom-width: 1px
|
||||
&.is-fullwidth
|
||||
width: 100%
|
||||
&.is-hoverable
|
||||
tbody
|
||||
tr:not(.is-selected)
|
||||
&:hover
|
||||
background-color: $table-row-hover-background-color
|
||||
&.is-striped
|
||||
tbody
|
||||
tr:not(.is-selected)
|
||||
&:hover
|
||||
background-color: $table-row-hover-background-color
|
||||
&:nth-child(even)
|
||||
background-color: $table-striped-row-even-hover-background-color
|
||||
&.is-narrow
|
||||
td,
|
||||
th
|
||||
padding: 0.25em 0.5em
|
||||
&.is-striped
|
||||
tbody
|
||||
tr:not(.is-selected)
|
||||
&:nth-child(even)
|
||||
background-color: $table-striped-row-even-background-color
|
||||
|
||||
.table-container
|
||||
@extend %block
|
||||
+overflow-touch
|
||||
overflow: auto
|
||||
overflow-y: hidden
|
||||
max-width: 100%
|
||||
130
hyperglass/static/sass/elements/tag.sass
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
$tag-background-color: $background !default
|
||||
$tag-color: $text !default
|
||||
$tag-radius: $radius !default
|
||||
$tag-delete-margin: 1px !default
|
||||
|
||||
.tags
|
||||
align-items: center
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
justify-content: flex-start
|
||||
.tag
|
||||
margin-bottom: 0.5rem
|
||||
&:not(:last-child)
|
||||
margin-right: 0.5rem
|
||||
&:last-child
|
||||
margin-bottom: -0.5rem
|
||||
&:not(:last-child)
|
||||
margin-bottom: 1rem
|
||||
// Sizes
|
||||
&.are-medium
|
||||
.tag:not(.is-normal):not(.is-large)
|
||||
font-size: $size-normal
|
||||
&.are-large
|
||||
.tag:not(.is-normal):not(.is-medium)
|
||||
font-size: $size-medium
|
||||
&.has-addons
|
||||
.tag
|
||||
margin-right: 0
|
||||
&:not(:first-child)
|
||||
border-bottom-left-radius: 0
|
||||
border-top-left-radius: 0
|
||||
&:not(:last-child)
|
||||
border-bottom-right-radius: 0
|
||||
border-top-right-radius: 0
|
||||
&.is-centered
|
||||
justify-content: center
|
||||
.tag
|
||||
margin-right: 0.25rem
|
||||
margin-left: 0.25rem
|
||||
&.is-right
|
||||
justify-content: flex-end
|
||||
.tag
|
||||
&:not(:first-child)
|
||||
margin-left: 0.5rem
|
||||
&:not(:last-child)
|
||||
margin-right: 0
|
||||
&.has-addons
|
||||
.tag
|
||||
margin-right: 0
|
||||
&:not(:first-child)
|
||||
margin-left: 0
|
||||
border-bottom-left-radius: 0
|
||||
border-top-left-radius: 0
|
||||
&:not(:last-child)
|
||||
border-bottom-right-radius: 0
|
||||
border-top-right-radius: 0
|
||||
|
||||
.tag:not(body)
|
||||
align-items: center
|
||||
background-color: $tag-background-color
|
||||
border-radius: $tag-radius
|
||||
color: $tag-color
|
||||
display: inline-flex
|
||||
font-size: $size-small
|
||||
height: 2em
|
||||
justify-content: center
|
||||
line-height: 1.5
|
||||
padding-left: 0.75em
|
||||
padding-right: 0.75em
|
||||
white-space: nowrap
|
||||
.delete
|
||||
margin-left: 0.25rem
|
||||
margin-right: -0.375rem
|
||||
// Colors
|
||||
@each $name, $pair in $colors
|
||||
$color: nth($pair, 1)
|
||||
$color-invert: nth($pair, 2)
|
||||
&.is-#{$name}
|
||||
background-color: $color
|
||||
color: $color-invert
|
||||
// Sizes
|
||||
&.is-normal
|
||||
font-size: $size-small
|
||||
&.is-medium
|
||||
font-size: $size-normal
|
||||
&.is-large
|
||||
font-size: $size-medium
|
||||
.icon
|
||||
&:first-child:not(:last-child)
|
||||
margin-left: -0.375em
|
||||
margin-right: 0.1875em
|
||||
&:last-child:not(:first-child)
|
||||
margin-left: 0.1875em
|
||||
margin-right: -0.375em
|
||||
&:first-child:last-child
|
||||
margin-left: -0.375em
|
||||
margin-right: -0.375em
|
||||
// Modifiers
|
||||
&.is-delete
|
||||
margin-left: $tag-delete-margin
|
||||
padding: 0
|
||||
position: relative
|
||||
width: 2em
|
||||
&::before,
|
||||
&::after
|
||||
background-color: currentColor
|
||||
content: ""
|
||||
display: block
|
||||
left: 50%
|
||||
position: absolute
|
||||
top: 50%
|
||||
transform: translateX(-50%) translateY(-50%) rotate(45deg)
|
||||
transform-origin: center center
|
||||
&::before
|
||||
height: 1px
|
||||
width: 50%
|
||||
&::after
|
||||
height: 50%
|
||||
width: 1px
|
||||
&:hover,
|
||||
&:focus
|
||||
background-color: darken($tag-background-color, 5%)
|
||||
&:active
|
||||
background-color: darken($tag-background-color, 10%)
|
||||
&.is-rounded
|
||||
border-radius: $radius-rounded
|
||||
|
||||
a.tag
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
64
hyperglass/static/sass/elements/title.sass
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
$title-color: $grey-darker !default
|
||||
$title-size: $size-3 !default
|
||||
$title-weight: $weight-semibold !default
|
||||
$title-line-height: 1.125 !default
|
||||
$title-strong-color: inherit !default
|
||||
$title-strong-weight: inherit !default
|
||||
$title-sub-size: 0.75em !default
|
||||
$title-sup-size: 0.75em !default
|
||||
|
||||
$subtitle-color: $grey-dark !default
|
||||
$subtitle-size: $size-5 !default
|
||||
$subtitle-weight: $weight-normal !default
|
||||
$subtitle-line-height: 1.25 !default
|
||||
$subtitle-strong-color: $grey-darker !default
|
||||
$subtitle-strong-weight: $weight-semibold !default
|
||||
$subtitle-negative-margin: -1.25rem !default
|
||||
|
||||
.title,
|
||||
.subtitle
|
||||
@extend %block
|
||||
word-break: break-word
|
||||
em,
|
||||
span
|
||||
font-weight: inherit
|
||||
sub
|
||||
font-size: $title-sub-size
|
||||
sup
|
||||
font-size: $title-sup-size
|
||||
.tag
|
||||
vertical-align: middle
|
||||
|
||||
.title
|
||||
color: $title-color
|
||||
font-size: $title-size
|
||||
font-weight: $title-weight
|
||||
line-height: $title-line-height
|
||||
strong
|
||||
color: $title-strong-color
|
||||
font-weight: $title-strong-weight
|
||||
& + .highlight
|
||||
margin-top: -0.75rem
|
||||
&:not(.is-spaced) + .subtitle
|
||||
margin-top: $subtitle-negative-margin
|
||||
// Sizes
|
||||
@each $size in $sizes
|
||||
$i: index($sizes, $size)
|
||||
&.is-#{$i}
|
||||
font-size: $size
|
||||
|
||||
.subtitle
|
||||
color: $subtitle-color
|
||||
font-size: $subtitle-size
|
||||
font-weight: $subtitle-weight
|
||||
line-height: $subtitle-line-height
|
||||
strong
|
||||
color: $subtitle-strong-color
|
||||
font-weight: $subtitle-strong-weight
|
||||
&:not(.is-spaced) + .title
|
||||
margin-top: $subtitle-negative-margin
|
||||
// Sizes
|
||||
@each $size in $sizes
|
||||
$i: index($sizes, $size)
|
||||
&.is-#{$i}
|
||||
font-size: $size
|
||||
4
hyperglass/static/sass/grid/_all.sass
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@charset "utf-8"
|
||||
|
||||
@import "columns.sass"
|
||||
@import "tiles.sass"
|
||||