aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js/components/flowview/messages.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/src/js/components/flowview/messages.js')
-rw-r--r--web/src/js/components/flowview/messages.js326
1 files changed, 326 insertions, 0 deletions
diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js
new file mode 100644
index 00000000..7ac95d85
--- /dev/null
+++ b/web/src/js/components/flowview/messages.js
@@ -0,0 +1,326 @@
+var React = require("react");
+var _ = require("lodash");
+
+var common = require("../common.js");
+var actions = require("../../actions.js");
+var flowutils = require("../../flow/utils.js");
+var utils = require("../../utils.js");
+var ContentView = require("./contentview.js");
+var ValueEditor = require("../editor.js").ValueEditor;
+
+var Headers = React.createClass({
+ propTypes: {
+ onChange: React.PropTypes.func.isRequired,
+ message: React.PropTypes.object.isRequired
+ },
+ onChange: function (row, col, val) {
+ var 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: function () {
+ this.refs["0-key"].focus();
+ },
+ onTab: function (row, col, e) {
+ var headers = this.props.message.headers;
+ if (row === headers.length - 1 && col === 1) {
+ e.preventDefault();
+
+ var nextHeaders = _.cloneDeep(this.props.message.headers);
+ nextHeaders.push(["Name", "Value"]);
+ this.props.onChange(nextHeaders);
+ this._nextSel = (row + 1) + "-key";
+ }
+ },
+ componentDidUpdate: function () {
+ if (this._nextSel && this.refs[this._nextSel]) {
+ this.refs[this._nextSel].focus();
+ this._nextSel = undefined;
+ }
+ },
+ onRemove: function (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: function () {
+
+ var rows = this.props.message.headers.map(function (header, i) {
+
+ var kEdit = <HeaderEditor
+ ref={i + "-key"}
+ content={header[0]}
+ onDone={this.onChange.bind(null, i, 0)}
+ onRemove={this.onRemove.bind(null, i, 0)}
+ onTab={this.onTab.bind(null, i, 0)}/>;
+ var vEdit = <HeaderEditor
+ ref={i + "-value"}
+ content={header[1]}
+ onDone={this.onChange.bind(null, i, 1)}
+ onRemove={this.onRemove.bind(null, i, 1)}
+ onTab={this.onTab.bind(null, i, 1)}/>;
+ return (
+ <tr key={i}>
+ <td className="header-name">{kEdit}:</td>
+ <td className="header-value">{vEdit}</td>
+ </tr>
+ );
+ }.bind(this));
+ return (
+ <table className="header-table">
+ <tbody>
+ {rows}
+ </tbody>
+ </table>
+ );
+ }
+});
+
+var HeaderEditor = React.createClass({
+ render: function () {
+ return <ValueEditor ref="input" {...this.props} onKeyDown={this.onKeyDown} inline/>;
+ },
+ focus: function () {
+ this.getDOMNode().focus();
+ },
+ onKeyDown: function (e) {
+ switch (e.keyCode) {
+ case utils.Key.BACKSPACE:
+ var s = window.getSelection().getRangeAt(0);
+ if (s.startOffset === 0 && s.endOffset === 0) {
+ this.props.onRemove(e);
+ }
+ break;
+ case utils.Key.TAB:
+ if (!e.shiftKey) {
+ this.props.onTab(e);
+ }
+ break;
+ }
+ }
+});
+
+var RequestLine = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ var url = flowutils.RequestUtils.pretty_url(flow.request);
+ var httpver = flow.request.http_version;
+
+ return <div className="first-line request-line">
+ <ValueEditor
+ ref="method"
+ content={flow.request.method}
+ onDone={this.onMethodChange}
+ inline/>
+ &nbsp;
+ <ValueEditor
+ ref="url"
+ content={url}
+ onDone={this.onUrlChange}
+ isValid={this.isValidUrl}
+ inline/>
+ &nbsp;
+ <ValueEditor
+ ref="httpVersion"
+ content={httpver}
+ onDone={this.onHttpVersionChange}
+ isValid={flowutils.isValidHttpVersion}
+ inline/>
+ </div>
+ },
+ isValidUrl: function (url) {
+ var u = flowutils.parseUrl(url);
+ return !!u.host;
+ },
+ onMethodChange: function (nextMethod) {
+ actions.FlowActions.update(
+ this.props.flow,
+ {request: {method: nextMethod}}
+ );
+ },
+ onUrlChange: function (nextUrl) {
+ var props = flowutils.parseUrl(nextUrl);
+ props.path = props.path || "";
+ actions.FlowActions.update(
+ this.props.flow,
+ {request: props}
+ );
+ },
+ onHttpVersionChange: function (nextVer) {
+ var ver = flowutils.parseHttpVersion(nextVer);
+ actions.FlowActions.update(
+ this.props.flow,
+ {request: {http_version: ver}}
+ );
+ }
+});
+
+var ResponseLine = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ var httpver = flow.response.http_version;
+ return <div className="first-line response-line">
+ <ValueEditor
+ ref="httpVersion"
+ content={httpver}
+ onDone={this.onHttpVersionChange}
+ isValid={flowutils.isValidHttpVersion}
+ inline/>
+ &nbsp;
+ <ValueEditor
+ ref="code"
+ content={flow.response.status_code + ""}
+ onDone={this.onCodeChange}
+ isValid={this.isValidCode}
+ inline/>
+ &nbsp;
+ <ValueEditor
+ ref="msg"
+ content={flow.response.msg}
+ onDone={this.onMsgChange}
+ inline/>
+ </div>;
+ },
+ isValidCode: function (code) {
+ return /^\d+$/.test(code);
+ },
+ onHttpVersionChange: function (nextVer) {
+ var ver = flowutils.parseHttpVersion(nextVer);
+ actions.FlowActions.update(
+ this.props.flow,
+ {response: {http_version: ver}}
+ );
+ },
+ onMsgChange: function (nextMsg) {
+ actions.FlowActions.update(
+ this.props.flow,
+ {response: {msg: nextMsg}}
+ );
+ },
+ onCodeChange: function (nextCode) {
+ nextCode = parseInt(nextCode);
+ actions.FlowActions.update(
+ this.props.flow,
+ {response: {code: nextCode}}
+ );
+ }
+});
+
+var Request = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ return (
+ <section className="request">
+ <RequestLine ref="requestLine" flow={flow}/>
+ {/*<ResponseLine flow={flow}/>*/}
+ <Headers ref="headers" message={flow.request} onChange={this.onHeaderChange}/>
+ <hr/>
+ <ContentView flow={flow} message={flow.request}/>
+ </section>
+ );
+ },
+ edit: function (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 "Unimplemented: " + k;
+ }
+ },
+ onHeaderChange: function (nextHeaders) {
+ actions.FlowActions.update(this.props.flow, {
+ request: {
+ headers: nextHeaders
+ }
+ });
+ }
+});
+
+var Response = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ return (
+ <section className="response">
+ {/*<RequestLine flow={flow}/>*/}
+ <ResponseLine ref="responseLine" flow={flow}/>
+ <Headers ref="headers" message={flow.response} onChange={this.onHeaderChange}/>
+ <hr/>
+ <ContentView flow={flow} message={flow.response}/>
+ </section>
+ );
+ },
+ edit: function (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 "Unimplemented: " + k;
+ }
+ },
+ onHeaderChange: function (nextHeaders) {
+ actions.FlowActions.update(this.props.flow, {
+ response: {
+ headers: nextHeaders
+ }
+ });
+ }
+});
+
+var Error = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ return (
+ <section>
+ <div className="alert alert-warning">
+ {flow.error.msg}
+ <div>
+ <small>{ utils.formatTimeStamp(flow.error.timestamp) }</small>
+ </div>
+ </div>
+ </section>
+ );
+ }
+});
+
+module.exports = {
+ Request: Request,
+ Response: Response,
+ Error: Error
+};