diff --git a/agent/pkg/api/socket_routes.go b/agent/pkg/api/socket_routes.go index 8feccebac..b82c7c338 100644 --- a/agent/pkg/api/socket_routes.go +++ b/agent/pkg/api/socket_routes.go @@ -6,6 +6,7 @@ import ( "fmt" "mizuserver/pkg/models" "net/http" + "strconv" "sync" "time" @@ -94,6 +95,8 @@ 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 { @@ -101,65 +104,75 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even break } - if !isTapper && !isQuerySet { - 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) + 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 } - } - handleMetaChannel := func(c *basenine.Connection, meta chan []byte) { - for { - bytes := <-meta + isQuerySet = true - if string(bytes) == basenine.CloseChannel { - return + 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) } - - 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) } + + 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) } - - go handleDataChannel(connection, data) - go handleMetaChannel(connection, meta) - - connection.Query(query, data, meta) } else { - eventHandlers.WebSocketMessage(socketId, msg) + id, err := strconv.Atoi(string(msg)) + if err != nil { + continue + } + focusEntryBytes, _ := models.CreateWebsocketFocusEntry(id) + SendToSocket(socketId, focusEntryBytes) } } } diff --git a/agent/pkg/models/models.go b/agent/pkg/models/models.go index 6add39eeb..17113180e 100644 --- a/agent/pkg/models/models.go +++ b/agent/pkg/models/models.go @@ -57,6 +57,11 @@ 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{ @@ -117,6 +122,16 @@ 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/shared/models.go b/shared/models.go index 1d3893274..59ba6c085 100644 --- a/shared/models.go +++ b/shared/models.go @@ -1,11 +1,12 @@ 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" ) @@ -21,6 +22,7 @@ const ( WebSocketMessageTypeToast WebSocketMessageType = "toast" WebSocketMessageTypeQueryMetadata WebSocketMessageType = "queryMetadata" WebSocketMessageTypeStartTime WebSocketMessageType = "startTime" + WebSocketMessageFocusEntry WebSocketMessageType = "focusEntry" ) type Resources struct { diff --git a/ui/src/components/EntryDetailed.tsx b/ui/src/components/EntryDetailed.tsx index 47dab4883..f8854a293 100644 --- a/ui/src/components/EntryDetailed.tsx +++ b/ui/src/components/EntryDetailed.tsx @@ -69,10 +69,10 @@ const EntrySummary: React.FC = ({data, updateQuery}) => { return ; }; diff --git a/ui/src/components/EntryListItem/EntryListItem.tsx b/ui/src/components/EntryListItem/EntryListItem.tsx index b27299221..4461b4f70 100644 --- a/ui/src/components/EntryListItem/EntryListItem.tsx +++ b/ui/src/components/EntryListItem/EntryListItem.tsx @@ -1,4 +1,4 @@ -import React, {useState} from "react"; +import React from "react"; import styles from './EntryListItem.module.sass'; import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode"; import Protocol, {ProtocolInterface} from "../UI/Protocol" @@ -37,16 +37,16 @@ interface Rules { interface EntryProps { entry: Entry; + focusedEntryId: string; setFocusedEntryId: (id: string) => void; style: object; updateQuery: any; - forceSelect: boolean; headingMode: boolean; } -export const EntryItem: React.FC = ({entry, setFocusedEntryId, style, updateQuery, forceSelect, headingMode}) => { +export const EntryItem: React.FC = ({entry, focusedEntryId, setFocusedEntryId, style, updateQuery, headingMode}) => { - const [isSelected, setIsSelected] = useState(!forceSelect ? false : true); + const isSelected = focusedEntryId === entry.id.toString(); const classification = getClassification(entry.statusCode) const numberOfRules = entry.rules.numberOfRules @@ -125,7 +125,6 @@ export const EntryItem: React.FC = ({entry, setFocusedEntryId, style ${isSelected && !rule && !contractEnabled ? styles.rowSelected : additionalRulesProperties}`} onClick={() => { if (!setFocusedEntryId) return; - setIsSelected(!isSelected); setFocusedEntryId(entry.id.toString()); }} style={{ diff --git a/ui/src/components/TrafficPage.tsx b/ui/src/components/TrafficPage.tsx index b530dea5d..f741c39e3 100644 --- a/ui/src/components/TrafficPage.tsx +++ b/ui/src/components/TrafficPage.tsx @@ -119,20 +119,20 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS switch (message.messageType) { case "entry": const entry = message.data; - var forceSelect = false; + var focusThis = false; if (!focusedEntryId) { + focusThis = true; setFocusedEntryId(entry.id.toString()); - forceSelect = true; } setEntriesBuffer([ ...entriesBuffer, ]); @@ -166,6 +166,16 @@ export const TrafficPage: React.FC = ({setAnalyzeStatus, onTLS 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}`) } @@ -191,6 +201,11 @@ 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);