diff --git a/docs/docs/ui.mdx b/docs/docs/ui.mdx
index 4b4bc4b..2906057 100644
--- a/docs/docs/ui.mdx
+++ b/docs/docs/ui.mdx
@@ -92,17 +92,19 @@ By default, no Opengraph image is set. If you define one with `image`, hyperglas
## `text`
-| Parameter | Type | Default | Description |
-| :--------------- | :----: | :------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `cache` | String | `'Results will be cached for {timeout} {period}.'` | User-facing text regarding the cache timeout. `{timeout}` and `{period}` are replaced with the a generated value derived from the length of the request_timeout setting. |
-| `fqdn_tooltip` | String | `'Use {protocol}'` | Text displayed when a user hovers over the IPv4 or IPv6 button on an FQDN target resolved by DNS. `{protocol}` is replaced with the relevant IP protocol. |
-| `query_location` | String | `'Location'` | Query Location (router) form label. |
-| `query_target` | String | `'Target'` | Query Target (IP/hostname/community/AS Path) form label. |
-| `query_type` | String | `'Query Type'` | Query Type (BGP Route, Ping, Traceroute, etc.) form label. |
-| `query_vrf` | String | `'Routing Table'` | Query VRF form label. |
-| `subtitle` | String | `'Network Looking Glass'` | Subtitle text. value. |
-| `title` | String | `'hyperglass'` | Title text. |
-| `title_mode` | String | `'text_only'` | Set the title mode. Must be text_only, logo_only, logo_subtitle, or all |
+| Parameter | Type | Default | Description |
+| :--------------- | :----: | :------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `cache_prefix` | String | `'Results cached for '` | Text displayed with the cache timeout countdown. |
+| `cache_icon` | String | `'Cached Response'` | Text displayed when a user hovers over the lightning bolt icon, which is displayed when a response from the server was a cached response. |
+| `completed_time` | String | `'Completed in {seconds}'` | Text displayed when a user hovers over the success icon for a query result. `{seconds}` will be replaced with 'n seconds' where n is the time a query took to complete. |
+| `fqdn_tooltip` | String | `'Use {protocol}'` | Text displayed when a user hovers over the IPv4 or IPv6 button on an FQDN target resolved by DNS. `{protocol}` is replaced with the relevant IP protocol. |
+| `query_location` | String | `'Location'` | Query Location (router) form label. |
+| `query_target` | String | `'Target'` | Query Target (IP/hostname/community/AS Path) form label. |
+| `query_type` | String | `'Query Type'` | Query Type (BGP Route, Ping, Traceroute, etc.) form label. |
+| `query_vrf` | String | `'Routing Table'` | Query VRF form label. |
+| `subtitle` | String | `'Network Looking Glass'` | Subtitle text. value. |
+| `title` | String | `'hyperglass'` | Title text. |
+| `title_mode` | String | `'text_only'` | Set the title mode. Must be text_only, logo_only, logo_subtitle, or all |
:::note Title Mode
The `title_mode` parameter behaves in the following manner:
diff --git a/hyperglass/api/models/response.py b/hyperglass/api/models/response.py
index f06565f..463b500 100644
--- a/hyperglass/api/models/response.py
+++ b/hyperglass/api/models/response.py
@@ -1,9 +1,9 @@
"""Response model."""
# Standard Library
-from typing import List
+from typing import List, Optional
# Third Party
-from pydantic import BaseModel, StrictStr, StrictBool, constr
+from pydantic import BaseModel, StrictInt, StrictStr, StrictBool, constr
# Project
from hyperglass.configuration import params
@@ -14,6 +14,7 @@ class QueryError(BaseModel):
output: StrictStr = params.messages.general
level: constr(regex=r"(success|warning|error|danger)") = "danger"
+ id: Optional[StrictStr]
keywords: List[StrictStr] = []
class Config:
@@ -56,6 +57,9 @@ class QueryResponse(BaseModel):
output: StrictStr
level: constr(regex=r"success") = "success"
+ id: StrictStr
+ cached: StrictBool
+ runtime: StrictInt
keywords: List[StrictStr] = []
class Config:
diff --git a/hyperglass/api/routes.py b/hyperglass/api/routes.py
index a731639..23b3880 100644
--- a/hyperglass/api/routes.py
+++ b/hyperglass/api/routes.py
@@ -71,8 +71,17 @@ async def query(query_data: Query, request: Request):
log.info(f"Starting query execution for query {query_data.summary}")
- # Check if cached entry exists
- if not await cache.get(cache_key):
+ cache_response = await cache.get(cache_key)
+
+ cached = False
+ if cache_response:
+ # If a cached response exists, reset the expiration time.
+ await cache.expire(cache_key, seconds=cache_timeout)
+
+ cached = True
+ runtime = 0
+
+ elif not cache_response:
log.debug(f"No existing cache entry for query {cache_key}")
log.debug(
f"Created new cache key {cache_key} entry for query {query_data.summary}"
@@ -94,13 +103,22 @@ async def query(query_data: Query, request: Request):
log.debug(f"Added cache entry for query: {cache_key}")
+ runtime = int(round(elapsedtime, 0))
+
# If it does, return the cached entry
cache_response = await cache.get(cache_key)
log.debug(f"Cache match for {cache_key}:\n {cache_response}")
log.success(f"Completed query execution for {query_data.summary}")
- return {"output": cache_response, "level": "success", "keywords": []}
+ return {
+ "output": cache_response,
+ "id": cache_key,
+ "cached": cached,
+ "runtime": runtime,
+ "level": "success",
+ "keywords": [],
+ }
async def import_certificate(encoded_request: EncodedRequest):
diff --git a/hyperglass/configuration/__init__.py b/hyperglass/configuration/__init__.py
index e7e92fb..03f3afc 100644
--- a/hyperglass/configuration/__init__.py
+++ b/hyperglass/configuration/__init__.py
@@ -4,7 +4,6 @@
import os
import copy
import json
-import math
from pathlib import Path
# Third Party
@@ -169,20 +168,6 @@ try:
**params.dict(exclude={"web", "queries", "messages"})
)
- # Automatically derive the cache timeout period term
- # (minutes, seconds) based on the cache timeout value.
- if params.cache.timeout >= 60:
- _cache_timeout = math.ceil(params.cache.timeout / 60)
- _cache_period = "minutes"
- elif params.cache.timeout < 60:
- _cache_timeout = params.cache.timeout
- _cache_period = "seconds"
-
- # Format the cache display text to match the real values.
- params.web.text.cache = params.web.text.cache.format(
- timeout=_cache_timeout, period=_cache_period
- )
-
# If keywords are unmodified (default), add the org name &
# site_title.
if _params.Params().site_keywords == params.site_keywords:
@@ -420,7 +405,7 @@ networks = _build_networks()
frontend_networks = _build_frontend_networks()
frontend_devices = _build_frontend_devices()
_frontend_fields = {
- "cache": {"show_text"},
+ "cache": {"show_text", "timeout"},
"debug": ...,
"developer_mode": ...,
"primary_asn": ...,
diff --git a/hyperglass/configuration/models/cache.py b/hyperglass/configuration/models/cache.py
index 7f372a8..efc2ded 100644
--- a/hyperglass/configuration/models/cache.py
+++ b/hyperglass/configuration/models/cache.py
@@ -28,7 +28,7 @@ class Cache(HyperglassModel):
show_text: StrictBool = Field(
True,
title="Show Text",
- description="Show the [cache](/fixme) text in the hyperglass UI.",
+ description="Show the cache text in the hyperglass UI.",
)
class Config:
diff --git a/hyperglass/configuration/models/web.py b/hyperglass/configuration/models/web.py
index 1d1f08b..f1572ec 100644
--- a/hyperglass/configuration/models/web.py
+++ b/hyperglass/configuration/models/web.py
@@ -138,7 +138,9 @@ class Text(HyperglassModel):
query_target: StrictStr = "Target"
query_vrf: StrictStr = "Routing Table"
fqdn_tooltip: StrictStr = "Use {protocol}" # Formatted by Javascript
- cache: StrictStr = "Results will be cached for {timeout} {period}."
+ cache_prefix: StrictStr = "Results cached for "
+ cache_icon: StrictStr = "Cached Response"
+ complete_time: StrictStr = "Completed in {seconds}" # Formatted by Javascript
@validator("title_mode")
def validate_title_mode(cls, value):
@@ -147,6 +149,11 @@ class Text(HyperglassModel):
value = "logo_subtitle"
return value
+ @validator("cache_prefix")
+ def validate_cache_prefix(cls, value):
+ """Ensure trailing whitespace."""
+ return " ".join(value.split()) + " "
+
class ThemeColors(HyperglassModel):
"""Validation model for theme colors."""
diff --git a/hyperglass/examples/commands.yaml b/hyperglass/examples/commands.yaml
index 3145145..e69de29 100644
--- a/hyperglass/examples/commands.yaml
+++ b/hyperglass/examples/commands.yaml
@@ -1,125 +0,0 @@
-# arista:
-# ipv4_default:
-# bgp_aspath: show ip bgp regexp {target}
-# bgp_community: show ip bgp community {target}
-# bgp_route: show ip bgp {target}
-# ping: ping ip {target} source {source}
-# traceroute: traceroute ip {target} source {source}
-# ipv4_vpn:
-# bgp_aspath: show ip bgp regexp {target} vrf {vrf}
-# bgp_community: show ip bgp community {target} vrf {vrf}
-# bgp_route: show ip bgp {target} vrf {vrf}
-# ping: ping vrf {vrf} ip {target} source {source}
-# traceroute: traceroute vrf {vrf} ip {target} source {source}
-# ipv6_default:
-# bgp_aspath: show ipv6 bgp regexp {target}
-# bgp_community: show ipv6 bgp community {target}
-# bgp_route: show ipv6 bgp {target}
-# ping: ping ipv6 {target} source {source}
-# traceroute: traceroute ipv6 {target} source {source}
-# ipv6_vpn:
-# bgp_aspath: show ipv6 bgp regexp {target} vrf {vrf}
-# bgp_community: show ipv6 bgp community {target} vrf {vrf}
-# bgp_route: show ipv6 bgp {target} vrf {vrf}
-# ping: ping vrf {vrf} ipv6 {target} source {source}
-# traceroute: traceroute vrf {vrf} ipv6 {target} source {source}
-# cisco_ios:
-# ipv4_default:
-# bgp_aspath: show bgp ipv4 unicast quote-regexp "{target}"
-# bgp_community: show bgp ipv4 unicast community {target}
-# bgp_route: show bgp ipv4 unicast {target} | exclude pathid:|Epoch
-# ping: ping {target} repeat 5 source {source}
-# traceroute: traceroute {target} timeout 1 probe 2 source {source}
-# ipv4_vpn:
-# bgp_aspath: show bgp vpnv4 unicast vrf {vrf} quote-regexp "{target}"
-# bgp_community: show bgp vpnv4 unicast vrf {vrf} community {target}
-# bgp_route: show bgp vpnv4 unicast vrf {vrf} {target}
-# ping: ping vrf {vrf} {target} repeat 5 source {source}
-# traceroute: traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}
-# ipv6_default:
-# bgp_aspath: show bgp ipv6 unicast quote-regexp "{target}"
-# bgp_community: show bgp ipv6 unicast community {target}
-# bgp_route: show bgp ipv6 unicast {target} | exclude pathid:|Epoch
-# ping: ping ipv6 {target} repeat 5 source {source}
-# traceroute: traceroute ipv6 {target} timeout 1 probe 2 source {source}
-# ipv6_vpn:
-# bgp_aspath: show bgp vpnv6 unicast vrf {vrf} quote-regexp "{target}"
-# bgp_community: show bgp vpnv6 unicast vrf {vrf} community {target}
-# bgp_route: show bgp vpnv6 unicast vrf {vrf} {target}
-# ping: ping vrf {vrf} {target} repeat 5 source {source}
-# traceroute: traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}
-# cisco_xr:
-# ipv4_default:
-# bgp_aspath: show bgp ipv4 unicast regexp {target}
-# bgp_community: show bgp ipv4 unicast community {target}
-# bgp_route: show bgp ipv4 unicast {target}
-# ping: ping ipv4 {target} count 5 source {source}
-# traceroute: traceroute ipv4 {target} timeout 1 probe 2 source {source}
-# ipv4_vpn:
-# bgp_aspath: show bgp vpnv4 unicast vrf {vrf} regexp {target}
-# bgp_community: show bgp vpnv4 unicast vrf {vrf} community {target}
-# bgp_route: show bgp vpnv4 unicast vrf {vrf} {target}
-# ping: ping vrf {vrf} {target} count 5 source {source}
-# traceroute: traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}
-# ipv6_default:
-# bgp_aspath: show bgp ipv6 unicast regexp {target}
-# bgp_community: show bgp ipv6 unicast community {target}
-# bgp_route: show bgp ipv6 unicast {target}
-# ping: ping ipv6 {target} count 5 source {source}
-# traceroute: traceroute ipv6 {target} timeout 1 probe 2 source {source}
-# ipv6_vpn:
-# bgp_aspath: show bgp vpnv6 unicast vrf {vrf} regexp {target}
-# bgp_community: show bgp vpnv6 unicast vrf {vrf} community {target}
-# bgp_route: show bgp vpnv6 unicast vrf {vrf} {target}
-# ping: ping vrf {vrf} {target} count 5 source {source}
-# traceroute: traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}
-# huawei:
-# ipv4_default:
-# bgp_aspath: display bgp routing-table regular-expression {target}
-# bgp_community: display bgp routing-table regular-expression {target}
-# bgp_route: display bgp routing-table {target}
-# ping: ping -c 5 -a {source} {target}
-# traceroute: tracert -q 2 -f 1 -a {source} {target}
-# ipv4_vpn:
-# bgp_aspath: display bgp vpnv4 vpn-instance {vrf} routing-table regular-expression {target}
-# bgp_community: display bgp vpnv4 vpn-instance {vrf} routing-table regular-expression {target}
-# bgp_route: display bgp vpnv4 vpn-instance {vrf} routing-table {target}
-# ping: ping -vpn-instance {vrf} -c 5 -a {source} {target}
-# traceroute: tracert -q 2 -f 1 -vpn-instance {vrf} -a {source} {target}
-# ipv6_default:
-# bgp_aspath: display bgp ipv6 routing-table regular-expression {target}
-# bgp_community: display bgp ipv6 routing-table community {target}
-# bgp_route: display bgp ipv6 routing-table {target}
-# ping: ping ipv6 -c 5 -a {source} {target}
-# traceroute: tracert ipv6 -q 2 -f 1 -a {source} {target}
-# ipv6_vpn:
-# bgp_aspath: display bgp vpnv6 vpn-instance {vrf} routing-table regular-expression {target}
-# bgp_community: display bgp vpnv6 vpn-instance {vrf} routing-table regular-expression {target}
-# bgp_route: display bgp vpnv6 vpn-instance {vrf} routing-table {target}
-# ping: ping vpnv6 vpn-instance {vrf} -c 5 -a {source} {target}
-# traceroute: tracert -q 2 -f 1 vpn-instance {vrf} -a {source} {target}
-# juniper:
-# ipv4_default:
-# bgp_aspath: show route protocol bgp table inet.0 aspath-regex "{target}"
-# bgp_community: show route protocol bgp table inet.0 community {target}
-# bgp_route: show route protocol bgp table inet.0 {target} detail | except Label | except Label | except "Next hop type" | except Task | except Address | except "Session Id" | except State | except "Next-hop reference" | except destinations | except "Announcement bits"
-# ping: ping inet {target} count 5 source {source}
-# traceroute: traceroute inet {target} wait 1 source {source}
-# ipv4_vpn:
-# bgp_aspath: show route protocol bgp table {vrf}.inet.0 aspath-regex "{target}"
-# bgp_community: show route protocol bgp table {vrf}.inet.0 community {target}
-# bgp_route: show route protocol bgp table {vrf}.inet.0 {target} detail | except Label | except Label | except "Next hop type" | except Task | except Address | except "Session Id" | except State | except "Next-hop reference" | except destinations | except "Announcement bits"
-# ping: ping inet routing-instance {vrf} {target} count 5 source {source}
-# traceroute: traceroute inet routing-instance {vrf} {target} wait 1 source {source}
-# ipv6_default:
-# bgp_aspath: show route protocol bgp table inet6.0 aspath-regex "{target}"
-# bgp_community: show route protocol bgp table inet6.0 community {target}
-# bgp_route: show route protocol bgp table inet6.0 {target} detail | except Label | except Label | except "Next hop type" | except Task | except Address | except "Session Id" | except State | except "Next-hop reference" | except destinations | except "Announcement bits"
-# ping: ping inet6 {target} count 5 source {source}
-# traceroute: traceroute inet6 {target} wait 2 source {source}
-# ipv6_vpn:
-# bgp_aspath: show route protocol bgp table {vrf}.inet6.0 aspath-regex "{target}"
-# bgp_community: show route protocol bgp table {vrf}.inet6.0 community {target}
-# bgp_route: show route protocol bgp table {vrf}.inet6.0 {target} detail | except Label | except Label | except "Next hop type" | except Task | except Address | except "Session Id" | except State | except "Next-hop reference" | except destinations | except "Announcement bits"
-# ping: ping inet6 routing-instance {vrf} {target} count 5 source {source}
-# traceroute: traceroute inet6 routing-instance {vrf} {target} wait 2 source {source}
diff --git a/hyperglass/examples/hyperglass.yaml b/hyperglass/examples/hyperglass.yaml
index 6703fce..e69de29 100644
--- a/hyperglass/examples/hyperglass.yaml
+++ b/hyperglass/examples/hyperglass.yaml
@@ -1,155 +0,0 @@
-# cache:
-# database: 1
-# host: localhost
-# port: 6379
-# show_text: true
-# timeout: 120
-# cors_origins: []
-# debug: false
-# developer_mode: false
-# docs:
-# base_url: https://lg.example.net
-# description: ""
-# devices:
-# description: List of all devices/locations with associated identifiers, display names, networks, & VRFs.
-# summary: Devices List
-# title: Devices
-# enable: true
-# mode: redoc
-# openapi_uri: /openapi.json
-# queries:
-# description: List of supported query types.
-# summary: Query Types
-# title: Supported Queries
-# query:
-# description: Request a query response per-location.
-# summary: Query the Looking Glass
-# title: Submit Query
-# title: "{site_title} API Documentation"
-# uri: /api/docs
-# listen_address: localhost
-# listen_port: 8001
-# messages:
-# acl_denied: "{target} is a member of {denied_network}, which is not allowed."
-# acl_not_allowed: "{target} is not allowed."
-# authentication_error: Authentication error occurred.
-# connection_error: "Error connecting to {device_name}: {error}"
-# feature_not_enabled: "{feature} is not enabled."
-# general: Something went wrong.
-# invalid_field: "{input} is an invalid {field}."
-# invalid_input: "{target} is not a valid {query_type} target."
-# no_input: "{field} must be specified."
-# no_output: No output.
-# no_response: No response.
-# request_timeout: Request timed out.
-# vrf_not_associated: VRF {vrf_name} is not associated with {device_name}.
-# vrf_not_found: VRF {vrf_name} is not defined.
-# netmiko_delay_factor: 0.1
-# org_name: Beloved Hyperglass User
-# primary_asn: "65001"
-# queries:
-# bgp_aspath:
-# display_name: BGP AS Path
-# enable: true
-# pattern:
-# asdot: ^(\^|^\_)((\d+\.\d+)\_|(\d+\.\d+)\$|(\d+\.\d+)\(\_\.\+\_\))+$
-# asplain: ^(\^|^\_)(\d+\_|\d+\$|\d+\(\_\.\+\_\))+$
-# mode: asplain
-# bgp_community:
-# display_name: BGP Community
-# enable: true
-# pattern:
-# decimal: ^[0-9]{1,10}$
-# extended_as: ^([0-9]{0,5})\:([0-9]{1,5})$
-# large: ^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$
-# bgp_route:
-# display_name: BGP Route
-# enable: true
-# ping:
-# display_name: Ping
-# enable: true
-# traceroute:
-# display_name: Traceroute
-# enable: true
-# request_timeout: 90
-# site_description: Beloved Hyperglass User Network Looking Glass
-# site_keywords:
-# - hyperglass
-# - looking glass
-# - lg
-# - peer
-# - peering
-# - ip
-# - ipv4
-# - ipv6
-# - transit
-# - community
-# - communities
-# - bgp
-# - routing
-# - network
-# - isp
-# - internet service provider
-# site_title: hyperglass
-# web:
-# credit:
-# enable: true
-# dns_provider:
-# name: cloudflare
-# url: https://cloudflare-dns.com/dns-query
-# external_link:
-# enable: true
-# title: PeeringDB
-# url: https://www.peeringdb.com/asn/{primary_asn}
-# help_menu:
-# enable: true
-# file: null
-# title: Help
-# logo:
-# dark: images/hyperglass-dark.png
-# favicons: ui/images/favicons/
-# height: null
-# light: images/hyperglass-light.png
-# width: 80%
-# opengraph:
-# height: 1132
-# image: images/hyperglass-opengraph.png
-# width: 7355
-# terms:
-# enable: true
-# file: null
-# title: Terms
-# text:
-# cache: Results will be cached for {timeout} {period}.
-# fqdn_tooltip: Use {protocol}
-# query_location: Location
-# query_target: Target
-# query_type: Query Type
-# query_vrf: Routing Table
-# subtitle: AS{primary_asn}
-# title: hyperglass
-# title_mode: logo_only
-# theme:
-# colors:
-# black: "#262626"
-# blue: "#314cb6"
-# cyan: "#118ab2"
-# danger: "#d84b4b"
-# error: "#ff6b35"
-# gray: "#c1c7cc"
-# green: "#35b246"
-# orange: "#ff6b35"
-# pink: "#f2607d"
-# primary: "#118ab2"
-# purple: "#8d30b5"
-# red: "#d84b4b"
-# secondary: "#314cb6"
-# success: "#35b246"
-# teal: "#35b299"
-# warning: "#edae49"
-# white: "#f7f7f7"
-# yellow: "#edae49"
-# default_color_mode: null
-# fonts:
-# body: Nunito
-# mono: Fira Code
diff --git a/hyperglass/ui/components/CacheTimeout.js b/hyperglass/ui/components/CacheTimeout.js
new file mode 100644
index 0000000..4c37beb
--- /dev/null
+++ b/hyperglass/ui/components/CacheTimeout.js
@@ -0,0 +1,41 @@
+import * as React from "react";
+import Countdown, { zeroPad } from "react-countdown";
+import { Text, useColorMode } from "@chakra-ui/core";
+
+const bg = { dark: "white", light: "black" };
+
+const Renderer = ({ hours, minutes, seconds, completed, props }) => {
+ if (completed) {
+ return ;
+ } else {
+ let time = [zeroPad(seconds)];
+ minutes !== 0 && time.unshift(zeroPad(minutes));
+ hours !== 0 && time.unshift(zeroPad(hours));
+ return (
+
+ {props.text}
+
+ {time.join(":")}
+
+
+ );
+ }
+};
+
+const CacheTimeout = ({ timeout, text }) => {
+ const then = timeout * 1000;
+ const { colorMode } = useColorMode();
+ return (
+
+ );
+};
+
+CacheTimeout.displayName = "CacheTimeout";
+
+export default CacheTimeout;
diff --git a/hyperglass/ui/components/Greeting.js b/hyperglass/ui/components/Greeting.js
index 52184f9..d4405c7 100644
--- a/hyperglass/ui/components/Greeting.js
+++ b/hyperglass/ui/components/Greeting.js
@@ -36,6 +36,7 @@ const Greeting = ({ greetingConfig, content, onClickThrough }) => {
size="full"
isCentered
closeOnOverlayClick={!greetingConfig.required}
+ closeOnEsc={!greetingConfig.required}
>
{
const patternStr = keywords.map((kw) => `(${kw})`).join("|");
@@ -48,6 +51,10 @@ const AccordionHeaderWrapper = styled(Flex)`
`;
const statusMap = { success: "success", warning: "warning", error: "warning", danger: "error" };
+const bg = { dark: "gray.800", light: "blackAlpha.100" };
+const color = { dark: "white", light: "black" };
+const selectionBg = { dark: "white", light: "black" };
+const selectionColor = { dark: "black", light: "white" };
const Result = React.forwardRef(
(
@@ -65,12 +72,8 @@ const Result = React.forwardRef(
ref
) => {
const config = useConfig();
- const theme = useTheme();
+ const { isSm } = useMedia();
const { colorMode } = useColorMode();
- const bg = { dark: theme.colors.gray[800], light: theme.colors.blackAlpha[100] };
- const color = { dark: theme.colors.white, light: theme.colors.black };
- const selectionBg = { dark: theme.colors.white, light: theme.colors.black };
- const selectionColor = { dark: theme.colors.black, light: theme.colors.white };
const [{ data, loading, error }, refetch] = useAxios({
url: "/api/query/",
method: "post",
@@ -91,7 +94,12 @@ const Result = React.forwardRef(
setOpen(!isOpen);
setOverride(true);
};
- const cleanOutput = data && data.output.split("\\n").join("\n").replace(/\n\n/g, "\n");
+ const cleanOutput =
+ data &&
+ data.output
+ .split("\\n")
+ .join("\n")
+ .replace(/\n\n/g, "\n");
const errorKw = (error && error.response?.data?.keywords) || [];
@@ -113,6 +121,33 @@ const Result = React.forwardRef(
const errorLevel =
(error?.response?.data?.level && statusMap[error.response?.data?.level]) ?? "error";
+ const cacheLg = (
+ <>
+
+ {data?.cached && (
+
+
+
+
+
+ )}
+ >
+ );
+ const cacheSm = (
+ <>
+ {data?.cached && (
+
+
+
+
+
+ )}
+
+ >
+ );
+
+ const cacheData = isSm ? cacheSm : cacheLg;
+
useEffect(() => {
!loading && resultsComplete === null && setComplete(index);
}, [loading, resultsComplete]);
@@ -130,7 +165,7 @@ const Result = React.forwardRef(
"&:first-of-type": { borderTop: "none" },
})}
>
-
+
@@ -186,31 +222,21 @@ const Result = React.forwardRef(
{error && (
- {/* {errorMsg} */}
)}
- {config.cache.show_text && (
-
-
-
- {config.web.text.cache}
-
-
+
+
+ {data && !error && config.cache.show_text && cacheData}
- )}
+
);
diff --git a/hyperglass/ui/components/ResultHeader.js b/hyperglass/ui/components/ResultHeader.js
index b3df955..aab8c30 100644
--- a/hyperglass/ui/components/ResultHeader.js
+++ b/hyperglass/ui/components/ResultHeader.js
@@ -1,14 +1,31 @@
import React from "react";
import { AccordionIcon, Icon, Spinner, Stack, Text, Tooltip, useColorMode } from "@chakra-ui/core";
+import format from "string-format";
+import useConfig from "~/components/HyperglassProvider";
-export default React.forwardRef(({ title, loading, error, errorMsg, errorLevel }, ref) => {
+format.extend(String.prototype, {});
+
+const runtimeText = (runtime, text) => {
+ let unit;
+ if (runtime > 1) {
+ unit = "seconds";
+ } else {
+ unit = "second";
+ }
+ const fmt = text.format({ seconds: runtime });
+ return `${fmt} ${unit}`;
+};
+
+const statusColor = { dark: "primary.300", light: "primary.500" };
+const warningColor = { dark: 300, light: 500 };
+const defaultStatusColor = {
+ dark: "success.300",
+ light: "success.500",
+};
+
+export default React.forwardRef(({ title, loading, error, errorMsg, errorLevel, runtime }, ref) => {
const { colorMode } = useColorMode();
- const statusColor = { dark: "primary.300", light: "primary.500" };
- const warningColor = { dark: 300, light: 500 };
- const defaultStatusColor = {
- dark: "success.300",
- light: "success.500"
- };
+ const config = useConfig();
return (
{loading ? (
@@ -23,7 +40,13 @@ export default React.forwardRef(({ title, loading, error, errorMsg, errorLevel }
/>
) : (
-
+
+
+
)}
{title}
diff --git a/hyperglass/ui/components/icons/LightningBolt.js b/hyperglass/ui/components/icons/LightningBolt.js
new file mode 100644
index 0000000..29d40d6
--- /dev/null
+++ b/hyperglass/ui/components/icons/LightningBolt.js
@@ -0,0 +1,25 @@
+import * as React from "react";
+import { useTheme } from "@chakra-ui/core";
+
+const LightningBolt = ({ size = 4, color = "currentColor" }) => {
+ const theme = useTheme();
+ return (
+
+ );
+};
+
+LightningBolt.displayName = "LightningBolt";
+
+export default LightningBolt;
diff --git a/hyperglass/ui/package.json b/hyperglass/ui/package.json
index cb4078c..eac339b 100644
--- a/hyperglass/ui/package.json
+++ b/hyperglass/ui/package.json
@@ -23,6 +23,7 @@
"lodash": "^4.17.15",
"next": "^9.3.1",
"react": "^16.13.1",
+ "react-countdown": "^2.2.1",
"react-dom": "^16.13.1",
"react-hook-form": "^5.1.1",
"react-icons": "^3.9.0",
@@ -54,11 +55,6 @@
"http-proxy-middleware": "0.20.0",
"prettier": "^1.19.1"
},
- "babel": {
- "presets": [
- "next/babel"
- ]
- },
"eslintConfig": {
"parser": "babel-eslint",
"rules": {
diff --git a/hyperglass/ui/yarn.lock b/hyperglass/ui/yarn.lock
index bee9df1..d153468 100644
--- a/hyperglass/ui/yarn.lock
+++ b/hyperglass/ui/yarn.lock
@@ -6455,6 +6455,13 @@ react-clientside-effect@^1.2.2:
dependencies:
"@babel/runtime" "^7.0.0"
+react-countdown@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/react-countdown/-/react-countdown-2.2.1.tgz#28d56fc1874b5b0d5238b7efe3ae914ed8033783"
+ integrity sha512-e8dUUhlysDqgci32VOOe0uDfeDMaiyyFNrWHdmMky5fithYDt4iOJa22EF96VbkU64R4D+Bww4AbLpqA/J4dww==
+ dependencies:
+ prop-types "^15.7.2"
+
react-dom@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"