mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 02:10:59 +00:00
flowtable: add selection indicator, add keyboard navigation
This commit is contained in:
parent
6a161be6b4
commit
0d64cc9327
@ -1,5 +1,6 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
import copy
|
import copy
|
||||||
|
import uuid
|
||||||
import netlib.tcp
|
import netlib.tcp
|
||||||
from .. import stateobject, utils, version
|
from .. import stateobject, utils, version
|
||||||
from ..proxy.connection import ClientConnection, ServerConnection
|
from ..proxy.connection import ClientConnection, ServerConnection
|
||||||
@ -60,6 +61,7 @@ class Flow(stateobject.StateObject):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, conntype, client_conn, server_conn, live=None):
|
def __init__(self, conntype, client_conn, server_conn, live=None):
|
||||||
self.conntype = conntype
|
self.conntype = conntype
|
||||||
|
self.id = str(uuid.uuid4())
|
||||||
self.client_conn = client_conn
|
self.client_conn = client_conn
|
||||||
"""@type: ClientConnection"""
|
"""@type: ClientConnection"""
|
||||||
self.server_conn = server_conn
|
self.server_conn = server_conn
|
||||||
@ -72,6 +74,7 @@ class Flow(stateobject.StateObject):
|
|||||||
self._backup = None
|
self._backup = None
|
||||||
|
|
||||||
_stateobject_attributes = dict(
|
_stateobject_attributes = dict(
|
||||||
|
id=str,
|
||||||
error=Error,
|
error=Error,
|
||||||
client_conn=ClientConnection,
|
client_conn=ClientConnection,
|
||||||
server_conn=ServerConnection,
|
server_conn=ServerConnection,
|
||||||
|
@ -56,7 +56,7 @@ body,
|
|||||||
#container > .eventlog {
|
#container > .eventlog {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
#main {
|
main {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
@ -111,6 +111,9 @@ header .menu {
|
|||||||
.flow-table tr {
|
.flow-table tr {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.flow-table tr.selected {
|
||||||
|
background-color: rgba(193, 215, 235, 0.5) !important;
|
||||||
|
}
|
||||||
.flow-table td {
|
.flow-table td {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
[{
|
[{
|
||||||
|
"id": "b5e5483c-e124-45bb-aa2e-360706e03ef4",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651311.107,
|
"timestamp_end": 1410651311.107,
|
||||||
"timestamp_start": 1410651311.106,
|
"timestamp_start": 1410651311.106,
|
||||||
@ -157,6 +158,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "85e9781f-d81d-43ca-a694-2cd86c76d991",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651311.657,
|
"timestamp_end": 1410651311.657,
|
||||||
"timestamp_start": 1410651311.653,
|
"timestamp_start": 1410651311.653,
|
||||||
@ -319,6 +321,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "1bf281fd-e02a-423c-a69c-aa65657bc3dd",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651312.362,
|
"timestamp_end": 1410651312.362,
|
||||||
"timestamp_start": 1410651312.359,
|
"timestamp_start": 1410651312.359,
|
||||||
@ -485,6 +488,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "833253a0-f7dd-48c7-893c-1f13a38a71ce",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651312.389,
|
"timestamp_end": 1410651312.389,
|
||||||
"timestamp_start": 1410651312.368,
|
"timestamp_start": 1410651312.368,
|
||||||
@ -651,6 +655,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "152d8e71-2469-4034-8d6d-11099bbb4248",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651312.386,
|
"timestamp_end": 1410651312.386,
|
||||||
"timestamp_start": 1410651312.368,
|
"timestamp_start": 1410651312.368,
|
||||||
@ -817,6 +822,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "b3758e4d-7bae-4771-b154-e100c0722d00",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651373.965,
|
"timestamp_end": 1410651373.965,
|
||||||
"timestamp_start": 1410651373.963,
|
"timestamp_start": 1410651373.963,
|
||||||
@ -947,6 +953,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "ea9e47ab-fd7b-4463-bfea-cfd64cc5f78d",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651374.391,
|
"timestamp_end": 1410651374.391,
|
||||||
"timestamp_start": 1410651374.387,
|
"timestamp_start": 1410651374.387,
|
||||||
@ -1081,6 +1088,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "13ee4cd1-08e0-43ef-9bee-56fc0d9cbf3f",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651374.396,
|
"timestamp_end": 1410651374.396,
|
||||||
"timestamp_start": 1410651374.394,
|
"timestamp_start": 1410651374.394,
|
||||||
@ -1211,6 +1219,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "5c50e1fc-5ac4-4748-aed1-c969ede63e4e",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651374.795,
|
"timestamp_end": 1410651374.795,
|
||||||
"timestamp_start": 1410651374.793,
|
"timestamp_start": 1410651374.793,
|
||||||
@ -1361,6 +1370,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "0285a0b2-380e-43eb-a7a9-a18893950216",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651375.084,
|
"timestamp_end": 1410651375.084,
|
||||||
"timestamp_start": 1410651375.078,
|
"timestamp_start": 1410651375.078,
|
||||||
@ -1507,6 +1517,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "c9af9c71-dc68-462e-8446-f3a4b2782400",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651374.778,
|
"timestamp_end": 1410651374.778,
|
||||||
"timestamp_start": 1410651374.766,
|
"timestamp_start": 1410651374.766,
|
||||||
@ -1637,6 +1648,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "310386ab-3ae1-4129-9a2e-8dd2ce60ecdb",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651374.778,
|
"timestamp_end": 1410651374.778,
|
||||||
"timestamp_start": 1410651374.766,
|
"timestamp_start": 1410651374.766,
|
||||||
@ -1767,6 +1779,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "b92e5f6e-bb0f-4e47-a50c-ef4072ea40b3",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651376.078,
|
"timestamp_end": 1410651376.078,
|
||||||
"timestamp_start": 1410651376.075,
|
"timestamp_start": 1410651376.075,
|
||||||
@ -1889,6 +1902,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"id": "597d086f-d836-49e3-85bb-77a983bed87f",
|
||||||
"request": {
|
"request": {
|
||||||
"timestamp_end": 1410651376.282,
|
"timestamp_end": 1410651376.282,
|
||||||
"timestamp_start": 1410651376.279,
|
"timestamp_start": 1410651376.279,
|
||||||
|
@ -12,6 +12,15 @@ var AutoScrollMixin = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var Key = {
|
||||||
|
UP: 38,
|
||||||
|
DOWN: 40,
|
||||||
|
LEFT: 37,
|
||||||
|
RIGHT: 39,
|
||||||
|
ENTER: 13,
|
||||||
|
ESC: 27
|
||||||
|
}
|
||||||
const PayloadSources = {
|
const PayloadSources = {
|
||||||
VIEW: "view",
|
VIEW: "view",
|
||||||
SERVER: "server"
|
SERVER: "server"
|
||||||
@ -274,9 +283,8 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {
|
|||||||
this.emit("change");
|
this.emit("change");
|
||||||
},
|
},
|
||||||
_update: function(flow){
|
_update: function(flow){
|
||||||
console.debug("FIXME: Use UUID");
|
|
||||||
var idx = _.findIndex(this.flows, function(f){
|
var idx = _.findIndex(this.flows, function(f){
|
||||||
return flow.request.timestamp_start == f.request.timestamp_start;
|
return flow.id === f.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(idx < 0){
|
if(idx < 0){
|
||||||
@ -300,7 +308,13 @@ _.extend(_FlowStore.prototype, EventEmitter.prototype, {
|
|||||||
var view = new FlowView(this, !since);
|
var view = new FlowView(this, !since);
|
||||||
|
|
||||||
$.getJSON("/static/flows.json", function(flows){
|
$.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);
|
view.add_bulk(flows);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
@ -447,38 +461,6 @@ var Header = React.createClass({displayName: 'Header',
|
|||||||
|
|
||||||
/** @jsx React.DOM */
|
/** @jsx React.DOM */
|
||||||
|
|
||||||
var FlowRow = React.createClass({displayName: 'FlowRow',
|
|
||||||
render: function(){
|
|
||||||
var flow = this.props.flow;
|
|
||||||
var columns = this.props.columns.map(function(column){
|
|
||||||
return column({
|
|
||||||
key: column.displayName,
|
|
||||||
flow: flow
|
|
||||||
});
|
|
||||||
}.bind(this));
|
|
||||||
return React.DOM.tr({onClick: this.props.onClick}, columns);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var FlowTableHead = React.createClass({displayName: 'FlowTableHead',
|
|
||||||
render: function(){
|
|
||||||
var columns = this.props.columns.map(function(column){
|
|
||||||
return column.renderTitle();
|
|
||||||
}.bind(this));
|
|
||||||
return React.DOM.thead(null, columns);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var FlowTableBody = React.createClass({displayName: 'FlowTableBody',
|
|
||||||
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});
|
|
||||||
}.bind(this));
|
|
||||||
return React.DOM.tbody(null, rows);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var TLSColumn = React.createClass({displayName: 'TLSColumn',
|
var TLSColumn = React.createClass({displayName: 'TLSColumn',
|
||||||
statics: {
|
statics: {
|
||||||
@ -573,6 +555,54 @@ var TimeColumn = React.createClass({displayName: 'TimeColumn',
|
|||||||
var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, TimeColumn];
|
var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, TimeColumn];
|
||||||
|
|
||||||
|
|
||||||
|
/** @jsx React.DOM */
|
||||||
|
|
||||||
|
var FlowRow = React.createClass({displayName: 'FlowRow',
|
||||||
|
render: function(){
|
||||||
|
var flow = this.props.flow;
|
||||||
|
var columns = this.props.columns.map(function(column){
|
||||||
|
return column({
|
||||||
|
key: column.displayName,
|
||||||
|
flow: flow
|
||||||
|
});
|
||||||
|
}.bind(this));
|
||||||
|
var className = "";
|
||||||
|
if(this.props.selected){
|
||||||
|
className += "selected";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
React.DOM.tr({className: className, onClick: this.props.selectFlow.bind(null, flow)},
|
||||||
|
columns
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var FlowTableHead = React.createClass({displayName: 'FlowTableHead',
|
||||||
|
render: function(){
|
||||||
|
var columns = this.props.columns.map(function(column){
|
||||||
|
return column.renderTitle();
|
||||||
|
}.bind(this));
|
||||||
|
return React.DOM.thead(null, columns);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var FlowTableBody = React.createClass({displayName: 'FlowTableBody',
|
||||||
|
render: function(){
|
||||||
|
var rows = this.props.flows.map(function(flow){
|
||||||
|
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 React.DOM.tbody({onKeyDown: this.props.onKeyDown, tabIndex: "0"}, rows);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
var FlowTable = React.createClass({displayName: 'FlowTable',
|
var FlowTable = React.createClass({displayName: 'FlowTable',
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
@ -593,17 +623,87 @@ var FlowTable = React.createClass({displayName: 'FlowTable',
|
|||||||
flows: this.flowStore.getAll()
|
flows: this.flowStore.getAll()
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onClick: function(e){
|
selectFlow: function(flow){
|
||||||
console.log("rowclick", e);
|
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 () {
|
render: function () {
|
||||||
var flows = this.state.flows.map(function(flow){
|
var flows = this.state.flows.map(function(flow){
|
||||||
return React.DOM.div(null, flow.request.method, " ", flow.request.scheme, "://", flow.request.host, flow.request.path);
|
return React.DOM.div(null, flow.request.method, " ", flow.request.scheme, "://", flow.request.host, flow.request.path);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
|
React.DOM.main({onScroll: this.onScroll},
|
||||||
React.DOM.table({className: "flow-table"},
|
React.DOM.table({className: "flow-table"},
|
||||||
FlowTableHead({columns: this.state.columns}),
|
FlowTableHead({ref: "head",
|
||||||
FlowTableBody({onClick: this.onClick, columns: this.state.columns, flows: this.state.flows})
|
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})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -691,7 +791,7 @@ var ProxyAppMain = React.createClass({displayName: 'ProxyAppMain',
|
|||||||
return (
|
return (
|
||||||
React.DOM.div({id: "container"},
|
React.DOM.div({id: "container"},
|
||||||
Header({settings: this.state.settings}),
|
Header({settings: this.state.settings}),
|
||||||
React.DOM.div({id: "main"}, this.props.activeRouteHandler(null)),
|
this.props.activeRouteHandler(null),
|
||||||
this.state.settings.showEventLog ? EventLog(null) : null,
|
this.state.settings.showEventLog ? EventLog(null) : null,
|
||||||
Footer({settings: this.state.settings})
|
Footer({settings: this.state.settings})
|
||||||
)
|
)
|
||||||
|
@ -42,6 +42,7 @@ var path = {
|
|||||||
'js/stores/flowstore.js',
|
'js/stores/flowstore.js',
|
||||||
'js/connection.js',
|
'js/connection.js',
|
||||||
'js/components/header.jsx.js',
|
'js/components/header.jsx.js',
|
||||||
|
'js/components/flowtable-columns.jsx.js',
|
||||||
'js/components/flowtable.jsx.js',
|
'js/components/flowtable.jsx.js',
|
||||||
'js/components/eventlog.jsx.js',
|
'js/components/eventlog.jsx.js',
|
||||||
'js/components/footer.jsx.js',
|
'js/components/footer.jsx.js',
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
tr {
|
tr {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&.selected {
|
||||||
|
background-color: hsla(209, 52%, 84%, 0.5) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
@ -19,8 +22,6 @@
|
|||||||
//tr:nth-child(odd) { background-color : white; }
|
//tr:nth-child(odd) { background-color : white; }
|
||||||
tr:nth-child(even) { background-color : rgba(0,0,0,0.05); }
|
tr:nth-child(even) { background-color : rgba(0,0,0,0.05); }
|
||||||
//tr:hover { background-color : hsla(209, 52%, 84%, 0.5); }
|
//tr:hover { background-color : hsla(209, 52%, 84%, 0.5); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.col-tls {
|
.col-tls {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
|
@ -13,7 +13,7 @@ html, body, #container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#main {
|
main {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
95
web/src/js/components/flowtable-columns.jsx.js
Normal file
95
web/src/js/components/flowtable-columns.jsx.js
Normal file
@ -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];
|
||||||
|
|
@ -9,7 +9,14 @@ var FlowRow = React.createClass({
|
|||||||
flow: flow
|
flow: flow
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.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({
|
var FlowTableBody = React.createClass({
|
||||||
render: function(){
|
render: function(){
|
||||||
var rows = this.props.flows.map(function(flow){
|
var rows = this.props.flows.map(function(flow){
|
||||||
//TODO: Add UUID
|
var selected = (flow == this.props.selected);
|
||||||
return <FlowRow onClick={this.props.onClick} flow={flow} columns={this.props.columns}/>;
|
return <FlowRow key={flow.id}
|
||||||
|
ref={flow.id}
|
||||||
|
flow={flow}
|
||||||
|
columns={this.props.columns}
|
||||||
|
selected={selected}
|
||||||
|
selectFlow={this.props.selectFlow}
|
||||||
|
/>;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
return <tbody>{rows}</tbody>;
|
return <tbody onKeyDown={this.props.onKeyDown} tabIndex="0">{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>;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
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({
|
var FlowTable = React.createClass({
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
@ -146,18 +66,88 @@ var FlowTable = React.createClass({
|
|||||||
flows: this.flowStore.getAll()
|
flows: this.flowStore.getAll()
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onClick: function(e){
|
selectFlow: function(flow){
|
||||||
console.log("rowclick", e);
|
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 () {
|
render: function () {
|
||||||
var flows = this.state.flows.map(function(flow){
|
var flows = this.state.flows.map(function(flow){
|
||||||
return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>;
|
return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>;
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
|
<main onScroll={this.onScroll}>
|
||||||
<table className="flow-table">
|
<table className="flow-table">
|
||||||
<FlowTableHead columns={this.state.columns}/>
|
<FlowTableHead ref="head"
|
||||||
<FlowTableBody onClick={this.onClick} columns={this.state.columns} flows={this.state.flows}/>
|
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>
|
</table>
|
||||||
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ var ProxyAppMain = React.createClass({
|
|||||||
return (
|
return (
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<Header settings={this.state.settings}/>
|
<Header settings={this.state.settings}/>
|
||||||
<div id="main"><this.props.activeRouteHandler/></div>
|
<this.props.activeRouteHandler/>
|
||||||
{this.state.settings.showEventLog ? <EventLog/> : null}
|
{this.state.settings.showEventLog ? <EventLog/> : null}
|
||||||
<Footer settings={this.state.settings}/>
|
<Footer settings={this.state.settings}/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,9 +35,8 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {
|
|||||||
this.emit("change");
|
this.emit("change");
|
||||||
},
|
},
|
||||||
_update: function(flow){
|
_update: function(flow){
|
||||||
console.debug("FIXME: Use UUID");
|
|
||||||
var idx = _.findIndex(this.flows, function(f){
|
var idx = _.findIndex(this.flows, function(f){
|
||||||
return flow.request.timestamp_start == f.request.timestamp_start;
|
return flow.id === f.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(idx < 0){
|
if(idx < 0){
|
||||||
@ -61,7 +60,13 @@ _.extend(_FlowStore.prototype, EventEmitter.prototype, {
|
|||||||
var view = new FlowView(this, !since);
|
var view = new FlowView(this, !since);
|
||||||
|
|
||||||
$.getJSON("/static/flows.json", function(flows){
|
$.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);
|
view.add_bulk(flows);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
|
@ -11,3 +11,13 @@ var AutoScrollMixin = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var Key = {
|
||||||
|
UP: 38,
|
||||||
|
DOWN: 40,
|
||||||
|
LEFT: 37,
|
||||||
|
RIGHT: 39,
|
||||||
|
ENTER: 13,
|
||||||
|
ESC: 27
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user