mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-06-27 08:39:49 +00:00
Add Queryable
component to show a green add circle icon for the queryable UI elements (#512)
* Add `Queryable` component to show a green circle and use it in `EntryViewLine`
* Refactor `Queryable` component
* Use the `Queryable` component `EntryDetailed`
* Use the `Queryable` component `Summary`
* Instead of passing the style to `Queryable`, pass the children components directly
* Make `useTooltip = true` by default in `Queryable`
* Refactor a lot of styling to achieve using `Queryable` in `Protocol` component
* Migrate the last queryable elements in `EntryListItem` to `Queryable` component
* Fix some of the styling issues
* Make horizontal `Protocol` `Queryable` too
* Remove unnecessary child constants
* Revert some of the changes in 2a93f365f5
* Fix rest of the styling issues
* Fix one more styling issue
* Update the screenshots and text in the cheatsheet according to the change
* Use `let` not `var`
* Add missing dependencies to the React hook
This commit is contained in:
parent
b745f65971
commit
af557f7052
@ -3,6 +3,7 @@ import EntryViewer from "./EntryDetailed/EntryViewer";
|
|||||||
import {EntryItem} from "./EntryListItem/EntryListItem";
|
import {EntryItem} from "./EntryListItem/EntryListItem";
|
||||||
import {makeStyles} from "@material-ui/core";
|
import {makeStyles} from "@material-ui/core";
|
||||||
import Protocol from "./UI/Protocol"
|
import Protocol from "./UI/Protocol"
|
||||||
|
import Queryable from "./UI/Queryable";
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles(() => ({
|
||||||
entryTitle: {
|
entryTitle: {
|
||||||
@ -37,28 +38,33 @@ const EntryTitle: React.FC<any> = ({protocol, data, bodySize, elapsedTime, updat
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const response = data.response;
|
const response = data.response;
|
||||||
|
|
||||||
|
|
||||||
return <div className={classes.entryTitle}>
|
return <div className={classes.entryTitle}>
|
||||||
<Protocol protocol={protocol} horizontal={true} updateQuery={null}/>
|
<Protocol protocol={protocol} horizontal={true} updateQuery={updateQuery}/>
|
||||||
<div style={{right: "30px", position: "absolute", display: "flex"}}>
|
<div style={{right: "30px", position: "absolute", display: "flex"}}>
|
||||||
{response && <div
|
{response && <Queryable
|
||||||
className="queryable"
|
query={`response.bodySize == ${bodySize}`}
|
||||||
style={{margin: "0 18px", opacity: 0.5}}
|
updateQuery={updateQuery}
|
||||||
onClick={() => {
|
style={{margin: "0 18px"}}
|
||||||
updateQuery(`response.bodySize == ${bodySize}`)
|
displayIconOnMouseOver={true}
|
||||||
}}
|
>
|
||||||
|
<div
|
||||||
|
style={{opacity: 0.5}}
|
||||||
>
|
>
|
||||||
{formatSize(bodySize)}
|
{formatSize(bodySize)}
|
||||||
</div>}
|
</div>
|
||||||
{response && <div
|
</Queryable>}
|
||||||
className="queryable"
|
{response && <Queryable
|
||||||
style={{marginRight: 18, opacity: 0.5}}
|
query={`elapsedTime >= ${elapsedTime}`}
|
||||||
onClick={() => {
|
updateQuery={updateQuery}
|
||||||
updateQuery(`elapsedTime >= ${elapsedTime}`)
|
style={{marginRight: 18}}
|
||||||
}}
|
displayIconOnMouseOver={true}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{opacity: 0.5}}
|
||||||
>
|
>
|
||||||
{Math.round(elapsedTime)}ms
|
{Math.round(elapsedTime)}ms
|
||||||
</div>}
|
</div>
|
||||||
|
</Queryable>}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
font-weight: 600
|
font-weight: 600
|
||||||
font-size: .75rem
|
font-size: .75rem
|
||||||
line-height: 1.2
|
line-height: 1.2
|
||||||
margin: .3rem 0
|
margin-bottom: -2px
|
||||||
|
|
||||||
.dataKey
|
.dataKey
|
||||||
color: $blue-gray
|
color: $blue-gray
|
||||||
|
@ -3,6 +3,7 @@ import React, {useState} from "react";
|
|||||||
import {SyntaxHighlighter} from "../UI/SyntaxHighlighter/index";
|
import {SyntaxHighlighter} from "../UI/SyntaxHighlighter/index";
|
||||||
import CollapsibleContainer from "../UI/CollapsibleContainer";
|
import CollapsibleContainer from "../UI/CollapsibleContainer";
|
||||||
import FancyTextDisplay from "../UI/FancyTextDisplay";
|
import FancyTextDisplay from "../UI/FancyTextDisplay";
|
||||||
|
import Queryable from "../UI/Queryable";
|
||||||
import Checkbox from "../UI/Checkbox";
|
import Checkbox from "../UI/Checkbox";
|
||||||
import ProtobufDecoder from "protobuf-decoder";
|
import ProtobufDecoder from "protobuf-decoder";
|
||||||
|
|
||||||
@ -15,22 +16,28 @@ interface EntryViewLineProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const EntryViewLine: React.FC<EntryViewLineProps> = ({label, value, updateQuery, selector, overrideQueryValue}) => {
|
const EntryViewLine: React.FC<EntryViewLineProps> = ({label, value, updateQuery, selector, overrideQueryValue}) => {
|
||||||
return (label && <tr className={styles.dataLine}>
|
let query: string;
|
||||||
<td
|
|
||||||
className={`queryable ${styles.dataKey}`}
|
|
||||||
onClick={() => {
|
|
||||||
if (!selector) {
|
if (!selector) {
|
||||||
return
|
query = "";
|
||||||
} else if (overrideQueryValue) {
|
} else if (overrideQueryValue) {
|
||||||
updateQuery(`${selector} == ${overrideQueryValue}`)
|
query = `${selector} == ${overrideQueryValue}`;
|
||||||
} else if (typeof(value) === "string") {
|
} else if (typeof(value) == "string") {
|
||||||
updateQuery(`${selector} == "${JSON.stringify(value).slice(1, -1)}"`)
|
query = `${selector} == "${JSON.stringify(value).slice(1, -1)}"`;
|
||||||
} else {
|
} else {
|
||||||
updateQuery(`${selector} == ${value}`)
|
query = `${selector} == ${value}`;
|
||||||
}
|
}
|
||||||
}}
|
return (label && <tr className={styles.dataLine}>
|
||||||
|
<td className={`${styles.dataKey}`}>
|
||||||
|
<Queryable
|
||||||
|
query={query}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
style={{float: "right", height: "0px"}}
|
||||||
|
iconStyle={{marginRight: "20px"}}
|
||||||
|
flipped={true}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
|
</Queryable>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<FancyTextDisplay
|
<FancyTextDisplay
|
||||||
@ -53,9 +60,9 @@ interface EntrySectionCollapsibleTitleProps {
|
|||||||
|
|
||||||
const EntrySectionCollapsibleTitle: React.FC<EntrySectionCollapsibleTitleProps> = ({title, color, isExpanded}) => {
|
const EntrySectionCollapsibleTitle: React.FC<EntrySectionCollapsibleTitleProps> = ({title, color, isExpanded}) => {
|
||||||
return <div className={styles.title}>
|
return <div className={styles.title}>
|
||||||
<span className={`${styles.button} ${isExpanded ? styles.expanded : ''}`} style={{backgroundColor: color}}>
|
<div className={`${styles.button} ${isExpanded ? styles.expanded : ''}`} style={{backgroundColor: color}}>
|
||||||
{isExpanded ? '-' : '+'}
|
{isExpanded ? '-' : '+'}
|
||||||
</span>
|
</div>
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
.rowSelected
|
.rowSelected
|
||||||
border: 1px $blue-color solid
|
border: 1px $blue-color solid
|
||||||
margin-right: 3px
|
|
||||||
|
|
||||||
.ruleSuccessRow
|
.ruleSuccessRow
|
||||||
background: #E8FFF1
|
background: #E8FFF1
|
||||||
@ -52,7 +51,6 @@
|
|||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
color: $secondary-font-color
|
color: $secondary-font-color
|
||||||
padding-left: 4px
|
padding-left: 4px
|
||||||
padding-top: 3px
|
|
||||||
padding-right: 10px
|
padding-right: 10px
|
||||||
display: flex
|
display: flex
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
@ -70,8 +68,9 @@
|
|||||||
flex-direction: column
|
flex-direction: column
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
padding-right: 10px
|
padding-right: 10px
|
||||||
padding-left: 10px
|
|
||||||
flex-grow: 1
|
flex-grow: 1
|
||||||
|
padding-top: 5px
|
||||||
|
margin-left: -6px
|
||||||
|
|
||||||
.separatorRight
|
.separatorRight
|
||||||
display: flex
|
display: flex
|
||||||
|
@ -3,6 +3,7 @@ import styles from './EntryListItem.module.sass';
|
|||||||
import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode";
|
import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode";
|
||||||
import Protocol, {ProtocolInterface} from "../UI/Protocol"
|
import Protocol, {ProtocolInterface} from "../UI/Protocol"
|
||||||
import {Summary} from "../UI/Summary";
|
import {Summary} from "../UI/Summary";
|
||||||
|
import Queryable from "../UI/Queryable";
|
||||||
import ingoingIconSuccess from "../assets/ingoing-traffic-success.svg"
|
import ingoingIconSuccess from "../assets/ingoing-traffic-success.svg"
|
||||||
import ingoingIconFailure from "../assets/ingoing-traffic-failure.svg"
|
import ingoingIconFailure from "../assets/ingoing-traffic-failure.svg"
|
||||||
import ingoingIconNeutral from "../assets/ingoing-traffic-neutral.svg"
|
import ingoingIconNeutral from "../assets/ingoing-traffic-neutral.svg"
|
||||||
@ -131,7 +132,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
|||||||
border: isSelected ? `1px ${entry.protocol.backgroundColor} solid` : "1px transparent solid",
|
border: isSelected ? `1px ${entry.protocol.backgroundColor} solid` : "1px transparent solid",
|
||||||
position: !headingMode ? "absolute" : "unset",
|
position: !headingMode ? "absolute" : "unset",
|
||||||
top: style['top'],
|
top: style['top'],
|
||||||
marginTop: style['marginTop'],
|
marginTop: !headingMode ? style['marginTop'] : "10px",
|
||||||
width: !headingMode ? "calc(100% - 25px)" : "calc(100% - 18px)",
|
width: !headingMode ? "calc(100% - 25px)" : "calc(100% - 18px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -146,15 +147,18 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
|||||||
<div className={styles.endpointServiceContainer}>
|
<div className={styles.endpointServiceContainer}>
|
||||||
<Summary method={entry.method} summary={entry.summary} updateQuery={updateQuery}/>
|
<Summary method={entry.method} summary={entry.summary} updateQuery={updateQuery}/>
|
||||||
<div className={styles.service}>
|
<div className={styles.service}>
|
||||||
|
<Queryable
|
||||||
|
query={`service == "${entry.service}"`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
style={{marginTop: "-4px"}}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
title="Service Name"
|
title="Service Name"
|
||||||
className="queryable"
|
|
||||||
onClick={() => {
|
|
||||||
updateQuery(`service == "${entry.service}"`)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{entry.service}
|
{entry.service}
|
||||||
</span>
|
</span>
|
||||||
|
</Queryable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
@ -172,35 +176,57 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
|||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
<div className={styles.separatorRight}>
|
<div className={styles.separatorRight}>
|
||||||
|
<Queryable
|
||||||
|
query={`src.ip == "${entry.sourceIp}"`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
flipped={true}
|
||||||
|
iconStyle={{marginRight: "16px"}}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className={`queryable ${styles.tcpInfo} ${styles.ip}`}
|
className={`${styles.tcpInfo} ${styles.ip}`}
|
||||||
title="Source IP"
|
title="Source IP"
|
||||||
onClick={() => {
|
|
||||||
updateQuery(`src.ip == "${entry.sourceIp}"`)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{entry.sourceIp}
|
{entry.sourceIp}
|
||||||
</span>
|
</span>
|
||||||
<span className={`${styles.tcpInfo}`}>:</span>
|
</Queryable>
|
||||||
|
<span className={`${styles.tcpInfo}`} style={{marginTop: "18px"}}>:</span>
|
||||||
|
<Queryable
|
||||||
|
query={`src.port == "${entry.sourcePort}"`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
flipped={true}
|
||||||
|
iconStyle={{marginTop: "28px"}}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className={`queryable ${styles.tcpInfo} ${styles.port}`}
|
className={`${styles.tcpInfo} ${styles.port}`}
|
||||||
title="Source Port"
|
title="Source Port"
|
||||||
onClick={() => {
|
|
||||||
updateQuery(`src.port == "${entry.sourcePort}"`)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{entry.sourcePort}
|
{entry.sourcePort}
|
||||||
</span>
|
</span>
|
||||||
|
</Queryable>
|
||||||
{entry.isOutgoing ?
|
{entry.isOutgoing ?
|
||||||
|
<Queryable
|
||||||
|
query={`outgoing == true`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
flipped={true}
|
||||||
|
iconStyle={{marginTop: "28px"}}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={outgoingIcon}
|
src={outgoingIcon}
|
||||||
alt="Ingoing traffic"
|
alt="Ingoing traffic"
|
||||||
title="Ingoing"
|
title="Ingoing"
|
||||||
onClick={() => {
|
|
||||||
updateQuery(`outgoing == true`)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
</Queryable>
|
||||||
:
|
:
|
||||||
|
<Queryable
|
||||||
|
query={`outgoing == true`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
flipped={true}
|
||||||
|
iconStyle={{marginTop: "28px"}}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={ingoingIcon}
|
src={ingoingIcon}
|
||||||
alt="Outgoing traffic"
|
alt="Outgoing traffic"
|
||||||
@ -209,37 +235,50 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
|||||||
updateQuery(`outgoing == false`)
|
updateQuery(`outgoing == false`)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Queryable>
|
||||||
}
|
}
|
||||||
|
<Queryable
|
||||||
|
query={`dst.ip == "${entry.destinationIp}"`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
flipped={false}
|
||||||
|
iconStyle={{marginTop: "28px"}}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className={`queryable ${styles.tcpInfo} ${styles.ip}`}
|
className={`${styles.tcpInfo} ${styles.ip}`}
|
||||||
title="Destination IP"
|
title="Destination IP"
|
||||||
onClick={() => {
|
|
||||||
updateQuery(`dst.ip == "${entry.destinationIp}"`)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{entry.destinationIp}
|
{entry.destinationIp}
|
||||||
</span>
|
</span>
|
||||||
<span className={`${styles.tcpInfo}`}>:</span>
|
</Queryable>
|
||||||
|
<span className={`${styles.tcpInfo}`} style={{marginTop: "18px"}}>:</span>
|
||||||
|
<Queryable
|
||||||
|
query={`dst.port == "${entry.destinationPort}"`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
flipped={false}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className={`queryable ${styles.tcpInfo} ${styles.port}`}
|
className={`${styles.tcpInfo} ${styles.port}`}
|
||||||
title="Destination Port"
|
title="Destination Port"
|
||||||
onClick={() => {
|
|
||||||
updateQuery(`dst.port == "${entry.destinationPort}"`)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{entry.destinationPort}
|
{entry.destinationPort}
|
||||||
</span>
|
</span>
|
||||||
|
</Queryable>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.timestamp}>
|
<div className={styles.timestamp}>
|
||||||
|
<Queryable
|
||||||
|
query={`timestamp >= datetime("${new Date(+entry.timestamp)?.toLocaleString("en-US", {timeZone: 'UTC' })}")`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
flipped={false}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
title="Timestamp"
|
title="Timestamp"
|
||||||
className="queryable"
|
|
||||||
onClick={() => {
|
|
||||||
updateQuery(`timestamp >= datetime("${new Date(+entry.timestamp)?.toLocaleString("en-US", {timeZone: 'UTC' })}")`)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{new Date(+entry.timestamp)?.toLocaleString("en-US")}
|
{new Date(+entry.timestamp)?.toLocaleString("en-US")}
|
||||||
</span>
|
</span>
|
||||||
|
</Queryable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -226,7 +226,7 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
|
|||||||
language="python"
|
language="python"
|
||||||
/>
|
/>
|
||||||
<Typography id="modal-modal-description">
|
<Typography id="modal-modal-description">
|
||||||
By clicking the UI elements in both left-pane and right-pane, you can automatically select a field and update the query:
|
By clicking the plus icon that appears beside the queryable UI elements on hovering in both left-pane and right-pane, you can automatically select a field and update the query:
|
||||||
</Typography>
|
</Typography>
|
||||||
<img
|
<img
|
||||||
src={filterUIExample1}
|
src={filterUIExample1}
|
||||||
@ -235,12 +235,12 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
|
|||||||
title="Clicking to UI elements (left-pane)"
|
title="Clicking to UI elements (left-pane)"
|
||||||
/>
|
/>
|
||||||
<Typography id="modal-modal-description">
|
<Typography id="modal-modal-description">
|
||||||
Such that; clicking this in left-pane, would append the query below:
|
Such that; clicking this icon in left-pane, would append the query below:
|
||||||
</Typography>
|
</Typography>
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
isWrapped={false}
|
isWrapped={false}
|
||||||
showLineNumbers={false}
|
showLineNumbers={false}
|
||||||
code={`and service == "http://carts.sock-shop"`}
|
code={`and service == "carts.sock-shop"`}
|
||||||
language="python"
|
language="python"
|
||||||
/>
|
/>
|
||||||
<Typography id="modal-modal-description">
|
<Typography id="modal-modal-description">
|
||||||
|
@ -21,7 +21,7 @@ const useLayoutStyles = makeStyles(() => ({
|
|||||||
padding: "12px 24px",
|
padding: "12px 24px",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
marginTop: 15,
|
marginTop: 15,
|
||||||
background: variables.headerBackgoundColor,
|
background: variables.headerBackgroundColor,
|
||||||
},
|
},
|
||||||
|
|
||||||
viewer: {
|
viewer: {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import styles from './style/Protocol.module.sass';
|
import styles from './style/Protocol.module.sass';
|
||||||
|
import Queryable from "./Queryable";
|
||||||
|
|
||||||
export interface ProtocolInterface {
|
export interface ProtocolInterface {
|
||||||
name: string
|
name: string
|
||||||
@ -22,7 +23,12 @@ interface ProtocolProps {
|
|||||||
|
|
||||||
const Protocol: React.FC<ProtocolProps> = ({protocol, horizontal, updateQuery}) => {
|
const Protocol: React.FC<ProtocolProps> = ({protocol, horizontal, updateQuery}) => {
|
||||||
if (horizontal) {
|
if (horizontal) {
|
||||||
return <a target="_blank" rel="noopener noreferrer" href={protocol.referenceLink}>
|
return <Queryable
|
||||||
|
query={protocol.macro}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
>
|
||||||
|
<a target="_blank" rel="noopener noreferrer" href={protocol.referenceLink}>
|
||||||
<span
|
<span
|
||||||
className={`${styles.base} ${styles.horizontal}`}
|
className={`${styles.base} ${styles.horizontal}`}
|
||||||
style={{
|
style={{
|
||||||
@ -35,8 +41,16 @@ const Protocol: React.FC<ProtocolProps> = ({protocol, horizontal, updateQuery})
|
|||||||
{protocol.longName}
|
{protocol.longName}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
</Queryable>
|
||||||
} else {
|
} else {
|
||||||
return <span
|
return <Queryable
|
||||||
|
query={protocol.macro}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
flipped={false}
|
||||||
|
iconStyle={{marginTop: "48px"}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
className={`${styles.base} ${styles.vertical}`}
|
className={`${styles.base} ${styles.vertical}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: protocol.backgroundColor,
|
backgroundColor: protocol.backgroundColor,
|
||||||
@ -44,12 +58,10 @@ const Protocol: React.FC<ProtocolProps> = ({protocol, horizontal, updateQuery})
|
|||||||
fontSize: protocol.fontSize,
|
fontSize: protocol.fontSize,
|
||||||
}}
|
}}
|
||||||
title={protocol.longName}
|
title={protocol.longName}
|
||||||
onClick={() => {
|
|
||||||
updateQuery(protocol.macro)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{protocol.abbr}
|
{protocol.abbr}
|
||||||
</span>
|
</span>
|
||||||
|
</Queryable>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
62
ui/src/components/UI/Queryable.tsx
Normal file
62
ui/src/components/UI/Queryable.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
|
import AddCircleIcon from '@material-ui/icons/AddCircle';
|
||||||
|
import './style/Queryable.sass';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
query: string,
|
||||||
|
updateQuery: any,
|
||||||
|
style?: object,
|
||||||
|
iconStyle?: object,
|
||||||
|
className?: string,
|
||||||
|
useTooltip?: boolean,
|
||||||
|
displayIconOnMouseOver?: boolean,
|
||||||
|
flipped?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Queryable: React.FC<Props> = ({query, updateQuery, style, iconStyle, className, useTooltip= true, displayIconOnMouseOver = false, flipped = false, children}) => {
|
||||||
|
const [showAddedNotification, setAdded] = useState(false);
|
||||||
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
|
|
||||||
|
const onCopy = () => {
|
||||||
|
setAdded(true)
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timer;
|
||||||
|
if (showAddedNotification) {
|
||||||
|
updateQuery(query);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
setAdded(false);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [showAddedNotification, query, updateQuery]);
|
||||||
|
|
||||||
|
const addButton = query ? <CopyToClipboard text={query} onCopy={onCopy}>
|
||||||
|
<span
|
||||||
|
className={`Queryable-Icon`}
|
||||||
|
title={`Add "${query}" to the filter`}
|
||||||
|
style={iconStyle}
|
||||||
|
>
|
||||||
|
<AddCircleIcon fontSize="small" color="inherit"></AddCircleIcon>
|
||||||
|
{showAddedNotification && <span className={'Queryable-AddNotifier'}>Added</span>}
|
||||||
|
</span>
|
||||||
|
</CopyToClipboard> : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`Queryable-Container displayIconOnMouseOver ${className ? className : ''} ${displayIconOnMouseOver ? 'displayIconOnMouseOver ' : ''}`}
|
||||||
|
style={style}
|
||||||
|
onMouseOver={ e => setShowTooltip(true)}
|
||||||
|
onMouseLeave={ e => setShowTooltip(false)}
|
||||||
|
>
|
||||||
|
{flipped && addButton}
|
||||||
|
{children}
|
||||||
|
{!flipped && addButton}
|
||||||
|
{useTooltip && showTooltip && <span className={'Queryable-Tooltip'}>{query}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Queryable;
|
@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import styles from './style/StatusCode.module.sass';
|
import styles from './style/StatusCode.module.sass';
|
||||||
|
import Queryable from "./Queryable";
|
||||||
|
|
||||||
export enum StatusCodeClassification {
|
export enum StatusCodeClassification {
|
||||||
SUCCESS = "success",
|
SUCCESS = "success",
|
||||||
@ -16,15 +17,19 @@ const StatusCode: React.FC<EntryProps> = ({statusCode, updateQuery}) => {
|
|||||||
|
|
||||||
const classification = getClassification(statusCode)
|
const classification = getClassification(statusCode)
|
||||||
|
|
||||||
return <span
|
return <Queryable
|
||||||
|
query={`response.status == ${statusCode}`}
|
||||||
|
updateQuery={updateQuery}
|
||||||
|
style={{minWidth: "68px"}}
|
||||||
|
displayIconOnMouseOver={true}
|
||||||
|
>
|
||||||
|
<span
|
||||||
title="Status Code"
|
title="Status Code"
|
||||||
className={`queryable ${styles[classification]} ${styles.base}`}
|
className={`${styles[classification]} ${styles.base}`}
|
||||||
onClick={() => {
|
|
||||||
updateQuery(`response.status == ${statusCode}`)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{statusCode}
|
{statusCode}
|
||||||
</span>
|
</span>
|
||||||
|
</Queryable>
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getClassification(statusCode: number): string {
|
export function getClassification(statusCode: number): string {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import miscStyles from "./style/misc.module.sass";
|
import miscStyles from "./style/misc.module.sass";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import styles from './style/Summary.module.sass';
|
import styles from './style/Summary.module.sass';
|
||||||
|
import Queryable from "./Queryable";
|
||||||
|
|
||||||
interface SummaryProps {
|
interface SummaryProps {
|
||||||
method: string
|
method: string
|
||||||
@ -9,24 +10,28 @@ interface SummaryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Summary: React.FC<SummaryProps> = ({method, summary, updateQuery}) => {
|
export const Summary: React.FC<SummaryProps> = ({method, summary, updateQuery}) => {
|
||||||
|
|
||||||
return <div className={styles.container}>
|
return <div className={styles.container}>
|
||||||
{method && <span
|
{method && <Queryable
|
||||||
title="Method"
|
query={`method == "${method}"`}
|
||||||
className={`queryable ${miscStyles.protocol} ${miscStyles.method}`}
|
className={`${miscStyles.protocol} ${miscStyles.method}`}
|
||||||
onClick={() => {
|
updateQuery={updateQuery}
|
||||||
updateQuery(`method == "${method}"`)
|
displayIconOnMouseOver={true}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
{method}
|
{method}
|
||||||
</span>}
|
</span>
|
||||||
{summary && <div
|
</Queryable>}
|
||||||
title="Summary"
|
{summary && <Queryable
|
||||||
className={`queryable ${styles.summary}`}
|
query={`summary == "${summary}"`}
|
||||||
onClick={() => {
|
updateQuery={updateQuery}
|
||||||
updateQuery(`summary == "${summary}"`)
|
displayIconOnMouseOver={true}
|
||||||
}}
|
>
|
||||||
|
<div
|
||||||
|
className={`${styles.summary}`}
|
||||||
>
|
>
|
||||||
{summary}
|
{summary}
|
||||||
</div>}
|
</div>
|
||||||
|
</Queryable>}
|
||||||
</div>
|
</div>
|
||||||
};
|
};
|
||||||
|
48
ui/src/components/UI/style/Queryable.sass
Normal file
48
ui/src/components/UI/style/Queryable.sass
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
.Queryable-Container
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
|
||||||
|
&.displayIconOnMouseOver
|
||||||
|
.Queryable-Icon
|
||||||
|
opacity: 0
|
||||||
|
width: 0px
|
||||||
|
pointer-events: none
|
||||||
|
&:hover
|
||||||
|
.Queryable-Icon
|
||||||
|
opacity: 1
|
||||||
|
pointer-events: all
|
||||||
|
|
||||||
|
|
||||||
|
.Queryable-Icon
|
||||||
|
height: 22px
|
||||||
|
width: 22px
|
||||||
|
cursor: pointer
|
||||||
|
color: #27AE60
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background-color: rgba(255, 255, 255, 0.06)
|
||||||
|
border-radius: 4px
|
||||||
|
color: #1E884B
|
||||||
|
|
||||||
|
.Queryable-AddNotifier
|
||||||
|
background-color: #1E884B
|
||||||
|
font-weight: normal
|
||||||
|
padding: 2px 5px
|
||||||
|
border-radius: 4px
|
||||||
|
position: absolute
|
||||||
|
transform: translate(0, 10%)
|
||||||
|
color: white
|
||||||
|
z-index: 1000
|
||||||
|
font-size: 11px
|
||||||
|
|
||||||
|
.Queryable-Tooltip
|
||||||
|
background-color: #1E884B
|
||||||
|
font-weight: normal
|
||||||
|
padding: 2px 5px
|
||||||
|
border-radius: 4px
|
||||||
|
position: absolute
|
||||||
|
transform: translate(0, -80%)
|
||||||
|
color: white
|
||||||
|
z-index: 1000
|
||||||
|
font-size: 11px
|
||||||
|
|
@ -3,6 +3,4 @@
|
|||||||
align-items: center
|
align-items: center
|
||||||
|
|
||||||
.summary
|
.summary
|
||||||
text-overflow: ellipsis
|
|
||||||
overflow: hidden
|
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
&.method
|
&.method
|
||||||
margin-right: 10px
|
margin-right: 10px
|
||||||
|
height: 12px
|
||||||
|
|
||||||
&.filterPlate
|
&.filterPlate
|
||||||
border-color: #bcc6dd20
|
border-color: #bcc6dd20
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 24 KiB |
@ -22,11 +22,6 @@ code
|
|||||||
.uppercase
|
.uppercase
|
||||||
text-transform: uppercase
|
text-transform: uppercase
|
||||||
|
|
||||||
.queryable
|
|
||||||
cursor: pointer
|
|
||||||
&:hover
|
|
||||||
text-decoration: underline
|
|
||||||
|
|
||||||
/****
|
/****
|
||||||
* Button
|
* Button
|
||||||
***/
|
***/
|
||||||
|
@ -11,7 +11,7 @@ $blue-gray: #494677;
|
|||||||
|
|
||||||
:export {
|
:export {
|
||||||
mainBackgroundColor: $main-background-color;
|
mainBackgroundColor: $main-background-color;
|
||||||
headerBackgoundColor: $header-background-color;
|
headerBackgroundColor: $header-background-color;
|
||||||
fontColor: $font-color;
|
fontColor: $font-color;
|
||||||
secondaryFontColor: $secondary-font-color;
|
secondaryFontColor: $secondary-font-color;
|
||||||
blueColor: $blue-color;
|
blueColor: $blue-color;
|
||||||
|
Loading…
Reference in New Issue
Block a user