aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2014-09-18 02:22:10 +0200
committerMaximilian Hils <git@maximilianhils.com>2014-09-18 02:22:10 +0200
commit0d64cc93278d39bd4c87cf5110d326f57574c8a1 (patch)
tree41cdb8ea3fdd51304c23234ca8fd7c2372d96988 /web/src/js
parent6a161be6b4c526fcc5f6581c7faff00a2c976f37 (diff)
downloadmitmproxy-0d64cc93278d39bd4c87cf5110d326f57574c8a1.tar.gz
mitmproxy-0d64cc93278d39bd4c87cf5110d326f57574c8a1.tar.bz2
mitmproxy-0d64cc93278d39bd4c87cf5110d326f57574c8a1.zip
flowtable: add selection indicator, add keyboard navigation
Diffstat (limited to 'web/src/js')
-rw-r--r--web/src/js/components/flowtable-columns.jsx.js95
-rw-r--r--web/src/js/components/flowtable.jsx.js192
-rw-r--r--web/src/js/components/proxyapp.jsx.js2
-rw-r--r--web/src/js/stores/flowstore.js9
-rw-r--r--web/src/js/utils.js10
5 files changed, 204 insertions, 104 deletions
diff --git a/web/src/js/components/flowtable-columns.jsx.js b/web/src/js/components/flowtable-columns.jsx.js
new file mode 100644
index 00000000..e0cee365
--- /dev/null
+++ b/web/src/js/components/flowtable-columns.jsx.js
@@ -0,0 +1,95 @@
+/** @jsx React.DOM */
+
+
+var TLSColumn = React.createClass({
+ statics: {
+ renderTitle: function(){
+ return <th key="tls" className="col-tls"></th>;
+ }
+ },
+ render: function(){
+ var flow = this.props.flow;
+ var ssl = (flow.request.scheme == "https");
+ return <td className={ssl ? "col-tls-https" : "col-tls-http"}></td>;
+ }
+});
+
+
+var IconColumn = React.createClass({
+ statics: {
+ renderTitle: function(){
+ return <th key="icon" className="col-icon"></th>;
+ }
+ },
+ render: function(){
+ var flow = this.props.flow;
+ return <td className="resource-icon resource-icon-plain"></td>;
+ }
+});
+
+var PathColumn = React.createClass({
+ statics: {
+ renderTitle: function(){
+ return <th key="path" className="col-path">Path</th>;
+ }
+ },
+ render: function(){
+ var flow = this.props.flow;
+ return <td>{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>;
+ }
+});
+
+
+var MethodColumn = React.createClass({
+ statics: {
+ renderTitle: function(){
+ return <th key="method" className="col-method">Method</th>;
+ }
+ },
+ render: function(){
+ var flow = this.props.flow;
+ return <td>{flow.request.method}</td>;
+ }
+});
+
+
+var StatusColumn = React.createClass({
+ statics: {
+ renderTitle: function(){
+ return <th key="status" className="col-status">Status</th>;
+ }
+ },
+ render: function(){
+ var flow = this.props.flow;
+ var status;
+ if(flow.response){
+ status = flow.response.code;
+ } else {
+ status = null;
+ }
+ return <td>{status}</td>;
+ }
+});
+
+
+var TimeColumn = React.createClass({
+ statics: {
+ renderTitle: function(){
+ return <th key="time" className="col-time">Time</th>;
+ }
+ },
+ render: function(){
+ var flow = this.props.flow;
+ var time;
+ if(flow.response){
+ time = Math.round(1000 * (flow.response.timestamp_end - flow.request.timestamp_start))+"ms";
+ } else {
+ time = "...";
+ }
+ return <td>{time}</td>;
+ }
+});
+
+
+var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, TimeColumn];
+
diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js
index 39721baf..b1b6fa98 100644
--- a/web/src/js/components/flowtable.jsx.js
+++ b/web/src/js/components/flowtable.jsx.js
@@ -9,7 +9,14 @@ var FlowRow = React.createClass({
flow: flow
});
}.bind(this));
- return <tr onClick={this.props.onClick} >{columns}</tr>;
+ var className = "";
+ if(this.props.selected){
+ className += "selected";
+ }
+ return (
+ <tr className={className} onClick={this.props.selectFlow.bind(null, flow)}>
+ {columns}
+ </tr>);
}
});
@@ -25,107 +32,20 @@ var FlowTableHead = React.createClass({
var FlowTableBody = React.createClass({
render: function(){
var rows = this.props.flows.map(function(flow){
- //TODO: Add UUID
- return <FlowRow onClick={this.props.onClick} flow={flow} columns={this.props.columns}/>;
+ var selected = (flow == this.props.selected);
+ return <FlowRow key={flow.id}
+ ref={flow.id}
+ flow={flow}
+ columns={this.props.columns}
+ selected={selected}
+ selectFlow={this.props.selectFlow}
+ />;
}.bind(this));
- return <tbody>{rows}</tbody>;
- }
-});
-
-
-var TLSColumn = React.createClass({
- statics: {
- renderTitle: function(){
- return <th key="tls" className="col-tls"></th>;
- }
- },
- render: function(){
- var flow = this.props.flow;
- var ssl = (flow.request.scheme == "https");
- return <td className={ssl ? "col-tls-https" : "col-tls-http"}></td>;
- }
-});
-
-
-var IconColumn = React.createClass({
- statics: {
- renderTitle: function(){
- return <th key="icon" className="col-icon"></th>;
- }
- },
- render: function(){
- var flow = this.props.flow;
- return <td className="resource-icon resource-icon-plain"></td>;
- }
-});
-
-var PathColumn = React.createClass({
- statics: {
- renderTitle: function(){
- return <th key="path" className="col-path">Path</th>;
- }
- },
- render: function(){
- var flow = this.props.flow;
- return <td>{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>;
- }
-});
-
-
-var MethodColumn = React.createClass({
- statics: {
- renderTitle: function(){
- return <th key="method" className="col-method">Method</th>;
- }
- },
- render: function(){
- var flow = this.props.flow;
- return <td>{flow.request.method}</td>;
+ return <tbody onKeyDown={this.props.onKeyDown} tabIndex="0">{rows}</tbody>;
}
});
-var StatusColumn = React.createClass({
- statics: {
- renderTitle: function(){
- return <th key="status" className="col-status">Status</th>;
- }
- },
- render: function(){
- var flow = this.props.flow;
- var status;
- if(flow.response){
- status = flow.response.code;
- } else {
- status = null;
- }
- return <td>{status}</td>;
- }
-});
-
-
-var TimeColumn = React.createClass({
- statics: {
- renderTitle: function(){
- return <th key="time" className="col-time">Time</th>;
- }
- },
- render: function(){
- var flow = this.props.flow;
- var time;
- if(flow.response){
- time = Math.round(1000 * (flow.response.timestamp_end - flow.request.timestamp_start))+"ms";
- } else {
- time = "...";
- }
- return <td>{time}</td>;
- }
-});
-
-
-var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, TimeColumn];
-
-
var FlowTable = React.createClass({
getInitialState: function () {
return {
@@ -146,18 +66,88 @@ var FlowTable = React.createClass({
flows: this.flowStore.getAll()
});
},
- onClick: function(e){
- console.log("rowclick", e);
+ selectFlow: function(flow){
+ this.setState({
+ selected: flow
+ });
+
+ // Now comes the fun part: Scroll the flow into the view.
+ var viewport = this.getDOMNode();
+ var flowNode = this.refs.body.refs[flow.id].getDOMNode();
+ var viewport_top = viewport.scrollTop;
+ var viewport_bottom = viewport_top + viewport.offsetHeight;
+ var flowNode_top = flowNode.offsetTop;
+ var flowNode_bottom = flowNode_top + flowNode.offsetHeight;
+
+ // Account for pinned thead by pretending that the flowNode starts
+ // -thead_height pixel earlier.
+ flowNode_top -= this.refs.body.getDOMNode().offsetTop;
+
+ if(flowNode_top < viewport_top){
+ viewport.scrollTop = flowNode_top;
+ } else if(flowNode_bottom > viewport_bottom) {
+ viewport.scrollTop = flowNode_bottom - viewport.offsetHeight;
+ }
+ },
+ selectRowRelative: function(i){
+ var index;
+ if(!this.state.selected){
+ if(i > 0){
+ index = this.flows.length-1;
+ } else {
+ index = 0;
+ }
+ } else {
+ index = _.findIndex(this.state.flows, function(f){
+ return f === this.state.selected;
+ }.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.DOWN:
+ this.selectRowRelative(+1);
+ return false;
+ break;
+ case Key.UP:
+ this.selectRowRelative(-1);
+ return false;
+ break;
+ case Key.ENTER:
+ console.log("Open details pane...", this.state.selected);
+ break;
+ case Key.ESC:
+ console.log("")
+ default:
+ console.debug("keydown", e.keyCode);
+ return;
+ }
+ return false;
+ },
+ onScroll: function(e){
+ //Abusing CSS transforms to set thead into position:fixed.
+ var head = this.refs.head.getDOMNode();
+ head.style.transform = "translate(0,"+this.getDOMNode().scrollTop+"px)";
},
render: function () {
var flows = this.state.flows.map(function(flow){
return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>;
});
return (
+ <main onScroll={this.onScroll}>
<table className="flow-table">
- <FlowTableHead columns={this.state.columns}/>
- <FlowTableBody onClick={this.onClick} columns={this.state.columns} flows={this.state.flows}/>
+ <FlowTableHead ref="head"
+ columns={this.state.columns}/>
+ <FlowTableBody ref="body"
+ selectFlow={this.selectFlow}
+ onKeyDown={this.onKeyDown}
+ selected={this.state.selected}
+ columns={this.state.columns}
+ flows={this.state.flows}/>
</table>
+ </main>
);
}
});
diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js
index 63998ffe..486e723f 100644
--- a/web/src/js/components/proxyapp.jsx.js
+++ b/web/src/js/components/proxyapp.jsx.js
@@ -26,7 +26,7 @@ var ProxyAppMain = React.createClass({
return (
<div id="container">
<Header settings={this.state.settings}/>
- <div id="main"><this.props.activeRouteHandler/></div>
+ <this.props.activeRouteHandler/>
{this.state.settings.showEventLog ? <EventLog/> : null}
<Footer settings={this.state.settings}/>
</div>
diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js
index a5cb74ba..ba5b0788 100644
--- a/web/src/js/stores/flowstore.js
+++ b/web/src/js/stores/flowstore.js
@@ -35,9 +35,8 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {
this.emit("change");
},
_update: function(flow){
- console.debug("FIXME: Use UUID");
var idx = _.findIndex(this.flows, function(f){
- return flow.request.timestamp_start == f.request.timestamp_start;
+ return flow.id === f.id;
});
if(idx < 0){
@@ -61,7 +60,13 @@ _.extend(_FlowStore.prototype, EventEmitter.prototype, {
var view = new FlowView(this, !since);
$.getJSON("/static/flows.json", function(flows){
+ flows = flows.concat(_.cloneDeep(flows)).concat(_.cloneDeep(flows));
+ var id = 1;
+ flows.forEach(function(flow){
+ flow.id = "uuid-"+id++;
+ })
view.add_bulk(flows);
+
});
return view;
diff --git a/web/src/js/utils.js b/web/src/js/utils.js
index 39ad92fb..6e545d8a 100644
--- a/web/src/js/utils.js
+++ b/web/src/js/utils.js
@@ -11,3 +11,13 @@ var AutoScrollMixin = {
}
},
};
+
+
+var Key = {
+ UP: 38,
+ DOWN: 40,
+ LEFT: 37,
+ RIGHT: 39,
+ ENTER: 13,
+ ESC: 27
+} \ No newline at end of file