diff options
| -rw-r--r-- | libmproxy/web/static/css/app.css | 14 | ||||
| -rw-r--r-- | libmproxy/web/static/js/app.js | 152 | ||||
| -rw-r--r-- | web/src/css/flowdetail.less | 22 | ||||
| -rw-r--r-- | web/src/css/flowtable.less | 4 | ||||
| -rw-r--r-- | web/src/js/components/flowdetail.jsx.js | 134 | ||||
| -rw-r--r-- | web/src/js/components/flowtable-columns.jsx.js | 4 | ||||
| -rw-r--r-- | web/src/js/components/header.jsx.js | 4 | ||||
| -rw-r--r-- | web/src/js/utils.js | 6 | 
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();      }  | 
