mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-01-30 23:09:44 +00:00
web: detailpane impl
This commit is contained in:
parent
01da54f1c3
commit
d1ba150ea7
@ -1,10 +1,12 @@
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
-moz-box-sizing: inherit;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
.resource-icon {
|
||||
width: 32px;
|
||||
@ -48,65 +50,134 @@ body,
|
||||
overflow: hidden;
|
||||
}
|
||||
#container {
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
#container > header,
|
||||
#container > footer,
|
||||
#container > .eventlog {
|
||||
flex: 0 0 auto;
|
||||
-webkit-flex: 0 0 auto;
|
||||
-ms-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
main {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
.main-view {
|
||||
-webkit-flex: 1 1 auto;
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
header {
|
||||
background-color: white;
|
||||
.main-view.vertical {
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
header .title-bar {
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
.main-view .flow-detail,
|
||||
.main-view .flow-table {
|
||||
-webkit-flex: 1 1 auto;
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
-webkit-flex-basis: 50%;
|
||||
-ms-flex-preferred-size: 50%;
|
||||
flex-basis: 50%;
|
||||
}
|
||||
header nav {
|
||||
.nav-tabs {
|
||||
border-bottom: solid #a6a6a6 1px;
|
||||
}
|
||||
header nav a {
|
||||
.nav-tabs a {
|
||||
display: inline-block;
|
||||
padding: 3px 14px;
|
||||
margin: 0 2px -1px;
|
||||
border: solid transparent 1px;
|
||||
text-decoration: none;
|
||||
}
|
||||
header nav a.active {
|
||||
.nav-tabs a.active {
|
||||
background-color: white;
|
||||
border-color: #a6a6a6;
|
||||
border-bottom-color: white;
|
||||
}
|
||||
header nav a:hover {
|
||||
/*
|
||||
@preview: lightgrey;
|
||||
border-top-color: @preview;
|
||||
border-left-color: @preview;
|
||||
border-right-color: @preview;
|
||||
*/
|
||||
text-decoration: none;
|
||||
}
|
||||
header nav a.special {
|
||||
.nav-tabs a.special {
|
||||
color: white;
|
||||
background-color: #396cad;
|
||||
border-bottom-color: #396cad;
|
||||
}
|
||||
header nav a.special:hover {
|
||||
.nav-tabs a.special:hover {
|
||||
background-color: #5386c6;
|
||||
}
|
||||
.nav-tabs-lg a {
|
||||
padding: 3px 14px;
|
||||
margin: 0 2px -1px;
|
||||
}
|
||||
.nav-tabs-sm a {
|
||||
padding: 0px 7px;
|
||||
margin: 2px 2px -1px;
|
||||
}
|
||||
header {
|
||||
background-color: white;
|
||||
/*
|
||||
nav {
|
||||
border-bottom: solid @separator-color 1px;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
padding: 3px 14px;
|
||||
margin: 0 2px -1px;
|
||||
border: solid transparent 1px;
|
||||
//text-transform: uppercase;
|
||||
//font-family: Lato;
|
||||
|
||||
&.active {
|
||||
border-color: @separator-color;
|
||||
border-bottom-color: white;
|
||||
}
|
||||
&.active, &:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
&.special {
|
||||
@special-color: #396cad;
|
||||
color: white;
|
||||
background-color: @special-color;
|
||||
border-bottom-color: @special-color;
|
||||
&:hover {
|
||||
background-color: lighten(@special-color, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
header .title-bar {
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
header .menu {
|
||||
padding: 10px;
|
||||
border-bottom: solid #a6a6a6 1px;
|
||||
}
|
||||
.flow-table {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.flow-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.flow-table thead {
|
||||
background-color: #dadada;
|
||||
background-color: #F2F2F2;
|
||||
line-height: 23px;
|
||||
}
|
||||
.flow-table th {
|
||||
font-weight: normal;
|
||||
box-shadow: 0 1px 0 #a6a6a6;
|
||||
}
|
||||
.flow-table tbody {
|
||||
outline: 0;
|
||||
}
|
||||
.flow-table tr {
|
||||
cursor: pointer;
|
||||
@ -138,10 +209,21 @@ header .menu {
|
||||
width: 50px;
|
||||
}
|
||||
.flow-table .col-time {
|
||||
width: 120px;
|
||||
width: 50px;
|
||||
}
|
||||
.flow-table td.col-time {
|
||||
text-align: right;
|
||||
}
|
||||
.flow-detail {
|
||||
overflow: auto;
|
||||
}
|
||||
.flow-detail nav {
|
||||
background-color: #F2F2F2;
|
||||
}
|
||||
.eventlog {
|
||||
flex: 0 0 auto;
|
||||
-webkit-flex: 0 0 auto;
|
||||
-ms-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
height: 200px;
|
||||
@ -151,6 +233,4 @@ header .menu {
|
||||
footer {
|
||||
box-shadow: 0 -1px 3px #d3d3d3;
|
||||
padding: 0px 10px 3px;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=../css/app.css.map */
|
||||
}
|
@ -12,15 +12,25 @@ var AutoScrollMixin = {
|
||||
},
|
||||
};
|
||||
|
||||
var StickyHeadMixin = {
|
||||
adjustHead: function(){
|
||||
// Abusing CSS transforms to set the element
|
||||
// referenced as head into some kind of position:sticky.
|
||||
var head = this.refs.head.getDOMNode();
|
||||
head.style.transform = "translate(0,"+this.getDOMNode().scrollTop+"px)";
|
||||
}
|
||||
};
|
||||
|
||||
var Key = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
LEFT: 37,
|
||||
RIGHT: 39,
|
||||
ENTER: 13,
|
||||
ESC: 27
|
||||
}
|
||||
};
|
||||
const PayloadSources = {
|
||||
VIEW: "view",
|
||||
SERVER: "server"
|
||||
@ -311,8 +321,8 @@ _.extend(_FlowStore.prototype, EventEmitter.prototype, {
|
||||
flows = flows.concat(_.cloneDeep(flows)).concat(_.cloneDeep(flows));
|
||||
var id = 1;
|
||||
flows.forEach(function(flow){
|
||||
flow.id = "uuid-"+id++;
|
||||
})
|
||||
flow.id = "uuid-" + id++;
|
||||
});
|
||||
view.add_bulk(flows);
|
||||
|
||||
});
|
||||
@ -372,6 +382,10 @@ var Connection = new _Connection(location.origin + "/updates");
|
||||
/** @jsx React.DOM */
|
||||
|
||||
var MainMenu = React.createClass({displayName: 'MainMenu',
|
||||
statics: {
|
||||
title: "Traffic",
|
||||
route: "flows"
|
||||
},
|
||||
toggleEventLog: function () {
|
||||
SettingsActions.update({
|
||||
showEventLog: !this.props.settings.showEventLog
|
||||
@ -387,72 +401,74 @@ var MainMenu = React.createClass({displayName: 'MainMenu',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var ToolsMenu = React.createClass({displayName: 'ToolsMenu',
|
||||
statics: {
|
||||
title: "Tools",
|
||||
route: "flows"
|
||||
},
|
||||
render: function () {
|
||||
return React.DOM.div(null, "Tools Menu");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var ReportsMenu = React.createClass({displayName: 'ReportsMenu',
|
||||
statics: {
|
||||
title: "Visualization",
|
||||
route: "reports"
|
||||
},
|
||||
render: function () {
|
||||
return React.DOM.div(null, "Reports Menu");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var _Header_Entries = {
|
||||
main: {
|
||||
title: "Traffic",
|
||||
route: "main",
|
||||
menu: MainMenu
|
||||
},
|
||||
tools: {
|
||||
title: "Tools",
|
||||
route: "main",
|
||||
menu: ToolsMenu
|
||||
},
|
||||
reports: {
|
||||
title: "Visualization",
|
||||
route: "reports",
|
||||
menu: ReportsMenu
|
||||
}
|
||||
};
|
||||
var header_entries = [MainMenu, ToolsMenu, ReportsMenu];
|
||||
|
||||
|
||||
var Header = React.createClass({displayName: 'Header',
|
||||
getInitialState: function () {
|
||||
return {
|
||||
active: "main"
|
||||
active: header_entries[0]
|
||||
};
|
||||
},
|
||||
handleClick: function (active) {
|
||||
ReactRouter.transitionTo(active.route);
|
||||
this.setState({active: active});
|
||||
ReactRouter.transitionTo(_Header_Entries[active].route);
|
||||
return false;
|
||||
},
|
||||
handleFileClick: function () {
|
||||
console.log("File click");
|
||||
},
|
||||
render: function () {
|
||||
var header = [];
|
||||
for (var item in _Header_Entries) {
|
||||
var classes = this.state.active == item ? "active" : "";
|
||||
header.push(React.DOM.a({key: item, href: "#", className: classes,
|
||||
onClick: this.handleClick.bind(this, item)}, _Header_Entries[item].title));
|
||||
}
|
||||
|
||||
var menu = _Header_Entries[this.state.active].menu({
|
||||
settings: this.props.settings
|
||||
});
|
||||
var header = header_entries.map(function(entry){
|
||||
var classes = React.addons.classSet({
|
||||
active: entry == this.state.active
|
||||
});
|
||||
return (
|
||||
React.DOM.a({key: entry.title,
|
||||
href: "#",
|
||||
className: classes,
|
||||
onClick: this.handleClick.bind(this, entry)
|
||||
},
|
||||
entry.title
|
||||
)
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
return (
|
||||
React.DOM.header(null,
|
||||
React.DOM.div({className: "title-bar"},
|
||||
"mitmproxy ", this.props.settings.version
|
||||
),
|
||||
React.DOM.nav(null,
|
||||
React.DOM.nav({className: "nav-tabs nav-tabs-lg"},
|
||||
React.DOM.a({href: "#", className: "special", onClick: this.handleFileClick}, " File "),
|
||||
header
|
||||
),
|
||||
React.DOM.div({className: "menu"},
|
||||
menu
|
||||
this.state.active({settings: this.props.settings})
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -471,7 +487,12 @@ var TLSColumn = React.createClass({displayName: 'TLSColumn',
|
||||
render: function(){
|
||||
var flow = this.props.flow;
|
||||
var ssl = (flow.request.scheme == "https");
|
||||
return React.DOM.td({className: ssl ? "col-tls-https" : "col-tls-http"});
|
||||
var classes = React.addons.classSet({
|
||||
"col-tls": true,
|
||||
"col-tls-https": ssl,
|
||||
"col-tls-http": !ssl
|
||||
});
|
||||
return React.DOM.td({className: classes});
|
||||
}
|
||||
});
|
||||
|
||||
@ -484,7 +505,7 @@ var IconColumn = React.createClass({displayName: 'IconColumn',
|
||||
},
|
||||
render: function(){
|
||||
var flow = this.props.flow;
|
||||
return React.DOM.td({className: "resource-icon resource-icon-plain"});
|
||||
return React.DOM.td({className: "col-icon"}, React.DOM.div({className: "resource-icon resource-icon-plain"}));
|
||||
}
|
||||
});
|
||||
|
||||
@ -496,7 +517,7 @@ var PathColumn = React.createClass({displayName: 'PathColumn',
|
||||
},
|
||||
render: function(){
|
||||
var flow = this.props.flow;
|
||||
return React.DOM.td(null, flow.request.scheme + "://" + flow.request.host + flow.request.path);
|
||||
return React.DOM.td({className: "col-path"}, flow.request.scheme + "://" + flow.request.host + flow.request.path);
|
||||
}
|
||||
});
|
||||
|
||||
@ -509,7 +530,7 @@ var MethodColumn = React.createClass({displayName: 'MethodColumn',
|
||||
},
|
||||
render: function(){
|
||||
var flow = this.props.flow;
|
||||
return React.DOM.td(null, flow.request.method);
|
||||
return React.DOM.td({className: "col-method"}, flow.request.method);
|
||||
}
|
||||
});
|
||||
|
||||
@ -528,7 +549,7 @@ var StatusColumn = React.createClass({displayName: 'StatusColumn',
|
||||
} else {
|
||||
status = null;
|
||||
}
|
||||
return React.DOM.td(null, status);
|
||||
return React.DOM.td({className: "col-status"}, status);
|
||||
}
|
||||
});
|
||||
|
||||
@ -547,7 +568,7 @@ var TimeColumn = React.createClass({displayName: 'TimeColumn',
|
||||
} else {
|
||||
time = "...";
|
||||
}
|
||||
return React.DOM.td(null, time);
|
||||
return React.DOM.td({className: "col-time"}, time);
|
||||
}
|
||||
});
|
||||
|
||||
@ -561,10 +582,7 @@ 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
|
||||
});
|
||||
return column({key: column.displayName, flow: flow});
|
||||
}.bind(this));
|
||||
var className = "";
|
||||
if(this.props.selected){
|
||||
@ -604,30 +622,13 @@ var FlowTableBody = React.createClass({displayName: 'FlowTableBody',
|
||||
|
||||
|
||||
var FlowTable = React.createClass({displayName: 'FlowTable',
|
||||
mixins: [StickyHeadMixin, AutoScrollMixin],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
flows: [],
|
||||
columns: all_columns
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.flowStore = FlowStore.getView();
|
||||
this.flowStore.addListener("change",this.onFlowChange);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
this.flowStore.removeListener("change",this.onFlowChange);
|
||||
this.flowStore.close();
|
||||
},
|
||||
onFlowChange: function () {
|
||||
this.setState({
|
||||
flows: this.flowStore.getAll()
|
||||
});
|
||||
},
|
||||
selectFlow: function(flow){
|
||||
this.setState({
|
||||
selected: flow
|
||||
});
|
||||
|
||||
scrollIntoView: function(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();
|
||||
@ -646,71 +647,196 @@ var FlowTable = React.createClass({displayName: 'FlowTable',
|
||||
viewport.scrollTop = flowNode_bottom - viewport.offsetHeight;
|
||||
}
|
||||
},
|
||||
selectRowRelative: function(i){
|
||||
selectFlowRelative: function(i){
|
||||
var index;
|
||||
if(!this.state.selected){
|
||||
if(!this.props.selected){
|
||||
if(i > 0){
|
||||
index = this.flows.length-1;
|
||||
index = this.props.flows.length-1;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
} else {
|
||||
index = _.findIndex(this.state.flows, function(f){
|
||||
return f === this.state.selected;
|
||||
index = _.findIndex(this.props.flows, function(f){
|
||||
return f === this.props.selected;
|
||||
}.bind(this));
|
||||
index = Math.min(Math.max(0, index+i), this.state.flows.length-1);
|
||||
index = Math.min(Math.max(0, index+i), this.props.flows.length-1);
|
||||
}
|
||||
this.selectFlow(this.state.flows[index]);
|
||||
this.props.selectFlow(this.props.flows[index]);
|
||||
},
|
||||
onKeyDown: function(e){
|
||||
switch(e.keyCode){
|
||||
case Key.DOWN:
|
||||
this.selectRowRelative(+1);
|
||||
return false;
|
||||
this.selectFlowRelative(+1);
|
||||
break;
|
||||
case Key.UP:
|
||||
this.selectRowRelative(-1);
|
||||
return false;
|
||||
this.selectFlowRelative(-1);
|
||||
break;
|
||||
case Key.ENTER:
|
||||
console.log("Open details pane...", this.state.selected);
|
||||
case Key.PAGE_DOWN:
|
||||
this.selectFlowRelative(+10);
|
||||
break;
|
||||
case Key.PAGE_UP:
|
||||
this.selectFlowRelative(-10);
|
||||
break;
|
||||
case Key.ESC:
|
||||
console.log("")
|
||||
this.props.selectFlow(null);
|
||||
break;
|
||||
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 React.DOM.div(null, flow.request.method, " ", flow.request.scheme, "://", flow.request.host, flow.request.path);
|
||||
});
|
||||
return (
|
||||
React.DOM.main({onScroll: this.onScroll},
|
||||
React.DOM.table({className: "flow-table"},
|
||||
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})
|
||||
React.DOM.div({className: "flow-table", onScroll: this.adjustHead},
|
||||
React.DOM.table(null,
|
||||
FlowTableHead({ref: "head",
|
||||
columns: this.state.columns}),
|
||||
FlowTableBody({ref: "body",
|
||||
flows: this.props.flows,
|
||||
selected: this.props.selected,
|
||||
selectFlow: this.props.selectFlow,
|
||||
columns: this.state.columns,
|
||||
onKeyDown: this.onKeyDown})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/** @jsx React.DOM */
|
||||
|
||||
var FlowDetailNav = React.createClass({displayName: 'FlowDetailNav',
|
||||
render: function(){
|
||||
|
||||
var items = ["request", "response", "details"].map(function(e){
|
||||
var str = e.charAt(0).toUpperCase() + e.slice(1);
|
||||
var className = this.props.active === e ? "active" : "";
|
||||
var onClick = function(){
|
||||
this.props.selectTab(e);
|
||||
return false;
|
||||
}.bind(this);
|
||||
return React.DOM.a({key: e,
|
||||
href: "#",
|
||||
className: className,
|
||||
onClick: onClick}, str);
|
||||
}.bind(this));
|
||||
return (
|
||||
React.DOM.nav({ref: "head", className: "nav-tabs nav-tabs-sm"},
|
||||
items
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var FlowDetailRequest = React.createClass({displayName: 'FlowDetailRequest',
|
||||
render: function(){
|
||||
return React.DOM.div(null, "request");
|
||||
}
|
||||
});
|
||||
|
||||
var FlowDetailResponse = React.createClass({displayName: 'FlowDetailResponse',
|
||||
render: function(){
|
||||
return React.DOM.div(null, "response");
|
||||
}
|
||||
});
|
||||
|
||||
var FlowDetailConnectionInfo = React.createClass({displayName: 'FlowDetailConnectionInfo',
|
||||
render: function(){
|
||||
return React.DOM.div(null, "details");
|
||||
}
|
||||
})
|
||||
|
||||
var tabs = {
|
||||
request: FlowDetailRequest,
|
||||
response: FlowDetailResponse,
|
||||
details: FlowDetailConnectionInfo
|
||||
}
|
||||
|
||||
var FlowDetail = React.createClass({displayName: 'FlowDetail',
|
||||
mixins: [StickyHeadMixin],
|
||||
render: function(){
|
||||
var flow = JSON.stringify(this.props.flow, null, 2);
|
||||
var Tab = tabs[this.props.active];
|
||||
return (
|
||||
React.DOM.div({className: "flow-detail", onScroll: this.adjustHead},
|
||||
FlowDetailNav({active: this.props.active, selectTab: this.props.selectTab}),
|
||||
Tab(null)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
/** @jsx React.DOM */
|
||||
|
||||
var MainView = React.createClass({displayName: 'MainView',
|
||||
getInitialState: function() {
|
||||
return {
|
||||
flows: [],
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
console.log("get view");
|
||||
this.flowStore = FlowStore.getView();
|
||||
this.flowStore.addListener("change",this.onFlowChange);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
this.flowStore.removeListener("change",this.onFlowChange);
|
||||
this.flowStore.close();
|
||||
},
|
||||
onFlowChange: function () {
|
||||
this.setState({
|
||||
flows: this.flowStore.getAll()
|
||||
});
|
||||
},
|
||||
selectFlow: function(flow) {
|
||||
if(flow){
|
||||
ReactRouter.replaceWith(
|
||||
"flow",
|
||||
{
|
||||
flowId: flow.id,
|
||||
detailTab: this.props.params.detailTab || "request"
|
||||
}
|
||||
);
|
||||
this.refs.flowTable.scrollIntoView(flow);
|
||||
} else {
|
||||
ReactRouter.replaceWith("flows");
|
||||
}
|
||||
},
|
||||
selectDetailTab: function(panel) {
|
||||
ReactRouter.replaceWith(
|
||||
"flow",
|
||||
{
|
||||
flowId: this.props.params.flowId,
|
||||
detailTab: panel
|
||||
}
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
var selected = _.find(this.state.flows, { id: this.props.params.flowId });
|
||||
|
||||
var details = null;
|
||||
if(selected){
|
||||
details = (
|
||||
FlowDetail({ref: "flowDetails",
|
||||
flow: selected,
|
||||
selectTab: this.selectDetailTab,
|
||||
active: this.props.params.detailTab})
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
React.DOM.div({className: "main-view"},
|
||||
FlowTable({ref: "flowTable",
|
||||
flows: this.state.flows,
|
||||
selectFlow: this.selectFlow,
|
||||
selected: selected}),
|
||||
details
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
/** @jsx React.DOM */
|
||||
|
||||
var EventLog = React.createClass({displayName: 'EventLog',
|
||||
mixins:[AutoScrollMixin],
|
||||
getInitialState: function () {
|
||||
@ -791,7 +917,7 @@ var ProxyAppMain = React.createClass({displayName: 'ProxyAppMain',
|
||||
return (
|
||||
React.DOM.div({id: "container"},
|
||||
Header({settings: this.state.settings}),
|
||||
this.props.activeRouteHandler(null),
|
||||
this.props.activeRouteHandler({settings: this.state.settings}),
|
||||
this.state.settings.showEventLog ? EventLog(null) : null,
|
||||
Footer({settings: this.state.settings})
|
||||
)
|
||||
@ -800,16 +926,22 @@ var ProxyAppMain = React.createClass({displayName: 'ProxyAppMain',
|
||||
});
|
||||
|
||||
|
||||
var Routes = ReactRouter.Routes;
|
||||
var Route = ReactRouter.Route;
|
||||
var Redirect = ReactRouter.Redirect;
|
||||
var DefaultRoute = ReactRouter.DefaultRoute;
|
||||
var NotFoundRoute = ReactRouter.NotFoundRoute;
|
||||
|
||||
|
||||
var ProxyApp = (
|
||||
ReactRouter.Routes({location: "hash"},
|
||||
ReactRouter.Route({name: "app", path: "/", handler: ProxyAppMain},
|
||||
ReactRouter.Route({name: "main", handler: FlowTable}),
|
||||
ReactRouter.Route({name: "reports", handler: Reports}),
|
||||
ReactRouter.Redirect({to: "main"})
|
||||
Routes({location: "hash"},
|
||||
Route({path: "/", handler: ProxyAppMain},
|
||||
Route({name: "flows", path: "flows", handler: MainView}),
|
||||
Route({name: "flow", path: "flows/:flowId/:detailTab", handler: MainView}),
|
||||
Route({name: "reports", handler: Reports})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$(function () {
|
||||
Connection.init();
|
||||
app = React.renderComponent(ProxyApp, document.body);
|
||||
|
@ -44,6 +44,8 @@ var path = {
|
||||
'js/components/header.jsx.js',
|
||||
'js/components/flowtable-columns.jsx.js',
|
||||
'js/components/flowtable.jsx.js',
|
||||
'js/components/flowdetail.jsx.js',
|
||||
'js/components/mainview.jsx.js',
|
||||
'js/components/eventlog.jsx.js',
|
||||
'js/components/footer.jsx.js',
|
||||
'js/components/proxyapp.jsx.js',
|
||||
|
@ -9,7 +9,9 @@ html {
|
||||
|
||||
@import (less) "sprites.less";
|
||||
@import (less) "layout.less";
|
||||
@import (less) "tabs.less";
|
||||
@import (less) "header.less";
|
||||
@import (less) "flowtable.less";
|
||||
@import (less) "flowdetail.less";
|
||||
@import (less) "eventlog.less";
|
||||
@import (less) "footer.less";
|
7
web/src/css/flowdetail.less
Normal file
7
web/src/css/flowdetail.less
Normal file
@ -0,0 +1,7 @@
|
||||
.flow-detail {
|
||||
overflow: auto;
|
||||
|
||||
nav {
|
||||
background-color: #F2F2F2;
|
||||
}
|
||||
}
|
@ -1,9 +1,24 @@
|
||||
.flow-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
overflow: auto;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
thead {
|
||||
background-color: #dadada;
|
||||
background-color: #F2F2F2;
|
||||
line-height: 23px;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: normal;
|
||||
box-shadow: 0 1px 0 #a6a6a6;
|
||||
}
|
||||
|
||||
tbody {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
tr {
|
||||
@ -19,9 +34,7 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
//tr:nth-child(odd) { background-color : white; }
|
||||
tr:nth-child(even) { background-color : rgba(0,0,0,0.05); }
|
||||
//tr:hover { background-color : hsla(209, 52%, 84%, 0.5); }
|
||||
|
||||
.col-tls {
|
||||
width: 10px;
|
||||
@ -39,6 +52,9 @@
|
||||
width: 50px;
|
||||
}
|
||||
.col-time {
|
||||
width: 120px;
|
||||
width: 50px;
|
||||
}
|
||||
td.col-time {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ header {
|
||||
}
|
||||
|
||||
@separator-color: lighten(grey, 15%);
|
||||
|
||||
/*
|
||||
nav {
|
||||
border-bottom: solid @separator-color 1px;
|
||||
|
||||
@ -20,16 +20,10 @@ header {
|
||||
//font-family: Lato;
|
||||
|
||||
&.active {
|
||||
border-color: @separator-color;
|
||||
border-bottom-color: white;
|
||||
border-color: @separator-color;
|
||||
border-bottom-color: white;
|
||||
}
|
||||
&:hover {
|
||||
/*
|
||||
@preview: lightgrey;
|
||||
border-top-color: @preview;
|
||||
border-left-color: @preview;
|
||||
border-right-color: @preview;
|
||||
*/
|
||||
&.active, &:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
&.special {
|
||||
@ -43,7 +37,7 @@ header {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
.menu {
|
||||
padding: 10px;
|
||||
border-bottom: solid @separator-color 1px;
|
||||
|
@ -13,7 +13,19 @@ html, body, #container {
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
.main-view {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flow-detail, .flow-table {
|
||||
flex: 1 1 auto;
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
}
|
45
web/src/css/tabs.less
Normal file
45
web/src/css/tabs.less
Normal file
@ -0,0 +1,45 @@
|
||||
.nav-tabs {
|
||||
|
||||
@separator-color: lighten(grey, 15%);
|
||||
|
||||
border-bottom: solid @separator-color 1px;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
border: solid transparent 1px;
|
||||
text-decoration: none;
|
||||
//text-transform: uppercase;
|
||||
//font-family: Lato;
|
||||
|
||||
&.active {
|
||||
background-color: white;
|
||||
border-color: @separator-color;
|
||||
border-bottom-color: white;
|
||||
}
|
||||
&.special {
|
||||
@special-color: #396cad;
|
||||
color: white;
|
||||
background-color: @special-color;
|
||||
border-bottom-color: @special-color;
|
||||
&:hover {
|
||||
background-color: lighten(@special-color, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.nav-tabs-lg {
|
||||
a {
|
||||
padding: 3px 14px;
|
||||
margin: 0 2px -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs-sm {
|
||||
a {
|
||||
padding: 0px 7px;
|
||||
margin: 2px 2px -1px;
|
||||
|
||||
}
|
||||
}
|
62
web/src/js/components/flowdetail.jsx.js
Normal file
62
web/src/js/components/flowdetail.jsx.js
Normal file
@ -0,0 +1,62 @@
|
||||
/** @jsx React.DOM */
|
||||
|
||||
var FlowDetailNav = React.createClass({
|
||||
render: function(){
|
||||
|
||||
var items = ["request", "response", "details"].map(function(e){
|
||||
var str = e.charAt(0).toUpperCase() + e.slice(1);
|
||||
var className = this.props.active === e ? "active" : "";
|
||||
var onClick = function(){
|
||||
this.props.selectTab(e);
|
||||
return false;
|
||||
}.bind(this);
|
||||
return <a key={e}
|
||||
href="#"
|
||||
className={className}
|
||||
onClick={onClick}>{str}</a>;
|
||||
}.bind(this));
|
||||
return (
|
||||
<nav ref="head" className="nav-tabs nav-tabs-sm">
|
||||
{items}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var FlowDetailRequest = React.createClass({
|
||||
render: function(){
|
||||
return <div>request</div>;
|
||||
}
|
||||
});
|
||||
|
||||
var FlowDetailResponse = React.createClass({
|
||||
render: function(){
|
||||
return <div>response</div>;
|
||||
}
|
||||
});
|
||||
|
||||
var FlowDetailConnectionInfo = React.createClass({
|
||||
render: function(){
|
||||
return <div>details</div>;
|
||||
}
|
||||
})
|
||||
|
||||
var tabs = {
|
||||
request: FlowDetailRequest,
|
||||
response: FlowDetailResponse,
|
||||
details: FlowDetailConnectionInfo
|
||||
}
|
||||
|
||||
var FlowDetail = React.createClass({
|
||||
mixins: [StickyHeadMixin],
|
||||
render: function(){
|
||||
var flow = JSON.stringify(this.props.flow, null, 2);
|
||||
var Tab = tabs[this.props.active];
|
||||
return (
|
||||
<div className="flow-detail" onScroll={this.adjustHead}>
|
||||
<FlowDetailNav active={this.props.active} selectTab={this.props.selectTab}/>
|
||||
<Tab/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -10,7 +10,12 @@ var TLSColumn = React.createClass({
|
||||
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 classes = React.addons.classSet({
|
||||
"col-tls": true,
|
||||
"col-tls-https": ssl,
|
||||
"col-tls-http": !ssl
|
||||
});
|
||||
return <td className={classes}></td>;
|
||||
}
|
||||
});
|
||||
|
||||
@ -23,7 +28,7 @@ var IconColumn = React.createClass({
|
||||
},
|
||||
render: function(){
|
||||
var flow = this.props.flow;
|
||||
return <td className="resource-icon resource-icon-plain"></td>;
|
||||
return <td className="col-icon"><div className="resource-icon resource-icon-plain"></div></td>;
|
||||
}
|
||||
});
|
||||
|
||||
@ -35,7 +40,7 @@ var PathColumn = React.createClass({
|
||||
},
|
||||
render: function(){
|
||||
var flow = this.props.flow;
|
||||
return <td>{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>;
|
||||
return <td className="col-path">{flow.request.scheme + "://" + flow.request.host + flow.request.path}</td>;
|
||||
}
|
||||
});
|
||||
|
||||
@ -48,7 +53,7 @@ var MethodColumn = React.createClass({
|
||||
},
|
||||
render: function(){
|
||||
var flow = this.props.flow;
|
||||
return <td>{flow.request.method}</td>;
|
||||
return <td className="col-method">{flow.request.method}</td>;
|
||||
}
|
||||
});
|
||||
|
||||
@ -67,7 +72,7 @@ var StatusColumn = React.createClass({
|
||||
} else {
|
||||
status = null;
|
||||
}
|
||||
return <td>{status}</td>;
|
||||
return <td className="col-status">{status}</td>;
|
||||
}
|
||||
});
|
||||
|
||||
@ -86,7 +91,7 @@ var TimeColumn = React.createClass({
|
||||
} else {
|
||||
time = "...";
|
||||
}
|
||||
return <td>{time}</td>;
|
||||
return <td className="col-time">{time}</td>;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -4,10 +4,7 @@ var FlowRow = React.createClass({
|
||||
render: function(){
|
||||
var flow = this.props.flow;
|
||||
var columns = this.props.columns.map(function(column){
|
||||
return column({
|
||||
key: column.displayName,
|
||||
flow: flow
|
||||
});
|
||||
return <column key={column.displayName} flow={flow}/>;
|
||||
}.bind(this));
|
||||
var className = "";
|
||||
if(this.props.selected){
|
||||
@ -47,30 +44,13 @@ var FlowTableBody = React.createClass({
|
||||
|
||||
|
||||
var FlowTable = React.createClass({
|
||||
mixins: [StickyHeadMixin, AutoScrollMixin],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
flows: [],
|
||||
columns: all_columns
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.flowStore = FlowStore.getView();
|
||||
this.flowStore.addListener("change",this.onFlowChange);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
this.flowStore.removeListener("change",this.onFlowChange);
|
||||
this.flowStore.close();
|
||||
},
|
||||
onFlowChange: function () {
|
||||
this.setState({
|
||||
flows: this.flowStore.getAll()
|
||||
});
|
||||
},
|
||||
selectFlow: function(flow){
|
||||
this.setState({
|
||||
selected: flow
|
||||
});
|
||||
|
||||
scrollIntoView: function(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();
|
||||
@ -89,65 +69,59 @@ var FlowTable = React.createClass({
|
||||
viewport.scrollTop = flowNode_bottom - viewport.offsetHeight;
|
||||
}
|
||||
},
|
||||
selectRowRelative: function(i){
|
||||
selectFlowRelative: function(i){
|
||||
var index;
|
||||
if(!this.state.selected){
|
||||
if(!this.props.selected){
|
||||
if(i > 0){
|
||||
index = this.flows.length-1;
|
||||
index = this.props.flows.length-1;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
} else {
|
||||
index = _.findIndex(this.state.flows, function(f){
|
||||
return f === this.state.selected;
|
||||
index = _.findIndex(this.props.flows, function(f){
|
||||
return f === this.props.selected;
|
||||
}.bind(this));
|
||||
index = Math.min(Math.max(0, index+i), this.state.flows.length-1);
|
||||
index = Math.min(Math.max(0, index+i), this.props.flows.length-1);
|
||||
}
|
||||
this.selectFlow(this.state.flows[index]);
|
||||
this.props.selectFlow(this.props.flows[index]);
|
||||
},
|
||||
onKeyDown: function(e){
|
||||
switch(e.keyCode){
|
||||
case Key.DOWN:
|
||||
this.selectRowRelative(+1);
|
||||
return false;
|
||||
this.selectFlowRelative(+1);
|
||||
break;
|
||||
case Key.UP:
|
||||
this.selectRowRelative(-1);
|
||||
return false;
|
||||
this.selectFlowRelative(-1);
|
||||
break;
|
||||
case Key.ENTER:
|
||||
console.log("Open details pane...", this.state.selected);
|
||||
case Key.PAGE_DOWN:
|
||||
this.selectFlowRelative(+10);
|
||||
break;
|
||||
case Key.PAGE_UP:
|
||||
this.selectFlowRelative(-10);
|
||||
break;
|
||||
case Key.ESC:
|
||||
console.log("")
|
||||
this.props.selectFlow(null);
|
||||
break;
|
||||
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 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>
|
||||
<div className="flow-table" onScroll={this.adjustHead}>
|
||||
<table>
|
||||
<FlowTableHead ref="head"
|
||||
columns={this.state.columns}/>
|
||||
<FlowTableBody ref="body"
|
||||
flows={this.props.flows}
|
||||
selected={this.props.selected}
|
||||
selectFlow={this.props.selectFlow}
|
||||
columns={this.state.columns}
|
||||
onKeyDown={this.onKeyDown}/>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,10 @@
|
||||
/** @jsx React.DOM */
|
||||
|
||||
var MainMenu = React.createClass({
|
||||
statics: {
|
||||
title: "Traffic",
|
||||
route: "flows"
|
||||
},
|
||||
toggleEventLog: function () {
|
||||
SettingsActions.update({
|
||||
showEventLog: !this.props.settings.showEventLog
|
||||
@ -16,72 +20,74 @@ var MainMenu = React.createClass({
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var ToolsMenu = React.createClass({
|
||||
statics: {
|
||||
title: "Tools",
|
||||
route: "flows"
|
||||
},
|
||||
render: function () {
|
||||
return <div>Tools Menu</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var ReportsMenu = React.createClass({
|
||||
statics: {
|
||||
title: "Visualization",
|
||||
route: "reports"
|
||||
},
|
||||
render: function () {
|
||||
return <div>Reports Menu</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var _Header_Entries = {
|
||||
main: {
|
||||
title: "Traffic",
|
||||
route: "main",
|
||||
menu: MainMenu
|
||||
},
|
||||
tools: {
|
||||
title: "Tools",
|
||||
route: "main",
|
||||
menu: ToolsMenu
|
||||
},
|
||||
reports: {
|
||||
title: "Visualization",
|
||||
route: "reports",
|
||||
menu: ReportsMenu
|
||||
}
|
||||
};
|
||||
var header_entries = [MainMenu, ToolsMenu, ReportsMenu];
|
||||
|
||||
|
||||
var Header = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
active: "main"
|
||||
active: header_entries[0]
|
||||
};
|
||||
},
|
||||
handleClick: function (active) {
|
||||
ReactRouter.transitionTo(active.route);
|
||||
this.setState({active: active});
|
||||
ReactRouter.transitionTo(_Header_Entries[active].route);
|
||||
return false;
|
||||
},
|
||||
handleFileClick: function () {
|
||||
console.log("File click");
|
||||
},
|
||||
render: function () {
|
||||
var header = [];
|
||||
for (var item in _Header_Entries) {
|
||||
var classes = this.state.active == item ? "active" : "";
|
||||
header.push(<a key={item} href="#" className={classes}
|
||||
onClick={this.handleClick.bind(this, item)}>{ _Header_Entries[item].title }</a>);
|
||||
}
|
||||
|
||||
var menu = _Header_Entries[this.state.active].menu({
|
||||
settings: this.props.settings
|
||||
});
|
||||
var header = header_entries.map(function(entry){
|
||||
var classes = React.addons.classSet({
|
||||
active: entry == this.state.active
|
||||
});
|
||||
return (
|
||||
<a key={entry.title}
|
||||
href="#"
|
||||
className={classes}
|
||||
onClick={this.handleClick.bind(this, entry)}
|
||||
>
|
||||
{ entry.title}
|
||||
</a>
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
return (
|
||||
<header>
|
||||
<div className="title-bar">
|
||||
mitmproxy { this.props.settings.version }
|
||||
</div>
|
||||
<nav>
|
||||
<nav className="nav-tabs nav-tabs-lg">
|
||||
<a href="#" className="special" onClick={this.handleFileClick}> File </a>
|
||||
{header}
|
||||
</nav>
|
||||
<div className="menu">
|
||||
{ menu }
|
||||
<this.state.active settings={this.props.settings}/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
69
web/src/js/components/mainview.jsx.js
Normal file
69
web/src/js/components/mainview.jsx.js
Normal file
@ -0,0 +1,69 @@
|
||||
/** @jsx React.DOM */
|
||||
|
||||
var MainView = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
flows: [],
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
console.log("get view");
|
||||
this.flowStore = FlowStore.getView();
|
||||
this.flowStore.addListener("change",this.onFlowChange);
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
this.flowStore.removeListener("change",this.onFlowChange);
|
||||
this.flowStore.close();
|
||||
},
|
||||
onFlowChange: function () {
|
||||
this.setState({
|
||||
flows: this.flowStore.getAll()
|
||||
});
|
||||
},
|
||||
selectFlow: function(flow) {
|
||||
if(flow){
|
||||
ReactRouter.replaceWith(
|
||||
"flow",
|
||||
{
|
||||
flowId: flow.id,
|
||||
detailTab: this.props.params.detailTab || "request"
|
||||
}
|
||||
);
|
||||
this.refs.flowTable.scrollIntoView(flow);
|
||||
} else {
|
||||
ReactRouter.replaceWith("flows");
|
||||
}
|
||||
},
|
||||
selectDetailTab: function(panel) {
|
||||
ReactRouter.replaceWith(
|
||||
"flow",
|
||||
{
|
||||
flowId: this.props.params.flowId,
|
||||
detailTab: panel
|
||||
}
|
||||
);
|
||||
},
|
||||
render: function() {
|
||||
var selected = _.find(this.state.flows, { id: this.props.params.flowId });
|
||||
|
||||
var details = null;
|
||||
if(selected){
|
||||
details = (
|
||||
<FlowDetail ref="flowDetails"
|
||||
flow={selected}
|
||||
selectTab={this.selectDetailTab}
|
||||
active={this.props.params.detailTab}/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="main-view">
|
||||
<FlowTable ref="flowTable"
|
||||
flows={this.state.flows}
|
||||
selectFlow={this.selectFlow}
|
||||
selected={selected} />
|
||||
{details}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -26,7 +26,7 @@ var ProxyAppMain = React.createClass({
|
||||
return (
|
||||
<div id="container">
|
||||
<Header settings={this.state.settings}/>
|
||||
<this.props.activeRouteHandler/>
|
||||
<this.props.activeRouteHandler settings={this.state.settings}/>
|
||||
{this.state.settings.showEventLog ? <EventLog/> : null}
|
||||
<Footer settings={this.state.settings}/>
|
||||
</div>
|
||||
@ -35,12 +35,19 @@ var ProxyAppMain = React.createClass({
|
||||
});
|
||||
|
||||
|
||||
var Routes = ReactRouter.Routes;
|
||||
var Route = ReactRouter.Route;
|
||||
var Redirect = ReactRouter.Redirect;
|
||||
var DefaultRoute = ReactRouter.DefaultRoute;
|
||||
var NotFoundRoute = ReactRouter.NotFoundRoute;
|
||||
|
||||
|
||||
var ProxyApp = (
|
||||
<ReactRouter.Routes location="hash">
|
||||
<ReactRouter.Route name="app" path="/" handler={ProxyAppMain}>
|
||||
<ReactRouter.Route name="main" handler={FlowTable}/>
|
||||
<ReactRouter.Route name="reports" handler={Reports}/>
|
||||
<ReactRouter.Redirect to="main"/>
|
||||
</ReactRouter.Route>
|
||||
</ReactRouter.Routes>
|
||||
);
|
||||
<Routes location="hash">
|
||||
<Route path="/" handler={ProxyAppMain}>
|
||||
<Route name="flows" path="flows" handler={MainView}/>
|
||||
<Route name="flow" path="flows/:flowId/:detailTab" handler={MainView}/>
|
||||
<Route name="reports" handler={Reports}/>
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
@ -63,8 +63,8 @@ _.extend(_FlowStore.prototype, EventEmitter.prototype, {
|
||||
flows = flows.concat(_.cloneDeep(flows)).concat(_.cloneDeep(flows));
|
||||
var id = 1;
|
||||
flows.forEach(function(flow){
|
||||
flow.id = "uuid-"+id++;
|
||||
})
|
||||
flow.id = "uuid-" + id++;
|
||||
});
|
||||
view.add_bulk(flows);
|
||||
|
||||
});
|
||||
|
@ -12,12 +12,22 @@ var AutoScrollMixin = {
|
||||
},
|
||||
};
|
||||
|
||||
var StickyHeadMixin = {
|
||||
adjustHead: function(){
|
||||
// Abusing CSS transforms to set the element
|
||||
// referenced as head into some kind of position:sticky.
|
||||
var head = this.refs.head.getDOMNode();
|
||||
head.style.transform = "translate(0,"+this.getDOMNode().scrollTop+"px)";
|
||||
}
|
||||
};
|
||||
|
||||
var Key = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
LEFT: 37,
|
||||
RIGHT: 39,
|
||||
ENTER: 13,
|
||||
ESC: 27
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user