diff --git a/tap/extensions/amqp/Makefile b/tap/extensions/amqp/Makefile index 2f6ad538c..baacf1b93 100644 --- a/tap/extensions/amqp/Makefile +++ b/tap/extensions/amqp/Makefile @@ -13,4 +13,4 @@ test-pull-bin: test-pull-expect: @mkdir -p expect - @[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect15/amqp/\* expect + @[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect16/amqp/\* expect diff --git a/tap/extensions/amqp/go.mod b/tap/extensions/amqp/go.mod index f5622f518..3c153988e 100644 --- a/tap/extensions/amqp/go.mod +++ b/tap/extensions/amqp/go.mod @@ -4,16 +4,20 @@ go 1.17 require ( github.com/stretchr/testify v1.7.0 + github.com/up9inc/mizu/logger v0.0.0 github.com/up9inc/mizu/tap/api v0.0.0 ) require ( github.com/davecgh/go-spew v1.1.0 // indirect + github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/up9inc/mizu/tap/dbgctl v0.0.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) +replace github.com/up9inc/mizu/logger v0.0.0 => ../../../logger + replace github.com/up9inc/mizu/tap/api v0.0.0 => ../../api replace github.com/up9inc/mizu/tap/dbgctl v0.0.0 => ../../dbgctl diff --git a/tap/extensions/amqp/go.sum b/tap/extensions/amqp/go.sum index acb88a48f..4886f6219 100644 --- a/tap/extensions/amqp/go.sum +++ b/tap/extensions/amqp/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/tap/extensions/amqp/helpers.go b/tap/extensions/amqp/helpers.go index 59c62235c..a73cb1aba 100644 --- a/tap/extensions/amqp/helpers.go +++ b/tap/extensions/amqp/helpers.go @@ -5,8 +5,8 @@ import ( "fmt" "sort" "strconv" - "time" + "github.com/up9inc/mizu/logger" "github.com/up9inc/mizu/tap/api" ) @@ -25,14 +25,14 @@ var connectionMethodMap = map[int]string{ 61: "connection unblocked", } -// var channelMethodMap = map[int]string{ -// 10: "channel open", -// 11: "channel open-ok", -// 20: "channel flow", -// 21: "channel flow-ok", -// 40: "channel close", -// 41: "channel close-ok", -// } +var channelMethodMap = map[int]string{ + 10: "channel open", + 11: "channel open-ok", + 20: "channel flow", + 21: "channel flow-ok", + 40: "channel close", + 41: "channel close-ok", +} var exchangeMethodMap = map[int]string{ 10: "exchange declare", @@ -94,29 +94,41 @@ type AMQPWrapper struct { Details interface{} `json:"details"` } -func emitAMQP(event interface{}, _type string, method string, connectionInfo *api.ConnectionInfo, captureTime time.Time, captureSize int, emitter api.Emitter, capture api.Capture) { - request := &api.GenericMessage{ - IsRequest: true, - CaptureTime: captureTime, - Payload: AMQPPayload{ - Data: &AMQPWrapper{ - Method: method, - Url: "", - Details: event, - }, - }, +type emptyResponse struct { +} + +const emptyMethod = "empty" + +func getIdent(reader api.TcpReader, methodFrame *MethodFrame) (ident string) { + tcpID := reader.GetTcpID() + // To match methods to their Ok(s) + methodId := methodFrame.MethodId - methodFrame.MethodId%10 + + if reader.GetIsClient() { + ident = fmt.Sprintf( + "%s_%s_%s_%s_%d_%d_%d", + tcpID.SrcIP, + tcpID.DstIP, + tcpID.SrcPort, + tcpID.DstPort, + methodFrame.ChannelId, + methodFrame.ClassId, + methodId, + ) + } else { + ident = fmt.Sprintf( + "%s_%s_%s_%s_%d_%d_%d", + tcpID.DstIP, + tcpID.SrcIP, + tcpID.DstPort, + tcpID.SrcPort, + methodFrame.ChannelId, + methodFrame.ClassId, + methodId, + ) } - item := &api.OutputChannelItem{ - Protocol: protocol, - Capture: capture, - Timestamp: captureTime.UnixNano() / int64(time.Millisecond), - ConnectionInfo: connectionInfo, - Pair: &api.RequestResponsePair{ - Request: *request, - Response: api.GenericMessage{}, - }, - } - emitter.Emit(item) + + return } func representProperties(properties map[string]interface{}, rep []interface{}) ([]interface{}, string, string) { @@ -460,6 +472,36 @@ func representQueueDeclare(event map[string]interface{}) []interface{} { return rep } +func representQueueDeclareOk(event map[string]interface{}) []interface{} { + rep := make([]interface{}, 0) + + details, _ := json.Marshal([]api.TableData{ + { + Name: "Queue", + Value: event["queue"].(string), + Selector: `response.queue`, + }, + { + Name: "Message Count", + Value: fmt.Sprintf("%g", event["messageCount"].(float64)), + Selector: `response.messageCount`, + }, + { + Name: "Consumer Count", + Value: fmt.Sprintf("%g", event["consumerCount"].(float64)), + Selector: `response.consumerCount`, + }, + }) + + rep = append(rep, api.SectionData{ + Type: api.TABLE, + Title: "Details", + Data: string(details), + }) + + return rep +} + func representExchangeDeclare(event map[string]interface{}) []interface{} { rep := make([]interface{}, 0) @@ -571,7 +613,7 @@ func representConnectionStart(event map[string]interface{}) []interface{} { x, _ := json.Marshal(value) outcome = string(x) default: - panic("Unknown data type for the server property!") + logger.Log.Info("Unknown data type for the server property!") } headers = append(headers, api.TableData{ Name: name, @@ -593,6 +635,65 @@ func representConnectionStart(event map[string]interface{}) []interface{} { return rep } +func representConnectionStartOk(event map[string]interface{}) []interface{} { + rep := make([]interface{}, 0) + + details, _ := json.Marshal([]api.TableData{ + { + Name: "Mechanism", + Value: event["mechanism"].(string), + Selector: `response.mechanism`, + }, + { + Name: "Mechanism", + Value: event["mechanism"].(string), + Selector: `response.response`, + }, + { + Name: "Locale", + Value: event["locale"].(string), + Selector: `response.locale`, + }, + }) + rep = append(rep, api.SectionData{ + Type: api.TABLE, + Title: "Details", + Data: string(details), + }) + + if event["clientProperties"] != nil { + headers := make([]api.TableData, 0) + for name, value := range event["clientProperties"].(map[string]interface{}) { + var outcome string + switch v := value.(type) { + case string: + outcome = v + case map[string]interface{}: + x, _ := json.Marshal(value) + outcome = string(x) + default: + logger.Log.Info("Unknown data type for the client property!") + } + headers = append(headers, api.TableData{ + Name: name, + Value: outcome, + Selector: fmt.Sprintf(`response.clientProperties["%s"]`, name), + }) + } + sort.Slice(headers, func(i, j int) bool { + return headers[i].Name < headers[j].Name + }) + headersMarshaled, _ := json.Marshal(headers) + rep = append(rep, api.SectionData{ + Type: api.TABLE, + Title: "Client Properties", + Data: string(headersMarshaled), + }) + } + + return rep +} + func representConnectionClose(event map[string]interface{}) []interface{} { replyCode := "" @@ -750,3 +851,122 @@ func representBasicConsume(event map[string]interface{}) []interface{} { return rep } + +func representBasicConsumeOk(event map[string]interface{}) []interface{} { + rep := make([]interface{}, 0) + + details, _ := json.Marshal([]api.TableData{ + { + Name: "Consumer Tag", + Value: event["consumerTag"].(string), + Selector: `response.consumerTag`, + }, + }) + + rep = append(rep, api.SectionData{ + Type: api.TABLE, + Title: "Details", + Data: string(details), + }) + + return rep +} + +func representConnectionOpen(event map[string]interface{}) []interface{} { + rep := make([]interface{}, 0) + + details, _ := json.Marshal([]api.TableData{ + { + Name: "Virtual Host", + Value: event["virtualHost"].(string), + Selector: `request.virtualHost`, + }, + }) + rep = append(rep, api.SectionData{ + Type: api.TABLE, + Title: "Details", + Data: string(details), + }) + + return rep +} + +func representConnectionTune(event map[string]interface{}) []interface{} { + rep := make([]interface{}, 0) + + details, _ := json.Marshal([]api.TableData{ + { + Name: "Channel Max", + Value: fmt.Sprintf("%g", event["channelMax"].(float64)), + Selector: `request.channelMax`, + }, + { + Name: "Frame Max", + Value: fmt.Sprintf("%g", event["frameMax"].(float64)), + Selector: `request.frameMax`, + }, + { + Name: "Heartbeat", + Value: fmt.Sprintf("%g", event["heartbeat"].(float64)), + Selector: `request.heartbeat`, + }, + }) + rep = append(rep, api.SectionData{ + Type: api.TABLE, + Title: "Details", + Data: string(details), + }) + + return rep +} + +func representBasicCancel(event map[string]interface{}) []interface{} { + rep := make([]interface{}, 0) + + details, _ := json.Marshal([]api.TableData{ + { + Name: "Consumer Tag", + Value: event["consumerTag"].(string), + Selector: `response.consumerTag`, + }, + { + Name: "NoWait", + Value: strconv.FormatBool(event["noWait"].(bool)), + Selector: `request.noWait`, + }, + }) + + rep = append(rep, api.SectionData{ + Type: api.TABLE, + Title: "Details", + Data: string(details), + }) + + return rep +} + +func representBasicCancelOk(event map[string]interface{}) []interface{} { + rep := make([]interface{}, 0) + + details, _ := json.Marshal([]api.TableData{ + { + Name: "Consumer Tag", + Value: event["consumerTag"].(string), + Selector: `response.consumerTag`, + }, + }) + + rep = append(rep, api.SectionData{ + Type: api.TABLE, + Title: "Details", + Data: string(details), + }) + + return rep +} + +func representEmpty(event map[string]interface{}) []interface{} { + rep := make([]interface{}, 0) + + return rep +} diff --git a/tap/extensions/amqp/main.go b/tap/extensions/amqp/main.go index ffd8ad12e..4662c40d7 100644 --- a/tap/extensions/amqp/main.go +++ b/tap/extensions/amqp/main.go @@ -46,22 +46,12 @@ func (d dissecting) Ping() { log.Printf("pong %s", protocol.Name) } -const amqpRequest string = "amqp_request" - func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.TrafficFilteringOptions) error { r := AmqpReader{b} var remaining int var header *HeaderFrame - connectionInfo := &api.ConnectionInfo{ - ClientIP: reader.GetTcpID().SrcIP, - ClientPort: reader.GetTcpID().SrcPort, - ServerIP: reader.GetTcpID().DstIP, - ServerPort: reader.GetTcpID().DstPort, - IsOutgoing: true, - } - eventBasicPublish := &BasicPublish{ Exchange: "", RoutingKey: "", @@ -83,6 +73,10 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api. var lastMethodFrameMessage Message + var ident string + isClient := reader.GetIsClient() + reqResMatcher := reader.GetReqResMatcher().(*requestResponseMatcher) + for { frameVal, err := r.readFrame() if err == io.EOF { @@ -121,16 +115,22 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api. switch lastMethodFrameMessage.(type) { case *BasicPublish: eventBasicPublish.Body = f.Body - emitAMQP(*eventBasicPublish, amqpRequest, basicMethodMap[40], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin()) + reqResMatcher.emitEvent(isClient, ident, basicMethodMap[40], *eventBasicPublish, reader) + reqResMatcher.emitEvent(!isClient, ident, emptyMethod, &emptyResponse{}, reader) + case *BasicDeliver: eventBasicDeliver.Body = f.Body - emitAMQP(*eventBasicDeliver, amqpRequest, basicMethodMap[60], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin()) + reqResMatcher.emitEvent(!isClient, ident, basicMethodMap[60], *eventBasicDeliver, reader) + reqResMatcher.emitEvent(isClient, ident, emptyMethod, &emptyResponse{}, reader) } case *MethodFrame: reader.GetParent().SetProtocol(&protocol) lastMethodFrameMessage = f.Method + + ident = getIdent(reader, f) + switch m := f.Method.(type) { case *BasicPublish: eventBasicPublish.Exchange = m.Exchange @@ -146,7 +146,10 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api. NoWait: m.NoWait, Arguments: m.Arguments, } - emitAMQP(*eventQueueBind, amqpRequest, queueMethodMap[20], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin()) + reqResMatcher.emitEvent(isClient, ident, queueMethodMap[20], *eventQueueBind, reader) + + case *QueueBindOk: + reqResMatcher.emitEvent(isClient, ident, queueMethodMap[21], m, reader) case *BasicConsume: eventBasicConsume := &BasicConsume{ @@ -158,7 +161,10 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api. NoWait: m.NoWait, Arguments: m.Arguments, } - emitAMQP(*eventBasicConsume, amqpRequest, basicMethodMap[20], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin()) + reqResMatcher.emitEvent(isClient, ident, basicMethodMap[20], *eventBasicConsume, reader) + + case *BasicConsumeOk: + reqResMatcher.emitEvent(isClient, ident, basicMethodMap[21], m, reader) case *BasicDeliver: eventBasicDeliver.ConsumerTag = m.ConsumerTag @@ -177,7 +183,10 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api. NoWait: m.NoWait, Arguments: m.Arguments, } - emitAMQP(*eventQueueDeclare, amqpRequest, queueMethodMap[10], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin()) + reqResMatcher.emitEvent(isClient, ident, queueMethodMap[10], *eventQueueDeclare, reader) + + case *QueueDeclareOk: + reqResMatcher.emitEvent(isClient, ident, queueMethodMap[11], m, reader) case *ExchangeDeclare: eventExchangeDeclare := &ExchangeDeclare{ @@ -190,17 +199,19 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api. NoWait: m.NoWait, Arguments: m.Arguments, } - emitAMQP(*eventExchangeDeclare, amqpRequest, exchangeMethodMap[10], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin()) + reqResMatcher.emitEvent(isClient, ident, exchangeMethodMap[10], *eventExchangeDeclare, reader) + + case *ExchangeDeclareOk: + reqResMatcher.emitEvent(isClient, ident, exchangeMethodMap[11], m, reader) case *ConnectionStart: - eventConnectionStart := &ConnectionStart{ - VersionMajor: m.VersionMajor, - VersionMinor: m.VersionMinor, - ServerProperties: m.ServerProperties, - Mechanisms: m.Mechanisms, - Locales: m.Locales, - } - emitAMQP(*eventConnectionStart, amqpRequest, connectionMethodMap[10], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin()) + // In our tests, *ConnectionStart does not result in *ConnectionStartOk + reqResMatcher.emitEvent(!isClient, ident, connectionMethodMap[10], m, reader) + reqResMatcher.emitEvent(isClient, ident, emptyMethod, &emptyResponse{}, reader) + + case *ConnectionStartOk: + // In our tests, *ConnectionStart does not result in *ConnectionStartOk + reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[11], m, reader) case *ConnectionClose: eventConnectionClose := &ConnectionClose{ @@ -209,7 +220,40 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api. ClassId: m.ClassId, MethodId: m.MethodId, } - emitAMQP(*eventConnectionClose, amqpRequest, connectionMethodMap[50], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin()) + reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[50], *eventConnectionClose, reader) + + case *ConnectionCloseOk: + reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[51], m, reader) + + case *connectionOpen: + eventConnectionOpen := &connectionOpen{ + VirtualHost: m.VirtualHost, + } + reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[40], *eventConnectionOpen, reader) + + case *connectionOpenOk: + reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[41], m, reader) + + case *channelOpen: + reqResMatcher.emitEvent(isClient, ident, channelMethodMap[10], m, reader) + + case *channelOpenOk: + reqResMatcher.emitEvent(isClient, ident, channelMethodMap[11], m, reader) + + case *connectionTune: + // In our tests, *connectionTune does not result in *connectionTuneOk + reqResMatcher.emitEvent(!isClient, ident, connectionMethodMap[30], m, reader) + reqResMatcher.emitEvent(isClient, ident, emptyMethod, &emptyResponse{}, reader) + + case *connectionTuneOk: + // In our tests, *connectionTune does not result in *connectionTuneOk + reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[31], m, reader) + + case *basicCancel: + reqResMatcher.emitEvent(isClient, ident, basicMethodMap[30], m, reader) + + case *basicCancelOk: + reqResMatcher.emitEvent(isClient, ident, basicMethodMap[31], m, reader) } default: @@ -220,9 +264,17 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api. func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string, namespace string) *api.Entry { request := item.Pair.Request.Payload.(map[string]interface{}) + response := item.Pair.Response.Payload.(map[string]interface{}) reqDetails := request["details"].(map[string]interface{}) + resDetails := response["details"].(map[string]interface{}) + + elapsedTime := item.Pair.Response.CaptureTime.Sub(item.Pair.Request.CaptureTime).Round(time.Millisecond).Milliseconds() + if elapsedTime < 0 { + elapsedTime = 0 + } reqDetails["method"] = request["method"] + resDetails["method"] = response["method"] return &api.Entry{ Protocol: protocol.ProtocolSummary, Capture: item.Capture, @@ -236,13 +288,15 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, IP: item.ConnectionInfo.ServerIP, Port: item.ConnectionInfo.ServerPort, }, - Namespace: namespace, - Outgoing: item.ConnectionInfo.IsOutgoing, - Request: reqDetails, - RequestSize: item.Pair.Request.CaptureSize, - Timestamp: item.Timestamp, - StartTime: item.Pair.Request.CaptureTime, - ElapsedTime: 0, + Namespace: namespace, + Outgoing: item.ConnectionInfo.IsOutgoing, + Request: reqDetails, + Response: resDetails, + RequestSize: item.Pair.Request.CaptureSize, + ResponseSize: item.Pair.Response.CaptureSize, + Timestamp: item.Timestamp, + StartTime: item.Pair.Request.CaptureTime, + ElapsedTime: elapsedTime, } } @@ -283,6 +337,21 @@ func (d dissecting) Summarize(entry *api.Entry) *api.BaseEntry { case basicMethodMap[20]: summary = entry.Request["queue"].(string) summaryQuery = fmt.Sprintf(`request.queue == "%s"`, summary) + case connectionMethodMap[40]: + summary = entry.Request["virtualHost"].(string) + summaryQuery = fmt.Sprintf(`request.virtualHost == "%s"`, summary) + case connectionMethodMap[30]: + summary = fmt.Sprintf("%g", entry.Request["channelMax"].(float64)) + summaryQuery = fmt.Sprintf(`request.channelMax == "%s"`, summary) + case connectionMethodMap[31]: + summary = fmt.Sprintf("%g", entry.Request["channelMax"].(float64)) + summaryQuery = fmt.Sprintf(`request.channelMax == "%s"`, summary) + case basicMethodMap[30]: + summary = entry.Request["consumerTag"].(string) + summaryQuery = fmt.Sprintf(`request.consumerTag == "%s"`, summary) + case basicMethodMap[31]: + summary = entry.Request["consumerTag"].(string) + summaryQuery = fmt.Sprintf(`request.consumerTag == "%s"`, summary) } return &api.BaseEntry{ @@ -306,6 +375,8 @@ func (d dissecting) Summarize(entry *api.Entry) *api.BaseEntry { func (d dissecting) Represent(request map[string]interface{}, response map[string]interface{}) (object []byte, err error) { representation := make(map[string]interface{}) var repRequest []interface{} + var repResponse []interface{} + switch request["method"].(string) { case basicMethodMap[40]: repRequest = representBasicPublish(request) @@ -323,9 +394,45 @@ func (d dissecting) Represent(request map[string]interface{}, response map[strin repRequest = representQueueBind(request) case basicMethodMap[20]: repRequest = representBasicConsume(request) + case connectionMethodMap[40]: + repRequest = representConnectionOpen(request) + case channelMethodMap[10]: + repRequest = representEmpty(request) + case connectionMethodMap[30]: + repRequest = representConnectionTune(request) + case basicMethodMap[30]: + repRequest = representBasicCancel(request) } + + switch response["method"].(string) { + case queueMethodMap[11]: + repResponse = representQueueDeclareOk(response) + case exchangeMethodMap[11]: + repResponse = representEmpty(response) + case connectionMethodMap[11]: + repResponse = representConnectionStartOk(response) + case connectionMethodMap[51]: + repResponse = representEmpty(response) + case basicMethodMap[21]: + repResponse = representBasicConsumeOk(response) + case queueMethodMap[21]: + repResponse = representEmpty(response) + case connectionMethodMap[41]: + repResponse = representEmpty(response) + case channelMethodMap[11]: + repResponse = representEmpty(request) + case connectionMethodMap[31]: + repResponse = representConnectionTune(request) + case basicMethodMap[31]: + repResponse = representBasicCancelOk(request) + case emptyMethod: + repResponse = representEmpty(response) + } + representation["request"] = repRequest + representation["response"] = repResponse object, err = json.Marshal(representation) + return } @@ -336,7 +443,7 @@ func (d dissecting) Macros() map[string]string { } func (d dissecting) NewResponseRequestMatcher() api.RequestResponseMatcher { - return nil + return createResponseRequestMatcher() } var Dissector dissecting diff --git a/tap/extensions/amqp/matcher.go b/tap/extensions/amqp/matcher.go new file mode 100644 index 000000000..1b33fe1cf --- /dev/null +++ b/tap/extensions/amqp/matcher.go @@ -0,0 +1,113 @@ +package amqp + +import ( + "sync" + "time" + + "github.com/up9inc/mizu/tap/api" +) + +// Key is {client_addr}_{client_port}_{dest_addr}_{dest_port}_{channel_id}_{class_id}_{method_id} +type requestResponseMatcher struct { + openMessagesMap *sync.Map +} + +func createResponseRequestMatcher() api.RequestResponseMatcher { + return &requestResponseMatcher{openMessagesMap: &sync.Map{}} +} + +func (matcher *requestResponseMatcher) GetMap() *sync.Map { + return matcher.openMessagesMap +} + +func (matcher *requestResponseMatcher) SetMaxTry(value int) { +} + +func (matcher *requestResponseMatcher) emitEvent(isRequest bool, ident string, method string, event interface{}, reader api.TcpReader) { + reader.GetParent().SetProtocol(&protocol) + + var item *api.OutputChannelItem + if isRequest { + item = matcher.registerRequest(ident, method, event, reader.GetCaptureTime(), reader.GetReadProgress().Current()) + } else { + item = matcher.registerResponse(ident, method, event, reader.GetCaptureTime(), reader.GetReadProgress().Current()) + } + + if item != nil { + item.ConnectionInfo = &api.ConnectionInfo{ + ClientIP: reader.GetTcpID().SrcIP, + ClientPort: reader.GetTcpID().SrcPort, + ServerIP: reader.GetTcpID().DstIP, + ServerPort: reader.GetTcpID().DstPort, + IsOutgoing: true, + } + item.Capture = reader.GetParent().GetOrigin() + reader.GetEmitter().Emit(item) + } +} + +func (matcher *requestResponseMatcher) registerRequest(ident string, method string, request interface{}, captureTime time.Time, captureSize int) *api.OutputChannelItem { + requestAMQPMessage := api.GenericMessage{ + IsRequest: true, + CaptureTime: captureTime, + CaptureSize: captureSize, + Payload: AMQPPayload{ + Data: &AMQPWrapper{ + Method: method, + Url: "", + Details: request, + }, + }, + } + + if response, found := matcher.openMessagesMap.LoadAndDelete(ident); found { + // Type assertion always succeeds because all of the map's values are of api.GenericMessage type + responseAMQPMessage := response.(*api.GenericMessage) + if responseAMQPMessage.IsRequest { + return nil + } + return matcher.preparePair(&requestAMQPMessage, responseAMQPMessage) + } + + matcher.openMessagesMap.Store(ident, &requestAMQPMessage) + return nil +} + +func (matcher *requestResponseMatcher) registerResponse(ident string, method string, response interface{}, captureTime time.Time, captureSize int) *api.OutputChannelItem { + responseAMQPMessage := api.GenericMessage{ + IsRequest: false, + CaptureTime: captureTime, + CaptureSize: captureSize, + Payload: AMQPPayload{ + Data: &AMQPWrapper{ + Method: method, + Url: "", + Details: response, + }, + }, + } + + if request, found := matcher.openMessagesMap.LoadAndDelete(ident); found { + // Type assertion always succeeds because all of the map's values are of api.GenericMessage type + requestAMQPMessage := request.(*api.GenericMessage) + if !requestAMQPMessage.IsRequest { + return nil + } + return matcher.preparePair(requestAMQPMessage, &responseAMQPMessage) + } + + matcher.openMessagesMap.Store(ident, &responseAMQPMessage) + return nil +} + +func (matcher *requestResponseMatcher) preparePair(requestAMQPMessage *api.GenericMessage, responseAMQPMessage *api.GenericMessage) *api.OutputChannelItem { + return &api.OutputChannelItem{ + Protocol: protocol, + Timestamp: requestAMQPMessage.CaptureTime.UnixNano() / int64(time.Millisecond), + ConnectionInfo: nil, + Pair: &api.RequestResponsePair{ + Request: *requestAMQPMessage, + Response: *responseAMQPMessage, + }, + } +} diff --git a/tap/extensions/amqp/spec091.go b/tap/extensions/amqp/spec091.go index b708bfa8a..d8ddd5b3e 100644 --- a/tap/extensions/amqp/spec091.go +++ b/tap/extensions/amqp/spec091.go @@ -81,10 +81,10 @@ func (msg *ConnectionStart) read(r io.Reader) (err error) { } type ConnectionStartOk struct { - ClientProperties Table - Mechanism string - Response string - Locale string + ClientProperties Table `json:"clientProperties"` + Mechanism string `json:"mechanism"` + Response string `json:"response"` + Locale string `json:"locale"` } func (msg *ConnectionStartOk) read(r io.Reader) (err error) { @@ -135,9 +135,9 @@ func (msg *connectionSecureOk) read(r io.Reader) (err error) { } type connectionTune struct { - ChannelMax uint16 - FrameMax uint32 - Heartbeat uint16 + ChannelMax uint16 `json:"channelMax"` + FrameMax uint32 `json:"frameMax"` + Heartbeat uint16 `json:"heartbeat"` } func (msg *connectionTune) read(r io.Reader) (err error) { @@ -181,7 +181,7 @@ func (msg *connectionTuneOk) read(r io.Reader) (err error) { } type connectionOpen struct { - VirtualHost string + VirtualHost string `json:"virtualHost"` reserved1 string reserved2 bool } @@ -580,9 +580,9 @@ func (msg *QueueDeclare) read(r io.Reader) (err error) { } type QueueDeclareOk struct { - Queue string - MessageCount uint32 - ConsumerCount uint32 + Queue string `json:"queue"` + MessageCount uint32 `json:"messageCount"` + ConsumerCount uint32 `json:"consumerCount"` } func (msg *QueueDeclareOk) read(r io.Reader) (err error) { @@ -840,7 +840,7 @@ func (msg *BasicConsume) read(r io.Reader) (err error) { } type BasicConsumeOk struct { - ConsumerTag string + ConsumerTag string `json:"consumerTag"` } func (msg *BasicConsumeOk) read(r io.Reader) (err error) { @@ -853,8 +853,8 @@ func (msg *BasicConsumeOk) read(r io.Reader) (err error) { } type basicCancel struct { - ConsumerTag string - NoWait bool + ConsumerTag string `json:"consumerTag"` + NoWait bool `json:"noWait"` } func (msg *basicCancel) read(r io.Reader) (err error) { @@ -873,7 +873,7 @@ func (msg *basicCancel) read(r io.Reader) (err error) { } type basicCancelOk struct { - ConsumerTag string + ConsumerTag string `json:"consumerTag"` } func (msg *basicCancelOk) read(r io.Reader) (err error) { diff --git a/ui-common/package-lock.json b/ui-common/package-lock.json index dbc2f9faf..e05226aa7 100644 --- a/ui-common/package-lock.json +++ b/ui-common/package-lock.json @@ -18,6 +18,7 @@ "@mui/styles": "^5.8.0", "@types/lodash": "^4.14.182", "@uiw/react-textarea-code-editor": "^1.6.0", + "ace-builds": "^1.6.0", "axios": "^0.27.2", "core-js": "^3.22.7", "highlight.js": "^11.5.1", @@ -30,12 +31,14 @@ "node-fetch": "^3.2.4", "numeral": "^2.0.6", "protobuf-decoder": "^0.1.2", + "react-ace": "^9.0.0", "react-graph-vis": "^1.0.7", "react-lowlight": "^3.0.0", "react-router-dom": "^6.3.0", "react-scrollable-feed-virtualized": "^1.4.9", "react-syntax-highlighter": "^15.5.0", "react-toastify": "^8.2.0", + "recharts": "^2.1.10", "redoc": "^2.0.0-rc.71", "styled-components": "^5.3.5", "web-vitals": "^2.1.4", @@ -44,6 +47,7 @@ "devDependencies": { "@rollup/plugin-node-resolve": "^13.3.0", "@svgr/rollup": "^6.2.1", + "@types/ace": "^0.0.48", "cross-env": "^7.0.3", "env-cmd": "^10.1.0", "gh-pages": "^4.0.0", @@ -4538,6 +4542,12 @@ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" }, + "node_modules/@types/ace": { + "version": "0.0.48", + "resolved": "https://registry.npmjs.org/@types/ace/-/ace-0.0.48.tgz", + "integrity": "sha512-esV6hOWiDOZ6d7w5S11iLu6LQsPGe/9RPzhri7gNNLdrK1LFpO9/m7IZhQL6dat0JHICJ7l51zvHAiCgnPLLHA==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -5316,6 +5326,11 @@ "node": ">= 0.6" } }, + "node_modules/ace-builds": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.7.1.tgz", + "integrity": "sha512-1mcbP5kXvr729sJ9dA/8tul0pjuvKbma0LF/ZMRwPEwjoNWNpe/x0OXpaPJo36aRpZCjRZMl5zsME3hAKTiaNw==" + }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -8842,6 +8857,11 @@ "node": ">=0.10.0" } }, + "node_modules/css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" + }, "node_modules/css-vendor": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", @@ -9178,6 +9198,73 @@ "type": "^1.0.1" } }, + "node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "node_modules/d3-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", + "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" + }, + "node_modules/d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "dependencies": { + "d3-color": "1 - 2" + } + }, + "node_modules/d3-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", + "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + }, + "node_modules/d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "dependencies": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "node_modules/d3-shape": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", + "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==", + "dependencies": { + "d3-path": "1 - 2" + } + }, + "node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "dependencies": { + "d3-array": "2" + } + }, + "node_modules/d3-time-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "dependencies": { + "d3-time": "1 - 2" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -9236,6 +9323,11 @@ "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", "peer": true }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/decko": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", @@ -9605,6 +9697,11 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "node_modules/diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -11566,6 +11663,11 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-equals": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz", + "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" + }, "node_modules/fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -13871,6 +13973,11 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "node_modules/ip": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", @@ -17624,6 +17731,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -24760,6 +24872,22 @@ "node": ">=0.10.0" } }, + "node_modules/react-ace": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-9.5.0.tgz", + "integrity": "sha512-4l5FgwGh6K7A0yWVMQlPIXDItM4Q9zzXRqOae8KkCl6MkOob7sC1CzHxZdOGvV+QioKWbX2p5HcdOVUv6cAdSg==", + "dependencies": { + "ace-builds": "^1.4.13", + "diff-match-patch": "^1.0.5", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0", + "react-dom": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/react-app-polyfill": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz", @@ -24987,6 +25115,11 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "peer": true }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "node_modules/react-lowlight": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-lowlight/-/react-lowlight-3.0.0.tgz", @@ -25013,6 +25146,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-resize-detector": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", + "integrity": "sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-router": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", @@ -25314,6 +25459,43 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0" } }, + "node_modules/react-smooth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.1.tgz", + "integrity": "sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA==", + "dependencies": { + "fast-equals": "^2.0.0", + "react-transition-group": "2.9.0" + }, + "peerDependencies": { + "prop-types": "^15.6.0", + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-smooth/node_modules/dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, + "node_modules/react-smooth/node_modules/react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "dependencies": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0", + "react-dom": ">=15.0.0" + } + }, "node_modules/react-syntax-highlighter": { "version": "15.5.0", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", @@ -25529,6 +25711,44 @@ "balanced-match": "^1.0.0" } }, + "node_modules/recharts": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.12.tgz", + "integrity": "sha512-dAzEuc9AjM+IF0A33QzEdBEUnyGKJcGUPa0MYm0vd38P3WouQjrj2egBrCNInE7ZcQwN+z3MoT7Rw03u8nP9HA==", + "dependencies": { + "classnames": "^2.2.5", + "d3-interpolate": "^2.0.0", + "d3-scale": "^3.0.0", + "d3-shape": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.19", + "react-is": "^16.10.2", + "react-resize-detector": "^7.1.2", + "react-smooth": "^2.0.1", + "recharts-scale": "^0.4.4", + "reduce-css-calc": "^2.1.8" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/recoil": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.3.tgz", @@ -25969,6 +26189,20 @@ "node": ">=10.13.0" } }, + "node_modules/reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "dependencies": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + } + }, + "node_modules/reduce-css-calc/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + }, "node_modules/refractor": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", @@ -36714,6 +36948,12 @@ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" }, + "@types/ace": { + "version": "0.0.48", + "resolved": "https://registry.npmjs.org/@types/ace/-/ace-0.0.48.tgz", + "integrity": "sha512-esV6hOWiDOZ6d7w5S11iLu6LQsPGe/9RPzhri7gNNLdrK1LFpO9/m7IZhQL6dat0JHICJ7l51zvHAiCgnPLLHA==", + "dev": true + }, "@types/babel__core": { "version": "7.1.19", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", @@ -37401,6 +37641,11 @@ "negotiator": "0.6.3" } }, + "ace-builds": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.7.1.tgz", + "integrity": "sha512-1mcbP5kXvr729sJ9dA/8tul0pjuvKbma0LF/ZMRwPEwjoNWNpe/x0OXpaPJo36aRpZCjRZMl5zsME3hAKTiaNw==" + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -40174,6 +40419,11 @@ } } }, + "css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" + }, "css-vendor": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", @@ -40426,6 +40676,73 @@ "type": "^1.0.1" } }, + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } + }, + "d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "d3-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", + "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" + }, + "d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "requires": { + "d3-color": "1 - 2" + } + }, + "d3-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", + "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + }, + "d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "requires": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "d3-shape": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", + "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==", + "requires": { + "d3-path": "1 - 2" + } + }, + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + } + }, + "d3-time-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "requires": { + "d3-time": "1 - 2" + } + }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -40467,6 +40784,11 @@ "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", "peer": true }, + "decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "decko": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", @@ -40750,6 +41072,11 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, + "diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -42256,6 +42583,11 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-equals": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz", + "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" + }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -44029,6 +44361,11 @@ "side-channel": "^1.0.4" } }, + "internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "ip": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", @@ -46856,6 +47193,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -52306,6 +52648,18 @@ "object-assign": "^4.1.1" } }, + "react-ace": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-9.5.0.tgz", + "integrity": "sha512-4l5FgwGh6K7A0yWVMQlPIXDItM4Q9zzXRqOae8KkCl6MkOob7sC1CzHxZdOGvV+QioKWbX2p5HcdOVUv6cAdSg==", + "requires": { + "ace-builds": "^1.4.13", + "diff-match-patch": "^1.0.5", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.7.2" + } + }, "react-app-polyfill": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz", @@ -52490,6 +52844,11 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "peer": true }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "react-lowlight": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-lowlight/-/react-lowlight-3.0.0.tgz", @@ -52504,6 +52863,14 @@ "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==", "peer": true }, + "react-resize-detector": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-7.1.2.tgz", + "integrity": "sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw==", + "requires": { + "lodash": "^4.17.21" + } + }, "react-router": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", @@ -52707,6 +53074,36 @@ "integrity": "sha512-YkFkPjdIXDUsaCNYhZ+Blpp3LF+CsJWscwn/0fGSjF5QBKCtPURO9AEUA362Qnjr4S8LF2IjSAOCCFedIEnVNw==", "requires": {} }, + "react-smooth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.1.tgz", + "integrity": "sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA==", + "requires": { + "fast-equals": "^2.0.0", + "react-transition-group": "2.9.0" + }, + "dependencies": { + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, "react-syntax-highlighter": { "version": "15.5.0", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", @@ -52880,6 +53277,39 @@ "balanced-match": "^1.0.0" } }, + "recharts": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.1.12.tgz", + "integrity": "sha512-dAzEuc9AjM+IF0A33QzEdBEUnyGKJcGUPa0MYm0vd38P3WouQjrj2egBrCNInE7ZcQwN+z3MoT7Rw03u8nP9HA==", + "requires": { + "classnames": "^2.2.5", + "d3-interpolate": "^2.0.0", + "d3-scale": "^3.0.0", + "d3-shape": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.19", + "react-is": "^16.10.2", + "react-resize-detector": "^7.1.2", + "react-smooth": "^2.0.1", + "recharts-scale": "^0.4.4", + "reduce-css-calc": "^2.1.8" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "requires": { + "decimal.js-light": "^2.4.1" + } + }, "recoil": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.3.tgz", @@ -53215,6 +53645,22 @@ } } }, + "reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "requires": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, "refractor": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", diff --git a/ui-common/package.json b/ui-common/package.json index 07a26c25a..3848cf906 100644 --- a/ui-common/package.json +++ b/ui-common/package.json @@ -26,11 +26,11 @@ "@craco/craco": "^6.4.3", "@types/jest": "^26.0.24", "@types/node": "^12.20.54", - "sass": "^1.52.3", "react": "^17.0.2", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^17.0.2", - "recoil": "^0.7.2" + "recoil": "^0.7.2", + "sass": "^1.52.3" }, "dependencies": { "@craco/craco": "^6.4.3", @@ -72,6 +72,7 @@ "devDependencies": { "@rollup/plugin-node-resolve": "^13.3.0", "@svgr/rollup": "^6.2.1", + "@types/ace": "^0.0.48", "cross-env": "^7.0.3", "env-cmd": "^10.1.0", "gh-pages": "^4.0.0", diff --git a/ui-common/src/components/EntryDetailed/EntryViewer/SectionsRepresentation.tsx b/ui-common/src/components/EntryDetailed/EntryViewer/SectionsRepresentation.tsx index 135b0f7e8..2ec62f56b 100644 --- a/ui-common/src/components/EntryDetailed/EntryViewer/SectionsRepresentation.tsx +++ b/ui-common/src/components/EntryDetailed/EntryViewer/SectionsRepresentation.tsx @@ -28,6 +28,10 @@ const SectionsRepresentation: React.FC = ({ data, color }) => { } } + if (sections.length === 0) { + sections.push(
This request or response has no data.
); + } + return {sections}; }