mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-06-25 07:45:01 +00:00
Feature/UI/filters (#32)
* UI filters
* refactor
* Revert "refactor"
This reverts commit 70e7d4b6ac
.
* remove recursive func
This commit is contained in:
parent
b9d0e0ee87
commit
c7a20ed9c0
@ -1,8 +1,9 @@
|
|||||||
import {HarEntry} from "./HarEntry";
|
import {HarEntry} from "./HarEntry";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||||
import styles from './style/HarEntriesList.module.sass';
|
import styles from './style/HarEntriesList.module.sass';
|
||||||
import spinner from './assets/spinner.svg';
|
import spinner from './assets/spinner.svg';
|
||||||
import ScrollableFeed from "react-scrollable-feed";
|
import ScrollableFeed from "react-scrollable-feed";
|
||||||
|
import {StatusType} from "./HarFilters";
|
||||||
|
|
||||||
interface HarEntriesListProps {
|
interface HarEntriesListProps {
|
||||||
entries: any[];
|
entries: any[];
|
||||||
@ -14,6 +15,9 @@ interface HarEntriesListProps {
|
|||||||
setNoMoreDataTop: (flag: boolean) => void;
|
setNoMoreDataTop: (flag: boolean) => void;
|
||||||
noMoreDataBottom: boolean;
|
noMoreDataBottom: boolean;
|
||||||
setNoMoreDataBottom: (flag: boolean) => void;
|
setNoMoreDataBottom: (flag: boolean) => void;
|
||||||
|
methodsFilter: Array<string>;
|
||||||
|
statusFilter: Array<string>;
|
||||||
|
pathFilter: string
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FetchOperator {
|
enum FetchOperator {
|
||||||
@ -21,91 +25,107 @@ enum FetchOperator {
|
|||||||
GT = "gt"
|
GT = "gt"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HarEntriesList: React.FC<HarEntriesListProps> = ({entries, setEntries, focusedEntryId, setFocusedEntryId, connectionOpen, noMoreDataTop, setNoMoreDataTop, noMoreDataBottom, setNoMoreDataBottom}) => {
|
export const HarEntriesList: React.FC<HarEntriesListProps> = ({entries, setEntries, focusedEntryId, setFocusedEntryId, connectionOpen, noMoreDataTop, setNoMoreDataTop, noMoreDataBottom, setNoMoreDataBottom, methodsFilter, statusFilter, pathFilter}) => {
|
||||||
|
|
||||||
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
||||||
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if(loadMoreTop && !connectionOpen && !noMoreDataTop)
|
|
||||||
fetchData(FetchOperator.LT);
|
|
||||||
}, [loadMoreTop, connectionOpen, noMoreDataTop]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const list = document.getElementById('list').firstElementChild;
|
const list = document.getElementById('list').firstElementChild;
|
||||||
list.addEventListener('scroll', (e) => {
|
list.addEventListener('scroll', (e) => {
|
||||||
const el: any = e.target;
|
const el: any = e.target;
|
||||||
if(el.scrollTop === 0) {
|
if(el.scrollTop === 0) {
|
||||||
setLoadMoreTop(true);
|
setLoadMoreTop(true);
|
||||||
|
} else {
|
||||||
|
setLoadMoreTop(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchData = async (operator) => {
|
const filterEntries = useCallback((entry) => {
|
||||||
|
if(methodsFilter.length > 0 && !methodsFilter.includes(entry.method.toLowerCase())) return;
|
||||||
|
if(pathFilter && entry.path?.toLowerCase()?.indexOf(pathFilter) === -1) return;
|
||||||
|
if(statusFilter.includes(StatusType.SUCCESS) && entry.statusCode >= 400) return;
|
||||||
|
if(statusFilter.includes(StatusType.ERROR) && entry.statusCode < 400) return;
|
||||||
|
return entry;
|
||||||
|
},[methodsFilter, pathFilter, statusFilter])
|
||||||
|
|
||||||
const timestamp = operator === FetchOperator.LT ? entries[0].timestamp : entries[entries.length - 1].timestamp;
|
const filteredEntries = useMemo(() => {
|
||||||
if(operator === FetchOperator.LT)
|
return entries.filter(filterEntries);
|
||||||
setIsLoadingTop(true);
|
},[entries, filterEntries])
|
||||||
|
|
||||||
fetch(`http://localhost:8899/api/entries?limit=50&operator=${operator}×tamp=${timestamp}`)
|
const fetchData = async (operator, timestamp) => {
|
||||||
.then(response => response.json())
|
const response = await fetch(`http://localhost:8899/api/entries?limit=50&operator=${operator}×tamp=${timestamp}`);
|
||||||
.then((data: any[]) => {
|
return await response.json();
|
||||||
let scrollTo;
|
}
|
||||||
if(operator === FetchOperator.LT) {
|
|
||||||
if(data.length === 0) {
|
|
||||||
setNoMoreDataTop(true);
|
|
||||||
scrollTo = document.getElementById("noMoreDataTop");
|
|
||||||
} else {
|
|
||||||
scrollTo = document.getElementById(entries[0].id);
|
|
||||||
}
|
|
||||||
const newEntries = [...data, ...entries];
|
|
||||||
if(newEntries.length >= 1000) {
|
|
||||||
newEntries.splice(1000);
|
|
||||||
}
|
|
||||||
setEntries(newEntries);
|
|
||||||
setLoadMoreTop(false);
|
|
||||||
setIsLoadingTop(false)
|
|
||||||
if(scrollTo) {
|
|
||||||
scrollTo.scrollIntoView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(operator === FetchOperator.GT) {
|
const getOldEntries = useCallback(async () => {
|
||||||
if(data.length === 0) {
|
setIsLoadingTop(true);
|
||||||
setNoMoreDataBottom(true);
|
const data = await fetchData(FetchOperator.LT, entries[0].timestamp);
|
||||||
}
|
setLoadMoreTop(false);
|
||||||
scrollTo = document.getElementById(entries[entries.length -1].id);
|
|
||||||
let newEntries = [...entries, ...data];
|
let scrollTo;
|
||||||
if(newEntries.length >= 1000) {
|
if(data.length === 0) {
|
||||||
setNoMoreDataTop(false);
|
setNoMoreDataTop(true);
|
||||||
newEntries = newEntries.slice(-1000);
|
scrollTo = document.getElementById("noMoreDataTop");
|
||||||
}
|
} else {
|
||||||
setEntries(newEntries);
|
scrollTo = document.getElementById(filteredEntries?.[0]?.id);
|
||||||
if(scrollTo) {
|
}
|
||||||
scrollTo.scrollIntoView({behavior: "smooth"});
|
setIsLoadingTop(false);
|
||||||
}
|
const newEntries = [...data, ...entries];
|
||||||
}
|
if(newEntries.length >= 1000) {
|
||||||
});
|
newEntries.splice(1000);
|
||||||
};
|
}
|
||||||
|
setEntries(newEntries);
|
||||||
|
|
||||||
|
if(scrollTo) {
|
||||||
|
scrollTo.scrollIntoView();
|
||||||
|
}
|
||||||
|
},[setLoadMoreTop, setIsLoadingTop, entries, setEntries, filteredEntries, setNoMoreDataTop])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!loadMoreTop || connectionOpen || noMoreDataTop) return;
|
||||||
|
getOldEntries();
|
||||||
|
}, [loadMoreTop, connectionOpen, noMoreDataTop, getOldEntries]);
|
||||||
|
|
||||||
|
const getNewEntries = async () => {
|
||||||
|
const data = await fetchData(FetchOperator.GT, entries[entries.length - 1].timestamp);
|
||||||
|
let scrollTo;
|
||||||
|
if(data.length === 0) {
|
||||||
|
setNoMoreDataBottom(true);
|
||||||
|
}
|
||||||
|
scrollTo = document.getElementById(filteredEntries?.[filteredEntries.length -1]?.id);
|
||||||
|
let newEntries = [...entries, ...data];
|
||||||
|
if(newEntries.length >= 1000) {
|
||||||
|
setNoMoreDataTop(false);
|
||||||
|
newEntries = newEntries.slice(-1000);
|
||||||
|
}
|
||||||
|
setEntries(newEntries);
|
||||||
|
if(scrollTo) {
|
||||||
|
scrollTo.scrollIntoView({behavior: "smooth"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className={styles.list}>
|
<div className={styles.list}>
|
||||||
<div id="list" className={styles.list}>
|
<div id="list" className={styles.list}>
|
||||||
{isLoadingTop && <div style={{display: "flex", justifyContent: "center", marginBottom: 10}}><img alt="spinner" src={spinner} style={{height: 25}}/></div>}
|
{isLoadingTop && <div className={styles.spinnerContainer}>
|
||||||
|
<img alt="spinner" src={spinner} style={{height: 25}}/>
|
||||||
|
</div>}
|
||||||
<ScrollableFeed>
|
<ScrollableFeed>
|
||||||
{noMoreDataTop && !connectionOpen && <div id="noMoreDataTop" style={{textAlign: "center", fontWeight: 600, color: "rgba(255,255,255,0.75)"}}>No more data available</div>}
|
{noMoreDataTop && !connectionOpen && <div id="noMoreDataTop" className={styles.noMoreDataAvailable}>No more data available</div>}
|
||||||
{entries?.map(entry => <HarEntry key={entry.id}
|
{filteredEntries.map(entry => <HarEntry key={entry.id}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
setFocusedEntryId={setFocusedEntryId}
|
setFocusedEntryId={setFocusedEntryId}
|
||||||
isSelected={focusedEntryId === entry.id}/>)}
|
isSelected={focusedEntryId === entry.id}/>)}
|
||||||
{!connectionOpen && !noMoreDataBottom && <div style={{width: "100%", display: "flex", justifyContent: "center", marginTop: 12, fontWeight: 600, color: "rgba(255,255,255,0.75)"}}>
|
{!connectionOpen && !noMoreDataBottom && <div className={styles.fetchButtonContainer}>
|
||||||
<div className={styles.styledButton} onClick={() => fetchData(FetchOperator.GT)}>Fetch more entries</div>
|
<div className={styles.styledButton} onClick={() => getNewEntries()}>Fetch more entries</div>
|
||||||
</div>}
|
</div>}
|
||||||
</ScrollableFeed>
|
</ScrollableFeed>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{entries?.length > 0 && <div className={styles.footer}>
|
{entries?.length > 0 && <div className={styles.footer}>
|
||||||
<div><b>{entries?.length}</b> requests</div>
|
<div><b>{filteredEntries?.length !== entries.length && `${filteredEntries?.length} / `} {entries?.length}</b> requests</div>
|
||||||
<div>Started listening at <span style={{marginRight: 5, fontWeight: 600, fontSize: 13}}>{new Date(+entries[0].timestamp)?.toLocaleString()}</span></div>
|
<div>Started listening at <span style={{marginRight: 5, fontWeight: 600, fontSize: 13}}>{new Date(+entries[0].timestamp)?.toLocaleString()}</span></div>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import React, {useEffect} from "react";
|
import React from "react";
|
||||||
import styles from './style/HarFilters.module.sass';
|
import styles from './style/HarFilters.module.sass';
|
||||||
import {HARFilterSelect} from "./HARFilterSelect";
|
import {HARFilterSelect} from "./HARFilterSelect";
|
||||||
import {TextField} from "@material-ui/core";
|
import {TextField} from "@material-ui/core";
|
||||||
|
import {ALL_KEY} from "./Select";
|
||||||
|
|
||||||
export const HarFilters: React.FC = () => {
|
interface HarFiltersProps {
|
||||||
|
methodsFilter: Array<string>;
|
||||||
|
setMethodsFilter: (methods: Array<string>) => void;
|
||||||
|
statusFilter: Array<string>;
|
||||||
|
setStatusFilter: (methods: Array<string>) => void;
|
||||||
|
pathFilter: string
|
||||||
|
setPathFilter: (val: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HarFilters: React.FC<HarFiltersProps> = ({methodsFilter, setMethodsFilter, statusFilter, setStatusFilter, pathFilter, setPathFilter}) => {
|
||||||
|
|
||||||
return <div className={styles.container}>
|
return <div className={styles.container}>
|
||||||
<ServiceFilter/>
|
<MethodFilter methodsFilter={methodsFilter} setMethodsFilter={setMethodsFilter}/>
|
||||||
<MethodFilter/>
|
<StatusTypesFilter statusFilter={statusFilter} setStatusFilter={setStatusFilter}/>
|
||||||
<StatusTypesFilter/>
|
<PathFilter pathFilter={pathFilter} setPathFilter={setPathFilter}/>
|
||||||
<SourcesFilter/>
|
|
||||||
<FetchModeFilter/>
|
|
||||||
<PathFilter/>
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,77 +30,6 @@ const FilterContainer: React.FC = ({children}) => {
|
|||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ServiceFilter: React.FC = () => {
|
|
||||||
const providerIds = []; //todo
|
|
||||||
const selectedServices = []; //todo
|
|
||||||
|
|
||||||
return <FilterContainer>
|
|
||||||
<HARFilterSelect
|
|
||||||
items={providerIds}
|
|
||||||
value={selectedServices}
|
|
||||||
onChange={(val) => {
|
|
||||||
//todo: harStore.updateFilter({toggleService: val})
|
|
||||||
}}
|
|
||||||
allowMultiple={true}
|
|
||||||
label={"Services"}
|
|
||||||
transformDisplay={_toUpperCase}
|
|
||||||
/>
|
|
||||||
</FilterContainer>
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const BROWSER_SOURCE = "_BROWSER_";
|
|
||||||
|
|
||||||
const SourcesFilter: React.FC = () => {
|
|
||||||
|
|
||||||
const sources = []; //todo
|
|
||||||
const selectedSource = null; //todo
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//todo: fetch sources
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <FilterContainer>
|
|
||||||
<HARFilterSelect
|
|
||||||
items={sources}
|
|
||||||
value={selectedSource}
|
|
||||||
onChange={(val) => {
|
|
||||||
//todo: harStore.updateFilter({toggleSource: val});
|
|
||||||
}}
|
|
||||||
allowMultiple={true}
|
|
||||||
label={"Sources"}
|
|
||||||
transformDisplay={item => item === BROWSER_SOURCE ? "BROWSER" : item.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</FilterContainer>
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
enum HARFetchMode {
|
|
||||||
UP_TO_REVISION = "Up to revision",
|
|
||||||
ALL = "All",
|
|
||||||
QUEUED = "Unprocessed"
|
|
||||||
}
|
|
||||||
|
|
||||||
const FetchModeFilter: React.FC = () => {
|
|
||||||
|
|
||||||
const selectedHarFetchMode = null;
|
|
||||||
|
|
||||||
return <FilterContainer>
|
|
||||||
<HARFilterSelect
|
|
||||||
items={Object.values(HARFetchMode)}
|
|
||||||
value={selectedHarFetchMode}
|
|
||||||
onChange={(val) => {
|
|
||||||
// selectedModelStore.har.setHarFetchMode(val);
|
|
||||||
// selectedModelStore.har.data.reset();
|
|
||||||
// selectedModelStore.har.data.fetch();
|
|
||||||
//todo
|
|
||||||
}}
|
|
||||||
label={"Processed"}
|
|
||||||
/>
|
|
||||||
</FilterContainer>
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
enum HTTPMethod {
|
enum HTTPMethod {
|
||||||
GET = "get",
|
GET = "get",
|
||||||
PUT = "put",
|
PUT = "put",
|
||||||
@ -103,58 +39,80 @@ enum HTTPMethod {
|
|||||||
PATCH = "patch"
|
PATCH = "patch"
|
||||||
}
|
}
|
||||||
|
|
||||||
const MethodFilter: React.FC = () => {
|
interface MethodFilterProps {
|
||||||
|
methodsFilter: Array<string>;
|
||||||
|
setMethodsFilter: (methods: Array<string>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const selectedMethods = [];
|
const MethodFilter: React.FC<MethodFilterProps> = ({methodsFilter, setMethodsFilter}) => {
|
||||||
|
|
||||||
|
const methodClicked = (val) => {
|
||||||
|
if(val === ALL_KEY) {
|
||||||
|
setMethodsFilter([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(methodsFilter.includes(val)) {
|
||||||
|
setMethodsFilter(methodsFilter.filter(method => method !== val))
|
||||||
|
} else {
|
||||||
|
setMethodsFilter([...methodsFilter, val]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <FilterContainer>
|
return <FilterContainer>
|
||||||
<HARFilterSelect
|
<HARFilterSelect
|
||||||
items={Object.values(HTTPMethod)}
|
items={Object.values(HTTPMethod)}
|
||||||
allowMultiple={true}
|
allowMultiple={true}
|
||||||
value={selectedMethods}
|
value={methodsFilter}
|
||||||
onChange={(val) => {
|
onChange={(val) => methodClicked(val)}
|
||||||
// harStore.updateFilter({toggleMethod: val}) todo
|
|
||||||
}}
|
|
||||||
transformDisplay={_toUpperCase}
|
transformDisplay={_toUpperCase}
|
||||||
label={"Methods"}
|
label={"Methods"}
|
||||||
/>
|
/>
|
||||||
</FilterContainer>;
|
</FilterContainer>;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum StatusType {
|
export enum StatusType {
|
||||||
SUCCESS = "success",
|
SUCCESS = "success",
|
||||||
ERROR = "error"
|
ERROR = "error"
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusTypesFilter: React.FC = () => {
|
interface StatusTypesFilterProps {
|
||||||
|
statusFilter: Array<string>;
|
||||||
|
setStatusFilter: (methods: Array<string>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const selectedStatusTypes = [];
|
const StatusTypesFilter: React.FC<StatusTypesFilterProps> = ({statusFilter, setStatusFilter}) => {
|
||||||
|
|
||||||
|
const statusClicked = (val) => {
|
||||||
|
if(val === ALL_KEY) {
|
||||||
|
setStatusFilter([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStatusFilter([val]);
|
||||||
|
}
|
||||||
|
|
||||||
return <FilterContainer>
|
return <FilterContainer>
|
||||||
<HARFilterSelect
|
<HARFilterSelect
|
||||||
items={Object.values(StatusType)}
|
items={Object.values(StatusType)}
|
||||||
allowMultiple={true}
|
allowMultiple={true}
|
||||||
value={selectedStatusTypes}
|
value={statusFilter}
|
||||||
onChange={(val) => {
|
onChange={(val) => statusClicked(val)}
|
||||||
// harStore.updateFilter({toggleStatusType: val}) todo
|
|
||||||
}}
|
|
||||||
transformDisplay={_toUpperCase}
|
transformDisplay={_toUpperCase}
|
||||||
label="Status"
|
label="Status"
|
||||||
/>
|
/>
|
||||||
</FilterContainer>;
|
</FilterContainer>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO path search is inclusive of the qs -> we want to avoid this - TRA-1681
|
interface PathFilterProps {
|
||||||
const PathFilter: React.FC = () => {
|
pathFilter: string;
|
||||||
|
setPathFilter: (val: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const onFilterChange = (value) => {
|
const PathFilter: React.FC<PathFilterProps> = ({pathFilter, setPathFilter}) => {
|
||||||
// harStore.updateFilter({setPathSearch: value}); todo
|
|
||||||
}
|
|
||||||
|
|
||||||
return <FilterContainer>
|
return <FilterContainer>
|
||||||
<div className={styles.filterLabel}>Path</div>
|
<div className={styles.filterLabel}>Path</div>
|
||||||
<div>
|
<div>
|
||||||
<TextField variant="outlined" className={styles.filterText} style={{minWidth: '150px'}} onKeyDown={(e: any) => e.key === "Enter" && onFilterChange(e.target.value)}/>
|
<TextField value={pathFilter} variant="outlined" className={styles.filterText} style={{minWidth: '150px'}} onChange={(e: any) => setPathFilter(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
</FilterContainer>;
|
</FilterContainer>;
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, {useEffect, useRef, useState} from "react";
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
// import {HarFilters} from "./HarFilters";
|
import {HarFilters} from "./HarFilters";
|
||||||
import {HarEntriesList} from "./HarEntriesList";
|
import {HarEntriesList} from "./HarEntriesList";
|
||||||
import {makeStyles} from "@material-ui/core";
|
import {makeStyles} from "@material-ui/core";
|
||||||
import "./style/HarPage.sass";
|
import "./style/HarPage.sass";
|
||||||
@ -43,6 +43,10 @@ export const HarPage: React.FC = () => {
|
|||||||
const [noMoreDataTop, setNoMoreDataTop] = useState(false);
|
const [noMoreDataTop, setNoMoreDataTop] = useState(false);
|
||||||
const [noMoreDataBottom, setNoMoreDataBottom] = useState(false);
|
const [noMoreDataBottom, setNoMoreDataBottom] = useState(false);
|
||||||
|
|
||||||
|
const [methodsFilter, setMethodsFilter] = useState([]);
|
||||||
|
const [statusFilter, setStatusFilter] = useState([]);
|
||||||
|
const [pathFilter, setPathFilter] = useState("");
|
||||||
|
|
||||||
const ws = useRef(null);
|
const ws = useRef(null);
|
||||||
|
|
||||||
const openWebSocket = () => {
|
const openWebSocket = () => {
|
||||||
@ -53,7 +57,6 @@ export const HarPage: React.FC = () => {
|
|||||||
|
|
||||||
if(ws.current) {
|
if(ws.current) {
|
||||||
ws.current.onmessage = e => {
|
ws.current.onmessage = e => {
|
||||||
console.log(connection);
|
|
||||||
if(!e?.data) return;
|
if(!e?.data) return;
|
||||||
const entry = JSON.parse(e.data);
|
const entry = JSON.parse(e.data);
|
||||||
if(connection === ConnectionStatus.Paused) {
|
if(connection === ConnectionStatus.Paused) {
|
||||||
@ -98,18 +101,35 @@ export const HarPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getConnectionTitle = () => {
|
||||||
|
switch (connection) {
|
||||||
|
case ConnectionStatus.Paused:
|
||||||
|
return "traffic paused";
|
||||||
|
case ConnectionStatus.Connected:
|
||||||
|
return "connected, waiting for traffic"
|
||||||
|
default:
|
||||||
|
return "not connected";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="HarPage">
|
<div className="HarPage">
|
||||||
<div style={{padding: "0 24px 24px 24px", display: "flex", alignItems: "center"}}>
|
<div style={{padding: "0 24px 24px 24px", display: "flex", alignItems: "center"}}>
|
||||||
<img style={{cursor: "pointer", marginRight: 15, height: 20}} alt="pause" src={connection === ConnectionStatus.Connected ? pauseIcon : playIcon} onClick={toggleConnection}/>
|
<img style={{cursor: "pointer", marginRight: 15, height: 20}} alt="pause" src={connection === ConnectionStatus.Connected ? pauseIcon : playIcon} onClick={toggleConnection}/>
|
||||||
<div className="connectionText">
|
<div className="connectionText">
|
||||||
{connection === ConnectionStatus.Connected ? "connected, waiting for traffic" : "not connected"}
|
{getConnectionTitle()}
|
||||||
<div className={getConnectionStatusClass()}/>
|
<div className={getConnectionStatusClass()}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{entries.length > 0 && <div className="HarPage-Container">
|
{entries.length > 0 && <div className="HarPage-Container">
|
||||||
<div className="HarPage-ListContainer">
|
<div className="HarPage-ListContainer">
|
||||||
{/*<HarFilters />*/}
|
<HarFilters methodsFilter={methodsFilter}
|
||||||
|
setMethodsFilter={setMethodsFilter}
|
||||||
|
statusFilter={statusFilter}
|
||||||
|
setStatusFilter={setStatusFilter}
|
||||||
|
pathFilter={pathFilter}
|
||||||
|
setPathFilter={setPathFilter}
|
||||||
|
/>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<HarEntriesList entries={entries}
|
<HarEntriesList entries={entries}
|
||||||
setEntries={setEntries}
|
setEntries={setEntries}
|
||||||
@ -119,7 +139,11 @@ export const HarPage: React.FC = () => {
|
|||||||
noMoreDataBottom={noMoreDataBottom}
|
noMoreDataBottom={noMoreDataBottom}
|
||||||
setNoMoreDataBottom={setNoMoreDataBottom}
|
setNoMoreDataBottom={setNoMoreDataBottom}
|
||||||
noMoreDataTop={noMoreDataTop}
|
noMoreDataTop={noMoreDataTop}
|
||||||
setNoMoreDataTop={setNoMoreDataTop}/>
|
setNoMoreDataTop={setNoMoreDataTop}
|
||||||
|
methodsFilter={methodsFilter}
|
||||||
|
statusFilter={statusFilter}
|
||||||
|
pathFilter={pathFilter}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.details}>
|
<div className={classes.details}>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import {ReactComponent as DefaultIconDown} from '../../../assets/default_icon_down.svg';
|
import {ReactComponent as DefaultIconDown} from './assets/default_icon_down.svg';
|
||||||
import {MenuItem, Select as MUISelect} from '@material-ui/core';
|
import {MenuItem, Select as MUISelect} from '@material-ui/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {SelectProps as MUISelectProps} from '@material-ui/core/Select/Select';
|
import {SelectProps as MUISelectProps} from '@material-ui/core/Select/Select';
|
||||||
import styles from './style/Select.module.sass';
|
import styles from './style/Select.module.sass';
|
||||||
|
|
||||||
const ALL_KEY= 'All';
|
export const ALL_KEY= 'All';
|
||||||
|
|
||||||
const menuProps: any = {
|
const menuProps: any = {
|
||||||
anchorOrigin: {
|
anchorOrigin: {
|
||||||
|
3
ui/src/components/assets/default_icon_down.svg
Normal file
3
ui/src/components/assets/default_icon_down.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="7.237" height="7.237" viewBox="0 0 7.237 7.237" fill="white">
|
||||||
|
<path id="icon_down" d="M5.117 0H3.07v3.07H0v2.047h5.117V0z" transform="rotate(45 1.809 4.367)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 218 B |
3
ui/src/components/style/HARFilterSelect.module.sass
Normal file
3
ui/src/components/style/HARFilterSelect.module.sass
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.HARSelectLabel
|
||||||
|
color: #8f9bb2
|
||||||
|
font-size: 11px
|
@ -36,3 +36,21 @@
|
|||||||
.styledButton:hover
|
.styledButton:hover
|
||||||
border: 1px solid #627ef7
|
border: 1px solid #627ef7
|
||||||
background-color: rgba(255, 255, 255, 0.06)
|
background-color: rgba(255, 255, 255, 0.06)
|
||||||
|
|
||||||
|
.spinnerContainer
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
.noMoreDataAvailable
|
||||||
|
text-align: center
|
||||||
|
font-weight: 600
|
||||||
|
color: rgba(255,255,255,0.75)
|
||||||
|
|
||||||
|
.fetchButtonContainer
|
||||||
|
width: 100%
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
margin-top: 12px
|
||||||
|
font-weight: 600
|
||||||
|
color: rgba(255,255,255,0.75)
|
@ -16,7 +16,8 @@
|
|||||||
border: solid 1px lighten(#4253a5, 20%)
|
border: solid 1px lighten(#4253a5, 20%)
|
||||||
|
|
||||||
.rowSelected
|
.rowSelected
|
||||||
border: solid 1px #4253a5
|
background: #293053
|
||||||
|
border: 1px #ffffff61 solid
|
||||||
|
|
||||||
.service
|
.service
|
||||||
text-overflow: ellipsis
|
text-overflow: ellipsis
|
||||||
|
@ -27,5 +27,6 @@
|
|||||||
background: #171922
|
background: #171922
|
||||||
border-radius: 12px
|
border-radius: 12px
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
|
color: white
|
||||||
fieldset
|
fieldset
|
||||||
border: none
|
border: none
|
||||||
|
@ -16,6 +16,124 @@ body
|
|||||||
code
|
code
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace
|
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace
|
||||||
|
|
||||||
|
.uppercase
|
||||||
|
text-transform: uppercase
|
||||||
|
|
||||||
|
/****
|
||||||
|
* Button
|
||||||
|
***/
|
||||||
|
button
|
||||||
|
span
|
||||||
|
line-height: 1
|
||||||
|
&:not(.MuiFab-root)
|
||||||
|
&.MuiButtonBase-root
|
||||||
|
box-sizing: border-box
|
||||||
|
font-weight: 500
|
||||||
|
line-height: 1
|
||||||
|
border-radius: 20px
|
||||||
|
letter-spacing: 0.02857em
|
||||||
|
text-transform: uppercase
|
||||||
|
img:not(.custom)
|
||||||
|
max-width: 13px
|
||||||
|
max-height: 13px
|
||||||
|
|
||||||
|
&.tiny
|
||||||
|
min-width: 0
|
||||||
|
|
||||||
|
/****
|
||||||
|
* Select
|
||||||
|
***/
|
||||||
|
.select
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: flex-start
|
||||||
|
color: white
|
||||||
|
.MuiInput-underline
|
||||||
|
&::before,
|
||||||
|
&::after
|
||||||
|
display: none
|
||||||
|
content: ''
|
||||||
|
|
||||||
|
.MuiSelect-root
|
||||||
|
&.MuiSelect-select
|
||||||
|
border-radius: 20px
|
||||||
|
cursor: pointer
|
||||||
|
min-width: 2rem
|
||||||
|
font-weight: normal
|
||||||
|
border: solid 0
|
||||||
|
padding: 3px 16px 4px 12px
|
||||||
|
|
||||||
|
.MuiInputBase-input
|
||||||
|
border-radius: 20px
|
||||||
|
background-color: rgba(255, 255, 255, 0.06)
|
||||||
|
cursor: pointer
|
||||||
|
padding-top: 0
|
||||||
|
padding-bottom: 0
|
||||||
|
font-size: 12px
|
||||||
|
font-weight: normal
|
||||||
|
font-stretch: normal
|
||||||
|
font-style: normal
|
||||||
|
letter-spacing: normal
|
||||||
|
text-align: left
|
||||||
|
line-height: 1.25
|
||||||
|
min-height: initial
|
||||||
|
&:focus
|
||||||
|
background-color: rgba(255, 255, 255, 0.06) !important
|
||||||
|
.MuiSelect-icon
|
||||||
|
top: 50%
|
||||||
|
transform: translateY(-50%)
|
||||||
|
right: 5px
|
||||||
|
position: absolute
|
||||||
|
pointer-events: none
|
||||||
|
&.MuiSelect-iconOpen
|
||||||
|
transform: translateY(-50%) rotate(180deg)
|
||||||
|
|
||||||
|
.ellipsis
|
||||||
|
display: block
|
||||||
|
overflow: hidden
|
||||||
|
white-space: nowrap
|
||||||
|
width: 100px
|
||||||
|
text-overflow: ellipsis
|
||||||
|
color: white
|
||||||
|
|
||||||
|
.selectLabel
|
||||||
|
margin-right: 8px
|
||||||
|
|
||||||
|
&.labelOnTop
|
||||||
|
flex-direction: column
|
||||||
|
align-items: flex-start
|
||||||
|
.selectLabel
|
||||||
|
margin-right: 0
|
||||||
|
margin-bottom: 4px
|
||||||
|
|
||||||
|
/****
|
||||||
|
* Paper/List/Menu list
|
||||||
|
***/
|
||||||
|
.MuiPaper-root
|
||||||
|
background-color: #344073 !important
|
||||||
|
&.MuiPaper-rounded
|
||||||
|
border-radius: 4px
|
||||||
|
|
||||||
|
.MuiList-root
|
||||||
|
padding: 0
|
||||||
|
&.MuiMenu-list
|
||||||
|
border-radius: 4px
|
||||||
|
box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.5)
|
||||||
|
color: #a0b2ff
|
||||||
|
.MuiListItem-root
|
||||||
|
&.MuiMenuItem-root
|
||||||
|
padding: 14px
|
||||||
|
font-size: 11px
|
||||||
|
font-weight: 600
|
||||||
|
font-stretch: normal
|
||||||
|
font-style: normal
|
||||||
|
line-height: 1.25
|
||||||
|
&:not(:last-child)
|
||||||
|
border-bottom: 1px solid rgb(53, 65, 114)
|
||||||
|
&.Mui-selected
|
||||||
|
background-color: #3f519a6e
|
||||||
|
color: white
|
||||||
|
|
||||||
// scroll-bar css
|
// scroll-bar css
|
||||||
|
|
||||||
::-webkit-scrollbar
|
::-webkit-scrollbar
|
||||||
|
Loading…
Reference in New Issue
Block a user