aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2014-11-28 19:16:47 +0100
committerMaximilian Hils <git@maximilianhils.com>2014-11-28 19:16:47 +0100
commitc39b6e4277357c9da1dfd5e3e8c41b5b3427e0ce (patch)
tree21a713fb0974242dc0cf7023eac1dfda7865419c
parent7ca1ac0f3b7856c0ae44bfbf3b27ae4a424a1cc2 (diff)
downloadmitmproxy-c39b6e4277357c9da1dfd5e3e8c41b5b3427e0ce.tar.gz
mitmproxy-c39b6e4277357c9da1dfd5e3e8c41b5b3427e0ce.tar.bz2
mitmproxy-c39b6e4277357c9da1dfd5e3e8c41b5b3427e0ce.zip
web: various fixes, add clear button
-rw-r--r--libmproxy/web/__init__.py2
-rw-r--r--libmproxy/web/app.py14
-rw-r--r--libmproxy/web/static/js/app.js105
-rw-r--r--web/src/js/components/flowdetail.jsx.js5
-rw-r--r--web/src/js/components/flowtable.jsx.js25
-rw-r--r--web/src/js/components/header.jsx.js9
-rw-r--r--web/src/js/components/mainview.jsx.js3
-rw-r--r--web/src/js/components/utils.jsx.js17
-rw-r--r--web/src/js/stores/flowstore.js46
9 files changed, 156 insertions, 70 deletions
diff --git a/libmproxy/web/__init__.py b/libmproxy/web/__init__.py
index f762466a..a110aa4d 100644
--- a/libmproxy/web/__init__.py
+++ b/libmproxy/web/__init__.py
@@ -27,7 +27,7 @@ class WebFlowView(flow.FlowView):
def _recalculate(self, flows):
super(WebFlowView, self)._recalculate(flows)
- app.FlowUpdates.broadcast("recalculate", None)
+ app.FlowUpdates.broadcast("reset", None)
class WebState(flow.State):
diff --git a/libmproxy/web/app.py b/libmproxy/web/app.py
index 4fdff783..05ca7e79 100644
--- a/libmproxy/web/app.py
+++ b/libmproxy/web/app.py
@@ -1,4 +1,5 @@
import os.path
+import sys
import tornado.web
import tornado.websocket
import logging
@@ -8,6 +9,7 @@ from .. import flow
class IndexHandler(tornado.web.RequestHandler):
def get(self):
+ _ = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645
self.render("index.html")
@@ -35,13 +37,18 @@ class WebSocketEventBroadcaster(tornado.websocket.WebSocketHandler):
logging.error("Error sending message", exc_info=True)
-class FlowsHandler(tornado.web.RequestHandler):
+class Flows(tornado.web.RequestHandler):
def get(self):
self.write(dict(
flows=[f.get_state(short=True) for f in self.application.state.flows]
))
+class FlowClear(tornado.web.RequestHandler):
+ def post(self):
+ self.application.state.clear()
+
+
class FlowUpdates(WebSocketEventBroadcaster):
connections = set()
@@ -56,14 +63,15 @@ class Application(tornado.web.Application):
handlers = [
(r"/", IndexHandler),
(r"/updates", ClientConnection),
- (r"/flows", FlowsHandler),
+ (r"/flows", Flows),
+ (r"/flows/clear", FlowClear),
(r"/flows/updates", FlowUpdates),
]
settings = dict(
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=True,
- cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
+ cookie_secret=os.urandom(256),
debug=debug,
)
tornado.web.Application.__init__(self, handlers, **settings)
diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js
index dd1259f4..64d6ba44 100644
--- a/libmproxy/web/static/js/app.js
+++ b/libmproxy/web/static/js/app.js
@@ -386,7 +386,8 @@ _.extend(FlowStore.prototype, {
function LiveFlowStore(endpoint) {
FlowStore.call(this);
- this.updates_before_init = []; // (empty array is true in js)
+ this.updates_before_fetch = undefined;
+ this._fetchxhr = false;
this.endpoint = endpoint || "/flows";
this.conn = new Connection(this.endpoint + "/updates");
this.conn.onopen = this._onopen.bind(this);
@@ -401,33 +402,46 @@ _.extend(LiveFlowStore.prototype, FlowStore.prototype, {
},
add: function (flow) {
// Make sure that deferred adds don't add an element twice.
- if (!this._pos_map[flow.id]) {
+ if (!(flow.id in this._pos_map)) {
FlowStore.prototype.add.call(this, flow);
}
},
+ _onopen: function () {
+ //Update stream openend, fetch list of flows.
+ console.log("Update Connection opened, fetching flows...");
+ this.fetch();
+ },
+ fetch: function () {
+ if (this._fetchxhr) {
+ this._fetchxhr.abort();
+ }
+ this._fetchxhr = $.getJSON(this.endpoint, this.handle_fetch.bind(this));
+ this.updates_before_fetch = []; // (JS: empty array is true)
+ },
handle_update: function (type, data) {
console.log("LiveFlowStore.handle_update", type, data);
- if (this.updates_before_init) {
+
+ if (type === "reset") {
+ return this.fetch();
+ }
+
+ if (this.updates_before_fetch) {
console.log("defer update", type, data);
- this.updates_before_init.push(arguments);
+ this.updates_before_fetch.push(arguments);
} else {
this[type](data);
}
},
handle_fetch: function (data) {
+ this._fetchxhr = false;
console.log("Flows fetched.");
this.reset(data.flows);
- var updates = this.updates_before_init;
- this.updates_before_init = false;
+ var updates = this.updates_before_fetch;
+ this.updates_before_fetch = false;
for (var i = 0; i < updates.length; i++) {
this.handle_update.apply(this, updates[i]);
}
},
- _onopen: function () {
- //Update stream openend, fetch list of flows.
- console.log("Update Connection opened, fetching flows...");
- $.getJSON(this.endpoint, this.handle_fetch.bind(this));
- },
});
function SortByInsertionOrder() {
@@ -471,20 +485,22 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {
//Ugly workaround: Call .sortfun() for each flow once in order,
//so that SortByInsertionOrder make sense.
- var i = flows.length;
- while(i--){
+ for(var i = 0; i < flows.length; i++) {
this.sortfun(flows[i]);
}
this.flows = flows.filter(this.filt);
this.flows.sort(function (a, b) {
- return this.sortfun(b) - this.sortfun(a);
+ return this.sortfun(a) - this.sortfun(b);
}.bind(this));
this.emit("recalculate");
},
+ index: function (flow) {
+ return _.sortedIndex(this.flows, flow, this.sortfun);
+ },
add: function (flow) {
if (this.filt(flow)) {
- var idx = _.sortedIndex(this.flows, flow, this.sortfun);
+ var idx = this.index(flow);
if (idx === this.flows.length) { //happens often, .push is way faster.
this.flows.push(flow);
} else {
@@ -665,6 +681,23 @@ var Splitter = React.createClass({displayName: 'Splitter',
);
}
});
+
+function getCookie(name) {
+ var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+ return r ? r[1] : undefined;
+}
+var xsrf = $.param({_xsrf: getCookie("_xsrf")});
+
+//Tornado XSRF Protection.
+$.ajaxPrefilter(function(options){
+ if(options.type === "post" && options.url[0] === "/"){
+ if(options.data){
+ options.data += ("&" + xsrf);
+ } else {
+ options.data = xsrf;
+ }
+ }
+});
var MainMenu = React.createClass({displayName: 'MainMenu',
statics: {
title: "Traffic",
@@ -675,12 +708,17 @@ var MainMenu = React.createClass({displayName: 'MainMenu',
showEventLog: !this.props.settings.showEventLog
});
},
+ clearFlows: function(){
+ $.post("/flows/clear");
+ },
render: function () {
return (
React.createElement("div", null,
React.createElement("button", {className: "btn " + (this.props.settings.showEventLog ? "btn-primary" : "btn-default"), onClick: this.toggleEventLog},
- React.createElement("i", {className: "fa fa-database"}),
- "Display Event Log"
+ React.createElement("i", {className: "fa fa-database"}), " Display Event Log"
+ ), " ",
+ React.createElement("button", {className: "btn btn-default", onClick: this.clearFlows},
+ React.createElement("i", {className: "fa fa-eraser"}), " Clear Flows"
)
)
);
@@ -999,20 +1037,23 @@ var FlowTable = React.createClass({displayName: 'FlowTable',
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();
+ var thead_height = this.refs.body.getDOMNode().offsetTop;
+
+ var flow_top = (this.props.view.index(flow) * ROW_HEIGHT) + thead_height;
+
var viewport_top = viewport.scrollTop;
var viewport_bottom = viewport_top + viewport.offsetHeight;
- var flowNode_top = flowNode.offsetTop;
- var flowNode_bottom = flowNode_top + flowNode.offsetHeight;
+ var flow_bottom = flow_top + ROW_HEIGHT;
- // Account for pinned thead by pretending that the flowNode starts
- // -thead_height pixel earlier.
- flowNode_top -= this.refs.body.getDOMNode().offsetTop;
+ // Account for pinned thead
- if (flowNode_top < viewport_top) {
- viewport.scrollTop = flowNode_top;
- } else if (flowNode_bottom > viewport_bottom) {
- viewport.scrollTop = flowNode_bottom - viewport.offsetHeight;
+
+ console.log("scrollInto", flow_top, flow_bottom, viewport_top, viewport_bottom, thead_height);
+
+ if (flow_top - thead_height < viewport_top) {
+ viewport.scrollTop = flow_top - thead_height;
+ } else if (flow_bottom > viewport_bottom) {
+ viewport.scrollTop = flow_bottom - viewport.offsetHeight;
}
},
render: function () {
@@ -1050,7 +1091,7 @@ var FlowTable = React.createClass({displayName: 'FlowTable',
React.createElement("table", null,
React.createElement(FlowTableHead, {ref: "head",
columns: this.state.columns}),
- React.createElement("tbody", null,
+ React.createElement("tbody", {ref: "body"},
React.createElement("tr", {style: {height: space_top}}),
fix_nth_row,
rows,
@@ -1068,9 +1109,9 @@ var FlowDetailNav = React.createClass({displayName: 'FlowDetailNav',
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 (e) {
+ var onClick = function (event) {
this.props.selectTab(e);
- e.preventDefault();
+ event.preventDefault();
}.bind(this);
return React.createElement("a", {key: e,
href: "#",
@@ -1366,7 +1407,6 @@ var FlowDetail = React.createClass({displayName: 'FlowDetail',
);
},
render: function () {
- var flow = JSON.stringify(this.props.flow, null, 2);
var Tab = tabs[this.props.active];
return (
React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead},
@@ -1416,8 +1456,7 @@ var MainView = React.createClass({displayName: 'MainView',
detailTab: this.getParams().detailTab || "request"
}
);
- console.log("TODO: Scroll into view");
- //this.refs.flowTable.scrollIntoView(flow);
+ this.refs.flowTable.scrollIntoView(flow);
} else {
this.replaceWith("flows");
}
diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js
index 5c4168a9..74522f57 100644
--- a/web/src/js/components/flowdetail.jsx.js
+++ b/web/src/js/components/flowdetail.jsx.js
@@ -4,9 +4,9 @@ var FlowDetailNav = React.createClass({
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 (e) {
+ var onClick = function (event) {
this.props.selectTab(e);
- e.preventDefault();
+ event.preventDefault();
}.bind(this);
return <a key={e}
href="#"
@@ -302,7 +302,6 @@ var FlowDetail = React.createClass({
);
},
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}>
diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js
index 76ceea41..6d0f5ee7 100644
--- a/web/src/js/components/flowtable.jsx.js
+++ b/web/src/js/components/flowtable.jsx.js
@@ -83,20 +83,23 @@ var FlowTable = React.createClass({
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();
+ var thead_height = this.refs.body.getDOMNode().offsetTop;
+
+ var flow_top = (this.props.view.index(flow) * ROW_HEIGHT) + thead_height;
+
var viewport_top = viewport.scrollTop;
var viewport_bottom = viewport_top + viewport.offsetHeight;
- var flowNode_top = flowNode.offsetTop;
- var flowNode_bottom = flowNode_top + flowNode.offsetHeight;
+ var flow_bottom = flow_top + ROW_HEIGHT;
+
+ // Account for pinned thead
+
- // Account for pinned thead by pretending that the flowNode starts
- // -thead_height pixel earlier.
- flowNode_top -= this.refs.body.getDOMNode().offsetTop;
+ console.log("scrollInto", flow_top, flow_bottom, viewport_top, viewport_bottom, thead_height);
- if (flowNode_top < viewport_top) {
- viewport.scrollTop = flowNode_top;
- } else if (flowNode_bottom > viewport_bottom) {
- viewport.scrollTop = flowNode_bottom - viewport.offsetHeight;
+ if (flow_top - thead_height < viewport_top) {
+ viewport.scrollTop = flow_top - thead_height;
+ } else if (flow_bottom > viewport_bottom) {
+ viewport.scrollTop = flow_bottom - viewport.offsetHeight;
}
},
render: function () {
@@ -134,7 +137,7 @@ var FlowTable = React.createClass({
<table>
<FlowTableHead ref="head"
columns={this.state.columns}/>
- <tbody>
+ <tbody ref="body">
<tr style={{height: space_top}}></tr>
{ fix_nth_row }
{rows}
diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js
index e50c4274..a3fe4d51 100644
--- a/web/src/js/components/header.jsx.js
+++ b/web/src/js/components/header.jsx.js
@@ -8,12 +8,17 @@ var MainMenu = React.createClass({
showEventLog: !this.props.settings.showEventLog
});
},
+ clearFlows: function(){
+ $.post("/flows/clear");
+ },
render: function () {
return (
<div>
<button className={"btn " + (this.props.settings.showEventLog ? "btn-primary" : "btn-default")} onClick={this.toggleEventLog}>
- <i className="fa fa-database"></i>
- Display Event Log
+ <i className="fa fa-database"></i>&nbsp;Display Event Log
+ </button>&nbsp;
+ <button className="btn btn-default" onClick={this.clearFlows}>
+ <i className="fa fa-eraser"></i>&nbsp;Clear Flows
</button>
</div>
);
diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js
index fd9fdb8d..fe5d1c7c 100644
--- a/web/src/js/components/mainview.jsx.js
+++ b/web/src/js/components/mainview.jsx.js
@@ -35,8 +35,7 @@ var MainView = React.createClass({
detailTab: this.getParams().detailTab || "request"
}
);
- console.log("TODO: Scroll into view");
- //this.refs.flowTable.scrollIntoView(flow);
+ this.refs.flowTable.scrollIntoView(flow);
} else {
this.replaceWith("flows");
}
diff --git a/web/src/js/components/utils.jsx.js b/web/src/js/components/utils.jsx.js
index b1d9a006..12775adc 100644
--- a/web/src/js/components/utils.jsx.js
+++ b/web/src/js/components/utils.jsx.js
@@ -95,4 +95,21 @@ var Splitter = React.createClass({
</div>
);
}
+});
+
+function getCookie(name) {
+ var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+ return r ? r[1] : undefined;
+}
+var xsrf = $.param({_xsrf: getCookie("_xsrf")});
+
+//Tornado XSRF Protection.
+$.ajaxPrefilter(function(options){
+ if(options.type === "post" && options.url[0] === "/"){
+ if(options.data){
+ options.data += ("&" + xsrf);
+ } else {
+ options.data = xsrf;
+ }
+ }
}); \ No newline at end of file
diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js
index 37eb40eb..cc7318a2 100644
--- a/web/src/js/stores/flowstore.js
+++ b/web/src/js/stores/flowstore.js
@@ -45,7 +45,8 @@ _.extend(FlowStore.prototype, {
function LiveFlowStore(endpoint) {
FlowStore.call(this);
- this.updates_before_init = []; // (empty array is true in js)
+ this.updates_before_fetch = undefined;
+ this._fetchxhr = false;
this.endpoint = endpoint || "/flows";
this.conn = new Connection(this.endpoint + "/updates");
this.conn.onopen = this._onopen.bind(this);
@@ -60,33 +61,46 @@ _.extend(LiveFlowStore.prototype, FlowStore.prototype, {
},
add: function (flow) {
// Make sure that deferred adds don't add an element twice.
- if (!this._pos_map[flow.id]) {
+ if (!(flow.id in this._pos_map)) {
FlowStore.prototype.add.call(this, flow);
}
},
+ _onopen: function () {
+ //Update stream openend, fetch list of flows.
+ console.log("Update Connection opened, fetching flows...");
+ this.fetch();
+ },
+ fetch: function () {
+ if (this._fetchxhr) {
+ this._fetchxhr.abort();
+ }
+ this._fetchxhr = $.getJSON(this.endpoint, this.handle_fetch.bind(this));
+ this.updates_before_fetch = []; // (JS: empty array is true)
+ },
handle_update: function (type, data) {
console.log("LiveFlowStore.handle_update", type, data);
- if (this.updates_before_init) {
+
+ if (type === "reset") {
+ return this.fetch();
+ }
+
+ if (this.updates_before_fetch) {
console.log("defer update", type, data);
- this.updates_before_init.push(arguments);
+ this.updates_before_fetch.push(arguments);
} else {
this[type](data);
}
},
handle_fetch: function (data) {
+ this._fetchxhr = false;
console.log("Flows fetched.");
this.reset(data.flows);
- var updates = this.updates_before_init;
- this.updates_before_init = false;
+ var updates = this.updates_before_fetch;
+ this.updates_before_fetch = false;
for (var i = 0; i < updates.length; i++) {
this.handle_update.apply(this, updates[i]);
}
},
- _onopen: function () {
- //Update stream openend, fetch list of flows.
- console.log("Update Connection opened, fetching flows...");
- $.getJSON(this.endpoint, this.handle_fetch.bind(this));
- },
});
function SortByInsertionOrder() {
@@ -130,20 +144,22 @@ _.extend(FlowView.prototype, EventEmitter.prototype, {
//Ugly workaround: Call .sortfun() for each flow once in order,
//so that SortByInsertionOrder make sense.
- var i = flows.length;
- while(i--){
+ for(var i = 0; i < flows.length; i++) {
this.sortfun(flows[i]);
}
this.flows = flows.filter(this.filt);
this.flows.sort(function (a, b) {
- return this.sortfun(b) - this.sortfun(a);
+ return this.sortfun(a) - this.sortfun(b);
}.bind(this));
this.emit("recalculate");
},
+ index: function (flow) {
+ return _.sortedIndex(this.flows, flow, this.sortfun);
+ },
add: function (flow) {
if (this.filt(flow)) {
- var idx = _.sortedIndex(this.flows, flow, this.sortfun);
+ var idx = this.index(flow);
if (idx === this.flows.length) { //happens often, .push is way faster.
this.flows.push(flow);
} else {