From 818c5918b648b29f3692bd2cc6ebcf326d4d2497 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 19 Sep 2014 17:56:54 +0200 Subject: web: display flow connection info --- web/src/js/components/flowdetail.jsx.js | 123 +++++++++++++++++++++++-- web/src/js/components/flowtable-columns.jsx.js | 39 ++++---- web/src/js/components/flowtable.jsx.js | 44 +-------- web/src/js/components/mainview.jsx.js | 77 ++++++++++++++-- web/src/js/components/utils.jsx.js | 6 +- web/src/js/utils.js | 24 ++++- 6 files changed, 233 insertions(+), 80 deletions(-) (limited to 'web/src/js') diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js index e5fe37a0..7c984193 100644 --- a/web/src/js/components/flowdetail.jsx.js +++ b/web/src/js/components/flowdetail.jsx.js @@ -3,7 +3,7 @@ var FlowDetailNav = React.createClass({ render: function(){ - var items = ["request", "response", "details"].map(function(e){ + var items = 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(){ @@ -28,11 +28,11 @@ var Headers = React.createClass({ var rows = this.props.message.headers.map(function(header){ return ( - {header[0]} + {header[0]+":"} {header[1]} ); - }) + }); return ( @@ -41,7 +41,7 @@ var Headers = React.createClass({
); } -}) +}); var FlowDetailRequest = React.createClass({ render: function(){ @@ -99,9 +99,106 @@ var FlowDetailResponse = React.createClass({ } }); +var TimeStamp = React.createClass({ + render: function() { + var ts, delta; + + if(!this.props.t && this.props.optional){ + //should be return null, but that triggers a React bug. + return ; + } 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 = {"(" + delta + ")"}; + } else { + delta = null; + } + } + + return {this.props.title + ":"}{ts} {delta}; + } +}); + +var ConnectionInfo = React.createClass({ + + render: function() { + var conn = this.props.conn; + var address = conn.address.address.join(":"); + + var sni = ; //should be null, but that triggers a React bug. + if(conn.sni){ + sni = TLS SNI:{conn.sni}; + } + return ( + + + + {sni} + + + + + +
Address:{address}
+ ); + } +}); + +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; + return ( +
+ {client_conn.cert ?

Client Certificate

: null} + {client_conn.cert ?
{client_conn.cert}
: null} + + {server_conn.cert ?

Server Certificate

: null} + {server_conn.cert ?
{server_conn.cert}
: null} +
+ ); + } +}); + var FlowDetailConnectionInfo = React.createClass({ render: function(){ - return
details
; + var flow = this.props.flow; + var client_conn = flow.client_conn; + var server_conn = flow.server_conn; + return ( +
+ +

Client Connection

+ + +

Server Connection

+ + + + +
+ ); } }); @@ -112,13 +209,27 @@ var tabs = { }; var FlowDetail = React.createClass({ + getDefaultProps: function(){ + return { + tabs: ["request","response", "details"] + }; + }, mixins: [StickyHeadMixin], + nextTab: function(i) { + var currentIndex = this.props.tabs.indexOf(this.props.active); + // JS modulo operator doesn't correct negative numbers, make sure that we are positive. + var nextIndex = (currentIndex + i + this.props.tabs.length) % this.props.tabs.length; + this.props.selectTab(this.props.tabs[nextIndex]); + }, render: function(){ var flow = JSON.stringify(this.props.flow, null, 2); var Tab = tabs[this.props.active]; return (
- +
); diff --git a/web/src/js/components/flowtable-columns.jsx.js b/web/src/js/components/flowtable-columns.jsx.js index 676b005b..728bc953 100644 --- a/web/src/js/components/flowtable-columns.jsx.js +++ b/web/src/js/components/flowtable-columns.jsx.js @@ -28,25 +28,32 @@ var IconColumn = React.createClass({ }, render: function(){ var flow = this.props.flow; - 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) { - icon = "resource-icon-redirect"; - } else if(contentType.indexOf("image") >= 0) { - icon = "resource-icon-image"; - } else if (contentType.indexOf("javascript") >= 0) { - icon = "resource-icon-js"; - } else if (contentType.indexOf("css") >= 0) { - icon = "resource-icon-css"; - } else if (contentType.indexOf("html") >= 0) { - icon = "resource-icon-document"; - } else { + if(flow.response){ + 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) { + icon = "resource-icon-redirect"; + } else if(contentType.indexOf("image") >= 0) { + icon = "resource-icon-image"; + } else if (contentType.indexOf("javascript") >= 0) { + icon = "resource-icon-js"; + } else if (contentType.indexOf("css") >= 0) { + icon = "resource-icon-css"; + } else if (contentType.indexOf("html") >= 0) { + icon = "resource-icon-document"; + } + } + if(!icon){ icon = "resource-icon-plain"; } + + icon += " resource-icon"; return
; } @@ -123,7 +130,7 @@ var TimeColumn = React.createClass({ var flow = this.props.flow; var time; if(flow.response){ - time = Math.round(1000 * (flow.response.timestamp_end - flow.request.timestamp_start))+"ms"; + time = formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); } else { time = "..."; } diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 47576d70..146d5264 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -38,7 +38,7 @@ var FlowTableBody = React.createClass({ selectFlow={this.props.selectFlow} />; }.bind(this)); - return {rows}; + return {rows}; } }); @@ -69,45 +69,6 @@ var FlowTable = React.createClass({ viewport.scrollTop = flowNode_bottom - viewport.offsetHeight; } }, - selectFlowRelative: function(i){ - var index; - if(!this.props.selected){ - if(i > 0){ - index = this.props.flows.length-1; - } else { - index = 0; - } - } else { - index = _.findIndex(this.props.flows, function(f){ - return f === this.props.selected; - }.bind(this)); - index = Math.min(Math.max(0, index+i), this.props.flows.length-1); - } - this.props.selectFlow(this.props.flows[index]); - }, - onKeyDown: function(e){ - switch(e.keyCode){ - case Key.DOWN: - this.selectFlowRelative(+1); - break; - case Key.UP: - this.selectFlowRelative(-1); - break; - case Key.PAGE_DOWN: - this.selectFlowRelative(+10); - break; - case Key.PAGE_UP: - this.selectFlowRelative(-10); - break; - case Key.ESC: - this.props.selectFlow(null); - break; - default: - console.debug("keydown", e.keyCode); - return; - } - return false; - }, render: function () { return (
@@ -118,8 +79,7 @@ var FlowTable = React.createClass({ flows={this.props.flows} selected={this.props.selected} selectFlow={this.props.selectFlow} - columns={this.state.columns} - onKeyDown={this.onKeyDown}/> + columns={this.state.columns}/>
); diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index 79eb58ea..d521635a 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -20,6 +20,15 @@ var MainView = React.createClass({ flows: this.flowStore.getAll() }); }, + selectDetailTab: function(panel) { + ReactRouter.replaceWith( + "flow", + { + flowId: this.props.params.flowId, + detailTab: panel + } + ); + }, selectFlow: function(flow) { if(flow){ ReactRouter.replaceWith( @@ -34,19 +43,65 @@ var MainView = React.createClass({ ReactRouter.replaceWith("flows"); } }, - selectDetailTab: function(panel) { - ReactRouter.replaceWith( - "flow", - { - flowId: this.props.params.flowId, - detailTab: panel + selectFlowRelative: function(i){ + var index; + if(!this.props.params.flowId){ + if(i > 0){ + index = this.state.flows.length-1; + } else { + index = 0; } - ); + } else { + index = _.findIndex(this.state.flows, function(f){ + return f.id === this.props.params.flowId; + }.bind(this)); + index = Math.min(Math.max(0, index+i), this.state.flows.length-1); + } + this.selectFlow(this.state.flows[index]); + }, + onKeyDown: function(e){ + switch(e.keyCode){ + case Key.K: + case Key.UP: + this.selectFlowRelative(-1); + break; + case Key.J: + case Key.DOWN: + this.selectFlowRelative(+1); + break; + case Key.SPACE: + case Key.PAGE_DOWN: + this.selectFlowRelative(+10); + break; + case Key.PAGE_UP: + this.selectFlowRelative(-10); + break; + case Key.ESC: + this.selectFlow(null); + break; + case Key.H: + case Key.LEFT: + if(this.refs.flowDetails){ + this.refs.flowDetails.nextTab(-1); + } + break; + case Key.L: + case Key.TAB: + case Key.RIGHT: + if(this.refs.flowDetails){ + this.refs.flowDetails.nextTab(+1); + } + break; + default: + console.debug("keydown", e.keyCode); + return; + } + return false; }, render: function() { var selected = _.find(this.state.flows, { id: this.props.params.flowId }); - var details = null; + var details; if(selected){ details = ( ); + } else { + details = null; } return ( -
+
- + { details ? : null } {details}
); diff --git a/web/src/js/components/utils.jsx.js b/web/src/js/components/utils.jsx.js index 442bef23..91cb8458 100644 --- a/web/src/js/components/utils.jsx.js +++ b/web/src/js/components/utils.jsx.js @@ -4,9 +4,9 @@ var Splitter = React.createClass({ getDefaultProps: function () { - return { - axis: "x" - } + return { + axis: "x" + }; }, getInitialState: function(){ return { diff --git a/web/src/js/utils.js b/web/src/js/utils.js index e53097f8..782618c2 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -29,14 +29,32 @@ var Key = { LEFT: 37, RIGHT: 39, ENTER: 13, - ESC: 27 + ESC: 27, + TAB: 9, + SPACE: 32, + J: 74, + K: 75, + H: 72, + L: 76 }; -var formatSize = function (size) { +var formatSize = function (bytes) { + var size = bytes; var prefix = ["B", "KB", "MB", "GB", "TB"]; while (size >= 1024 && prefix.length > 1) { prefix.shift(); size = size / 1024; } - return (Math.floor(size * 100) / 100.0) + prefix.shift(); + return (Math.floor(size * 100) / 100.0).toFixed(2) + prefix.shift(); +}; + +var formatTimeDelta = function (milliseconds) { + var time = milliseconds; + var prefix = ["ms", "s", "m", "h"]; + var div = [1000, 60, 60]; + while (time >= div[0] && prefix.length > 1) { + prefix.shift(); + time = time / div.shift(); + } + return Math.round(time) + prefix.shift(); }; \ No newline at end of file -- cgit v1.2.3