diff --git a/agent/pkg/controllers/entries_controller.go b/agent/pkg/controllers/entries_controller.go index d5d637a2e..267430eab 100644 --- a/agent/pkg/controllers/entries_controller.go +++ b/agent/pkg/controllers/entries_controller.go @@ -236,9 +236,12 @@ func GetEntry(c *gin.Context) { // "msg": "Can't get entry details", // }) // } + extension := extensionsMap[entryData.ProtocolName] + representation, _ := extension.Dissector.Represent(entryData.Entry) c.JSON(http.StatusOK, tapApi.MizuEntryWrapper{ - Protocol: extensionsMap[entryData.ProtocolName].Protocol, - Data: entryData, + Protocol: extension.Protocol, + Representation: string(representation), + Data: entryData, }) } diff --git a/tap/api/api.go b/tap/api/api.go index 0f8936785..ec5f8acba 100644 --- a/tap/api/api.go +++ b/tap/api/api.go @@ -66,6 +66,7 @@ type Dissector interface { Dissect(b *bufio.Reader, isClient bool, tcpID *TcpID, emitter Emitter) Analyze(item *OutputChannelItem, entryId string, resolvedSource string, resolvedDestination string) *MizuEntry Summarize(entry *MizuEntry) *BaseEntryDetails + Represent(entry string) ([]byte, error) } type Emitting struct { @@ -109,8 +110,9 @@ type MizuEntry struct { } type MizuEntryWrapper struct { - Protocol Protocol `json:"protocol"` - Data MizuEntry `json:"data"` + Protocol Protocol `json:"protocol"` + Representation string `json:"representation"` + Data MizuEntry `json:"data"` } type BaseEntryDetails struct { diff --git a/tap/extensions/amqp/main.go b/tap/extensions/amqp/main.go index 54c49d4b5..2842dc0a5 100644 --- a/tap/extensions/amqp/main.go +++ b/tap/extensions/amqp/main.go @@ -47,4 +47,9 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails { return nil } +func (d dissecting) Represent(entry string) ([]byte, error) { + // TODO: Implement + return nil, nil +} + var Dissector dissecting diff --git a/tap/extensions/http/main.go b/tap/extensions/http/main.go index f6b10c272..ae500e543 100644 --- a/tap/extensions/http/main.go +++ b/tap/extensions/http/main.go @@ -17,12 +17,12 @@ var responseCounter uint var protocol api.Protocol = api.Protocol{ Name: "http", - LongName: "Hypertext Transfer Protocol -- HTTP/1.0", + LongName: "Hypertext Transfer Protocol -- HTTP/1.1", Abbreviation: "HTTP", BackgroundColor: "#205cf5", ForegroundColor: "#ffffff", FontSize: 10, - ReferenceLink: "https://www.ietf.org/rfc/rfc1945.txt", + ReferenceLink: "https://datatracker.ietf.org/doc/html/rfc2616", OutboundPorts: []string{"80", "8080", "443"}, InboundPorts: []string{}, } @@ -160,4 +160,134 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails { } } +func representRequest(request map[string]interface{}) []interface{} { + repRequest := make([]interface{}, 0) + + details, _ := json.Marshal([]map[string]string{ + { + "name": "Method", + "value": request["method"].(string), + }, + { + "name": "URL", + "value": request["url"].(string), + }, + { + "name": "Body Size", + "value": fmt.Sprintf("%g bytes", request["bodySize"].(float64)), + }, + }) + repRequest = append(repRequest, map[string]string{ + "type": "table", + "title": "Details", + "data": string(details), + }) + + headers, _ := json.Marshal(request["headers"].([]interface{})) + repRequest = append(repRequest, map[string]string{ + "type": "table", + "title": "Headers", + "data": string(headers), + }) + + cookies, _ := json.Marshal(request["cookies"].([]interface{})) + repRequest = append(repRequest, map[string]string{ + "type": "table", + "title": "Cookies", + "data": string(cookies), + }) + + queryString, _ := json.Marshal(request["queryString"].([]interface{})) + repRequest = append(repRequest, map[string]string{ + "type": "table", + "title": "Query String", + "data": string(queryString), + }) + + postData, _ := request["postData"].(map[string]interface{}) + mimeType, _ := postData["mimeType"] + if mimeType == nil { + mimeType = "N/A" + } + text, _ := postData["text"] + if text != nil { + repRequest = append(repRequest, map[string]string{ + "type": "body", + "title": "POST Data", + "encoding": "base64", // FIXME: Does `request` have it? + "mime_type": mimeType.(string), + "data": text.(string), + }) + } + + return repRequest +} + +func representResponse(response map[string]interface{}) []interface{} { + repResponse := make([]interface{}, 0) + + details, _ := json.Marshal([]map[string]string{ + { + "name": "Status", + "value": fmt.Sprintf("%g", response["status"].(float64)), + }, + { + "name": "Status Text", + "value": response["statusText"].(string), + }, + { + "name": "Body Size", + "value": fmt.Sprintf("%g bytes", response["bodySize"].(float64)), + }, + }) + repResponse = append(repResponse, map[string]string{ + "type": "table", + "title": "Details", + "data": string(details), + }) + + headers, _ := json.Marshal(response["headers"].([]interface{})) + repResponse = append(repResponse, map[string]string{ + "type": "table", + "title": "Headers", + "data": string(headers), + }) + + cookies, _ := json.Marshal(response["cookies"].([]interface{})) + repResponse = append(repResponse, map[string]string{ + "type": "table", + "title": "Cookies", + "data": string(cookies), + }) + + content, _ := response["content"].(map[string]interface{}) + mimeType, _ := content["mimeType"] + encoding, _ := content["encoding"] + text, _ := content["text"] + if text != nil { + repResponse = append(repResponse, map[string]string{ + "type": "body", + "title": "Body", + "encoding": encoding.(string), + "mime_type": mimeType.(string), + "data": text.(string), + }) + } + + return repResponse +} + +func (d dissecting) Represent(entry string) ([]byte, error) { + var root map[string]interface{} + json.Unmarshal([]byte(entry), &root) + representation := make(map[string]interface{}, 0) + request := root["request"].(map[string]interface{})["payload"].(map[string]interface{}) + response := root["response"].(map[string]interface{})["payload"].(map[string]interface{}) + repRequest := representRequest(request) + repResponse := representResponse(response) + representation["request"] = repRequest + representation["response"] = repResponse + return json.Marshal(representation) +} + var Dissector dissecting diff --git a/tap/extensions/kafka/main.go b/tap/extensions/kafka/main.go index 56b2153e5..59c1ac61a 100644 --- a/tap/extensions/kafka/main.go +++ b/tap/extensions/kafka/main.go @@ -47,4 +47,9 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails { return nil } +func (d dissecting) Represent(entry string) ([]byte, error) { + // TODO: Implement + return nil, nil +} + var Dissector dissecting diff --git a/ui/src/components/HarEntryDetailed.tsx b/ui/src/components/HarEntryDetailed.tsx index 3eb878215..0482f7b8d 100644 --- a/ui/src/components/HarEntryDetailed.tsx +++ b/ui/src/components/HarEntryDetailed.tsx @@ -77,10 +77,7 @@ export const HAREntryDetailed: React.FC = ({classes, harE {har && } <> - {har && } + {har && } }; diff --git a/ui/src/components/HarEntryViewer/HAREntrySections.module.sass b/ui/src/components/HarEntryViewer/HAREntrySections.module.sass index 50bb0cc19..871dc63bc 100644 --- a/ui/src/components/HarEntryViewer/HAREntrySections.module.sass +++ b/ui/src/components/HarEntryViewer/HAREntrySections.module.sass @@ -31,7 +31,6 @@ margin: .3rem 0 .dataKey - text-transform: capitalize color: $blue-gray margin: 0 0.5rem 0 0 text-align: right diff --git a/ui/src/components/HarEntryViewer/HAREntrySections.tsx b/ui/src/components/HarEntryViewer/HAREntrySections.tsx index 088dd3f51..b6fa6d549 100644 --- a/ui/src/components/HarEntryViewer/HAREntrySections.tsx +++ b/ui/src/components/HarEntryViewer/HAREntrySections.tsx @@ -80,9 +80,9 @@ export const HAREntryBodySection: React.FC = ({ const bodyBuf = encoding === 'base64' ? atob(chunk) : chunk; try { - if (jsonLikeFormats.some(format => content?.mimeType?.indexOf(format) > -1)) { + if (jsonLikeFormats.some(format => contentType?.indexOf(format) > -1)) { return JSON.stringify(JSON.parse(bodyBuf), null, 2); - } else if (protobufFormats.some(format => content?.mimeType?.indexOf(format) > -1)) { + } else if (protobufFormats.some(format => contentType?.indexOf(format) > -1)) { // Replace all non printable characters (ASCII) const protobufDecoder = new ProtobufDecoder(bodyBuf, true); return JSON.stringify(protobufDecoder.decode().toSimple(), null, 2); @@ -94,17 +94,17 @@ export const HAREntryBodySection: React.FC = ({ } const getLanguage = (mimetype) => { - const chunk = content.text?.slice(0, 100); + const chunk = content?.slice(0, 100); if (chunk.indexOf('html') > 0 || chunk.indexOf('HTML') > 0) return supportedLanguages[0][1]; const language = supportedLanguages.find(el => (mimetype + contentType).indexOf(el[0]) > -1); return language ? language[1] : 'default'; } return - {content && content.text?.length > 0 && + {content && content?.length > 0 && - +
@@ -118,7 +118,7 @@ export const HAREntryBodySection: React.FC = ({
} @@ -205,52 +205,47 @@ export const HAREntryTablePolicySection: React.FC = {arrayToIterate.map(({rule, matched}, index) => { - - return ( = latency : true)? "Success" : "Failure"}> { - <> { - rule.Key != "" ? + rule.Key !== "" ? : null } { - rule.Latency != "" ? + rule.Latency !== "" ? : null } { - rule.Method != "" ? + rule.Method !== "" ? : null } { - rule.Path != "" ? + rule.Path !== "" ? : null } { - rule.Service != "" ? + rule.Service !== "" ? : null } { - rule.Type != "" ? + rule.Type !== "" ? : null } { - rule.Value != "" ? + rule.Value !== "" ? : null } } - - ) } @@ -259,8 +254,7 @@ export const HAREntryTablePolicySection: React.FC =
Key:{rule.Key}
Latency: {rule.Latency}
Method: {rule.Method}
Path: {rule.Path}
Service: {service}
Type: {rule.Type}
Value: {rule.Value}
- : }
-} \ No newline at end of file +} diff --git a/ui/src/components/HarEntryViewer/HAREntryViewer.tsx b/ui/src/components/HarEntryViewer/HAREntryViewer.tsx index d93c108db..5aad2a1bf 100644 --- a/ui/src/components/HarEntryViewer/HAREntryViewer.tsx +++ b/ui/src/components/HarEntryViewer/HAREntryViewer.tsx @@ -3,11 +3,34 @@ import styles from './HAREntryViewer.module.sass'; import Tabs from "../Tabs"; import {HAREntryTableSection, HAREntryBodySection, HAREntryTablePolicySection} from "./HAREntrySections"; -const MIME_TYPE_KEY = 'mimeType'; +const SectionsRepresentation: React.FC = ({data}) => { + const sections = [] -const HAREntryDisplay: React.FC = ({har, entry, isCollapsed: initialIsCollapsed, isResponseMocked}) => { - const {request, response} = JSON.parse(entry); - const rulesMatched = har.log.entries[0].rulesMatched + for (const [i, row] of data.entries()) { + switch (row.type) { + case "table": + sections.push( + + ) + break; + case "body": + sections.push( + + ) + break; + default: + break; + } + } + + return <>{sections}; +} + +const AutoRepresentation: React.FC = ({representation, isResponseMocked}) => { + console.log(representation) + const {request, response} = JSON.parse(representation); + + const rulesMatched = [] const TABS = [ {tab: 'request'}, { @@ -22,50 +45,33 @@ const HAREntryDisplay: React.FC = ({har, entry, isCollapsed: initialIsColla const [currentTab, setCurrentTab] = useState(TABS[0].tab); return
- - {!initialIsCollapsed &&
+ {
{request?.url && {request.payload.url}}
- { - currentTab === TABS[0].tab && - - - - - {request.payload?.postData && } - - - - } + {currentTab === TABS[0].tab && + + } {currentTab === TABS[1].tab && - - - - - + } {currentTab === TABS[2].tab && - + {// FIXME: Fix here + } }
}
; } interface Props { - harObject: any; - className?: string; + representation: any; isResponseMocked?: boolean; showTitle?: boolean; } -const HAREntryViewer: React.FC = ({harObject, className, isResponseMocked, showTitle=true}) => { - const {log: {entries}} = harObject; - const isCollapsed = entries.length > 1; - return
- {Object.keys(entries).map((entry: any, index) => )} -
+const HAREntryViewer: React.FC = ({representation, isResponseMocked, showTitle=true}) => { + return }; export default HAREntryViewer;