mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
improve cache handling
This commit is contained in:
@@ -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:
|
||||
|
@@ -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):
|
||||
|
@@ -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": ...,
|
||||
|
@@ -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:
|
||||
|
@@ -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."""
|
||||
|
@@ -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}
|
||||
|
@@ -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
|
||||
|
41
hyperglass/ui/components/CacheTimeout.js
Normal file
41
hyperglass/ui/components/CacheTimeout.js
Normal file
@@ -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 <Text fontSize="xs" />;
|
||||
} else {
|
||||
let time = [zeroPad(seconds)];
|
||||
minutes !== 0 && time.unshift(zeroPad(minutes));
|
||||
hours !== 0 && time.unshift(zeroPad(hours));
|
||||
return (
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
{props.text}
|
||||
<Text as="span" fontSize="xs" color={bg[props.colorMode]}>
|
||||
{time.join(":")}
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const CacheTimeout = ({ timeout, text }) => {
|
||||
const then = timeout * 1000;
|
||||
const { colorMode } = useColorMode();
|
||||
return (
|
||||
<Countdown
|
||||
date={Date.now() + then}
|
||||
renderer={Renderer}
|
||||
daysInHours
|
||||
text={text}
|
||||
colorMode={colorMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
CacheTimeout.displayName = "CacheTimeout";
|
||||
|
||||
export default CacheTimeout;
|
@@ -36,6 +36,7 @@ const Greeting = ({ greetingConfig, content, onClickThrough }) => {
|
||||
size="full"
|
||||
isCentered
|
||||
closeOnOverlayClick={!greetingConfig.required}
|
||||
closeOnEsc={!greetingConfig.required}
|
||||
>
|
||||
<AnimatedModalOverlay
|
||||
initial={{ opacity: 0 }}
|
||||
|
@@ -8,18 +8,21 @@ import {
|
||||
ButtonGroup,
|
||||
css,
|
||||
Flex,
|
||||
Tooltip,
|
||||
Text,
|
||||
useTheme,
|
||||
useColorMode,
|
||||
} from "@chakra-ui/core";
|
||||
import styled from "@emotion/styled";
|
||||
import LightningBolt from "~/components/icons/LightningBolt";
|
||||
import useAxios from "axios-hooks";
|
||||
import strReplace from "react-string-replace";
|
||||
import { startCase } from "lodash";
|
||||
import useConfig from "~/components/HyperglassProvider";
|
||||
import useMedia from "~/components/MediaProvider";
|
||||
import CopyButton from "~/components/CopyButton";
|
||||
import RequeryButton from "~/components/RequeryButton";
|
||||
import ResultHeader from "~/components/ResultHeader";
|
||||
import { startCase } from "lodash";
|
||||
import CacheTimeout from "~/components/CacheTimeout";
|
||||
|
||||
const FormattedError = ({ keywords, message }) => {
|
||||
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 = (
|
||||
<>
|
||||
<CacheTimeout timeout={config.cache.timeout} text={config.web.text.cache_prefix} />
|
||||
{data?.cached && (
|
||||
<Tooltip hasArrow label={config.web.text.cache_icon} placement="top">
|
||||
<Box ml={1}>
|
||||
<LightningBolt color={color[colorMode]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
const cacheSm = (
|
||||
<>
|
||||
{data?.cached && (
|
||||
<Tooltip hasArrow label={config.web.text.cache_icon} placement="top">
|
||||
<Box mr={1}>
|
||||
<LightningBolt color={color[colorMode]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
<CacheTimeout timeout={config.cache.timeout} text={config.web.text.cache_prefix} />
|
||||
</>
|
||||
);
|
||||
|
||||
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" },
|
||||
})}
|
||||
>
|
||||
<AccordionHeaderWrapper hoverBg={theme.colors.blackAlpha[50]}>
|
||||
<AccordionHeaderWrapper hoverBg="blackAlpha.50">
|
||||
<AccordionHeader
|
||||
flex="1 0 auto"
|
||||
py={2}
|
||||
@@ -145,6 +180,7 @@ const Result = React.forwardRef(
|
||||
error={error}
|
||||
errorMsg={errorMsg}
|
||||
errorLevel={errorLevel}
|
||||
runtime={data?.runtime}
|
||||
/>
|
||||
</AccordionHeader>
|
||||
<ButtonGroup px={3} py={2}>
|
||||
@@ -186,31 +222,21 @@ const Result = React.forwardRef(
|
||||
{error && (
|
||||
<Alert rounded="lg" my={2} py={4} status={errorLevel}>
|
||||
<FormattedError keywords={errorKw} message={errorMsg} />
|
||||
{/* {errorMsg} */}
|
||||
</Alert>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{config.cache.show_text && (
|
||||
<Flex direction="row" flexWrap="wrap">
|
||||
<Flex
|
||||
px={3}
|
||||
mt={2}
|
||||
justifyContent={[
|
||||
"flex-start",
|
||||
"flex-start",
|
||||
"flex-end",
|
||||
"flex-end",
|
||||
]}
|
||||
flex="1 0 auto"
|
||||
>
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
{config.web.text.cache}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex direction="row" flexWrap="wrap">
|
||||
<Flex
|
||||
px={3}
|
||||
mt={2}
|
||||
justifyContent={["flex-start", "flex-start", "flex-end", "flex-end"]}
|
||||
flex="1 0 auto"
|
||||
>
|
||||
{data && !error && config.cache.show_text && cacheData}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
|
@@ -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 (
|
||||
<Stack ref={ref} isInline alignItems="center" w="100%">
|
||||
{loading ? (
|
||||
@@ -23,7 +40,13 @@ export default React.forwardRef(({ title, loading, error, errorMsg, errorLevel }
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Icon name="check" color={defaultStatusColor[colorMode]} mr={4} size={6} />
|
||||
<Tooltip
|
||||
hasArrow
|
||||
label={runtimeText(runtime, config.web.text.complete_time)}
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="check" color={defaultStatusColor[colorMode]} mr={4} size={6} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Text fontSize="lg">{title}</Text>
|
||||
<AccordionIcon ml="auto" />
|
||||
|
25
hyperglass/ui/components/icons/LightningBolt.js
Normal file
25
hyperglass/ui/components/icons/LightningBolt.js
Normal file
@@ -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 (
|
||||
<svg
|
||||
width={theme.space[size]}
|
||||
height={theme.space[size]}
|
||||
viewBox="0 0 16 16"
|
||||
fill={theme.colors[color]}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M11.251.068a.5.5 0 0 1 .227.58L9.677 6.5H13a.5.5 0 0 1 .364.843l-8 8.5a.5.5 0 0 1-.842-.49L6.323 9.5H3a.5.5 0 0 1-.364-.843l8-8.5a.5.5 0 0 1 .615-.09z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
LightningBolt.displayName = "LightningBolt";
|
||||
|
||||
export default LightningBolt;
|
6
hyperglass/ui/package.json
vendored
6
hyperglass/ui/package.json
vendored
@@ -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": {
|
||||
|
7
hyperglass/ui/yarn.lock
vendored
7
hyperglass/ui/yarn.lock
vendored
@@ -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"
|
||||
|
Reference in New Issue
Block a user