aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'web/src/js')
-rw-r--r--web/src/js/actions.js1
-rw-r--r--web/src/js/components/eventlog.js1
-rw-r--r--web/src/js/components/flowdetail.js399
-rw-r--r--web/src/js/components/flowview/contentview.js158
-rw-r--r--web/src/js/components/flowview/details.js181
-rw-r--r--web/src/js/components/flowview/index.js74
-rw-r--r--web/src/js/components/flowview/messages.js91
-rw-r--r--web/src/js/components/flowview/nav.js61
-rw-r--r--web/src/js/components/header.js2
-rw-r--r--web/src/js/components/mainview.js6
-rw-r--r--web/src/js/flow/utils.js12
-rw-r--r--web/src/js/store/view.js8
-rw-r--r--web/src/js/utils.js1
13 files changed, 582 insertions, 413 deletions
diff --git a/web/src/js/actions.js b/web/src/js/actions.js
index 64cd68a7..1b29438c 100644
--- a/web/src/js/actions.js
+++ b/web/src/js/actions.js
@@ -117,5 +117,6 @@ module.exports = {
ConnectionActions: ConnectionActions,
FlowActions: FlowActions,
StoreCmds: StoreCmds,
+ SettingsActions: SettingsActions,
Query: Query
}; \ No newline at end of file
diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js
index 23508275..de69462b 100644
--- a/web/src/js/components/eventlog.js
+++ b/web/src/js/components/eventlog.js
@@ -3,6 +3,7 @@ var common = require("./common.js");
var Query = require("../actions.js").Query;
var VirtualScrollMixin = require("./virtualscroll.js");
var views = require("../store/view.js");
+var _ = require("lodash");
var LogMessage = React.createClass({
render: function () {
diff --git a/web/src/js/components/flowdetail.js b/web/src/js/components/flowdetail.js
deleted file mode 100644
index 1d019ffb..00000000
--- a/web/src/js/components/flowdetail.js
+++ /dev/null
@@ -1,399 +0,0 @@
-var React = require("react");
-var _ = require("lodash");
-
-var common = require("./common.js");
-var actions = require("../actions.js");
-var flowutils = require("../flow/utils.js");
-var toputils = require("../utils.js");
-
-var NavAction = React.createClass({
- onClick: function (e) {
- e.preventDefault();
- this.props.onClick();
- },
- render: function () {
- return (
- <a title={this.props.title}
- href="#"
- className="nav-action"
- onClick={this.onClick}>
- <i className={"fa fa-fw " + this.props.icon}></i>
- </a>
- );
- }
-});
-
-var FlowDetailNav = React.createClass({
- render: function () {
- var flow = this.props.flow;
-
- var tabs = this.props.tabs.map(function (e) {
- var str = e.charAt(0).toUpperCase() + e.slice(1);
- var className = this.props.active === e ? "active" : "";
- var onClick = function (event) {
- this.props.selectTab(e);
- event.preventDefault();
- }.bind(this);
- return <a key={e}
- href="#"
- className={className}
- onClick={onClick}>{str}</a>;
- }.bind(this));
-
- var acceptButton = null;
- if(flow.intercepted){
- acceptButton = <NavAction title="[a]ccept intercepted flow" icon="fa-play" onClick={actions.FlowActions.accept.bind(null, flow)} />;
- }
- var revertButton = null;
- if(flow.modified){
- revertButton = <NavAction title="revert changes to flow [V]" icon="fa-history" onClick={actions.FlowActions.revert.bind(null, flow)} />;
- }
-
- return (
- <nav ref="head" className="nav-tabs nav-tabs-sm">
- {tabs}
- <NavAction title="[d]elete flow" icon="fa-trash" onClick={actions.FlowActions.delete.bind(null, flow)} />
- <NavAction title="[D]uplicate flow" icon="fa-copy" onClick={actions.FlowActions.duplicate.bind(null, flow)} />
- <NavAction disabled title="[r]eplay flow" icon="fa-repeat" onClick={actions.FlowActions.replay.bind(null, flow)} />
- {acceptButton}
- {revertButton}
- </nav>
- );
- }
-});
-
-var Headers = React.createClass({
- render: function () {
- var rows = this.props.message.headers.map(function (header, i) {
- return (
- <tr key={i}>
- <td className="header-name">{header[0] + ":"}</td>
- <td className="header-value">{header[1]}</td>
- </tr>
- );
- });
- return (
- <table className="header-table">
- <tbody>
- {rows}
- </tbody>
- </table>
- );
- }
-});
-
-var FlowDetailRequest = React.createClass({
- render: function () {
- var flow = this.props.flow;
- var first_line = [
- flow.request.method,
- flowutils.RequestUtils.pretty_url(flow.request),
- "HTTP/" + flow.request.httpversion.join(".")
- ].join(" ");
- var content = null;
- if (flow.request.contentLength > 0) {
- content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength);
- } else {
- content = <div className="alert alert-info">No Content</div>;
- }
-
- //TODO: Styling
-
- return (
- <section>
- <div className="first-line">{ first_line }</div>
- <Headers message={flow.request}/>
- <hr/>
- {content}
- </section>
- );
- }
-});
-
-var FlowDetailResponse = React.createClass({
- render: function () {
- var flow = this.props.flow;
- var first_line = [
- "HTTP/" + flow.response.httpversion.join("."),
- flow.response.code,
- flow.response.msg
- ].join(" ");
- var content = null;
- if (flow.response.contentLength > 0) {
- content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength);
- } else {
- content = <div className="alert alert-info">No Content</div>;
- }
-
- //TODO: Styling
-
- return (
- <section>
- <div className="first-line">{ first_line }</div>
- <Headers message={flow.response}/>
- <hr/>
- {content}
- </section>
- );
- }
-});
-
-var FlowDetailError = React.createClass({
- render: function () {
- var flow = this.props.flow;
- return (
- <section>
- <div className="alert alert-warning">
- {flow.error.msg}
- <div>
- <small>{ toputils.formatTimeStamp(flow.error.timestamp) }</small>
- </div>
- </div>
- </section>
- );
- }
-});
-
-var TimeStamp = React.createClass({
- render: function () {
-
- if (!this.props.t) {
- //should be return null, but that triggers a React bug.
- return <tr></tr>;
- }
-
- var ts = toputils.formatTimeStamp(this.props.t);
-
- var delta;
- if (this.props.deltaTo) {
- delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo));
- delta = <span className="text-muted">{"(" + delta + ")"}</span>;
- } else {
- delta = null;
- }
-
- return <tr>
- <td>{this.props.title + ":"}</td>
- <td>{ts} {delta}</td>
- </tr>;
- }
-});
-
-var ConnectionInfo = React.createClass({
-
- render: function () {
- var conn = this.props.conn;
- var address = conn.address.address.join(":");
-
- var sni = <tr key="sni"></tr>; //should be null, but that triggers a React bug.
- if (conn.sni) {
- sni = <tr key="sni">
- <td>
- <abbr title="TLS Server Name Indication">TLS SNI:</abbr>
- </td>
- <td>{conn.sni}</td>
- </tr>;
- }
- return (
- <table className="connection-table">
- <tbody>
- <tr key="address">
- <td>Address:</td>
- <td>{address}</td>
- </tr>
- {sni}
- </tbody>
- </table>
- );
- }
-});
-
-var CertificateInfo = React.createClass({
- render: function () {
- //TODO: We should fetch human-readable certificate representation
- // from the server
- var flow = this.props.flow;
- var client_conn = flow.client_conn;
- var server_conn = flow.server_conn;
-
- var preStyle = {maxHeight: 100};
- return (
- <div>
- {client_conn.cert ? <h4>Client Certificate</h4> : null}
- {client_conn.cert ? <pre style={preStyle}>{client_conn.cert}</pre> : null}
-
- {server_conn.cert ? <h4>Server Certificate</h4> : null}
- {server_conn.cert ? <pre style={preStyle}>{server_conn.cert}</pre> : null}
- </div>
- );
- }
-});
-
-var Timing = React.createClass({
- render: function () {
- var flow = this.props.flow;
- var sc = flow.server_conn;
- var cc = flow.client_conn;
- var req = flow.request;
- var resp = flow.response;
-
- var 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
- }
- ];
-
- if (flow.response) {
- timestamps.push(
- {
- title: "First response byte",
- t: resp.timestamp_start,
- deltaTo: req.timestamp_start
- }, {
- title: "Response complete",
- t: resp.timestamp_end,
- deltaTo: req.timestamp_start
- }
- );
- }
-
- //Add unique key for each row.
- timestamps.forEach(function (e) {
- e.key = e.title;
- });
-
- timestamps = _.sortBy(timestamps, 't');
-
- var rows = timestamps.map(function (e) {
- return <TimeStamp {...e}/>;
- });
-
- return (
- <div>
- <h4>Timing</h4>
- <table className="timing-table">
- <tbody>
- {rows}
- </tbody>
- </table>
- </div>
- );
- }
-});
-
-var FlowDetailConnectionInfo = React.createClass({
- render: function () {
- var flow = this.props.flow;
- var client_conn = flow.client_conn;
- var server_conn = flow.server_conn;
- return (
- <section>
-
- <h4>Client Connection</h4>
- <ConnectionInfo conn={client_conn}/>
-
- <h4>Server Connection</h4>
- <ConnectionInfo conn={server_conn}/>
-
- <CertificateInfo flow={flow}/>
-
- <Timing flow={flow}/>
-
- </section>
- );
- }
-});
-
-var allTabs = {
- request: FlowDetailRequest,
- response: FlowDetailResponse,
- error: FlowDetailError,
- details: FlowDetailConnectionInfo
-};
-
-var FlowDetail = React.createClass({
- mixins: [common.StickyHeadMixin, common.Navigation, common.State],
- getTabs: function (flow) {
- var tabs = [];
- ["request", "response", "error"].forEach(function (e) {
- if (flow[e]) {
- tabs.push(e);
- }
- });
- tabs.push("details");
- return tabs;
- },
- nextTab: function (i) {
- var tabs = this.getTabs(this.props.flow);
- var currentIndex = tabs.indexOf(this.getParams().detailTab);
- // JS modulo operator doesn't correct negative numbers, make sure that we are positive.
- var nextIndex = (currentIndex + i + tabs.length) % tabs.length;
- this.selectTab(tabs[nextIndex]);
- },
- selectTab: function (panel) {
- this.replaceWith(
- "flow",
- {
- flowId: this.getParams().flowId,
- detailTab: panel
- }
- );
- },
- render: function () {
- var flow = this.props.flow;
- var tabs = this.getTabs(flow);
- var active = this.getParams().detailTab;
-
- if (!_.contains(tabs, active)) {
- if (active === "response" && flow.error) {
- active = "error";
- } else if (active === "error" && flow.response) {
- active = "response";
- } else {
- active = tabs[0];
- }
- this.selectTab(active);
- }
-
- var Tab = allTabs[active];
- return (
- <div className="flow-detail" onScroll={this.adjustHead}>
- <FlowDetailNav ref="head"
- flow={flow}
- tabs={tabs}
- active={active}
- selectTab={this.selectTab}/>
- <Tab flow={flow}/>
- </div>
- );
- }
-});
-
-module.exports = {
- FlowDetail: FlowDetail
-}; \ No newline at end of file
diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js
new file mode 100644
index 00000000..09a64bb2
--- /dev/null
+++ b/web/src/js/components/flowview/contentview.js
@@ -0,0 +1,158 @@
+var React = require("react");
+var _ = require("lodash");
+
+var MessageUtils = require("../../flow/utils.js").MessageUtils;
+var utils = require("../../utils.js");
+
+var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i;
+var Image = React.createClass({
+ statics: {
+ matches: function (message) {
+ return image_regex.test(MessageUtils.getContentType(message));
+ }
+ },
+ render: function () {
+ var message_name = this.props.flow.request === this.props.message ? "request" : "response";
+ var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content";
+ return <div className="flowview-image">
+ <img src={url} alt="preview" className="img-thumbnail"/>
+ </div>;
+ }
+});
+
+var Raw = React.createClass({
+ statics: {
+ matches: function (message) {
+ return true;
+ }
+ },
+ render: function () {
+ //FIXME
+ return <div>raw</div>;
+ }
+});
+
+
+var Auto = React.createClass({
+ statics: {
+ matches: function () {
+ return false; // don't match itself
+ },
+ findView: function (message) {
+ for (var i = 0; i < all.length; i++) {
+ if (all[i].matches(message)) {
+ return all[i];
+ }
+ }
+ return all[all.length - 1];
+ }
+ },
+ render: function () {
+ var View = Auto.findView(this.props.message);
+ return <View {...this.props}/>;
+ }
+});
+
+var all = [Auto, Image, Raw];
+
+
+var ContentEmpty = React.createClass({
+ render: function () {
+ var message_name = this.props.flow.request === this.props.message ? "request" : "response";
+ return <div className="alert alert-info">No {message_name} content.</div>;
+ }
+});
+
+var ContentMissing = React.createClass({
+ render: function () {
+ var message_name = this.props.flow.request === this.props.message ? "Request" : "Response";
+ return <div className="alert alert-info">{message_name} content missing.</div>;
+ }
+});
+
+var TooLarge = React.createClass({
+ render: function () {
+ var size = utils.formatSize(this.props.message.contentLength);
+ return <div className="alert alert-warning">
+ <button onClick={this.props.onClick} className="btn btn-xs btn-warning pull-right">Display anyway</button>
+ {size} content size.
+ </div>;
+ }
+});
+
+var ViewSelector = React.createClass({
+ render: function () {
+ var views = [];
+ for (var i = 0; i < all.length; i++) {
+ var view = all[i];
+ var className = "btn btn-default";
+ if (view === this.props.active) {
+ className += " active";
+ }
+ var text;
+ if (view === Auto) {
+ text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase();
+ } else {
+ text = view.displayName.toLowerCase();
+ }
+ views.push(
+ <button
+ key={view.displayName}
+ onClick={this.props.selectView.bind(null, view)}
+ className={className}>
+ {text}
+ </button>
+ );
+ }
+
+ return <div className="view-selector btn-group btn-group-xs">{views}</div>;
+ }
+});
+
+var ContentView = React.createClass({
+ getInitialState: function () {
+ return {
+ displayLarge: false,
+ View: Auto
+ };
+ },
+ propTypes: {
+ // It may seem a bit weird at the first glance:
+ // Every view takes the flow and the message as props, e.g.
+ // <Auto flow={flow} message={flow.request}/>
+ flow: React.PropTypes.object.isRequired,
+ message: React.PropTypes.object.isRequired,
+ },
+ selectView: function (view) {
+ this.setState({
+ View: view
+ });
+ },
+ displayLarge: function () {
+ this.setState({displayLarge: true});
+ },
+ componentWillReceiveProps: function (nextProps) {
+ if (nextProps.message !== this.props.message) {
+ this.setState(this.getInitialState());
+ }
+ },
+ render: function () {
+ var message = this.props.message;
+ if (message.contentLength === 0) {
+ return <ContentEmpty {...this.props}/>;
+ } else if (message.contentLength === null) {
+ return <ContentMissing {...this.props}/>;
+ } else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) {
+ return <TooLarge {...this.props} onClick={this.displayLarge}/>;
+ }
+
+ return <div>
+ <this.state.View {...this.props} />
+ <div className="text-center">
+ <ViewSelector selectView={this.selectView} active={this.state.View} message={message}/>
+ </div>
+ </div>;
+ }
+});
+
+module.exports = ContentView; \ No newline at end of file
diff --git a/web/src/js/components/flowview/details.js b/web/src/js/components/flowview/details.js
new file mode 100644
index 00000000..00e0116c
--- /dev/null
+++ b/web/src/js/components/flowview/details.js
@@ -0,0 +1,181 @@
+var React = require("react");
+var _ = require("lodash");
+
+var utils = require("../../utils.js");
+
+var TimeStamp = React.createClass({
+ render: function () {
+
+ if (!this.props.t) {
+ //should be return null, but that triggers a React bug.
+ return <tr></tr>;
+ }
+
+ var ts = utils.formatTimeStamp(this.props.t);
+
+ var delta;
+ if (this.props.deltaTo) {
+ delta = utils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo));
+ delta = <span className="text-muted">{"(" + delta + ")"}</span>;
+ } else {
+ delta = null;
+ }
+
+ return <tr>
+ <td>{this.props.title + ":"}</td>
+ <td>{ts} {delta}</td>
+ </tr>;
+ }
+});
+
+var ConnectionInfo = React.createClass({
+
+ render: function () {
+ var conn = this.props.conn;
+ var address = conn.address.address.join(":");
+
+ var sni = <tr key="sni"></tr>; //should be null, but that triggers a React bug.
+ if (conn.sni) {
+ sni = <tr key="sni">
+ <td>
+ <abbr title="TLS Server Name Indication">TLS SNI:</abbr>
+ </td>
+ <td>{conn.sni}</td>
+ </tr>;
+ }
+ return (
+ <table className="connection-table">
+ <tbody>
+ <tr key="address">
+ <td>Address:</td>
+ <td>{address}</td>
+ </tr>
+ {sni}
+ </tbody>
+ </table>
+ );
+ }
+});
+
+var CertificateInfo = React.createClass({
+ render: function () {
+ //TODO: We should fetch human-readable certificate representation
+ // from the server
+ var flow = this.props.flow;
+ var client_conn = flow.client_conn;
+ var server_conn = flow.server_conn;
+
+ var preStyle = {maxHeight: 100};
+ return (
+ <div>
+ {client_conn.cert ? <h4>Client Certificate</h4> : null}
+ {client_conn.cert ? <pre style={preStyle}>{client_conn.cert}</pre> : null}
+
+ {server_conn.cert ? <h4>Server Certificate</h4> : null}
+ {server_conn.cert ? <pre style={preStyle}>{server_conn.cert}</pre> : null}
+ </div>
+ );
+ }
+});
+
+var Timing = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ var sc = flow.server_conn;
+ var cc = flow.client_conn;
+ var req = flow.request;
+ var resp = flow.response;
+
+ var 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
+ }
+ ];
+
+ if (flow.response) {
+ timestamps.push(
+ {
+ title: "First response byte",
+ t: resp.timestamp_start,
+ deltaTo: req.timestamp_start
+ }, {
+ title: "Response complete",
+ t: resp.timestamp_end,
+ deltaTo: req.timestamp_start
+ }
+ );
+ }
+
+ //Add unique key for each row.
+ timestamps.forEach(function (e) {
+ e.key = e.title;
+ });
+
+ timestamps = _.sortBy(timestamps, 't');
+
+ var rows = timestamps.map(function (e) {
+ return <TimeStamp {...e}/>;
+ });
+
+ return (
+ <div>
+ <h4>Timing</h4>
+ <table className="timing-table">
+ <tbody>
+ {rows}
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+});
+
+var Details = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ var client_conn = flow.client_conn;
+ var server_conn = flow.server_conn;
+ return (
+ <section>
+
+ <h4>Client Connection</h4>
+ <ConnectionInfo conn={client_conn}/>
+
+ <h4>Server Connection</h4>
+ <ConnectionInfo conn={server_conn}/>
+
+ <CertificateInfo flow={flow}/>
+
+ <Timing flow={flow}/>
+
+ </section>
+ );
+ }
+});
+
+module.exports = Details; \ No newline at end of file
diff --git a/web/src/js/components/flowview/index.js b/web/src/js/components/flowview/index.js
new file mode 100644
index 00000000..0c31aca5
--- /dev/null
+++ b/web/src/js/components/flowview/index.js
@@ -0,0 +1,74 @@
+var React = require("react");
+var _ = require("lodash");
+
+var common = require("../common.js");
+var Nav = require("./nav.js");
+var Messages = require("./messages.js");
+var Details = require("./details.js");
+
+var allTabs = {
+ request: Messages.Request,
+ response: Messages.Response,
+ error: Messages.Error,
+ details: Details
+};
+
+var FlowView = React.createClass({
+ mixins: [common.StickyHeadMixin, common.Navigation, common.State],
+ getTabs: function (flow) {
+ var tabs = [];
+ ["request", "response", "error"].forEach(function (e) {
+ if (flow[e]) {
+ tabs.push(e);
+ }
+ });
+ tabs.push("details");
+ return tabs;
+ },
+ nextTab: function (i) {
+ var tabs = this.getTabs(this.props.flow);
+ var currentIndex = tabs.indexOf(this.getParams().detailTab);
+ // JS modulo operator doesn't correct negative numbers, make sure that we are positive.
+ var nextIndex = (currentIndex + i + tabs.length) % tabs.length;
+ this.selectTab(tabs[nextIndex]);
+ },
+ selectTab: function (panel) {
+ this.replaceWith(
+ "flow",
+ {
+ flowId: this.getParams().flowId,
+ detailTab: panel
+ }
+ );
+ },
+ render: function () {
+ var flow = this.props.flow;
+ var tabs = this.getTabs(flow);
+ var active = this.getParams().detailTab;
+
+ if (!_.contains(tabs, active)) {
+ if (active === "response" && flow.error) {
+ active = "error";
+ } else if (active === "error" && flow.response) {
+ active = "response";
+ } else {
+ active = tabs[0];
+ }
+ this.selectTab(active);
+ }
+
+ var Tab = allTabs[active];
+ return (
+ <div className="flow-detail" onScroll={this.adjustHead}>
+ <Nav ref="head"
+ flow={flow}
+ tabs={tabs}
+ active={active}
+ selectTab={this.selectTab}/>
+ <Tab flow={flow}/>
+ </div>
+ );
+ }
+});
+
+module.exports = FlowView; \ No newline at end of file
diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js
new file mode 100644
index 00000000..fe8fa200
--- /dev/null
+++ b/web/src/js/components/flowview/messages.js
@@ -0,0 +1,91 @@
+var React = require("react");
+
+var flowutils = require("../../flow/utils.js");
+var utils = require("../../utils.js");
+var ContentView = require("./contentview.js");
+
+var Headers = React.createClass({
+ render: function () {
+ var rows = this.props.message.headers.map(function (header, i) {
+ return (
+ <tr key={i}>
+ <td className="header-name">{header[0] + ":"}</td>
+ <td className="header-value">{header[1]}</td>
+ </tr>
+ );
+ });
+ return (
+ <table className="header-table">
+ <tbody>
+ {rows}
+ </tbody>
+ </table>
+ );
+ }
+});
+
+var Request = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ var first_line = [
+ flow.request.method,
+ flowutils.RequestUtils.pretty_url(flow.request),
+ "HTTP/" + flow.request.httpversion.join(".")
+ ].join(" ");
+
+ //TODO: Styling
+
+ return (
+ <section>
+ <div className="first-line">{ first_line }</div>
+ <Headers message={flow.request}/>
+ <hr/>
+ <ContentView flow={flow} message={flow.request}/>
+ </section>
+ );
+ }
+});
+
+var Response = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+ var first_line = [
+ "HTTP/" + flow.response.httpversion.join("."),
+ flow.response.code,
+ flow.response.msg
+ ].join(" ");
+
+ //TODO: Styling
+
+ return (
+ <section>
+ <div className="first-line">{ first_line }</div>
+ <Headers message={flow.response}/>
+ <hr/>
+ <ContentView flow={flow} message={flow.response}/>
+ </section>
+ );
+ }
+});
+
+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
+}; \ No newline at end of file
diff --git a/web/src/js/components/flowview/nav.js b/web/src/js/components/flowview/nav.js
new file mode 100644
index 00000000..46eda707
--- /dev/null
+++ b/web/src/js/components/flowview/nav.js
@@ -0,0 +1,61 @@
+var React = require("react");
+
+var actions = require("../../actions.js");
+
+var NavAction = React.createClass({
+ onClick: function (e) {
+ e.preventDefault();
+ this.props.onClick();
+ },
+ render: function () {
+ return (
+ <a title={this.props.title}
+ href="#"
+ className="nav-action"
+ onClick={this.onClick}>
+ <i className={"fa fa-fw " + this.props.icon}></i>
+ </a>
+ );
+ }
+});
+
+var Nav = React.createClass({
+ render: function () {
+ var flow = this.props.flow;
+
+ var tabs = this.props.tabs.map(function (e) {
+ var str = e.charAt(0).toUpperCase() + e.slice(1);
+ var className = this.props.active === e ? "active" : "";
+ var onClick = function (event) {
+ this.props.selectTab(e);
+ event.preventDefault();
+ }.bind(this);
+ return <a key={e}
+ href="#"
+ className={className}
+ onClick={onClick}>{str}</a>;
+ }.bind(this));
+
+ var acceptButton = null;
+ if(flow.intercepted){
+ acceptButton = <NavAction title="[a]ccept intercepted flow" icon="fa-play" onClick={actions.FlowActions.accept.bind(null, flow)} />;
+ }
+ var revertButton = null;
+ if(flow.modified){
+ revertButton = <NavAction title="revert changes to flow [V]" icon="fa-history" onClick={actions.FlowActions.revert.bind(null, flow)} />;
+ }
+
+ return (
+ <nav ref="head" className="nav-tabs nav-tabs-sm">
+ {tabs}
+ <NavAction title="[d]elete flow" icon="fa-trash" onClick={actions.FlowActions.delete.bind(null, flow)} />
+ <NavAction title="[D]uplicate flow" icon="fa-copy" onClick={actions.FlowActions.duplicate.bind(null, flow)} />
+ <NavAction disabled title="[r]eplay flow" icon="fa-repeat" onClick={actions.FlowActions.replay.bind(null, flow)} />
+ {acceptButton}
+ {revertButton}
+ </nav>
+ );
+ }
+});
+
+module.exports = Nav; \ No newline at end of file
diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js
index d1684fb2..eca06e74 100644
--- a/web/src/js/components/header.js
+++ b/web/src/js/components/header.js
@@ -173,7 +173,7 @@ var MainMenu = React.createClass({
this.setQuery(d);
},
onInterceptChange: function (val) {
- SettingsActions.update({intercept: val});
+ actions.SettingsActions.update({intercept: val});
},
render: function () {
var filter = this.getQuery()[Query.FILTER] || "";
diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js
index 184ef49f..81bf3b03 100644
--- a/web/src/js/components/mainview.js
+++ b/web/src/js/components/mainview.js
@@ -7,7 +7,7 @@ var toputils = require("../utils.js");
var views = require("../store/view.js");
var Filt = require("../filt/filt.js");
FlowTable = require("./flowtable.js");
-var flowdetail = require("./flowdetail.js");
+var FlowView = require("./flowview/index.js");
var MainView = React.createClass({
mixins: [common.Navigation, common.State],
@@ -203,6 +203,8 @@ var MainView = React.createClass({
actions.FlowActions.revert(flow);
}
break;
+ case toputils.Key.SHIFT:
+ break;
default:
console.debug("keydown", e.keyCode);
return;
@@ -219,7 +221,7 @@ var MainView = React.createClass({
if (selected) {
details = [
<common.Splitter key="splitter"/>,
- <flowdetail.FlowDetail key="flowDetails" ref="flowDetails" flow={selected}/>
+ <FlowView key="flowDetails" ref="flowDetails" flow={selected}/>
];
} else {
details = null;
diff --git a/web/src/js/flow/utils.js b/web/src/js/flow/utils.js
index a95d4ffe..dd7f763b 100644
--- a/web/src/js/flow/utils.js
+++ b/web/src/js/flow/utils.js
@@ -1,6 +1,6 @@
var _ = require("lodash");
-var _MessageUtils = {
+var MessageUtils = {
getContentType: function (message) {
return this.get_first_header(message, /^Content-Type$/i);
},
@@ -42,7 +42,7 @@ var defaultPorts = {
"https": 443
};
-var RequestUtils = _.extend(_MessageUtils, {
+var RequestUtils = _.extend(MessageUtils, {
pretty_host: function (request) {
//FIXME: Add hostheader
return request.host;
@@ -56,11 +56,11 @@ var RequestUtils = _.extend(_MessageUtils, {
}
});
-var ResponseUtils = _.extend(_MessageUtils, {});
+var ResponseUtils = _.extend(MessageUtils, {});
module.exports = {
ResponseUtils: ResponseUtils,
- RequestUtils: RequestUtils
-
-} \ No newline at end of file
+ RequestUtils: RequestUtils,
+ MessageUtils: MessageUtils
+}; \ No newline at end of file
diff --git a/web/src/js/store/view.js b/web/src/js/store/view.js
index 204d22da..d13822d5 100644
--- a/web/src/js/store/view.js
+++ b/web/src/js/store/view.js
@@ -14,8 +14,6 @@ var default_filt = function (elem) {
function StoreView(store, filt, sortfun) {
EventEmitter.call(this);
- filt = filt || default_filt;
- sortfun = sortfun || default_sort;
this.store = store;
@@ -39,10 +37,10 @@ _.extend(StoreView.prototype, EventEmitter.prototype, {
this.store.removeListener("recalculate", this.recalculate);
},
recalculate: function (filt, sortfun) {
- filt = filt || default_filt;
- sortfun = sortfun || default_sort;
+ filt = filt || this.filt || default_filt;
+ sortfun = sortfun || this.sortfun || default_sort;
filt = filt.bind(this);
- sortfun = sortfun.bind(this)
+ sortfun = sortfun.bind(this);
this.filt = filt;
this.sortfun = sortfun;
diff --git a/web/src/js/utils.js b/web/src/js/utils.js
index bcd3958d..48f69880 100644
--- a/web/src/js/utils.js
+++ b/web/src/js/utils.js
@@ -15,6 +15,7 @@ var Key = {
TAB: 9,
SPACE: 32,
BACKSPACE: 8,
+ SHIFT: 16
};
// Add A-Z
for (var i = 65; i <= 90; i++) {