mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-07-03 03:19:40 +00:00
Fix the selected entry behavior by propagating the focusedEntryId
through WebSocket (before #452) TRA-3983 (#513)
* Revert the select entry behavior into its original state RACING! (before #452) [TRA-3983 alternative 3] * Remove the remaining `forceSelect`(s) * Add a missing `focusedEntryId` prop * Fix the race condition * Propagate the `focusedEntryId` through WebSocket to prevent racing
This commit is contained in:
parent
9696ad9bad
commit
873f252544
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"mizuserver/pkg/models"
|
"mizuserver/pkg/models"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -94,6 +95,8 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
|||||||
startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(startTime)
|
startTimeBytes, _ := models.CreateWebsocketStartTimeMessage(startTime)
|
||||||
SendToSocket(socketId, startTimeBytes)
|
SendToSocket(socketId, startTimeBytes)
|
||||||
|
|
||||||
|
queryRecieved := false
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, msg, err := ws.ReadMessage()
|
_, msg, err := ws.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -101,65 +104,75 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isTapper && !isQuerySet {
|
if !queryRecieved {
|
||||||
query := string(msg)
|
if !isTapper && !isQuerySet {
|
||||||
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
|
queryRecieved = true
|
||||||
if err != nil {
|
query := string(msg)
|
||||||
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
|
err = basenine.Validate(shared.BasenineHost, shared.BaseninePort, query)
|
||||||
Type: "error",
|
if err != nil {
|
||||||
AutoClose: 5000,
|
toastBytes, _ := models.CreateWebsocketToastMessage(&models.ToastMessage{
|
||||||
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
|
Type: "error",
|
||||||
})
|
AutoClose: 5000,
|
||||||
SendToSocket(socketId, toastBytes)
|
Text: fmt.Sprintf("Syntax error: %s", err.Error()),
|
||||||
break
|
})
|
||||||
}
|
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) {
|
isQuerySet = true
|
||||||
for {
|
|
||||||
bytes := <-meta
|
|
||||||
|
|
||||||
if string(bytes) == basenine.CloseChannel {
|
handleDataChannel := func(c *basenine.Connection, data chan []byte) {
|
||||||
return
|
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 {
|
} else {
|
||||||
eventHandlers.WebSocketMessage(socketId, msg)
|
id, err := strconv.Atoi(string(msg))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
focusEntryBytes, _ := models.CreateWebsocketFocusEntry(id)
|
||||||
|
SendToSocket(socketId, focusEntryBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,11 @@ type WebSocketStartTimeMessage struct {
|
|||||||
Data int64 `json:"data"`
|
Data int64 `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebSocketFocusEntryMessage struct {
|
||||||
|
*shared.WebSocketMessageMetadata
|
||||||
|
Id int `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
func CreateBaseEntryWebSocketMessage(base map[string]interface{}) ([]byte, error) {
|
func CreateBaseEntryWebSocketMessage(base map[string]interface{}) ([]byte, error) {
|
||||||
message := &WebSocketEntryMessage{
|
message := &WebSocketEntryMessage{
|
||||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||||
@ -117,6 +122,16 @@ func CreateWebsocketStartTimeMessage(base int64) ([]byte, error) {
|
|||||||
return json.Marshal(message)
|
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.
|
// ExtendedHAR is the top level object of a HAR log.
|
||||||
type ExtendedHAR struct {
|
type ExtendedHAR struct {
|
||||||
Log *ExtendedLog `json:"log"`
|
Log *ExtendedLog `json:"log"`
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package shared
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
"github.com/up9inc/mizu/shared/logger"
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
"github.com/up9inc/mizu/tap/api"
|
"github.com/up9inc/mizu/tap/api"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@ -21,6 +22,7 @@ const (
|
|||||||
WebSocketMessageTypeToast WebSocketMessageType = "toast"
|
WebSocketMessageTypeToast WebSocketMessageType = "toast"
|
||||||
WebSocketMessageTypeQueryMetadata WebSocketMessageType = "queryMetadata"
|
WebSocketMessageTypeQueryMetadata WebSocketMessageType = "queryMetadata"
|
||||||
WebSocketMessageTypeStartTime WebSocketMessageType = "startTime"
|
WebSocketMessageTypeStartTime WebSocketMessageType = "startTime"
|
||||||
|
WebSocketMessageFocusEntry WebSocketMessageType = "focusEntry"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resources struct {
|
type Resources struct {
|
||||||
|
@ -69,10 +69,10 @@ const EntrySummary: React.FC<any> = ({data, updateQuery}) => {
|
|||||||
return <EntryItem
|
return <EntryItem
|
||||||
key={entry.id}
|
key={entry.id}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
|
focusedEntryId={null}
|
||||||
setFocusedEntryId={null}
|
setFocusedEntryId={null}
|
||||||
style={{}}
|
style={{}}
|
||||||
updateQuery={updateQuery}
|
updateQuery={updateQuery}
|
||||||
forceSelect={false}
|
|
||||||
headingMode={true}
|
headingMode={true}
|
||||||
/>;
|
/>;
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, {useState} from "react";
|
import React from "react";
|
||||||
import styles from './EntryListItem.module.sass';
|
import styles from './EntryListItem.module.sass';
|
||||||
import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode";
|
import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode";
|
||||||
import Protocol, {ProtocolInterface} from "../UI/Protocol"
|
import Protocol, {ProtocolInterface} from "../UI/Protocol"
|
||||||
@ -37,16 +37,16 @@ interface Rules {
|
|||||||
|
|
||||||
interface EntryProps {
|
interface EntryProps {
|
||||||
entry: Entry;
|
entry: Entry;
|
||||||
|
focusedEntryId: string;
|
||||||
setFocusedEntryId: (id: string) => void;
|
setFocusedEntryId: (id: string) => void;
|
||||||
style: object;
|
style: object;
|
||||||
updateQuery: any;
|
updateQuery: any;
|
||||||
forceSelect: boolean;
|
|
||||||
headingMode: boolean;
|
headingMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, style, updateQuery, forceSelect, headingMode}) => {
|
export const EntryItem: React.FC<EntryProps> = ({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 classification = getClassification(entry.statusCode)
|
||||||
const numberOfRules = entry.rules.numberOfRules
|
const numberOfRules = entry.rules.numberOfRules
|
||||||
@ -125,7 +125,6 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, style
|
|||||||
${isSelected && !rule && !contractEnabled ? styles.rowSelected : additionalRulesProperties}`}
|
${isSelected && !rule && !contractEnabled ? styles.rowSelected : additionalRulesProperties}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!setFocusedEntryId) return;
|
if (!setFocusedEntryId) return;
|
||||||
setIsSelected(!isSelected);
|
|
||||||
setFocusedEntryId(entry.id.toString());
|
setFocusedEntryId(entry.id.toString());
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
@ -119,20 +119,20 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
|||||||
switch (message.messageType) {
|
switch (message.messageType) {
|
||||||
case "entry":
|
case "entry":
|
||||||
const entry = message.data;
|
const entry = message.data;
|
||||||
var forceSelect = false;
|
var focusThis = false;
|
||||||
if (!focusedEntryId) {
|
if (!focusedEntryId) {
|
||||||
|
focusThis = true;
|
||||||
setFocusedEntryId(entry.id.toString());
|
setFocusedEntryId(entry.id.toString());
|
||||||
forceSelect = true;
|
|
||||||
}
|
}
|
||||||
setEntriesBuffer([
|
setEntriesBuffer([
|
||||||
...entriesBuffer,
|
...entriesBuffer,
|
||||||
<EntryItem
|
<EntryItem
|
||||||
key={entry.id}
|
key={entry.id}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
|
focusedEntryId={focusThis ? entry.id.toString() : focusedEntryId}
|
||||||
setFocusedEntryId={setFocusedEntryId}
|
setFocusedEntryId={setFocusedEntryId}
|
||||||
style={{}}
|
style={{}}
|
||||||
updateQuery={updateQuery}
|
updateQuery={updateQuery}
|
||||||
forceSelect={forceSelect}
|
|
||||||
headingMode={false}
|
headingMode={false}
|
||||||
/>
|
/>
|
||||||
]);
|
]);
|
||||||
@ -166,6 +166,16 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
|||||||
case "startTime":
|
case "startTime":
|
||||||
setStartTime(message.data);
|
setStartTime(message.data);
|
||||||
break;
|
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:
|
default:
|
||||||
console.error(`unsupported websocket message type, Got: ${message.messageType}`)
|
console.error(`unsupported websocket message type, Got: ${message.messageType}`)
|
||||||
}
|
}
|
||||||
@ -191,6 +201,11 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!focusedEntryId) return;
|
if (!focusedEntryId) return;
|
||||||
setSelectedEntryData(null);
|
setSelectedEntryData(null);
|
||||||
|
|
||||||
|
if (ws.current.readyState === WebSocket.OPEN) {
|
||||||
|
ws.current.send(focusedEntryId);
|
||||||
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const entryData = await api.getEntry(focusedEntryId);
|
const entryData = await api.getEntry(focusedEntryId);
|
||||||
|
Loading…
Reference in New Issue
Block a user