Performance fixes (#972)

This commit is contained in:
lirazyehezkel 2022-04-04 20:03:57 +03:00 committed by GitHub
parent d99c632102
commit 0b0b9ce6d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 258 additions and 266 deletions

View File

@ -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>;
}; };

View File

@ -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}

View File

@ -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();
} }

View File

@ -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>
} }

View File

@ -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>

View File

@ -0,0 +1,8 @@
import { atom } from "recoil"
const leftOffTopAtom = atom({
key: "leftOffTopAtom",
default: null
})
export default leftOffTopAtom;

View File

@ -0,0 +1,2 @@
import atom from "./atom";
export default atom;