aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libmproxy/web/static/css/app.css14
-rw-r--r--libmproxy/web/static/js/app.js152
-rw-r--r--web/src/css/flowdetail.less22
-rw-r--r--web/src/css/flowtable.less4
-rw-r--r--web/src/js/components/flowdetail.jsx.js134
-rw-r--r--web/src/js/components/flowtable-columns.jsx.js4
-rw-r--r--web/src/js/components/header.jsx.js4
-rw-r--r--web/src/js/utils.js6
8 files changed, 234 insertions, 106 deletions
diff --git a/libmproxy/web/static/css/app.css b/libmproxy/web/static/css/app.css
index 2459f406..f0ed95dd 100644
--- a/libmproxy/web/static/css/app.css
+++ b/libmproxy/web/static/css/app.css
@@ -148,9 +148,6 @@ header .menu {
font-weight: normal;
box-shadow: 0 1px 0 #a6a6a6;
}
-.flow-table tbody {
- outline: 0;
-}
.flow-table tr {
cursor: pointer;
}
@@ -198,11 +195,16 @@ header .menu {
background-color: #F2F2F2;
}
.flow-detail section {
- padding: 5px;
+ padding: 5px 12px;
}
-.flow-detail code {
+.flow-detail .first-line {
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+ background-color: #428bca;
+ color: white;
+ margin: 0 -8px;
+ padding: 4px 8px;
+ border-radius: 5px;
word-break: break-all;
- padding-left: 0;
}
.flow-detail table {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js
index b86e462a..5d40f069 100644
--- a/libmproxy/web/static/js/app.js
+++ b/libmproxy/web/static/js/app.js
@@ -41,7 +41,7 @@ var Key = {
var formatSize = function (bytes) {
var size = bytes;
var prefix = ["B", "KB", "MB", "GB", "TB"];
- while (size >= 1024 && prefix.length > 1) {
+ while (Math.abs(size) >= 1024 && prefix.length > 1) {
prefix.shift();
size = size / 1024;
}
@@ -50,9 +50,9 @@ var formatSize = function (bytes) {
var formatTimeDelta = function (milliseconds) {
var time = milliseconds;
- var prefix = ["ms", "s", "m", "h"];
+ var prefix = ["ms", "s", "min", "h"];
var div = [1000, 60, 60];
- while (time >= div[0] && prefix.length > 1) {
+ while (Math.abs(time) >= div[0] && prefix.length > 1) {
prefix.shift();
time = time / div.shift();
}
@@ -617,12 +617,12 @@ var Header = React.createClass({displayName: 'Header',
console.log("File click");
},
render: function () {
- var header = header_entries.map(function(entry){
+ var header = header_entries.map(function(entry, i){
var classes = React.addons.classSet({
active: entry == this.state.active
});
return (
- React.DOM.a({key: entry.title,
+ React.DOM.a({key: i,
href: "#",
className: classes,
onClick: this.handleClick.bind(this, entry)
@@ -685,7 +685,6 @@ var IconColumn = React.createClass({displayName: 'IconColumn',
var contentType = ResponseUtils.getContentType(flow.response);
//TODO: We should assign a type to the flow somewhere else.
- var icon;
if(flow.response.code == 304) {
icon = "resource-icon-not-modified";
} else if(300 <= flow.response.code && flow.response.code < 400) {
@@ -763,9 +762,10 @@ var SizeColumn = React.createClass({displayName: 'SizeColumn',
},
render: function(){
var flow = this.props.flow;
+
var total = flow.request.contentLength;
if(flow.response){
- total += flow.response.contentLength;
+ total += flow.response.contentLength || 0;
}
var size = formatSize(total);
return React.DOM.td({className: "col-size"}, size);
@@ -917,9 +917,9 @@ var FlowDetailNav = React.createClass({displayName: 'FlowDetailNav',
var Headers = React.createClass({displayName: 'Headers',
render: function(){
- var rows = this.props.message.headers.map(function(header){
+ var rows = this.props.message.headers.map(function(header, i){
return (
- React.DOM.tr(null,
+ React.DOM.tr({key: i},
React.DOM.td({className: "header-name"}, header[0]+":"),
React.DOM.td({className: "header-value"}, header[1])
)
@@ -954,7 +954,7 @@ var FlowDetailRequest = React.createClass({displayName: 'FlowDetailRequest',
return (
React.DOM.section(null,
- React.DOM.code(null, first_line ),
+ React.DOM.div({className: "first-line"}, first_line ),
Headers({message: flow.request}),
React.DOM.hr(null),
content
@@ -982,7 +982,7 @@ var FlowDetailResponse = React.createClass({displayName: 'FlowDetailResponse',
return (
React.DOM.section(null,
- React.DOM.code(null, first_line ),
+ React.DOM.div({className: "first-line"}, first_line ),
Headers({message: flow.response}),
React.DOM.hr(null),
content
@@ -993,23 +993,21 @@ var FlowDetailResponse = React.createClass({displayName: 'FlowDetailResponse',
var TimeStamp = React.createClass({displayName: 'TimeStamp',
render: function() {
- var ts, delta;
- if(!this.props.t && this.props.optional){
+ if(!this.props.t){
//should be return null, but that triggers a React bug.
return React.DOM.tr(null);
- } else if (!this.props.t){
- ts = "active";
- } else {
- ts = (new Date(this.props.t * 1000)).toISOString();
- ts = ts.replace("T", " ").replace("Z","");
+ }
- if(this.props.deltaTo){
- delta = Math.round((this.props.t-this.props.deltaTo)*1000) + "ms";
- delta = React.DOM.span({className: "text-muted"}, "(" + delta + ")");
- } else {
- delta = null;
- }
+ var ts = (new Date(this.props.t * 1000)).toISOString();
+ ts = ts.replace("T", " ").replace("Z","");
+
+ var delta;
+ if(this.props.deltaTo){
+ delta = formatTimeDelta(1000 * (this.props.t-this.props.deltaTo));
+ delta = React.DOM.span({className: "text-muted"}, "(" + delta + ")");
+ } else {
+ delta = null;
}
return React.DOM.tr(null, React.DOM.td(null, this.props.title + ":"), React.DOM.td(null, ts, " ", delta));
@@ -1030,24 +1028,7 @@ var ConnectionInfo = React.createClass({displayName: 'ConnectionInfo',
React.DOM.table({className: "connection-table"},
React.DOM.tbody(null,
React.DOM.tr({key: "address"}, React.DOM.td(null, "Address:"), React.DOM.td(null, address)),
- sni,
- TimeStamp({title: "Start time",
- key: "start",
- t: conn.timestamp_start}),
- TimeStamp({title: "TCP Setup",
- key: "tcpsetup",
- t: conn.timestamp_tcp_setup,
- deltaTo: conn.timestamp_start,
- optional: true}),
- TimeStamp({title: "SSL handshake",
- key: "sslsetup",
- t: conn.timestamp_ssl_setup,
- deltaTo: conn.timestamp_start,
- optional: true}),
- TimeStamp({title: "End time",
- key: "end",
- t: conn.timestamp_end,
- deltaTo: conn.timestamp_start})
+ sni
)
)
);
@@ -1061,13 +1042,92 @@ var CertificateInfo = React.createClass({displayName: 'CertificateInfo',
var flow = this.props.flow;
var client_conn = flow.client_conn;
var server_conn = flow.server_conn;
+
+ var preStyle = {maxHeight: 100};
return (
React.DOM.div(null,
client_conn.cert ? React.DOM.h4(null, "Client Certificate") : null,
- client_conn.cert ? React.DOM.pre(null, client_conn.cert) : null,
+ client_conn.cert ? React.DOM.pre({style: preStyle}, client_conn.cert) : null,
server_conn.cert ? React.DOM.h4(null, "Server Certificate") : null,
- server_conn.cert ? React.DOM.pre(null, server_conn.cert) : null
+ server_conn.cert ? React.DOM.pre({style: preStyle}, server_conn.cert) : null
+ )
+ );
+ }
+});
+
+var Timing = React.createClass({displayName: 'Timing',
+ 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 (
+ React.DOM.div(null,
+ React.DOM.h4(null, "Timing"),
+ React.DOM.table(null,
+ React.DOM.tbody(null,
+ rows
+ )
+ )
)
);
}
@@ -1087,7 +1147,9 @@ var FlowDetailConnectionInfo = React.createClass({displayName: 'FlowDetailConnec
React.DOM.h4(null, "Server Connection"),
ConnectionInfo({conn: server_conn}),
- CertificateInfo({flow: flow})
+ CertificateInfo({flow: flow}),
+
+ Timing({flow: flow})
)
);
diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less
index 5e27e7e6..94920caa 100644
--- a/web/src/css/flowdetail.less
+++ b/web/src/css/flowdetail.less
@@ -1,3 +1,9 @@
+//TODO: Move into some utils
+.monospace(){
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+}
+
+
.flow-detail {
width: 100%;
overflow: auto;
@@ -7,20 +13,20 @@
}
section {
- padding: 5px;
+ padding: 5px 12px;
}
- //FIXME: Style properly
- code {
+ .first-line {
+ .monospace();
+ background-color: #428bca;
+ color: white;
+ margin: 0 -8px;
+ padding: 4px 8px;
+ border-radius: 5px;
word-break: break-all;
- padding-left: 0;
}
}
-//TODO: Move into some utils
-.monospace(){
- font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
-}
.flow-detail table {
.monospace();
diff --git a/web/src/css/flowtable.less b/web/src/css/flowtable.less
index db4c6f12..64fd86b9 100644
--- a/web/src/css/flowtable.less
+++ b/web/src/css/flowtable.less
@@ -17,10 +17,6 @@
box-shadow: 0 1px 0 #a6a6a6;
}
- tbody {
- outline: 0;
- }
-
tr {
cursor: pointer;
&.selected {
diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js
index 7c984193..ad1cfe67 100644
--- a/web/src/js/components/flowdetail.jsx.js
+++ b/web/src/js/components/flowdetail.jsx.js
@@ -25,9 +25,9 @@ var FlowDetailNav = React.createClass({
var Headers = React.createClass({
render: function(){
- var rows = this.props.message.headers.map(function(header){
+ var rows = this.props.message.headers.map(function(header, i){
return (
- <tr>
+ <tr key={i}>
<td className="header-name">{header[0]+":"}</td>
<td className="header-value">{header[1]}</td>
</tr>
@@ -62,7 +62,7 @@ var FlowDetailRequest = React.createClass({
return (
<section>
- <code>{ first_line }</code>
+ <div className="first-line">{ first_line }</div>
<Headers message={flow.request}/>
<hr/>
{content}
@@ -90,7 +90,7 @@ var FlowDetailResponse = React.createClass({
return (
<section>
- <code>{ first_line }</code>
+ <div className="first-line">{ first_line }</div>
<Headers message={flow.response}/>
<hr/>
{content}
@@ -101,23 +101,21 @@ var FlowDetailResponse = React.createClass({
var TimeStamp = React.createClass({
render: function() {
- var ts, delta;
- if(!this.props.t && this.props.optional){
+ if(!this.props.t){
//should be return null, but that triggers a React bug.
return <tr></tr>;
- } else if (!this.props.t){
- ts = "active";
+ }
+
+ var ts = (new Date(this.props.t * 1000)).toISOString();
+ ts = ts.replace("T", " ").replace("Z","");
+
+ var delta;
+ if(this.props.deltaTo){
+ delta = formatTimeDelta(1000 * (this.props.t-this.props.deltaTo));
+ delta = <span className="text-muted">{"(" + delta + ")"}</span>;
} else {
- ts = (new Date(this.props.t * 1000)).toISOString();
- ts = ts.replace("T", " ").replace("Z","");
-
- if(this.props.deltaTo){
- delta = Math.round((this.props.t-this.props.deltaTo)*1000) + "ms";
- delta = <span className="text-muted">{"(" + delta + ")"}</span>;
- } else {
- delta = null;
- }
+ delta = null;
}
return <tr><td>{this.props.title + ":"}</td><td>{ts} {delta}</td></tr>;
@@ -139,23 +137,6 @@ var ConnectionInfo = React.createClass({
<tbody>
<tr key="address"><td>Address:</td><td>{address}</td></tr>
{sni}
- <TimeStamp title="Start time"
- key="start"
- t={conn.timestamp_start} />
- <TimeStamp title="TCP Setup"
- key="tcpsetup"
- t={conn.timestamp_tcp_setup}
- deltaTo={conn.timestamp_start}
- optional={true} />
- <TimeStamp title="SSL handshake"
- key="sslsetup"
- t={conn.timestamp_ssl_setup}
- deltaTo={conn.timestamp_start}
- optional={true} />
- <TimeStamp title="End time"
- key="end"
- t={conn.timestamp_end}
- deltaTo={conn.timestamp_start} />
</tbody>
</table>
);
@@ -169,13 +150,92 @@ var CertificateInfo = React.createClass({
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>{client_conn.cert}</pre> : null}
+ {client_conn.cert ? <pre style={preStyle}>{client_conn.cert}</pre> : null}
{server_conn.cert ? <h4>Server Certificate</h4> : null}
- {server_conn.cert ? <pre>{server_conn.cert}</pre> : 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>
+ <tbody>
+ {rows}
+ </tbody>
+ </table>
</div>
);
}
@@ -197,6 +257,8 @@ var FlowDetailConnectionInfo = React.createClass({
<CertificateInfo flow={flow}/>
+ <Timing flow={flow}/>
+
</section>
);
}
diff --git a/web/src/js/components/flowtable-columns.jsx.js b/web/src/js/components/flowtable-columns.jsx.js
index ec63b03f..88e0cf22 100644
--- a/web/src/js/components/flowtable-columns.jsx.js
+++ b/web/src/js/components/flowtable-columns.jsx.js
@@ -34,7 +34,6 @@ var IconColumn = React.createClass({
var contentType = ResponseUtils.getContentType(flow.response);
//TODO: We should assign a type to the flow somewhere else.
- var icon;
if(flow.response.code == 304) {
icon = "resource-icon-not-modified";
} else if(300 <= flow.response.code && flow.response.code < 400) {
@@ -112,9 +111,10 @@ var SizeColumn = React.createClass({
},
render: function(){
var flow = this.props.flow;
+
var total = flow.request.contentLength;
if(flow.response){
- total += flow.response.contentLength;
+ total += flow.response.contentLength || 0;
}
var size = formatSize(total);
return <td className="col-size">{size}</td>;
diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js
index 92a58282..994bc759 100644
--- a/web/src/js/components/header.jsx.js
+++ b/web/src/js/components/header.jsx.js
@@ -62,12 +62,12 @@ var Header = React.createClass({
console.log("File click");
},
render: function () {
- var header = header_entries.map(function(entry){
+ var header = header_entries.map(function(entry, i){
var classes = React.addons.classSet({
active: entry == this.state.active
});
return (
- <a key={entry.title}
+ <a key={i}
href="#"
className={classes}
onClick={this.handleClick.bind(this, entry)}
diff --git a/web/src/js/utils.js b/web/src/js/utils.js
index 782618c2..95859381 100644
--- a/web/src/js/utils.js
+++ b/web/src/js/utils.js
@@ -41,7 +41,7 @@ var Key = {
var formatSize = function (bytes) {
var size = bytes;
var prefix = ["B", "KB", "MB", "GB", "TB"];
- while (size >= 1024 && prefix.length > 1) {
+ while (Math.abs(size) >= 1024 && prefix.length > 1) {
prefix.shift();
size = size / 1024;
}
@@ -50,9 +50,9 @@ var formatSize = function (bytes) {
var formatTimeDelta = function (milliseconds) {
var time = milliseconds;
- var prefix = ["ms", "s", "m", "h"];
+ var prefix = ["ms", "s", "min", "h"];
var div = [1000, 60, 60];
- while (time >= div[0] && prefix.length > 1) {
+ while (Math.abs(time) >= div[0] && prefix.length > 1) {
prefix.shift();
time = time / div.shift();
}