From e5bf1e930a5b6ba0b3300b02daf792d65d795202 Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 14 Jun 2016 23:52:00 +0800 Subject: [web] FlowView and ContentView --- web/src/js/components/FlowView/Details.jsx | 133 ++++++++++++++++++++++ web/src/js/components/FlowView/Headers.jsx | 130 +++++++++++++++++++++ web/src/js/components/FlowView/Messages.jsx | 168 ++++++++++++++++++++++++++++ web/src/js/components/FlowView/Nav.jsx | 57 ++++++++++ 4 files changed, 488 insertions(+) create mode 100644 web/src/js/components/FlowView/Details.jsx create mode 100644 web/src/js/components/FlowView/Headers.jsx create mode 100644 web/src/js/components/FlowView/Messages.jsx create mode 100644 web/src/js/components/FlowView/Nav.jsx (limited to 'web/src/js/components/FlowView') diff --git a/web/src/js/components/FlowView/Details.jsx b/web/src/js/components/FlowView/Details.jsx new file mode 100644 index 00000000..78e68ecf --- /dev/null +++ b/web/src/js/components/FlowView/Details.jsx @@ -0,0 +1,133 @@ +import React from 'react' +import _ from 'lodash' +import { formatTimeStamp, formatTimeDelta } from '../../utils.js' + +export function TimeStamp({ t, deltaTo, title }) { + return t ? ( + + {title}: + + {formatTimeStamp(t)} + {deltaTo && ( + + ({formatTimeDelta(1000 * (t - deltaTo))}) + + )} + + + ) : ( + + ) +} + +export function ConnectionInfo({ conn }) { + return ( + + + + + + + {conn.sni ? ( + + ) : ( + + + + + )} + +
Address:{conn.address.address.join(':')}
+ TLS SNI: + {conn.sni}
+ ) +} + +export function CertificateInfo({ flow }) { + // @todo We should fetch human-readable certificate representation from the server + return ( +
+ {flow.client_conn.cert && [ +

Client Certificate

, +
{flow.client_conn.cert}
+ ]} + + {flow.server_conn.cert && [ +

Server Certificate

, +
{flow.server_conn.cert}
+ ]} +
+ ) +} + +export function Timing({ flow }) { + const { server_conn: sc, client_conn: cc, request: req, response: res } = flow + + const timestamps = [ + { + title: "Server conn. initiated", + t: sc.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Server conn. TCP handshake", + t: sc.timestamp_tcp_setup, + deltaTo: req.timestamp_start + }, { + title: "Server conn. SSL handshake", + t: sc.timestamp_ssl_setup, + deltaTo: req.timestamp_start + }, { + title: "Client conn. established", + t: cc.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Client conn. SSL handshake", + t: cc.timestamp_ssl_setup, + deltaTo: req.timestamp_start + }, { + title: "First request byte", + t: req.timestamp_start, + }, { + title: "Request complete", + t: req.timestamp_end, + deltaTo: req.timestamp_start + }, res && { + title: "First response byte", + t: res.timestamp_start, + deltaTo: req.timestamp_start + }, res && { + title: "Response complete", + t: res.timestamp_end, + deltaTo: req.timestamp_start + } + ] + + return ( +
+

Timing

+ + + {timestamps.filter(v => v).sort((a, b) => a.t - b.t).map(item => ( + + ))} + +
+
+ ) +} + +export default function Details({ flow }) { + return ( +
+

Client Connection

+ + +

Server Connection

+ + + + + +
+ ) +} diff --git a/web/src/js/components/FlowView/Headers.jsx b/web/src/js/components/FlowView/Headers.jsx new file mode 100644 index 00000000..b8f9b50f --- /dev/null +++ b/web/src/js/components/FlowView/Headers.jsx @@ -0,0 +1,130 @@ +import React, { Component, PropTypes } from 'react' +import ReactDOM from 'react-dom' +import { ValueEditor } from '../editor' +import { Key } from '../../utils.js' + +class HeaderEditor extends Component { + + render() { + return + } + + focus() { + ReactDOM.findDOMNode(this).focus() + } + + onKeyDown(e) { + switch (e.keyCode) { + case Key.BACKSPACE: + var s = window.getSelection().getRangeAt(0) + if (s.startOffset === 0 && s.endOffset === 0) { + this.props.onRemove(e) + } + break + case Key.TAB: + if (!e.shiftKey) { + this.props.onTab(e) + } + break + } + } +} + +export default class Headers extends Component { + + static propTypes = { + onChange: PropTypes.func.isRequired, + message: PropTypes.object.isRequired, + } + + onChange(row, col, val) { + const nextHeaders = _.cloneDeep(this.props.message.headers) + + nextHeaders[row][col] = val + + if (!nextHeaders[row][0] && !nextHeaders[row][1]) { + // do not delete last row + if (nextHeaders.length === 1) { + nextHeaders[0][0] = 'Name' + nextHeaders[0][1] = 'Value' + } else { + nextHeaders.splice(row, 1) + // manually move selection target if this has been the last row. + if (row === nextHeaders.length) { + this._nextSel = `${row - 1}-value` + } + } + } + + this.props.onChange(nextHeaders) + } + + edit() { + this.refs['0-key'].focus() + } + + onTab(row, col, e) { + const headers = this.props.message.headers + + if (row !== headers.length - 1 || col !== 1) { + return + } + + e.preventDefault() + + const nextHeaders = _.cloneDeep(this.props.message.headers) + nextHeaders.push(['Name', 'Value']) + this.props.onChange(nextHeaders) + this._nextSel = `${row + 1}-key` + } + + componentDidUpdate() { + if (this._nextSel && this.refs[this._nextSel]) { + this.refs[this._nextSel].focus() + this._nextSel = undefined + } + } + + onRemove(row, col, e) { + if (col === 1) { + e.preventDefault() + this.refs[`${row}-key`].focus() + } else if (row > 0) { + e.preventDefault() + this.refs[`${row - 1}-value`].focus() + } + } + + render() { + const { message } = this.props + + return ( + + + {message.headers.map((header, i) => ( + + + + + ))} + +
+ this.onChange(i, 0, val)} + onRemove={event => this.onRemove(i, 0, event)} + onTab={event => this.onTab(i, 0, event)} + />: + + this.onChange(i, 1, val)} + onRemove={event => this.onRemove(i, 1, event)} + onTab={event => this.onTab(i, 1, event)} + /> +
+ ) + } +} diff --git a/web/src/js/components/FlowView/Messages.jsx b/web/src/js/components/FlowView/Messages.jsx new file mode 100644 index 00000000..ce17c294 --- /dev/null +++ b/web/src/js/components/FlowView/Messages.jsx @@ -0,0 +1,168 @@ +import React, { Component } from 'react' +import _ from 'lodash' + +import { FlowActions } from '../../actions.js' +import { RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion } from '../../flow/utils.js' +import { Key, formatTimeStamp } from '../../utils.js' +import ContentView from '../ContentView' +import { ValueEditor } from '../editor' +import Headers from './Headers' + +class RequestLine extends Component { + + render() { + const { flow } = this.props + + return ( +
+ FlowActions.update(flow, { request: { method } })} + inline + /> +   + FlowActions.update(flow, { request: Object.assign({ path: '' }, parseUrl(url)) })} + isValid={url => !!parseUrl(url).host} + inline + /> +   + FlowActions.update(flow, { request: { http_version: parseHttpVersion(ver) } })} + isValid={isValidHttpVersion} + inline + /> +
+ ) + } +} + +class ResponseLine extends Component { + + render() { + const { flow } = this.props + + return ( +
+ FlowActions.update(flow, { response: { http_version: parseHttpVersion(nextVer) } })} + isValid={isValidHttpVersion} + inline + /> +   + FlowActions.update(flow, { response: { code: parseInt(code) } })} + isValid={code => /^\d+$/.test(code)} + inline + /> +   + FlowActions.update(flow, { response: { msg } })} + inline + /> +
+ ) + } +} + +export class Request extends Component { + + render() { + const { flow } = this.props + + return ( +
+ + FlowActions.update(flow, { request: { headers } })} + /> +
+ +
+ ) + } + + edit(k) { + switch (k) { + case 'm': + this.refs.requestLine.refs.method.focus() + break + case 'u': + this.refs.requestLine.refs.url.focus() + break + case 'v': + this.refs.requestLine.refs.httpVersion.focus() + break + case 'h': + this.refs.headers.edit() + break + default: + throw new Error(`Unimplemented: ${k}`) + } + } +} + +export class Response extends Component { + + render() { + const { flow } = this.props + + return ( +
+ + FlowActions.update(flow, { response: { headers } })} + /> +
+ +
+ ) + } + + edit(k) { + switch (k) { + case 'c': + this.refs.responseLine.refs.status_code.focus() + break + case 'm': + this.refs.responseLine.refs.msg.focus() + break + case 'v': + this.refs.responseLine.refs.httpVersion.focus() + break + case 'h': + this.refs.headers.edit() + break + default: + throw new Error(`'Unimplemented: ${k}`) + } + } +} + +export function Error({ flow }) { + return ( +
+
+ {flow.error.msg} +
+ {formatTimeStamp(flow.error.timestamp)} +
+
+
+ ) +} diff --git a/web/src/js/components/FlowView/Nav.jsx b/web/src/js/components/FlowView/Nav.jsx new file mode 100644 index 00000000..386c3a6c --- /dev/null +++ b/web/src/js/components/FlowView/Nav.jsx @@ -0,0 +1,57 @@ +import React, { PropTypes } from 'react' +import classnames from 'classnames' +import { FlowActions } from '../../actions.js' + +NavAction.propTypes = { + icon: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, +} + +function NavAction({ icon, title, onClick }) { + return ( + { + event.preventDefault() + onClick(event) + }}> + + + ) +} + +Nav.propTypes = { + flow: PropTypes.object.isRequired, + active: PropTypes.string.isRequired, + tabs: PropTypes.array.isRequired, + onSelectTab: PropTypes.func.isRequired, +} + +export default function Nav({ flow, active, tabs, onSelectTab }) { + return ( + + ) +} -- cgit v1.2.3 From f5c597a9e351b8dfb84f0fe3f09046e772482fc6 Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 15 Jun 2016 00:46:59 +0800 Subject: [web] Editor & Prompt --- web/src/js/components/FlowView/Headers.jsx | 2 +- web/src/js/components/FlowView/Messages.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'web/src/js/components/FlowView') diff --git a/web/src/js/components/FlowView/Headers.jsx b/web/src/js/components/FlowView/Headers.jsx index b8f9b50f..880eeda1 100644 --- a/web/src/js/components/FlowView/Headers.jsx +++ b/web/src/js/components/FlowView/Headers.jsx @@ -1,6 +1,6 @@ import React, { Component, PropTypes } from 'react' import ReactDOM from 'react-dom' -import { ValueEditor } from '../editor' +import ValueEditor from '../ValueEditor' import { Key } from '../../utils.js' class HeaderEditor extends Component { diff --git a/web/src/js/components/FlowView/Messages.jsx b/web/src/js/components/FlowView/Messages.jsx index ce17c294..ba6a5f2b 100644 --- a/web/src/js/components/FlowView/Messages.jsx +++ b/web/src/js/components/FlowView/Messages.jsx @@ -5,7 +5,7 @@ import { FlowActions } from '../../actions.js' import { RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion } from '../../flow/utils.js' import { Key, formatTimeStamp } from '../../utils.js' import ContentView from '../ContentView' -import { ValueEditor } from '../editor' +import ValueEditor from '../ValueEditor' import Headers from './Headers' class RequestLine extends Component { -- cgit v1.2.3