diff --git a/libmproxy/main.py b/libmproxy/main.py index a3c91c021..2d6a01196 100644 --- a/libmproxy/main.py +++ b/libmproxy/main.py @@ -220,7 +220,6 @@ def mitmweb(): # pragma: nocover from . import web check_versions() - assert_utf8_env() web_options, proxy_config = mitmweb_cmdline() server = get_server(web_options.no_server, proxy_config) diff --git a/libmproxy/web/__init__.py b/libmproxy/web/__init__.py index 50b41b80a..83a7bde4b 100644 --- a/libmproxy/web/__init__.py +++ b/libmproxy/web/__init__.py @@ -101,7 +101,7 @@ class WebMaster(flow.FlowMaster): def handle_log(self, l): app.ClientConnection.broadcast( - "event", { + "add_event", { "message": l.msg, "level": l.level } diff --git a/libmproxy/web/static/css/app.css b/libmproxy/web/static/css/app.css index e5b9d1346..6dd17c7db 100644 --- a/libmproxy/web/static/css/app.css +++ b/libmproxy/web/static/css/app.css @@ -69,22 +69,11 @@ header .menu { } .eventlog { flex: 0 0 auto; -} -.eventlog pre { margin: 0; border-radius: 0; height: 200px; overflow: auto; -} -.eventlog .close-button { - float: right; - margin: -9px; - padding: 4px; - cursor: pointer; - color: grey; -} -.eventlog .close-button:hover { - color: black; + overflow-y: scroll; } footer { box-shadow: 0 -1px 3px #d3d3d3; diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js index 84eece870..5bc8de0d7 100644 --- a/libmproxy/web/static/js/app.js +++ b/libmproxy/web/static/js/app.js @@ -1,6 +1,20 @@ +// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) +var AutoScrollMixin = { + componentWillUpdate: function () { + var node = this.getDOMNode(); + this._shouldScrollBottom = node.scrollTop + node.clientHeight === node.scrollHeight; + }, + componentDidUpdate: function () { + if (this._shouldScrollBottom) { + var node = this.getDOMNode(); + node.scrollTop = node.scrollHeight; + } + }, +}; + const PayloadSources = { - VIEW_ACTION: "VIEW_ACTION", - SERVER_ACTION: "SERVER_ACTION" + VIEW: "view", + SERVER: "server" }; @@ -26,17 +40,17 @@ Dispatcher.prototype.dispatch = function (payload) { AppDispatcher = new Dispatcher(); AppDispatcher.dispatchViewAction = function (action) { - action.actionSource = PayloadSources.VIEW_ACTION; + action.source = PayloadSources.VIEW; this.dispatch(action); }; AppDispatcher.dispatchServerAction = function (action) { - action.actionSource = PayloadSources.SERVER_ACTION; + action.source = PayloadSources.SERVER; this.dispatch(action); }; var ActionTypes = { - SETTINGS_UPDATE: "SETTINGS_UPDATE", - EVENTLOG_ADD: "EVENTLOG_ADD" + UPDATE_SETTINGS: "update_settings", + ADD_EVENT: "add_event" }; var SettingsActions = { @@ -46,7 +60,7 @@ var SettingsActions = { //Facebook Flux: We do an optimistic update on the client already. AppDispatcher.dispatchViewAction({ - actionType: ActionTypes.SETTINGS_UPDATE, + type: ActionTypes.UPDATE_SETTINGS, settings: settings }); } @@ -59,8 +73,9 @@ EventEmitter.prototype.emit = function (event) { if (!(event in this.listeners)) { return; } + var args = Array.prototype.slice.call(arguments, 1); this.listeners[event].forEach(function (listener) { - listener.apply(this, arguments); + listener.apply(this, args); }.bind(this)); }; EventEmitter.prototype.addListener = function (event, f) { @@ -92,8 +107,8 @@ _.extend(_SettingsStore.prototype, EventEmitter.prototype, { return this.settings; }, handle: function (action) { - switch (action.actionType) { - case ActionTypes.SETTINGS_UPDATE: + switch (action.type) { + case ActionTypes.UPDATE_SETTINGS: this.settings = action.settings; this.emit("change"); break; @@ -115,19 +130,19 @@ AppDispatcher.register(SettingsStore.handle.bind(SettingsStore)); // See also: components/EventLog.react.js function EventLogView(store, live) { EventEmitter.call(this); - this.$EventLogView_store = store; + this._store = store; this.live = live; this.log = []; this.add = this.add.bind(this); if (live) { - this.$EventLogView_store.addListener("new_entry", this.add); + this._store.addListener(ActionTypes.ADD_EVENT, this.add); } } _.extend(EventLogView.prototype, EventEmitter.prototype, { close: function () { - this.$EventLogView_store.removeListener("new_entry", this.add); + this._store.removeListener(ActionTypes.ADD_EVENT, this.add); }, getAll: function () { return this.log; @@ -154,7 +169,8 @@ function _EventLogStore() { _.extend(_EventLogStore.prototype, EventEmitter.prototype, { getView: function (since) { var view = new EventLogView(this, !since); - + return view; + /* //TODO: Really do bulk retrieval of last messages. window.setTimeout(function () { view.add_bulk([ @@ -185,11 +201,12 @@ _.extend(_EventLogStore.prototype, EventEmitter.prototype, { }); }, 1000); return view; + */ }, handle: function (action) { - switch (action.actionType) { - case ActionTypes.EVENTLOG_ADD: - this.emit("new_message", action.message); + switch (action.type) { + case ActionTypes.ADD_EVENT: + this.emit(ActionTypes.ADD_EVENT, action.data); break; default: return; @@ -222,14 +239,7 @@ _Connection.prototype.onopen = function (open) { _Connection.prototype.onmessage = function (message) { //AppDispatcher.dispatchServerAction(...); var m = JSON.parse(message.data); - switch (m.type){ - case "flow": - console.log("flow", m.data); - break; - case "event": - console.log("event", m.data.message) - break; - } + AppDispatcher.dispatchServerAction(m); }; _Connection.prototype.onerror = function (error) { console.log("onerror", this, arguments); @@ -368,6 +378,7 @@ var TrafficTable = React.createClass({displayName: 'TrafficTable', /** @jsx React.DOM */ var EventLog = React.createClass({displayName: 'EventLog', + mixins:[AutoScrollMixin], getInitialState: function () { return { log: [] @@ -392,16 +403,10 @@ var EventLog = React.createClass({displayName: 'EventLog', }); }, render: function () { - //var messages = this.state.log.map(row => (
{row.message}
)); - var messages = []; - return ( - React.DOM.div({className: "eventlog"}, - React.DOM.pre(null, - React.DOM.i({className: "fa fa-close close-button", onClick: this.close}), - messages - ) - ) - ); + var messages = this.state.log.map(function(row) { + return (React.DOM.div({key: row.id}, row.message)); + }); + return React.DOM.pre({className: "eventlog"}, messages); } }); @@ -447,7 +452,7 @@ var ProxyAppMain = React.createClass({displayName: 'ProxyAppMain', React.DOM.div({id: "container"}, Header({settings: this.state.settings}), React.DOM.div({id: "main"}, this.props.activeRouteHandler(null)), - this.state.settings.showEventLog ? EventLog(null) : null, + this.state.settings.showEventLog ? EventLog(null) : null, Footer({settings: this.state.settings}) ) ); diff --git a/web/gulpfile.js b/web/gulpfile.js index 4130012f3..be44081d2 100644 --- a/web/gulpfile.js +++ b/web/gulpfile.js @@ -32,6 +32,7 @@ var path = { 'vendor/react-bootstrap/react-bootstrap.js' ], app: [ + 'js/utils.js', 'js/dispatcher.js', 'js/actions.js', 'js/stores/base.js', diff --git a/web/src/css/eventlog.less b/web/src/css/eventlog.less index 6b0c0e615..993304cfb 100644 --- a/web/src/css/eventlog.less +++ b/web/src/css/eventlog.less @@ -2,21 +2,9 @@ flex: 0 0 auto; - pre { - margin: 0; - border-radius: 0; - height: 200px; - overflow: auto; - } - - .close-button { - float: right; - margin: -9px; - padding: 4px; - cursor: pointer; - color: grey; - &:hover { - color: black; - } - } + margin: 0; + border-radius: 0; + height: 200px; + overflow: auto; + overflow-y: scroll; } \ No newline at end of file diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 35cb83da6..e55a1d162 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -1,6 +1,6 @@ var ActionTypes = { - SETTINGS_UPDATE: "SETTINGS_UPDATE", - EVENTLOG_ADD: "EVENTLOG_ADD" + UPDATE_SETTINGS: "update_settings", + ADD_EVENT: "add_event" }; var SettingsActions = { @@ -10,7 +10,7 @@ var SettingsActions = { //Facebook Flux: We do an optimistic update on the client already. AppDispatcher.dispatchViewAction({ - actionType: ActionTypes.SETTINGS_UPDATE, + type: ActionTypes.UPDATE_SETTINGS, settings: settings }); } diff --git a/web/src/js/components/eventlog.jsx b/web/src/js/components/eventlog.jsx index 340de8bd1..32fc01eed 100644 --- a/web/src/js/components/eventlog.jsx +++ b/web/src/js/components/eventlog.jsx @@ -1,6 +1,7 @@ /** @jsx React.DOM */ var EventLog = React.createClass({ + mixins:[AutoScrollMixin], getInitialState: function () { return { log: [] @@ -25,15 +26,9 @@ var EventLog = React.createClass({ }); }, render: function () { - //var messages = this.state.log.map(row => (
{row.message}
)); - var messages = []; - return ( -
-
-                    
-                    {messages}
-                
-
- ); + var messages = this.state.log.map(function(row) { + return (
{row.message}
); + }); + return
{messages}
; } }); diff --git a/web/src/js/components/proxyapp.jsx b/web/src/js/components/proxyapp.jsx index fff1d8d02..2f1a98616 100644 --- a/web/src/js/components/proxyapp.jsx +++ b/web/src/js/components/proxyapp.jsx @@ -27,7 +27,7 @@ var ProxyAppMain = React.createClass({
- {this.state.settings.showEventLog ? : null} + {this.state.settings.showEventLog ? : null}
); diff --git a/web/src/js/connection.js b/web/src/js/connection.js index a4369b5ad..5d464e528 100644 --- a/web/src/js/connection.js +++ b/web/src/js/connection.js @@ -19,14 +19,7 @@ _Connection.prototype.onopen = function (open) { _Connection.prototype.onmessage = function (message) { //AppDispatcher.dispatchServerAction(...); var m = JSON.parse(message.data); - switch (m.type){ - case "flow": - console.log("flow", m.data); - break; - case "event": - console.log("event", m.data.message) - break; - } + AppDispatcher.dispatchServerAction(m); }; _Connection.prototype.onerror = function (error) { console.log("onerror", this, arguments); diff --git a/web/src/js/dispatcher.js b/web/src/js/dispatcher.js index 028ac4dc1..65c4a4a02 100644 --- a/web/src/js/dispatcher.js +++ b/web/src/js/dispatcher.js @@ -1,6 +1,6 @@ const PayloadSources = { - VIEW_ACTION: "VIEW_ACTION", - SERVER_ACTION: "SERVER_ACTION" + VIEW: "view", + SERVER: "server" }; @@ -26,10 +26,10 @@ Dispatcher.prototype.dispatch = function (payload) { AppDispatcher = new Dispatcher(); AppDispatcher.dispatchViewAction = function (action) { - action.actionSource = PayloadSources.VIEW_ACTION; + action.source = PayloadSources.VIEW; this.dispatch(action); }; AppDispatcher.dispatchServerAction = function (action) { - action.actionSource = PayloadSources.SERVER_ACTION; + action.source = PayloadSources.SERVER; this.dispatch(action); }; diff --git a/web/src/js/stores/base.js b/web/src/js/stores/base.js index 9a2dbd64c..952fa8474 100644 --- a/web/src/js/stores/base.js +++ b/web/src/js/stores/base.js @@ -5,8 +5,9 @@ EventEmitter.prototype.emit = function (event) { if (!(event in this.listeners)) { return; } + var args = Array.prototype.slice.call(arguments, 1); this.listeners[event].forEach(function (listener) { - listener.apply(this, arguments); + listener.apply(this, args); }.bind(this)); }; EventEmitter.prototype.addListener = function (event, f) { diff --git a/web/src/js/stores/eventlogstore.js b/web/src/js/stores/eventlogstore.js index 54c459397..976f75243 100644 --- a/web/src/js/stores/eventlogstore.js +++ b/web/src/js/stores/eventlogstore.js @@ -7,19 +7,19 @@ // See also: components/EventLog.react.js function EventLogView(store, live) { EventEmitter.call(this); - this.$EventLogView_store = store; + this._store = store; this.live = live; this.log = []; this.add = this.add.bind(this); if (live) { - this.$EventLogView_store.addListener("new_entry", this.add); + this._store.addListener(ActionTypes.ADD_EVENT, this.add); } } _.extend(EventLogView.prototype, EventEmitter.prototype, { close: function () { - this.$EventLogView_store.removeListener("new_entry", this.add); + this._store.removeListener(ActionTypes.ADD_EVENT, this.add); }, getAll: function () { return this.log; @@ -46,7 +46,8 @@ function _EventLogStore() { _.extend(_EventLogStore.prototype, EventEmitter.prototype, { getView: function (since) { var view = new EventLogView(this, !since); - + return view; + /* //TODO: Really do bulk retrieval of last messages. window.setTimeout(function () { view.add_bulk([ @@ -77,11 +78,12 @@ _.extend(_EventLogStore.prototype, EventEmitter.prototype, { }); }, 1000); return view; + */ }, handle: function (action) { - switch (action.actionType) { - case ActionTypes.EVENTLOG_ADD: - this.emit("new_message", action.message); + switch (action.type) { + case ActionTypes.ADD_EVENT: + this.emit(ActionTypes.ADD_EVENT, action.data); break; default: return; diff --git a/web/src/js/stores/settingstore.js b/web/src/js/stores/settingstore.js index 0ba7d8004..7eef9b8ff 100644 --- a/web/src/js/stores/settingstore.js +++ b/web/src/js/stores/settingstore.js @@ -13,8 +13,8 @@ _.extend(_SettingsStore.prototype, EventEmitter.prototype, { return this.settings; }, handle: function (action) { - switch (action.actionType) { - case ActionTypes.SETTINGS_UPDATE: + switch (action.type) { + case ActionTypes.UPDATE_SETTINGS: this.settings = action.settings; this.emit("change"); break; diff --git a/web/src/js/utils.js b/web/src/js/utils.js new file mode 100644 index 000000000..39ad92fb1 --- /dev/null +++ b/web/src/js/utils.js @@ -0,0 +1,13 @@ +// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) +var AutoScrollMixin = { + componentWillUpdate: function () { + var node = this.getDOMNode(); + this._shouldScrollBottom = node.scrollTop + node.clientHeight === node.scrollHeight; + }, + componentDidUpdate: function () { + if (this._shouldScrollBottom) { + var node = this.getDOMNode(); + node.scrollTop = node.scrollHeight; + } + }, +};