diff --git a/Dockerfile b/Dockerfile index ce4b8449c..dffc183fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,8 +42,8 @@ RUN go build -ldflags="-s -w \ -X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent . # Download Basenine executable, verify the sha1sum and move it to a directory in $PATH -ADD https://github.com/up9inc/basenine/releases/download/v0.2.13/basenine_linux_amd64 ./basenine_linux_amd64 -ADD https://github.com/up9inc/basenine/releases/download/v0.2.13/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256 +ADD https://github.com/up9inc/basenine/releases/download/v0.2.16/basenine_linux_amd64 ./basenine_linux_amd64 +ADD https://github.com/up9inc/basenine/releases/download/v0.2.16/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256 RUN shasum -a 256 -c basenine_linux_amd64.sha256 RUN chmod +x ./basenine_linux_amd64 diff --git a/agent/go.mod b/agent/go.mod index b0c7c1e5c..822531ba8 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -16,7 +16,7 @@ require ( github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/up9inc/basenine/client/go v0.0.0-20211130202146-cf837626a065 + github.com/up9inc/basenine/client/go v0.0.0-20211207165834-2ced7577f9e6 github.com/up9inc/mizu/shared v0.0.0 github.com/up9inc/mizu/tap v0.0.0 github.com/up9inc/mizu/tap/api v0.0.0 diff --git a/agent/go.sum b/agent/go.sum index 54425e23c..9627935ed 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -450,8 +450,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/up9inc/basenine/client/go v0.0.0-20211130202146-cf837626a065 h1:kfciLExAWkJMeMoKtnO5G5czqNv5/d0zjupG2nAeBmo= -github.com/up9inc/basenine/client/go v0.0.0-20211130202146-cf837626a065/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI= +github.com/up9inc/basenine/client/go v0.0.0-20211207165834-2ced7577f9e6 h1:8JOkoaZHhUPi4r7vSL/xo83foSz8BHPSabTDpxmtHFU= +github.com/up9inc/basenine/client/go v0.0.0-20211207165834-2ced7577f9e6/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= diff --git a/agent/pkg/api/socket_routes.go b/agent/pkg/api/socket_routes.go index b82c7c338..8feccebac 100644 --- a/agent/pkg/api/socket_routes.go +++ b/agent/pkg/api/socket_routes.go @@ -6,7 +6,6 @@ import ( "fmt" "mizuserver/pkg/models" "net/http" - "strconv" "sync" "time" @@ -95,8 +94,6 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(startTime) SendToSocket(socketId, startTimeBytes) - queryRecieved := false - for { _, msg, err := ws.ReadMessage() if err != nil { @@ -104,75 +101,65 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even break } - if !queryRecieved { - if !isTapper && !isQuerySet { - queryRecieved = true - query := string(msg) - err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query) - if err != nil { - toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{ - Type: "error", - AutoClose: 5000, - Text: fmt.Sprintf("Syntax error: %s", err.Error()), - }) - SendToSocket(socketId, toastBytes) - break - } - - isQuerySet = true - - handleDataChannel := func(c *basenine.Connection, data chan []byte) { - for { - bytes := <-data - - if string(bytes) == basenine.CloseChannel { - return - } - - var dataMap map[string]interface{} - err = json.Unmarshal(bytes, &dataMap) - - base := dataMap["base"].(map[string]interface{}) - base["id"] = uint(dataMap["id"].(float64)) - - baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(base) - SendToSocket(socketId, baseEntryBytes) - } - } - - handleMetaChannel := func(c *basenine.Connection, meta chan []byte) { - for { - bytes := <-meta - - if string(bytes) == basenine.CloseChannel { - return - } - - var metadata *basenine.Metadata - err = json.Unmarshal(bytes, &metadata) - if err != nil { - logger.Log.Debugf("Error recieving metadata: %v", err.Error()) - } - - metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata) - SendToSocket(socketId, metadataBytes) - } - } - - go handleDataChannel(connection, data) - go handleMetaChannel(connection, meta) - - connection.Query(query, data, meta) - } else { - eventHandlers.WebSocketMessage(socketId, msg) - } - } else { - id, err := strconv.Atoi(string(msg)) + if !isTapper && !isQuerySet { + query := string(msg) + err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query) if err != nil { - continue + toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{ + Type: "error", + AutoClose: 5000, + Text: fmt.Sprintf("Syntax error: %s", err.Error()), + }) + SendToSocket(socketId, toastBytes) + break } - focusEntryBytes, _ := models.CreateWebsocketFocusEntry(id) - SendToSocket(socketId, focusEntryBytes) + + isQuerySet = true + + handleDataChannel := func(c *basenine.Connection, data chan []byte) { + for { + bytes := <-data + + if string(bytes) == basenine.CloseChannel { + return + } + + var dataMap map[string]interface{} + err = json.Unmarshal(bytes, &dataMap) + + base := dataMap["base"].(map[string]interface{}) + base["id"] = uint(dataMap["id"].(float64)) + + baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(base) + SendToSocket(socketId, baseEntryBytes) + } + } + + handleMetaChannel := func(c *basenine.Connection, meta chan []byte) { + for { + bytes := <-meta + + if string(bytes) == basenine.CloseChannel { + return + } + + var metadata *basenine.Metadata + err = json.Unmarshal(bytes, &metadata) + if err != nil { + logger.Log.Debugf("Error recieving metadata: %v", err.Error()) + } + + metadataBytes, _ := models.CreateWebsocketQueryMetadataMessage(metadata) + SendToSocket(socketId, metadataBytes) + } + } + + go handleDataChannel(connection, data) + go handleMetaChannel(connection, meta) + + connection.Query(query, data, meta) + } else { + eventHandlers.WebSocketMessage(socketId, msg) } } } diff --git a/agent/pkg/models/models.go b/agent/pkg/models/models.go index 9094ae5b1..84f8e9842 100644 --- a/agent/pkg/models/models.go +++ b/agent/pkg/models/models.go @@ -70,11 +70,6 @@ type WebSocketStartTimeMessage struct { Data int64 `json:"data"` } -type WebSocketFocusEntryMessage struct { - *shared.WebSocketMessageMetadata - Id int `json:"id"` -} - func CreateBaseEntryWebSocketMessage(base map[string]interface{}) ([]byte, error) { message := &WebSocketEntryMessage{ WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{ @@ -135,16 +130,6 @@ func CreateWebsocketStartTimeMessage(base int64) ([]byte, error) { return json.Marshal(message) } -func CreateWebsocketFocusEntry(id int) ([]byte, error) { - message := &WebSocketFocusEntryMessage{ - WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{ - MessageType: shared.WebSocketMessageFocusEntry, - }, - Id: id, - } - return json.Marshal(message) -} - // ExtendedHAR is the top level object of a HAR log. type ExtendedHAR struct { Log *ExtendedLog `json:"log"` diff --git a/debug.Dockerfile b/debug.Dockerfile index 9673a4048..db1827b5d 100644 --- a/debug.Dockerfile +++ b/debug.Dockerfile @@ -37,8 +37,8 @@ COPY agent . RUN go build -gcflags="all=-N -l" -o mizuagent . # Download Basenine executable, verify the sha1sum and move it to a directory in $PATH -ADD https://github.com/up9inc/basenine/releases/download/v0.2.13/basenine_linux_amd64 ./basenine_linux_amd64 -ADD https://github.com/up9inc/basenine/releases/download/v0.2.13/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256 +ADD https://github.com/up9inc/basenine/releases/download/v0.2.16/basenine_linux_amd64 ./basenine_linux_amd64 +ADD https://github.com/up9inc/basenine/releases/download/v0.2.16/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256 RUN shasum -a 256 -c basenine_linux_amd64.sha256 RUN chmod +x ./basenine_linux_amd64 diff --git a/shared/models.go b/shared/models.go index 59ba6c085..1d3893274 100644 --- a/shared/models.go +++ b/shared/models.go @@ -1,12 +1,11 @@ package shared import ( - "io/ioutil" - "strings" - "github.com/op/go-logging" "github.com/up9inc/mizu/shared/logger" "github.com/up9inc/mizu/tap/api" + "io/ioutil" + "strings" "gopkg.in/yaml.v3" ) @@ -22,7 +21,6 @@ const ( WebSocketMessageTypeToast WebSocketMessageType = "toast" WebSocketMessageTypeQueryMetadata WebSocketMessageType = "queryMetadata" WebSocketMessageTypeStartTime WebSocketMessageType = "startTime" - WebSocketMessageFocusEntry WebSocketMessageType = "focusEntry" ) type Resources struct { diff --git a/ui/package-lock.json b/ui/package-lock.json index c0d6ddd5a..f653eadce 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -13644,9 +13644,9 @@ } }, "react-scrollable-feed-virtualized": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/react-scrollable-feed-virtualized/-/react-scrollable-feed-virtualized-1.4.8.tgz", - "integrity": "sha512-zsSO/9QB+4V6HEk39lxeMEUA6JFSZjfV4stw7RF17+vZdlVhyATsTBCzsj8hZywY4F29cBfH+3/GKrMhwmhAsw==" + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/react-scrollable-feed-virtualized/-/react-scrollable-feed-virtualized-1.4.9.tgz", + "integrity": "sha512-YkFkPjdIXDUsaCNYhZ+Blpp3LF+CsJWscwn/0fGSjF5QBKCtPURO9AEUA362Qnjr4S8LF2IjSAOCCFedIEnVNw==" }, "react-syntax-highlighter": { "version": "15.4.3", diff --git a/ui/package.json b/ui/package.json index 68dc98301..c042966e1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -23,7 +23,7 @@ "react-copy-to-clipboard": "^5.0.3", "react-dom": "^17.0.2", "react-scripts": "4.0.3", - "react-scrollable-feed-virtualized": "^1.4.8", + "react-scrollable-feed-virtualized": "^1.4.9", "react-syntax-highlighter": "^15.4.3", "react-toastify": "^8.0.3", "typescript": "^4.2.4", diff --git a/ui/src/components/EntriesList.tsx b/ui/src/components/EntriesList.tsx index 081c1966f..e6bfab092 100644 --- a/ui/src/components/EntriesList.tsx +++ b/ui/src/components/EntriesList.tsx @@ -1,33 +1,132 @@ -import React, {useRef} from "react"; +import React, {useCallback, useEffect, useMemo, useRef, useState} from "react"; import styles from './style/EntriesList.module.sass'; import ScrollableFeedVirtualized from "react-scrollable-feed-virtualized"; +import {EntryItem} from "./EntryListItem/EntryListItem"; import down from "./assets/downImg.svg"; +import spinner from './assets/spinner.svg'; +import Api from "../helpers/api"; interface EntriesListProps { entries: any[]; + setEntries: any; + query: string; listEntryREF: any; onSnapBrokenEvent: () => void; isSnappedToBottom: boolean; setIsSnappedToBottom: any; queriedCurrent: number; + setQueriedCurrent: any; queriedTotal: number; startTime: number; + noMoreDataTop: boolean; + setNoMoreDataTop: (flag: boolean) => void; + focusedEntryId: string; + setFocusedEntryId: (id: string) => void; + updateQuery: any; + leftOffTop: number; + setLeftOffTop: (leftOffTop: number) => void; + reconnectWebSocket: any; + isWebSocketConnectionClosed: boolean; + closeWebSocket: any; } -export const EntriesList: React.FC = ({entries, listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, queriedTotal, startTime}) => { +const api = new Api(); +export const EntriesList: React.FC = ({entries, setEntries, query, listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, setQueriedCurrent, queriedTotal, startTime, noMoreDataTop, setNoMoreDataTop, focusedEntryId, setFocusedEntryId, updateQuery, leftOffTop, setLeftOffTop, reconnectWebSocket, isWebSocketConnectionClosed, closeWebSocket}) => { + const [loadMoreTop, setLoadMoreTop] = useState(false); + const [isLoadingTop, setIsLoadingTop] = useState(false); const scrollableRef = useRef(null); + useEffect(() => { + 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(() => { + return entries; + },[entries]); + + const getOldEntries = useCallback(async () => { + setLoadMoreTop(false); + if (leftOffTop === null || leftOffTop <= 0) { + return; + } + setIsLoadingTop(true); + const data = await api.fetchEntries(leftOffTop, -1, query, 100, 3000); + if (!data || !data.meta) { + setNoMoreDataTop(true); + setIsLoadingTop(false); + return; + } + setLeftOffTop(data.meta.leftOff); + + let scrollTo: boolean; + if (data.meta.leftOff === 0) { + setNoMoreDataTop(true); + scrollTo = false; + } else { + scrollTo = true; + } + setIsLoadingTop(false); + + const newEntries = [...data.data.reverse(), ...entries]; + setEntries(newEntries); + + setQueriedCurrent(queriedCurrent + data.meta.current); + + if (scrollTo) { + scrollableRef.current.scrollToIndex(data.data.length - 1); + } + },[setLoadMoreTop, setIsLoadingTop, entries, setEntries, query, setNoMoreDataTop, leftOffTop, setLeftOffTop, queriedCurrent, setQueriedCurrent]); + + useEffect(() => { + if(!isWebSocketConnectionClosed || !loadMoreTop || noMoreDataTop) return; + getOldEntries(); + }, [loadMoreTop, noMoreDataTop, getOldEntries, isWebSocketConnectionClosed]); + + const scrollbarVisible = scrollableRef.current?.childWrapperRef.current.clientHeight > scrollableRef.current?.wrapperRef.current.clientHeight; + return <>
+ {isLoadingTop &&
+ spinner +
} + {noMoreDataTop &&
No more data available
} - {false /* TODO: why there is a need for something here (not necessarily false)? */} - {entries} + {false /* It's because the first child is ignored by ScrollableFeedVirtualized */} + {memoizedEntries.map(entry => )} +
-
Displaying {entries?.length} results (queried {queriedCurrent}/{queriedTotal})
+
Displaying {entries?.length} results out of {queriedTotal} total
{startTime !== 0 &&
Started listening at {new Date(startTime).toLocaleString()}
}
diff --git a/ui/src/components/EntryDetailed.tsx b/ui/src/components/EntryDetailed.tsx index 9a331a6ba..72f8ba5c7 100644 --- a/ui/src/components/EntryDetailed.tsx +++ b/ui/src/components/EntryDetailed.tsx @@ -73,7 +73,7 @@ const EntrySummary: React.FC = ({data, updateQuery}) => { const entry = data.base; return = ({entry, focusedEntryId, setFocus return <>
{ diff --git a/ui/src/components/Filters.tsx b/ui/src/components/Filters.tsx index 9168b3472..fba4a0565 100644 --- a/ui/src/components/Filters.tsx +++ b/ui/src/components/Filters.tsx @@ -13,7 +13,7 @@ interface FiltersProps { setQuery: any backgroundColor: string ws: any - openWebSocket: (query: string, resetEntriesBuffer: boolean) => void; + openWebSocket: (query: string, resetEntries: boolean) => void; } export const Filters: React.FC = ({query, setQuery, backgroundColor, ws, openWebSocket}) => { @@ -33,7 +33,7 @@ interface QueryFormProps { setQuery: any backgroundColor: string ws: any - openWebSocket: (query: string, resetEntriesBuffer: boolean) => void; + openWebSocket: (query: string, resetEntries: boolean) => void; } const style = { @@ -64,7 +64,11 @@ export const QueryForm: React.FC = ({query, setQuery, background const handleSubmit = (e) => { ws.close(); - openWebSocket(query, true); + if (query) { + openWebSocket(`(${query}) and leftOff(-1)`, true); + } else { + openWebSocket(`leftOff(-1)`, true); + } e.preventDefault(); } diff --git a/ui/src/components/TrafficPage.tsx b/ui/src/components/TrafficPage.tsx index 0887ef0e0..76fe8fd47 100644 --- a/ui/src/components/TrafficPage.tsx +++ b/ui/src/components/TrafficPage.tsx @@ -1,7 +1,6 @@ import React, {useEffect, useRef, useState} from "react"; import {Filters} from "./Filters"; import {EntriesList} from "./EntriesList"; -import {EntryItem} from "./EntryListItem/EntryListItem"; import {makeStyles} from "@material-ui/core"; import "./style/TrafficPage.sass"; import styles from './style/EntriesList.module.sass'; @@ -51,11 +50,12 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS const classes = useLayoutStyles(); const [entries, setEntries] = useState([] as any); - const [entriesBuffer, setEntriesBuffer] = useState([] as any); const [focusedEntryId, setFocusedEntryId] = useState(null); const [selectedEntryData, setSelectedEntryData] = useState(null); const [connection, setConnection] = useState(ConnectionStatus.Closed); + const [noMoreDataTop, setNoMoreDataTop] = useState(false); + const [tappingStatus, setTappingStatus] = useState(null); const [isSnappedToBottom, setIsSnappedToBottom] = useState(true); @@ -66,7 +66,8 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS const [queriedCurrent, setQueriedCurrent] = useState(0); const [queriedTotal, setQueriedTotal] = useState(0); - const [leftOff, setLeftOff] = useState(0); + const [leftOffBottom, setLeftOffBottom] = useState(0); + const [leftOffTop, setLeftOffTop] = useState(null); const [startTime, setStartTime] = useState(0); @@ -101,13 +102,13 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS const listEntry = useRef(null); - const openWebSocket = (query: string, resetEntriesBuffer: boolean) => { - if (resetEntriesBuffer) { + const openWebSocket = (query: string, resetEntries: boolean) => { + if (resetEntries) { setFocusedEntryId(null); setEntries([]); - setEntriesBuffer([]); - } else { - setEntriesBuffer(entries); + setQueriedCurrent(0); + setLeftOffTop(null); + setNoMoreDataTop(false); } ws.current = new WebSocket(MizuWebsocketURL); ws.current.onopen = () => { @@ -120,9 +121,9 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS ws.current.onerror = (event) => { console.error("WebSocket error:", event); if (query) { - openWebSocket(`(${query}) and leftOff(${leftOff})`, false); + openWebSocket(`(${query}) and leftOff(${leftOffBottom})`, false); } else { - openWebSocket(`leftOff(${leftOff})`, false); + openWebSocket(`leftOff(${leftOffBottom})`, false); } } } @@ -134,23 +135,14 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS switch (message.messageType) { case "entry": const entry = message.data; - var focusThis = false; - if (!focusedEntryId) { - focusThis = true; - setFocusedEntryId(entry.id.toString()); + if (!focusedEntryId) setFocusedEntryId(entry.id.toString()) + const newEntries = [...entries, entry]; + if (newEntries.length === 10001) { + setLeftOffTop(newEntries[0].entry.id); + newEntries.shift(); + setNoMoreDataTop(false); } - setEntriesBuffer([ - ...entriesBuffer, - - ]); + setEntries(newEntries); break case "status": setTappingStatus(message.tappingStatus); @@ -174,24 +166,16 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS }); break; case "queryMetadata": - setQueriedCurrent(message.data.current); + setQueriedCurrent(queriedCurrent + message.data.current); setQueriedTotal(message.data.total); - setLeftOff(message.data.leftOff); - setEntries(entriesBuffer); + setLeftOffBottom(message.data.leftOff); + if (leftOffTop === null) { + setLeftOffTop(message.data.leftOff - 1); + } break; case "startTime": setStartTime(message.data); break; - case "focusEntry": - // To achieve selecting only one entry, render all elements in the buffer - // with the current `focusedEntryId` value. - entriesBuffer.forEach((entry: any, i: number) => { - entriesBuffer[i] = React.cloneElement(entry, { - focusedEntryId: focusedEntryId - }); - }) - setEntries(entriesBuffer); - break; default: console.error(`unsupported websocket message type, Got: ${message.messageType}`) } @@ -217,11 +201,6 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS useEffect(() => { if (!focusedEntryId) return; setSelectedEntryData(null); - - if (ws.current.readyState === WebSocket.OPEN) { - ws.current.send(focusedEntryId); - } - (async () => { try { const entryData = await api.getEntry(focusedEntryId); @@ -241,18 +220,27 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS } console.error(error); } - })() - }, [focusedEntryId]) + })(); + // eslint-disable-next-line + }, [focusedEntryId]); + + const closeWebSocket = () => { + ws.current.close(); + } + + const reconnectWebSocket = () => { + if (query) { + openWebSocket(`(${query}) and leftOff(${leftOffBottom})`, false); + } else { + openWebSocket(`leftOff(${leftOffBottom})`, false); + } + } const toggleConnection = () => { if (connection === ConnectionStatus.Connected) { - ws.current.close(); + closeWebSocket(); } else { - if (query) { - openWebSocket(`(${query}) and leftOff(${leftOff})`, false); - } else { - openWebSocket(`leftOff(${leftOff})`, false); - } + reconnectWebSocket(); } } @@ -276,7 +264,10 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS } const onSnapBrokenEvent = () => { - setIsSnappedToBottom(false) + setIsSnappedToBottom(false); + if (connection === ConnectionStatus.Connected) { + closeWebSocket(); + } } return ( @@ -305,13 +296,26 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS
diff --git a/ui/src/components/style/EntriesList.module.sass b/ui/src/components/style/EntriesList.module.sass index 9d83ac5bb..5295dd656 100644 --- a/ui/src/components/style/EntriesList.module.sass +++ b/ui/src/components/style/EntriesList.module.sass @@ -38,6 +38,31 @@ border: 1px solid #627ef7 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: $secondary-font-color + +.btnOld + position: absolute + top: 20px + right: 10px + background: #205CF5 + border-radius: 50% + height: 35px + width: 35px + border: none + cursor: pointer + z-index: 1 + img + height: 10px + transform: scaleY(-1) + .btnLive position: absolute bottom: 10px diff --git a/ui/src/helpers/api.js b/ui/src/helpers/api.js index 164eabce8..ef7404996 100644 --- a/ui/src/helpers/api.js +++ b/ui/src/helpers/api.js @@ -38,6 +38,14 @@ export default class Api { return response.data; } + fetchEntries = async (leftOff, direction, query, limit, timeoutMs) => { + const response = await this.client.get(`/entries/?leftOff=${leftOff}&direction=${direction}&query=${query}&limit=${limit}&timeoutMs=${timeoutMs}`).catch(function (thrown) { + console.error(thrown.message); + return {}; + }); + return response.data; + } + getRecentTLSLinks = async () => { const response = await this.client.get("/status/recentTLSLinks"); return response.data;