mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-08-08 11:59:17 +00:00
Performance fixes (#972)
This commit is contained in:
parent
d99c632102
commit
0b0b9ce6d1
@ -5,107 +5,120 @@ import Moment from 'moment';
|
|||||||
import {EntryItem} from "./EntryListItem/EntryListItem";
|
import {EntryItem} from "./EntryListItem/EntryListItem";
|
||||||
import down from "assets/downImg.svg";
|
import down from "assets/downImg.svg";
|
||||||
import spinner from 'assets/spinner.svg';
|
import spinner from 'assets/spinner.svg';
|
||||||
import {RecoilState, useRecoilState, useRecoilValue} from "recoil";
|
import {RecoilState, useRecoilState, useRecoilValue, useSetRecoilState} from "recoil";
|
||||||
import entriesAtom from "../../recoil/entries";
|
import entriesAtom from "../../recoil/entries";
|
||||||
import queryAtom from "../../recoil/query";
|
import queryAtom from "../../recoil/query";
|
||||||
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi";
|
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi";
|
||||||
import TrafficViewerApi from "./TrafficViewerApi";
|
import TrafficViewerApi from "./TrafficViewerApi";
|
||||||
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
import {TOAST_CONTAINER_ID} from "../../configs/Consts";
|
||||||
|
import tappingStatusAtom from "../../recoil/tappingStatus";
|
||||||
|
import leftOffTopAtom from "../../recoil/leftOffTop";
|
||||||
|
|
||||||
interface EntriesListProps {
|
interface EntriesListProps {
|
||||||
listEntryREF: any;
|
listEntryREF: any;
|
||||||
onSnapBrokenEvent: () => void;
|
onSnapBrokenEvent: () => void;
|
||||||
isSnappedToBottom: boolean;
|
isSnappedToBottom: boolean;
|
||||||
setIsSnappedToBottom: any;
|
setIsSnappedToBottom: any;
|
||||||
queriedCurrent: number;
|
noMoreDataTop: boolean;
|
||||||
setQueriedCurrent: any;
|
setNoMoreDataTop: (flag: boolean) => void;
|
||||||
startTime: number;
|
openWebSocket: (query: string, resetEntries: boolean) => void;
|
||||||
noMoreDataTop: boolean;
|
scrollableRef: any;
|
||||||
setNoMoreDataTop: (flag: boolean) => void;
|
ws: any;
|
||||||
leftOffTop: number;
|
|
||||||
setLeftOffTop: (leftOffTop: number) => void;
|
|
||||||
openWebSocket: (query: string, resetEntries: boolean) => void;
|
|
||||||
leftOffBottom: number;
|
|
||||||
truncatedTimestamp: number;
|
|
||||||
setTruncatedTimestamp: any;
|
|
||||||
scrollableRef: any;
|
|
||||||
ws: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, setQueriedCurrent, startTime, noMoreDataTop, setNoMoreDataTop, leftOffTop, setLeftOffTop, openWebSocket, leftOffBottom, truncatedTimestamp, setTruncatedTimestamp, scrollableRef, ws}) => {
|
export const EntriesList: React.FC<EntriesListProps> = ({
|
||||||
|
listEntryREF,
|
||||||
|
onSnapBrokenEvent,
|
||||||
|
isSnappedToBottom,
|
||||||
|
setIsSnappedToBottom,
|
||||||
|
noMoreDataTop,
|
||||||
|
setNoMoreDataTop,
|
||||||
|
openWebSocket,
|
||||||
|
scrollableRef,
|
||||||
|
ws
|
||||||
|
}) => {
|
||||||
|
|
||||||
const [entries, setEntries] = useRecoilState(entriesAtom);
|
const [entries, setEntries] = useRecoilState(entriesAtom);
|
||||||
const query = useRecoilValue(queryAtom);
|
const query = useRecoilValue(queryAtom);
|
||||||
const isWsConnectionClosed = ws?.current?.readyState !== WebSocket.OPEN;
|
const isWsConnectionClosed = ws?.current?.readyState !== WebSocket.OPEN;
|
||||||
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
|
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
|
||||||
|
const [leftOffTop, setLeftOffTop] = useRecoilState(leftOffTopAtom);
|
||||||
|
const setTappingStatus = useSetRecoilState(tappingStatusAtom);
|
||||||
|
|
||||||
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||||
|
|
||||||
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
||||||
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
||||||
const [queriedTotal, setQueriedTotal] = useState(0);
|
const [queriedTotal, setQueriedTotal] = useState(0);
|
||||||
|
const [startTime, setStartTime] = useState(0);
|
||||||
|
const [truncatedTimestamp, setTruncatedTimestamp] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
const leftOffBottom = entries.length > 0 ? entries[entries.length - 1].id : -1;
|
||||||
const list = document.getElementById('list').firstElementChild;
|
|
||||||
list.addEventListener('scroll', (e) => {
|
|
||||||
const el: any = e.target;
|
|
||||||
if(el.scrollTop === 0) {
|
|
||||||
setLoadMoreTop(true);
|
|
||||||
} else {
|
|
||||||
setNoMoreDataTop(false);
|
|
||||||
setLoadMoreTop(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [setLoadMoreTop, setNoMoreDataTop]);
|
|
||||||
|
|
||||||
const memoizedEntries = useMemo(() => {
|
useEffect(() => {
|
||||||
return entries;
|
const list = document.getElementById('list').firstElementChild;
|
||||||
},[entries]);
|
list.addEventListener('scroll', (e) => {
|
||||||
|
const el: any = e.target;
|
||||||
const getOldEntries = useCallback(async () => {
|
if (el.scrollTop === 0) {
|
||||||
|
setLoadMoreTop(true);
|
||||||
|
} else {
|
||||||
|
setNoMoreDataTop(false);
|
||||||
setLoadMoreTop(false);
|
setLoadMoreTop(false);
|
||||||
if (leftOffTop === null || leftOffTop <= 0) {
|
}
|
||||||
return;
|
});
|
||||||
}
|
}, [setLoadMoreTop, setNoMoreDataTop]);
|
||||||
setIsLoadingTop(true);
|
|
||||||
const data = await trafficViewerApi.fetchEntries(leftOffTop, -1, query, 100, 3000);
|
|
||||||
if (!data || data.data === null || data.meta === null) {
|
|
||||||
setNoMoreDataTop(true);
|
|
||||||
setIsLoadingTop(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLeftOffTop(data.meta.leftOff);
|
|
||||||
|
|
||||||
let scrollTo: boolean;
|
const memoizedEntries = useMemo(() => {
|
||||||
if (data.meta.leftOff === 0) {
|
return entries;
|
||||||
setNoMoreDataTop(true);
|
}, [entries]);
|
||||||
scrollTo = false;
|
|
||||||
} else {
|
|
||||||
scrollTo = true;
|
|
||||||
}
|
|
||||||
setIsLoadingTop(false);
|
|
||||||
|
|
||||||
const newEntries = [...data.data.reverse(), ...entries];
|
const getOldEntries = useCallback(async () => {
|
||||||
setEntries(newEntries);
|
setLoadMoreTop(false);
|
||||||
|
if (leftOffTop === null || leftOffTop <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsLoadingTop(true);
|
||||||
|
const data = await trafficViewerApi.fetchEntries(leftOffTop, -1, query, 100, 3000);
|
||||||
|
if (!data || data.data === null || data.meta === null) {
|
||||||
|
setNoMoreDataTop(true);
|
||||||
|
setIsLoadingTop(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLeftOffTop(data.meta.leftOff);
|
||||||
|
|
||||||
setQueriedCurrent(queriedCurrent + data.meta.current);
|
let scrollTo: boolean;
|
||||||
setQueriedTotal(data.meta.total);
|
if (data.meta.leftOff === 0) {
|
||||||
setTruncatedTimestamp(data.meta.truncatedTimestamp);
|
setNoMoreDataTop(true);
|
||||||
|
scrollTo = false;
|
||||||
|
} else {
|
||||||
|
scrollTo = true;
|
||||||
|
}
|
||||||
|
setIsLoadingTop(false);
|
||||||
|
|
||||||
if (scrollTo) {
|
const newEntries = [...data.data.reverse(), ...entries];
|
||||||
scrollableRef.current.scrollToIndex(data.data.length - 1);
|
setEntries(newEntries);
|
||||||
}
|
|
||||||
},[setLoadMoreTop, setIsLoadingTop, entries, setEntries, query, setNoMoreDataTop, leftOffTop, setLeftOffTop, queriedCurrent, setQueriedCurrent, setQueriedTotal, setTruncatedTimestamp, scrollableRef]);
|
setQueriedTotal(data.meta.total);
|
||||||
|
setTruncatedTimestamp(data.meta.truncatedTimestamp);
|
||||||
|
|
||||||
|
if (scrollTo) {
|
||||||
|
scrollableRef.current.scrollToIndex(data.data.length - 1);
|
||||||
|
}
|
||||||
|
}, [setLoadMoreTop, setIsLoadingTop, entries, setEntries, query, setNoMoreDataTop, leftOffTop, setLeftOffTop, setQueriedTotal, setTruncatedTimestamp, scrollableRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isWsConnectionClosed || !loadMoreTop || noMoreDataTop) return;
|
||||||
|
getOldEntries();
|
||||||
|
}, [loadMoreTop, noMoreDataTop, getOldEntries, isWsConnectionClosed]);
|
||||||
|
|
||||||
|
const scrollbarVisible = scrollableRef.current?.childWrapperRef.current.clientHeight > scrollableRef.current?.wrapperRef.current.clientHeight;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if(!isWsConnectionClosed || !loadMoreTop || noMoreDataTop) return;
|
|
||||||
getOldEntries();
|
|
||||||
}, [loadMoreTop, noMoreDataTop, getOldEntries, isWsConnectionClosed]);
|
|
||||||
|
|
||||||
const scrollbarVisible = scrollableRef.current?.childWrapperRef.current.clientHeight > scrollableRef.current?.wrapperRef.current.clientHeight;
|
|
||||||
|
|
||||||
if (ws.current) {
|
if (ws.current) {
|
||||||
ws.current.addEventListener("message", (e) => {
|
ws.current.onmessage = (e) => {
|
||||||
if (!e?.data) return;
|
if (!e?.data) return;
|
||||||
const message = JSON.parse(e.data);
|
const message = JSON.parse(e.data);
|
||||||
switch (message.messageType) {
|
switch (message.messageType) {
|
||||||
@ -120,60 +133,86 @@ export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBro
|
|||||||
}
|
}
|
||||||
setEntries(newEntries);
|
setEntries(newEntries);
|
||||||
break;
|
break;
|
||||||
|
case "status":
|
||||||
|
setTappingStatus(message.tappingStatus);
|
||||||
|
break;
|
||||||
|
case "toast":
|
||||||
|
toast[message.data.type](message.data.text, {
|
||||||
|
theme: "colored",
|
||||||
|
autoClose: message.data.autoClose,
|
||||||
|
pauseOnHover: true,
|
||||||
|
progress: undefined,
|
||||||
|
containerId: TOAST_CONTAINER_ID
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "queryMetadata":
|
case "queryMetadata":
|
||||||
|
setTruncatedTimestamp(message.data.truncatedTimestamp);
|
||||||
setQueriedTotal(message.data.total);
|
setQueriedTotal(message.data.total);
|
||||||
|
if (leftOffTop === null) {
|
||||||
|
setLeftOffTop(message.data.leftOff - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "startTime":
|
||||||
|
setStartTime(message.data);
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<div className={styles.list}>
|
<div className={styles.list}>
|
||||||
<div id="list" ref={listEntryREF} className={styles.list}>
|
<div id="list" ref={listEntryREF} className={styles.list}>
|
||||||
{isLoadingTop && <div className={styles.spinnerContainer}>
|
{isLoadingTop && <div className={styles.spinnerContainer}>
|
||||||
<img alt="spinner" src={spinner} style={{height: 25}}/>
|
<img alt="spinner" src={spinner} style={{height: 25}}/>
|
||||||
</div>}
|
</div>}
|
||||||
{noMoreDataTop && <div id="noMoreDataTop" className={styles.noMoreDataAvailable}>No more data available</div>}
|
{noMoreDataTop && <div id="noMoreDataTop" className={styles.noMoreDataAvailable}>No more data available</div>}
|
||||||
<ScrollableFeedVirtualized ref={scrollableRef} itemHeight={48} marginTop={10} onSnapBroken={onSnapBrokenEvent}>
|
<ScrollableFeedVirtualized ref={scrollableRef} itemHeight={48} marginTop={10} onSnapBroken={onSnapBrokenEvent}>
|
||||||
{false /* It's because the first child is ignored by ScrollableFeedVirtualized */}
|
{false /* It's because the first child is ignored by ScrollableFeedVirtualized */}
|
||||||
{memoizedEntries.map(entry => <EntryItem
|
{memoizedEntries.map(entry => <EntryItem
|
||||||
key={`entry-${entry.id}`}
|
key={`entry-${entry.id}`}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
style={{}}
|
style={{}}
|
||||||
headingMode={false}
|
headingMode={false}
|
||||||
/>)}
|
/>)}
|
||||||
</ScrollableFeedVirtualized>
|
</ScrollableFeedVirtualized>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
title="Fetch old records"
|
title="Fetch old records"
|
||||||
className={`${styles.btnOld} ${!scrollbarVisible && leftOffTop > 0 ? styles.showButton : styles.hideButton}`}
|
className={`${styles.btnOld} ${!scrollbarVisible && leftOffTop > 0 ? styles.showButton : styles.hideButton}`}
|
||||||
onClick={(_) => {
|
onClick={(_) => {
|
||||||
trafficViewerApi.webSocket.close()
|
trafficViewerApi.webSocket.close()
|
||||||
getOldEntries();
|
getOldEntries();
|
||||||
}}>
|
}}>
|
||||||
<img alt="down" src={down} />
|
<img alt="down" src={down}/>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
title="Snap to bottom"
|
title="Snap to bottom"
|
||||||
className={`${styles.btnLive} ${isSnappedToBottom && !isWsConnectionClosed ? styles.hideButton : styles.showButton}`}
|
className={`${styles.btnLive} ${isSnappedToBottom && !isWsConnectionClosed ? styles.hideButton : styles.showButton}`}
|
||||||
onClick={(_) => {
|
onClick={(_) => {
|
||||||
if (isWsConnectionClosed) {
|
if (isWsConnectionClosed) {
|
||||||
if (query) {
|
if (query) {
|
||||||
openWebSocket(`(${query}) and leftOff(${leftOffBottom})`, false);
|
openWebSocket(`(${query}) and leftOff(${leftOffBottom})`, false);
|
||||||
} else {
|
} else {
|
||||||
openWebSocket(`leftOff(${leftOffBottom})`, false);
|
openWebSocket(`leftOff(${leftOffBottom})`, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scrollableRef.current.jumpToBottom();
|
scrollableRef.current.jumpToBottom();
|
||||||
setIsSnappedToBottom(true);
|
setIsSnappedToBottom(true);
|
||||||
}}>
|
}}>
|
||||||
<img alt="down" src={down} />
|
<img alt="down" src={down}/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
<div>Displaying <b id="entries-length">{entries?.length}</b> results out of <b id="total-entries">{queriedTotal}</b> total</div>
|
<div>Displaying <b id="entries-length">{entries?.length}</b> results out of <b
|
||||||
{startTime !== 0 && <div>Started listening at <span style={{marginRight: 5, fontWeight: 600, fontSize: 13}}>{Moment(truncatedTimestamp ? truncatedTimestamp : startTime).utc().format('MM/DD/YYYY, h:mm:ss.SSS A')}</span></div>}
|
id="total-entries">{queriedTotal}</b> total
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{startTime !== 0 && <div>Started listening at <span style={{
|
||||||
</React.Fragment>;
|
marginRight: 5,
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 13
|
||||||
|
}}>{Moment(truncatedTimestamp ? truncatedTimestamp : startTime).utc().format('MM/DD/YYYY, h:mm:ss.SSS A')}</span>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
|
@ -5,14 +5,14 @@ import { makeStyles } from "@material-ui/core";
|
|||||||
import Protocol from "../UI/Protocol"
|
import Protocol from "../UI/Protocol"
|
||||||
import Queryable from "../UI/Queryable";
|
import Queryable from "../UI/Queryable";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { RecoilState, useRecoilState, useRecoilValue } from "recoil";
|
import { RecoilState, useRecoilValue } from "recoil";
|
||||||
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||||
import trafficViewerApi from "../../recoil/TrafficViewerApi";
|
|
||||||
import TrafficViewerApi from "./TrafficViewerApi";
|
import TrafficViewerApi from "./TrafficViewerApi";
|
||||||
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi/atom";
|
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi/atom";
|
||||||
import queryAtom from "../../recoil/query/atom";
|
import queryAtom from "../../recoil/query/atom";
|
||||||
import useWindowDimensions, { useRequestTextByWidth } from "../../hooks/WindowDimensionsHook";
|
import useWindowDimensions, { useRequestTextByWidth } from "../../hooks/WindowDimensionsHook";
|
||||||
import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
||||||
|
import spinner from "assets/spinner.svg";
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles(() => ({
|
||||||
entryTitle: {
|
entryTitle: {
|
||||||
@ -105,12 +105,13 @@ export const EntryDetailed = () => {
|
|||||||
const focusedEntryId = useRecoilValue(focusedEntryIdAtom);
|
const focusedEntryId = useRecoilValue(focusedEntryIdAtom);
|
||||||
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||||
const query = useRecoilValue(queryAtom);
|
const query = useRecoilValue(queryAtom);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [entryData, setEntryData] = useState(null);
|
const [entryData, setEntryData] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!focusedEntryId) return;
|
if (!focusedEntryId) return;
|
||||||
setEntryData(null);
|
setEntryData(null);
|
||||||
|
setIsLoading(true);
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const entryData = await trafficViewerApi.getEntry(focusedEntryId, query);
|
const entryData = await trafficViewerApi.getEntry(focusedEntryId, query);
|
||||||
@ -125,20 +126,23 @@ export const EntryDetailed = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [focusedEntryId]);
|
}, [focusedEntryId]);
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
{entryData && <EntryTitle
|
{isLoading && <div style={{textAlign: "center", width: "100%", marginTop: 50}}><img alt="spinner" src={spinner} style={{height: 60}}/></div>}
|
||||||
|
{!isLoading && entryData && <EntryTitle
|
||||||
protocol={entryData.protocol}
|
protocol={entryData.protocol}
|
||||||
data={entryData.data}
|
data={entryData.data}
|
||||||
elapsedTime={entryData.data.elapsedTime}
|
elapsedTime={entryData.data.elapsedTime}
|
||||||
/>}
|
/>}
|
||||||
{entryData && <EntrySummary entry={entryData.base} />}
|
{!isLoading && entryData && <EntrySummary entry={entryData.base} />}
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{entryData && <EntryViewer
|
{!isLoading && entryData && <EntryViewer
|
||||||
representation={entryData.representation}
|
representation={entryData.representation}
|
||||||
isRulesEnabled={entryData.isRulesEnabled}
|
isRulesEnabled={entryData.isRulesEnabled}
|
||||||
rulesMatched={entryData.rulesMatched}
|
rulesMatched={entryData.rulesMatched}
|
||||||
|
@ -16,21 +16,21 @@ import trafficViewerApiAtom from "../../recoil/TrafficViewerApi"
|
|||||||
|
|
||||||
interface FiltersProps {
|
interface FiltersProps {
|
||||||
backgroundColor: string
|
backgroundColor: string
|
||||||
openWebSocket: (query: string, resetEntries: boolean) => void;
|
reopenConnection: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Filters: React.FC<FiltersProps> = ({backgroundColor, openWebSocket}) => {
|
export const Filters: React.FC<FiltersProps> = ({backgroundColor, reopenConnection}) => {
|
||||||
return <div className={styles.container}>
|
return <div className={styles.container}>
|
||||||
<QueryForm
|
<QueryForm
|
||||||
backgroundColor={backgroundColor}
|
backgroundColor={backgroundColor}
|
||||||
openWebSocket={openWebSocket}
|
reopenConnection={reopenConnection}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface QueryFormProps {
|
interface QueryFormProps {
|
||||||
backgroundColor: string
|
backgroundColor: string
|
||||||
openWebSocket: (query: string, resetEntries: boolean) => void;
|
reopenConnection: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const modalStyle = {
|
export const modalStyle = {
|
||||||
@ -47,11 +47,10 @@ export const modalStyle = {
|
|||||||
color: '#000',
|
color: '#000',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QueryForm: React.FC<QueryFormProps> = ({backgroundColor, openWebSocket}) => {
|
export const QueryForm: React.FC<QueryFormProps> = ({backgroundColor, reopenConnection}) => {
|
||||||
|
|
||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
const [query, setQuery] = useRecoilState(queryAtom);
|
const [query, setQuery] = useRecoilState(queryAtom);
|
||||||
const trafficViewerApi = useRecoilValue(trafficViewerApiAtom)
|
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
@ -63,12 +62,7 @@ export const QueryForm: React.FC<QueryFormProps> = ({backgroundColor, openWebSoc
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
trafficViewerApi.webSocket.close()
|
reopenConnection();
|
||||||
if (query) {
|
|
||||||
openWebSocket(`(${query}) and leftOff(-1)`, true);
|
|
||||||
} else {
|
|
||||||
openWebSocket(`leftOff(-1)`, true);
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, {useEffect, useMemo, useRef, useState} from "react";
|
||||||
import { Filters } from "./Filters";
|
import {Filters} from "./Filters";
|
||||||
import { EntriesList } from "./EntriesList";
|
import {EntriesList} from "./EntriesList";
|
||||||
import { makeStyles } from "@material-ui/core";
|
import {makeStyles} from "@material-ui/core";
|
||||||
import TrafficViewerStyles from "./TrafficViewer.module.sass";
|
import TrafficViewerStyles from "./TrafficViewer.module.sass";
|
||||||
import styles from '../style/EntriesList.module.sass';
|
import styles from '../style/EntriesList.module.sass';
|
||||||
import { EntryDetailed } from "./EntryDetailed";
|
import {EntryDetailed} from "./EntryDetailed";
|
||||||
import playIcon from 'assets/run.svg';
|
import playIcon from 'assets/run.svg';
|
||||||
import pauseIcon from 'assets/pause.svg';
|
import pauseIcon from 'assets/pause.svg';
|
||||||
import variables from '../../variables.module.scss';
|
import variables from '../../variables.module.scss';
|
||||||
import { toast, ToastContainer } from 'react-toastify';
|
import {ToastContainer} from 'react-toastify';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { RecoilRoot, RecoilState, useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
|
import {RecoilRoot, RecoilState, useRecoilState, useRecoilValue, useSetRecoilState} from "recoil";
|
||||||
import entriesAtom from "../../recoil/entries";
|
import entriesAtom from "../../recoil/entries";
|
||||||
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||||
import queryAtom from "../../recoil/query";
|
import queryAtom from "../../recoil/query";
|
||||||
import { TLSWarning } from "../TLSWarning/TLSWarning";
|
import {TLSWarning} from "../TLSWarning/TLSWarning";
|
||||||
import trafficViewerApiAtom from "../../recoil/TrafficViewerApi"
|
import trafficViewerApiAtom from "../../recoil/TrafficViewerApi"
|
||||||
import TrafficViewerApi from "./TrafficViewerApi";
|
import TrafficViewerApi from "./TrafficViewerApi";
|
||||||
import { StatusBar } from "../UI/StatusBar";
|
import {StatusBar} from "../UI/StatusBar";
|
||||||
import tappingStatusAtom from "../../recoil/tappingStatus/atom";
|
import tappingStatusAtom from "../../recoil/tappingStatus/atom";
|
||||||
import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
import {TOAST_CONTAINER_ID} from "../../configs/Consts";
|
||||||
|
import leftOffTopAtom from "../../recoil/leftOffTop";
|
||||||
|
|
||||||
const useLayoutStyles = makeStyles(() => ({
|
const useLayoutStyles = makeStyles(() => ({
|
||||||
details: {
|
details: {
|
||||||
@ -52,14 +53,16 @@ interface TrafficViewerProps {
|
|||||||
isDemoBannerView: boolean
|
isDemoBannerView: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus, trafficViewerApiProp,
|
export const TrafficViewer: React.FC<TrafficViewerProps> = ({
|
||||||
actionButtons, isShowStatusBar, webSocketUrl,
|
setAnalyzeStatus, trafficViewerApiProp,
|
||||||
isCloseWebSocket, isDemoBannerView }) => {
|
actionButtons, isShowStatusBar, webSocketUrl,
|
||||||
|
isCloseWebSocket, isDemoBannerView
|
||||||
|
}) => {
|
||||||
|
|
||||||
const classes = useLayoutStyles();
|
const classes = useLayoutStyles();
|
||||||
|
|
||||||
const setEntries = useSetRecoilState(entriesAtom);
|
const setEntries = useSetRecoilState(entriesAtom);
|
||||||
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
|
const setFocusedEntryId = useSetRecoilState(focusedEntryIdAtom);
|
||||||
const query = useRecoilValue(queryAtom);
|
const query = useRecoilValue(queryAtom);
|
||||||
const setTrafficViewerApiState = useSetRecoilState(trafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
const setTrafficViewerApiState = useSetRecoilState(trafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||||
const [tappingStatus, setTappingStatus] = useRecoilState(tappingStatusAtom);
|
const [tappingStatus, setTappingStatus] = useRecoilState(tappingStatusAtom);
|
||||||
@ -69,12 +72,7 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
|
|
||||||
const [queryBackgroundColor, setQueryBackgroundColor] = useState("#f5f5f5");
|
const [queryBackgroundColor, setQueryBackgroundColor] = useState("#f5f5f5");
|
||||||
|
|
||||||
const [queriedCurrent, setQueriedCurrent] = useState(0);
|
const setLeftOffTop = useSetRecoilState(leftOffTopAtom);
|
||||||
const [leftOffBottom, setLeftOffBottom] = useState(0);
|
|
||||||
const [leftOffTop, setLeftOffTop] = useState(null);
|
|
||||||
const [truncatedTimestamp, setTruncatedTimestamp] = useState(0);
|
|
||||||
|
|
||||||
const [startTime, setStartTime] = useState(0);
|
|
||||||
const scrollableRef = useRef(null);
|
const scrollableRef = useRef(null);
|
||||||
|
|
||||||
const [showTLSWarning, setShowTLSWarning] = useState(false);
|
const [showTLSWarning, setShowTLSWarning] = useState(false);
|
||||||
@ -124,7 +122,7 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closeWebSocket = () => {
|
const closeWebSocket = () => {
|
||||||
if(ws?.current?.readyState === WebSocket.OPEN) {
|
if (ws?.current?.readyState === WebSocket.OPEN) {
|
||||||
ws.current.close();
|
ws.current.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -135,7 +133,6 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
if (resetEntries) {
|
if (resetEntries) {
|
||||||
setFocusedEntryId(null);
|
setFocusedEntryId(null);
|
||||||
setEntries([]);
|
setEntries([]);
|
||||||
setQueriedCurrent(0);
|
|
||||||
setLeftOffTop(null);
|
setLeftOffTop(null);
|
||||||
setNoMoreDataTop(false);
|
setNoMoreDataTop(false);
|
||||||
}
|
}
|
||||||
@ -155,67 +152,26 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
if (ws?.current?.readyState === WebSocket.OPEN) {
|
if (ws?.current?.readyState === WebSocket.OPEN) {
|
||||||
ws.current.close();
|
ws.current.close();
|
||||||
}
|
}
|
||||||
if (query) {
|
|
||||||
openWebSocket(`(${query}) and leftOff(${leftOffBottom})`, false);
|
|
||||||
} else {
|
|
||||||
openWebSocket(`leftOff(${leftOffBottom})`, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) { }
|
} catch (e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendQueryWhenWsOpen = (query) => {
|
const sendQueryWhenWsOpen = (query) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (ws?.current?.readyState === WebSocket.OPEN) {
|
if (ws?.current?.readyState === WebSocket.OPEN) {
|
||||||
ws.current.send(JSON.stringify({ "query": query, "enableFullEntries": false }));
|
ws.current.send(JSON.stringify({"query": query, "enableFullEntries": false}));
|
||||||
} else {
|
} else {
|
||||||
sendQueryWhenWsOpen(query);
|
sendQueryWhenWsOpen(query);
|
||||||
}
|
}
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ws.current) {
|
|
||||||
ws.current.addEventListener("message", (e) => {
|
|
||||||
if (!e?.data) return;
|
|
||||||
const message = JSON.parse(e.data);
|
|
||||||
switch (message.messageType) {
|
|
||||||
case "status":
|
|
||||||
setTappingStatus(message.tappingStatus);
|
|
||||||
break;
|
|
||||||
case "analyzeStatus":
|
|
||||||
setAnalyzeStatus(message.analyzeStatus);
|
|
||||||
break;
|
|
||||||
case "outboundLink":
|
|
||||||
onTLSDetected(message.Data.DstIP);
|
|
||||||
break;
|
|
||||||
case "toast":
|
|
||||||
toast[message.data.type](message.data.text, {
|
|
||||||
theme: "colored",
|
|
||||||
autoClose: message.data.autoClose,
|
|
||||||
pauseOnHover: true,
|
|
||||||
progress: undefined,
|
|
||||||
containerId: TOAST_CONTAINER_ID
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "queryMetadata":
|
|
||||||
setQueriedCurrent(queriedCurrent + message.data.current);
|
|
||||||
setLeftOffBottom(message.data.leftOff);
|
|
||||||
setTruncatedTimestamp(message.data.truncatedTimestamp);
|
|
||||||
if (leftOffTop === null) {
|
|
||||||
setLeftOffTop(message.data.leftOff - 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "startTime":
|
|
||||||
setStartTime(message.data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTrafficViewerApiState({ ...trafficViewerApiProp, webSocket: { close: closeWebSocket } });
|
setTrafficViewerApiState({...trafficViewerApiProp, webSocket: {close: closeWebSocket}});
|
||||||
(async () => {
|
(async () => {
|
||||||
try{
|
try {
|
||||||
const tapStatusResponse = await trafficViewerApiProp.tapStatus();
|
const tapStatusResponse = await trafficViewerApiProp.tapStatus();
|
||||||
setTappingStatus(tapStatusResponse);
|
setTappingStatus(tapStatusResponse);
|
||||||
if (setAnalyzeStatus) {
|
if (setAnalyzeStatus) {
|
||||||
@ -226,11 +182,10 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
// eslint-disable-next-line
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleConnection = () => {
|
const toggleConnection = () => {
|
||||||
if(!closeWebSocket()) {
|
if (!closeWebSocket()) {
|
||||||
openEmptyWebSocket();
|
openEmptyWebSocket();
|
||||||
scrollableRef.current.jumpToBottom();
|
scrollableRef.current.jumpToBottom();
|
||||||
setIsSnappedToBottom(true);
|
setIsSnappedToBottom(true);
|
||||||
@ -240,6 +195,8 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
const reopenConnection = async () => {
|
const reopenConnection = async () => {
|
||||||
closeWebSocket()
|
closeWebSocket()
|
||||||
openEmptyWebSocket();
|
openEmptyWebSocket();
|
||||||
|
scrollableRef.current.jumpToBottom();
|
||||||
|
setIsSnappedToBottom(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -248,24 +205,17 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onTLSDetected = (destAddress: string) => {
|
|
||||||
addressesWithTLS.add(destAddress);
|
|
||||||
setAddressesWithTLS(new Set(addressesWithTLS));
|
|
||||||
|
|
||||||
if (!userDismissedTLSWarning) {
|
|
||||||
setShowTLSWarning(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getConnectionIndicator = () => {
|
const getConnectionIndicator = () => {
|
||||||
switch (wsReadyState) {
|
switch (wsReadyState) {
|
||||||
case WebSocket.OPEN:
|
case WebSocket.OPEN:
|
||||||
return <div className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.greenIndicatorContainer}`}>
|
return <div
|
||||||
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.greenIndicator}`} />
|
className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.greenIndicatorContainer}`}>
|
||||||
|
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.greenIndicator}`}/>
|
||||||
</div>
|
</div>
|
||||||
default:
|
default:
|
||||||
return <div className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.redIndicatorContainer}`}>
|
return <div
|
||||||
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.redIndicator}`} />
|
className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.redIndicatorContainer}`}>
|
||||||
|
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.redIndicator}`}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,13 +238,16 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={TrafficViewerStyles.TrafficPage}>
|
<div className={TrafficViewerStyles.TrafficPage}>
|
||||||
{tappingStatus && isShowStatusBar && <StatusBar isDemoBannerView={isDemoBannerView} />}
|
{tappingStatus && isShowStatusBar && <StatusBar isDemoBannerView={isDemoBannerView}/>}
|
||||||
<div className={TrafficViewerStyles.TrafficPageHeader}>
|
<div className={TrafficViewerStyles.TrafficPageHeader}>
|
||||||
<div className={TrafficViewerStyles.TrafficPageStreamStatus}>
|
<div className={TrafficViewerStyles.TrafficPageStreamStatus}>
|
||||||
<img className={TrafficViewerStyles.playPauseIcon} style={{ visibility: wsReadyState === WebSocket.OPEN ? "visible" : "hidden" }} alt="pause"
|
<img className={TrafficViewerStyles.playPauseIcon}
|
||||||
src={pauseIcon} onClick={toggleConnection} />
|
style={{visibility: wsReadyState === WebSocket.OPEN ? "visible" : "hidden"}} alt="pause"
|
||||||
<img className={TrafficViewerStyles.playPauseIcon} style={{ position: "absolute", visibility: wsReadyState === WebSocket.OPEN ? "hidden" : "visible" }} alt="play"
|
src={pauseIcon} onClick={toggleConnection}/>
|
||||||
src={playIcon} onClick={toggleConnection} />
|
<img className={TrafficViewerStyles.playPauseIcon}
|
||||||
|
style={{position: "absolute", visibility: wsReadyState === WebSocket.OPEN ? "hidden" : "visible"}}
|
||||||
|
alt="play"
|
||||||
|
src={playIcon} onClick={toggleConnection}/>
|
||||||
<div className={TrafficViewerStyles.connectionText}>
|
<div className={TrafficViewerStyles.connectionText}>
|
||||||
{getConnectionTitle()}
|
{getConnectionTitle()}
|
||||||
{getConnectionIndicator()}
|
{getConnectionIndicator()}
|
||||||
@ -306,8 +259,7 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
<div className={TrafficViewerStyles.TrafficPageListContainer}>
|
<div className={TrafficViewerStyles.TrafficPageListContainer}>
|
||||||
<Filters
|
<Filters
|
||||||
backgroundColor={queryBackgroundColor}
|
backgroundColor={queryBackgroundColor}
|
||||||
openWebSocket={openWebSocket}
|
reopenConnection={reopenConnection}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<EntriesList
|
<EntriesList
|
||||||
@ -315,54 +267,48 @@ export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus,
|
|||||||
onSnapBrokenEvent={onSnapBrokenEvent}
|
onSnapBrokenEvent={onSnapBrokenEvent}
|
||||||
isSnappedToBottom={isSnappedToBottom}
|
isSnappedToBottom={isSnappedToBottom}
|
||||||
setIsSnappedToBottom={setIsSnappedToBottom}
|
setIsSnappedToBottom={setIsSnappedToBottom}
|
||||||
queriedCurrent={queriedCurrent}
|
|
||||||
setQueriedCurrent={setQueriedCurrent}
|
|
||||||
startTime={startTime}
|
|
||||||
noMoreDataTop={noMoreDataTop}
|
noMoreDataTop={noMoreDataTop}
|
||||||
setNoMoreDataTop={setNoMoreDataTop}
|
setNoMoreDataTop={setNoMoreDataTop}
|
||||||
leftOffTop={leftOffTop}
|
|
||||||
setLeftOffTop={setLeftOffTop}
|
|
||||||
openWebSocket={openWebSocket}
|
openWebSocket={openWebSocket}
|
||||||
leftOffBottom={leftOffBottom}
|
|
||||||
truncatedTimestamp={truncatedTimestamp}
|
|
||||||
setTruncatedTimestamp={setTruncatedTimestamp}
|
|
||||||
scrollableRef={scrollableRef}
|
scrollableRef={scrollableRef}
|
||||||
ws={ws}
|
ws={ws}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.details} id="rightSideContainer">
|
<div className={classes.details} id="rightSideContainer">
|
||||||
{focusedEntryId && <EntryDetailed />}
|
<EntryDetailed/>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
<TLSWarning showTLSWarning={showTLSWarning}
|
<TLSWarning showTLSWarning={showTLSWarning}
|
||||||
setShowTLSWarning={setShowTLSWarning}
|
setShowTLSWarning={setShowTLSWarning}
|
||||||
addressesWithTLS={addressesWithTLS}
|
addressesWithTLS={addressesWithTLS}
|
||||||
setAddressesWithTLS={setAddressesWithTLS}
|
setAddressesWithTLS={setAddressesWithTLS}
|
||||||
userDismissedTLSWarning={userDismissedTLSWarning}
|
userDismissedTLSWarning={userDismissedTLSWarning}
|
||||||
setUserDismissedTLSWarning={setUserDismissedTLSWarning} />
|
setUserDismissedTLSWarning={setUserDismissedTLSWarning}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MemoiedTrafficViewer = React.memo(TrafficViewer)
|
const MemoiedTrafficViewer = React.memo(TrafficViewer)
|
||||||
const TrafficViewerContainer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus, trafficViewerApiProp,
|
const TrafficViewerContainer: React.FC<TrafficViewerProps> = ({
|
||||||
actionButtons, isShowStatusBar = true,
|
setAnalyzeStatus, trafficViewerApiProp,
|
||||||
webSocketUrl, isCloseWebSocket, isDemoBannerView }) => {
|
actionButtons, isShowStatusBar = true,
|
||||||
|
webSocketUrl, isCloseWebSocket, isDemoBannerView
|
||||||
|
}) => {
|
||||||
return <RecoilRoot>
|
return <RecoilRoot>
|
||||||
<MemoiedTrafficViewer actionButtons={actionButtons} isShowStatusBar={isShowStatusBar} webSocketUrl={webSocketUrl}
|
<MemoiedTrafficViewer actionButtons={actionButtons} isShowStatusBar={isShowStatusBar} webSocketUrl={webSocketUrl}
|
||||||
isCloseWebSocket={isCloseWebSocket} trafficViewerApiProp={trafficViewerApiProp}
|
isCloseWebSocket={isCloseWebSocket} trafficViewerApiProp={trafficViewerApiProp}
|
||||||
setAnalyzeStatus={setAnalyzeStatus} isDemoBannerView={isDemoBannerView} />
|
setAnalyzeStatus={setAnalyzeStatus} isDemoBannerView={isDemoBannerView}/>
|
||||||
<ToastContainer enableMultiContainer containerId={TOAST_CONTAINER_ID}
|
<ToastContainer enableMultiContainer containerId={TOAST_CONTAINER_ID}
|
||||||
position="bottom-right"
|
position="bottom-right"
|
||||||
autoClose={5000}
|
autoClose={5000}
|
||||||
hideProgressBar={false}
|
hideProgressBar={false}
|
||||||
newestOnTop={false}
|
newestOnTop={false}
|
||||||
closeOnClick
|
closeOnClick
|
||||||
rtl={false}
|
rtl={false}
|
||||||
pauseOnFocusLoss
|
pauseOnFocusLoss
|
||||||
draggable
|
draggable
|
||||||
pauseOnHover />
|
pauseOnHover/>
|
||||||
</RecoilRoot>
|
</RecoilRoot>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,10 @@ interface StatusBarProps {
|
|||||||
isDemoBannerView: boolean;
|
isDemoBannerView: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StatusBar = ({isDemoBannerView}) => {
|
export const StatusBar: React.FC<StatusBarProps> = ({isDemoBannerView}) => {
|
||||||
const tappingStatus = useRecoilValue(tappingStatusAtom);
|
const tappingStatus = useRecoilValue(tappingStatusAtom);
|
||||||
const [expandedBar, setExpandedBar] = useState(false);
|
const [expandedBar, setExpandedBar] = useState(false);
|
||||||
const {uniqueNamespaces, amountOfPods, amountOfTappedPods, amountOfUntappedPods} = useRecoilValue(tappingStatusDetails);
|
const {uniqueNamespaces, amountOfPods, amountOfTappedPods, amountOfUntappedPods} = useRecoilValue(tappingStatusDetails);
|
||||||
|
|
||||||
return <div className={`${isDemoBannerView ? `${style.banner}` : ''} ${style.statusBar} ${(expandedBar ? `${style.expandedStatusBar}` : "")}`} onMouseOver={() => setExpandedBar(true)} onMouseLeave={() => setExpandedBar(false)} data-cy="expandedStatusBar">
|
return <div className={`${isDemoBannerView ? `${style.banner}` : ''} ${style.statusBar} ${(expandedBar ? `${style.expandedStatusBar}` : "")}`} onMouseOver={() => setExpandedBar(true)} onMouseLeave={() => setExpandedBar(false)} data-cy="expandedStatusBar">
|
||||||
<div className={style.podsCount}>
|
<div className={style.podsCount}>
|
||||||
{tappingStatus.some(pod => !pod.isTapped) && <img src={warningIcon} alt="warning"/>}
|
{tappingStatus.some(pod => !pod.isTapped) && <img src={warningIcon} alt="warning"/>}
|
||||||
@ -39,7 +38,7 @@ export const StatusBar = ({isDemoBannerView}) => {
|
|||||||
{tappingStatus.map(pod => <tr key={pod.name}>
|
{tappingStatus.map(pod => <tr key={pod.name}>
|
||||||
<td style={{width: "40%"}}>{pod.name}</td>
|
<td style={{width: "40%"}}>{pod.name}</td>
|
||||||
<td style={{width: "40%"}}>{pod.namespace}</td>
|
<td style={{width: "40%"}}>{pod.namespace}</td>
|
||||||
<td style={{width: "20%", textAlign: "center"}}><img style={{height: 20}} alt="status" src={pod.isTapped ? successIcon : failIcon}/></td>
|
<td style={{width: "20%", textAlign: "center"}}>{pod.isTapped ? <img style={{height: 20}} alt="status" src={successIcon}/> : <img style={{height: 20}} alt="status" src={failIcon}/>}</td>
|
||||||
</tr>)}
|
</tr>)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
8
ui-common/src/recoil/leftOffTop/atom.ts
Normal file
8
ui-common/src/recoil/leftOffTop/atom.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { atom } from "recoil"
|
||||||
|
|
||||||
|
const leftOffTopAtom = atom({
|
||||||
|
key: "leftOffTopAtom",
|
||||||
|
default: null
|
||||||
|
})
|
||||||
|
|
||||||
|
export default leftOffTopAtom;
|
2
ui-common/src/recoil/leftOffTop/index.ts
Normal file
2
ui-common/src/recoil/leftOffTop/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import atom from "./atom";
|
||||||
|
export default atom;
|
Loading…
Reference in New Issue
Block a user