diff options
Diffstat (limited to 'frontend/src/components/ConnectionContent.js')
-rw-r--r-- | frontend/src/components/ConnectionContent.js | 146 |
1 files changed, 102 insertions, 44 deletions
diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js index 2100a68..51dbb67 100644 --- a/frontend/src/components/ConnectionContent.js +++ b/frontend/src/components/ConnectionContent.js @@ -1,7 +1,11 @@ import React, {Component} from 'react'; import './ConnectionContent.scss'; -import {Dropdown, Button} from 'react-bootstrap'; +import {Button, Dropdown, Row} from 'react-bootstrap'; import axios from 'axios'; +import {timestampToDateTime, timestampToTime} from "../utils"; +import MessageAction from "./MessageAction"; + +const classNames = require('classnames'); class ConnectionContent extends Component { @@ -12,6 +16,7 @@ class ConnectionContent extends Component { connectionContent: null, format: "default", decoded: false, + messageActionDialog: null }; this.validFormats = ["default", "hex", "hexdump", "base32", "base64", "ascii", "binary", "decimal", "octal"]; @@ -42,62 +47,115 @@ class ConnectionContent extends Component { this.setState({decoded: !this.state.decoded}); } + tryParseConnectionMessage(connectionMessage) { + if (connectionMessage.metadata == null) { + return connectionMessage.content; + } + if (connectionMessage["is_metadata_continuation"]) { + return <span>already parsed in previous messages</span>; + } + + let unrollMap = (obj) => obj == null ? null : Object.entries(obj).map(([key, value]) => + <p><strong>{key}</strong>: {value}</p> + ); + + let m = connectionMessage.metadata; + switch (m.type) { + case "http-request": + let url = <i><u><a href={"http://" + m.host + m.url} target="_blank">{m.host}{m.url}</a></u></i>; + return <span className="type-http-request"> + <p style={{"margin-bottom": "7px"}}><strong>{m.method}</strong> {url} {m.protocol}</p> + {unrollMap(m.headers)} + <div style={{"margin": "20px 0"}}>{m.body}</div> + {unrollMap(m.trailers)} + </span>; + case "http-response": + return <span className="type-http-response"> + <p style={{"margin-bottom": "7px"}}>{m.protocol} <strong>{m.status}</strong></p> + {unrollMap(m.headers)} + <div style={{"margin": "20px 0"}}>{m.body}</div> + {unrollMap(m.trailers)} + </span>; + default: + return connectionMessage.content; + } + } + + connectionsActions(connectionMessage) { + if (connectionMessage.metadata == null || connectionMessage.metadata["reproducers"] === undefined) { + return null; + } + + return Object.entries(connectionMessage.metadata["reproducers"]).map(([actionName, actionValue]) => + <Button size="sm" onClick={() => { + this.setState({ + messageActionDialog: <MessageAction actionName={actionName} actionValue={actionValue} onHide={() => this.setState({messageActionDialog: null})}/> + }); + }}>{actionName}</Button> + ); + } + render() { let content = this.state.connectionContent; - if (content === null) { - return <div>nope</div>; + if (content == null) { + return <div>select a connection to view</div>; } let payload = content.map((c, i) => - <span key={`content-${i}`} className={c.from_client ? "from-client" : "from-server"}> - {c.from_client - ? - <div id="content">{c.content}</div> - : - <> - {c.decoded_content - ? - <> - <div style={{display: this.state.decoded ? 'none':'inherit'}} id="content">{c.content}</div> - <div style={{display: this.state.decoded ? 'inherit':'none'}} id="decoded_content">{c.decoded_content}</div> - </> - : - <div id="content">{c.content}</div> - } - </> - } - </span> + <div key={`content-${i}`} + className={classNames("connection-message", c.from_client ? "from-client" : "from-server")}> + <div className="connection-message-header container-fluid"> + <div className="row"> + <div className="connection-message-info col"> + <span><strong>offset</strong>: {c.index}</span> | <span><strong>timestamp</strong>: {c.timestamp} + </span> | <span><strong>retransmitted</strong>: {c["is_retransmitted"] ? "yes" : "no"}</span> + </div> + <div className="connection-message-actions col-auto">{this.connectionsActions(c)}</div> + </div> + </div> + <div className="connection-message-label">{c.from_client ? "client" : "server"}</div> + <div className={classNames("message-content", this.state.decoded ? "message-parsed" : "message-original")}> + {this.state.decoded ? this.tryParseConnectionMessage(c) : c.content} + </div> + </div> ); return ( <div className="connection-content"> - <div className="connection-content-options"> - <Dropdown onSelect={this.setFormat} > - <Dropdown.Toggle size="sm" id="dropdown-basic"> - format - </Dropdown.Toggle> - - <Dropdown.Menu> - <Dropdown.Item eventKey="default" active={this.state.format === "default"}>plain</Dropdown.Item> - <Dropdown.Item eventKey="hex" active={this.state.format === "hex"}>hex</Dropdown.Item> - <Dropdown.Item eventKey="hexdump" active={this.state.format === "hexdump"}>hexdump</Dropdown.Item> - <Dropdown.Item eventKey="base32" active={this.state.format === "base32"}>base32</Dropdown.Item> - <Dropdown.Item eventKey="base64" active={this.state.format === "base64"}>base64</Dropdown.Item> - <Dropdown.Item eventKey="ascii" active={this.state.format === "ascii"}>ascii</Dropdown.Item> - <Dropdown.Item eventKey="binary" active={this.state.format === "binary"}>binary</Dropdown.Item> - <Dropdown.Item eventKey="decimal" active={this.state.format === "decimal"}>decimal</Dropdown.Item> - <Dropdown.Item eventKey="octal" active={this.state.format === "octal"}>octal</Dropdown.Item> - </Dropdown.Menu> - <Button onClick={() => this.toggleDecoded()}>{this.state.decoded ? "Encode" : "Decode"}</Button> - - - </Dropdown> - - + <div className="connection-content-header container-fluid"> + <Row> + <div className="header-info col"> + <span><strong>flow</strong>: {this.props.connection.ip_src}:{this.props.connection.port_src} -> {this.props.connection.ip_dst}:{this.props.connection.port_dst}</span> + <span> | <strong>timestamp</strong>: {this.props.connection.started_at}</span> + </div> + <div className="col-auto"> + <Dropdown onSelect={this.setFormat} > + <Dropdown.Toggle size="sm" id="dropdown-basic"> + format + </Dropdown.Toggle> + + <Dropdown.Menu> + <Dropdown.Item eventKey="default" active={this.state.format === "default"}>plain</Dropdown.Item> + <Dropdown.Item eventKey="hex" active={this.state.format === "hex"}>hex</Dropdown.Item> + <Dropdown.Item eventKey="hexdump" active={this.state.format === "hexdump"}>hexdump</Dropdown.Item> + <Dropdown.Item eventKey="base32" active={this.state.format === "base32"}>base32</Dropdown.Item> + <Dropdown.Item eventKey="base64" active={this.state.format === "base64"}>base64</Dropdown.Item> + <Dropdown.Item eventKey="ascii" active={this.state.format === "ascii"}>ascii</Dropdown.Item> + <Dropdown.Item eventKey="binary" active={this.state.format === "binary"}>binary</Dropdown.Item> + <Dropdown.Item eventKey="decimal" active={this.state.format === "decimal"}>decimal</Dropdown.Item> + <Dropdown.Item eventKey="octal" active={this.state.format === "octal"}>octal</Dropdown.Item> + </Dropdown.Menu> + <Button size="sm" onClick={() => this.toggleDecoded()}>{this.state.decoded ? "Encode" : "Decode"}</Button> + + + </Dropdown> + </div> + </Row> </div> <pre>{payload}</pre> + {this.state.messageActionDialog} </div> ); } |