From 7afac747a8448ac524774a23c12ab23c9d4675f6 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 2 Jun 2016 23:40:30 -0700 Subject: [PATCH] web: reduxify event log store --- mitmproxy/web/__init__.py | 2 +- mitmproxy/web/static/app.js | 500 ++++++++++++++++++++++-------- web/package.json | 1 + web/src/js/app.js | 17 +- web/src/js/components/eventlog.js | 69 ++--- web/src/js/connection.js | 4 +- web/src/js/ducks/eventLog.js | 32 +- web/src/js/ducks/flows.js | 15 - web/src/js/ducks/index.js | 2 - web/src/js/ducks/list.js | 21 ++ web/src/js/ducks/websocket.js | 4 - 11 files changed, 462 insertions(+), 205 deletions(-) delete mode 100644 web/src/js/ducks/flows.js create mode 100644 web/src/js/ducks/list.js diff --git a/mitmproxy/web/__init__.py b/mitmproxy/web/__init__.py index 80a658863..5449c1bcc 100644 --- a/mitmproxy/web/__init__.py +++ b/mitmproxy/web/__init__.py @@ -73,7 +73,7 @@ class WebState(flow.State): } self.events.append(entry) app.ClientConnection.broadcast( - type="events", + type="UPDATE_EVENTLOG", cmd="add", data=entry ) diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index d97a567d4..2a5cc44eb 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -301,6 +301,235 @@ function isUndefined(arg) { },{}],2:[function(require,module,exports){ "use strict"; +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; } + +var repeat = function repeat(str, times) { + return new Array(times + 1).join(str); +}; +var pad = function pad(num, maxLength) { + return repeat("0", maxLength - num.toString().length) + num; +}; +var formatTime = function formatTime(time) { + return "@ " + pad(time.getHours(), 2) + ":" + pad(time.getMinutes(), 2) + ":" + pad(time.getSeconds(), 2) + "." + pad(time.getMilliseconds(), 3); +}; + +// Use the new performance api to get better precision if available +var timer = typeof performance !== "undefined" && typeof performance.now === "function" ? performance : Date; + +/** + * parse the level option of createLogger + * + * @property {string | function | object} level - console[level] + * @property {object} action + * @property {array} payload + * @property {string} type + */ + +function getLogLevel(level, action, payload, type) { + switch (typeof level === "undefined" ? "undefined" : _typeof(level)) { + case "object": + return typeof level[type] === "function" ? level[type].apply(level, _toConsumableArray(payload)) : level[type]; + case "function": + return level(action); + default: + return level; + } +} + +/** + * Creates logger with followed options + * + * @namespace + * @property {object} options - options for logger + * @property {string | function | object} options.level - console[level] + * @property {boolean} options.duration - print duration of each action? + * @property {boolean} options.timestamp - print timestamp with each action? + * @property {object} options.colors - custom colors + * @property {object} options.logger - implementation of the `console` API + * @property {boolean} options.logErrors - should errors in action execution be caught, logged, and re-thrown? + * @property {boolean} options.collapsed - is group collapsed? + * @property {boolean} options.predicate - condition which resolves logger behavior + * @property {function} options.stateTransformer - transform state before print + * @property {function} options.actionTransformer - transform action before print + * @property {function} options.errorTransformer - transform error before print + */ + +function createLogger() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var _options$level = options.level; + var level = _options$level === undefined ? "log" : _options$level; + var _options$logger = options.logger; + var logger = _options$logger === undefined ? console : _options$logger; + var _options$logErrors = options.logErrors; + var logErrors = _options$logErrors === undefined ? true : _options$logErrors; + var collapsed = options.collapsed; + var predicate = options.predicate; + var _options$duration = options.duration; + var duration = _options$duration === undefined ? false : _options$duration; + var _options$timestamp = options.timestamp; + var timestamp = _options$timestamp === undefined ? true : _options$timestamp; + var transformer = options.transformer; + var _options$stateTransfo = options.stateTransformer; + var // deprecated + stateTransformer = _options$stateTransfo === undefined ? function (state) { + return state; + } : _options$stateTransfo; + var _options$actionTransf = options.actionTransformer; + var actionTransformer = _options$actionTransf === undefined ? function (actn) { + return actn; + } : _options$actionTransf; + var _options$errorTransfo = options.errorTransformer; + var errorTransformer = _options$errorTransfo === undefined ? function (error) { + return error; + } : _options$errorTransfo; + var _options$colors = options.colors; + var colors = _options$colors === undefined ? { + title: function title() { + return "#000000"; + }, + prevState: function prevState() { + return "#9E9E9E"; + }, + action: function action() { + return "#03A9F4"; + }, + nextState: function nextState() { + return "#4CAF50"; + }, + error: function error() { + return "#F20404"; + } + } : _options$colors; + + // exit if console undefined + + if (typeof logger === "undefined") { + return function () { + return function (next) { + return function (action) { + return next(action); + }; + }; + }; + } + + if (transformer) { + console.error("Option 'transformer' is deprecated, use stateTransformer instead"); + } + + var logBuffer = []; + function printBuffer() { + logBuffer.forEach(function (logEntry, key) { + var started = logEntry.started; + var startedTime = logEntry.startedTime; + var action = logEntry.action; + var prevState = logEntry.prevState; + var error = logEntry.error; + var took = logEntry.took; + var nextState = logEntry.nextState; + + var nextEntry = logBuffer[key + 1]; + if (nextEntry) { + nextState = nextEntry.prevState; + took = nextEntry.started - started; + } + // message + var formattedAction = actionTransformer(action); + var isCollapsed = typeof collapsed === "function" ? collapsed(function () { + return nextState; + }, action) : collapsed; + + var formattedTime = formatTime(startedTime); + var titleCSS = colors.title ? "color: " + colors.title(formattedAction) + ";" : null; + var title = "action " + (timestamp ? formattedTime : "") + " " + formattedAction.type + " " + (duration ? "(in " + took.toFixed(2) + " ms)" : ""); + + // render + try { + if (isCollapsed) { + if (colors.title) logger.groupCollapsed("%c " + title, titleCSS);else logger.groupCollapsed(title); + } else { + if (colors.title) logger.group("%c " + title, titleCSS);else logger.group(title); + } + } catch (e) { + logger.log(title); + } + + var prevStateLevel = getLogLevel(level, formattedAction, [prevState], "prevState"); + var actionLevel = getLogLevel(level, formattedAction, [formattedAction], "action"); + var errorLevel = getLogLevel(level, formattedAction, [error, prevState], "error"); + var nextStateLevel = getLogLevel(level, formattedAction, [nextState], "nextState"); + + if (prevStateLevel) { + if (colors.prevState) logger[prevStateLevel]("%c prev state", "color: " + colors.prevState(prevState) + "; font-weight: bold", prevState);else logger[prevStateLevel]("prev state", prevState); + } + + if (actionLevel) { + if (colors.action) logger[actionLevel]("%c action", "color: " + colors.action(formattedAction) + "; font-weight: bold", formattedAction);else logger[actionLevel]("action", formattedAction); + } + + if (error && errorLevel) { + if (colors.error) logger[errorLevel]("%c error", "color: " + colors.error(error, prevState) + "; font-weight: bold", error);else logger[errorLevel]("error", error); + } + + if (nextStateLevel) { + if (colors.nextState) logger[nextStateLevel]("%c next state", "color: " + colors.nextState(nextState) + "; font-weight: bold", nextState);else logger[nextStateLevel]("next state", nextState); + } + + try { + logger.groupEnd(); + } catch (e) { + logger.log("—— log end ——"); + } + }); + logBuffer.length = 0; + } + + return function (_ref) { + var getState = _ref.getState; + return function (next) { + return function (action) { + // exit early if predicate function returns false + if (typeof predicate === "function" && !predicate(getState, action)) { + return next(action); + } + + var logEntry = {}; + logBuffer.push(logEntry); + + logEntry.started = timer.now(); + logEntry.startedTime = new Date(); + logEntry.prevState = stateTransformer(getState()); + logEntry.action = action; + + var returnedValue = undefined; + if (logErrors) { + try { + returnedValue = next(action); + } catch (e) { + logEntry.error = errorTransformer(e); + } + } else { + returnedValue = next(action); + } + + logEntry.took = timer.now() - logEntry.started; + logEntry.nextState = stateTransformer(getState()); + + printBuffer(); + + if (logEntry.error) throw logEntry.error; + return returnedValue; + }; + }; + }; +} + +module.exports = createLogger; +},{}],3:[function(require,module,exports){ +"use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); @@ -442,7 +671,7 @@ var Query = exports.Query = { SHOW_EVENTLOG: "e" }; -},{"./dispatcher.js":22,"jquery":"jquery","lodash":"lodash"}],3:[function(require,module,exports){ +},{"./dispatcher.js":23,"jquery":"jquery","lodash":"lodash"}],4:[function(require,module,exports){ 'use strict'; var _react = require('react'); @@ -455,29 +684,35 @@ var _redux = require('redux'); var _reactRedux = require('react-redux'); +var _reduxLogger = require('redux-logger'); + +var _reduxLogger2 = _interopRequireDefault(_reduxLogger); + var _connection = require('./connection'); var _connection2 = _interopRequireDefault(_connection); var _proxyapp = require('./components/proxyapp.js'); -var _actions = require('./actions.js'); - var _index = require('./ducks/index'); var _index2 = _interopRequireDefault(_index); +var _eventLog = require('./ducks/eventLog'); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -var store = (0, _redux.createStore)(_index2.default); +// logger must be last +var logger = (0, _reduxLogger2.default)(); +var store = (0, _redux.createStore)(_index2.default, (0, _redux.applyMiddleware)(logger)); + +window.onerror = function (msg) { + store.dispatch((0, _eventLog.addLogEntry)(msg)); +}; document.addEventListener('DOMContentLoaded', function () { window.ws = new _connection2.default("/updates", store.dispatch); - window.onerror = function (msg) { - _actions.EventLogActions.add_event(msg); - }; - (0, _reactDom.render)(_react2.default.createElement( _reactRedux.Provider, { store: store }, @@ -485,7 +720,7 @@ document.addEventListener('DOMContentLoaded', function () { ), document.getElementById("mitmproxy")); }); -},{"./actions.js":2,"./components/proxyapp.js":20,"./connection":21,"./ducks/index":25,"react":"react","react-dom":"react-dom","react-redux":"react-redux","redux":"redux"}],4:[function(require,module,exports){ +},{"./components/proxyapp.js":21,"./connection":22,"./ducks/eventLog":24,"./ducks/index":25,"react":"react","react-dom":"react-dom","react-redux":"react-redux","redux":"redux","redux-logger":2}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -704,7 +939,7 @@ ToggleInputButton.propTypes = { onToggleChanged: _react2.default.PropTypes.func.isRequired }; -},{"../utils.js":31,"lodash":"lodash","react":"react","react-dom":"react-dom"}],5:[function(require,module,exports){ +},{"../utils.js":32,"lodash":"lodash","react":"react","react-dom":"react-dom"}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -962,7 +1197,7 @@ var ValueEditor = exports.ValueEditor = _react2.default.createClass({ } }); -},{"../utils.js":31,"react":"react","react-dom":"react-dom"}],6:[function(require,module,exports){ +},{"../utils.js":32,"react":"react","react-dom":"react-dom"}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -994,8 +1229,6 @@ var _AutoScroll2 = _interopRequireDefault(_AutoScroll); var _VirtualScroll = require("./helpers/VirtualScroll"); -var _view = require("../store/view.js"); - var _common = require("./common"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -1006,22 +1239,35 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } +function LogIcon(_ref) { + var entry = _ref.entry; + + var icon = { web: "html5", debug: "bug" }[entry.level] || "info"; + return _react2.default.createElement("i", { className: "fa fa-fw fa-" + icon }); +} + +function LogEntry(_ref2) { + var entry = _ref2.entry; + + return _react2.default.createElement( + "div", + null, + _react2.default.createElement(LogIcon, { entry: entry }), + entry.message + ); +} + var EventLogContents = function (_React$Component) { _inherits(EventLogContents, _React$Component); - function EventLogContents(props, context) { + function EventLogContents(props) { _classCallCheck(this, EventLogContents); - var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(EventLogContents).call(this, props, context)); - - _this.view = new _view.StoreView(_this.context.eventStore, function (entry) { - return _this.props.filter[entry.level]; - }); + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(EventLogContents).call(this, props)); _this.heights = {}; - _this.state = { entries: _this.view.list, vScroll: (0, _VirtualScroll.calcVScroll)() }; + _this.state = { vScroll: (0, _VirtualScroll.calcVScroll)() }; - _this.onChange = _this.onChange.bind(_this); _this.onViewportUpdate = _this.onViewportUpdate.bind(_this); return _this; } @@ -1030,32 +1276,18 @@ var EventLogContents = function (_React$Component) { key: "componentDidMount", value: function componentDidMount() { window.addEventListener("resize", this.onViewportUpdate); - this.view.addListener("add", this.onChange); - this.view.addListener("recalculate", this.onChange); this.onViewportUpdate(); } }, { key: "componentWillUnmount", value: function componentWillUnmount() { window.removeEventListener("resize", this.onViewportUpdate); - this.view.removeListener("add", this.onChange); - this.view.removeListener("recalculate", this.onChange); - this.view.close(); } }, { key: "componentDidUpdate", value: function componentDidUpdate() { this.onViewportUpdate(); } - }, { - key: "componentWillReceiveProps", - value: function componentWillReceiveProps(nextProps) { - if (nextProps.filter !== this.props.filter) { - this.view.recalculate(function (entry) { - return nextProps.filter[entry.level]; - }); - } - } }, { key: "onViewportUpdate", value: function onViewportUpdate() { @@ -1064,11 +1296,11 @@ var EventLogContents = function (_React$Component) { var viewport = _reactDom2.default.findDOMNode(this); var vScroll = (0, _VirtualScroll.calcVScroll)({ - itemCount: this.state.entries.length, + itemCount: this.props.events.length, rowHeight: this.props.rowHeight, viewportTop: viewport.scrollTop, viewportHeight: viewport.offsetHeight, - itemHeights: this.state.entries.map(function (entry) { + itemHeights: this.props.events.map(function (entry) { return _this2.heights[entry.id]; }) }); @@ -1077,11 +1309,6 @@ var EventLogContents = function (_React$Component) { this.setState({ vScroll: vScroll }); } } - }, { - key: "onChange", - value: function onChange() { - this.setState({ entries: this.view.list }); - } }, { key: "setHeight", value: function setHeight(id, ref) { @@ -1093,31 +1320,21 @@ var EventLogContents = function (_React$Component) { } } } - }, { - key: "getIcon", - value: function getIcon(level) { - return { web: "html5", debug: "bug" }[level] || "info"; - } }, { key: "render", value: function render() { var _this3 = this; var vScroll = this.state.vScroll; - var entries = this.state.entries.slice(vScroll.start, vScroll.end); + var events = this.props.events.slice(vScroll.start, vScroll.end).map(function (entry) { + return _react2.default.createElement(LogEntry, { entry: entry, key: entry.id, ref: _this3.setHeight.bind(_this3, entry.id) }); + }); return _react2.default.createElement( "pre", { onScroll: this.onViewportUpdate }, _react2.default.createElement("div", { style: { height: vScroll.paddingTop } }), - entries.map(function (entry, index) { - return _react2.default.createElement( - "div", - { key: entry.id, ref: _this3.setHeight.bind(_this3, entry.id) }, - _react2.default.createElement("i", { className: "fa fa-fw fa-" + _this3.getIcon(entry.level) }), - entry.message - ); - }), + events, _react2.default.createElement("div", { style: { height: vScroll.paddingBottom } }) ); } @@ -1126,9 +1343,6 @@ var EventLogContents = function (_React$Component) { return EventLogContents; }(_react2.default.Component); -EventLogContents.contextTypes = { - eventStore: _react2.default.PropTypes.object.isRequired -}; EventLogContents.defaultProps = { rowHeight: 18 }; @@ -1138,7 +1352,7 @@ EventLogContents = (0, _AutoScroll2.default)(EventLogContents); var EventLogContentsContainer = (0, _reactRedux.connect)(function (state) { return { - filter: state.eventLog.filter + events: state.eventLog.filteredEvents }; })(EventLogContents); @@ -1166,8 +1380,8 @@ var ToggleFilter = (0, _reactRedux.connect)(function (state, ownProps) { }; })(_common.ToggleButton); -var EventLog = function EventLog(_ref) { - var close = _ref.close; +var EventLog = function EventLog(_ref3) { + var close = _ref3.close; return _react2.default.createElement( "div", { className: "eventlog" }, @@ -1202,7 +1416,7 @@ var EventLogContainer = (0, _reactRedux.connect)(undefined, function (dispatch) exports.default = EventLogContainer; -},{"../ducks/eventLog":23,"../store/view.js":30,"./common":4,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"react":"react","react-dom":"react-dom","react-redux":"react-redux","shallowequal":"shallowequal"}],7:[function(require,module,exports){ +},{"../ducks/eventLog":24,"./common":5,"./helpers/AutoScroll":17,"./helpers/VirtualScroll":18,"react":"react","react-dom":"react-dom","react-redux":"react-redux","shallowequal":"shallowequal"}],8:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1471,7 +1685,7 @@ var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn exports.default = all_columns; -},{"../flow/utils.js":28,"../utils.js":31,"react":"react"}],8:[function(require,module,exports){ +},{"../flow/utils.js":29,"../utils.js":32,"react":"react"}],9:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1763,7 +1977,7 @@ FlowTable.defaultProps = { }; exports.default = (0, _AutoScroll2.default)(FlowTable); -},{"../utils.js":31,"./flowtable-columns.js":7,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"classnames":"classnames","lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],9:[function(require,module,exports){ +},{"../utils.js":32,"./flowtable-columns.js":8,"./helpers/AutoScroll":17,"./helpers/VirtualScroll":18,"classnames":"classnames","lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],10:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2117,7 +2331,7 @@ var ContentView = _react2.default.createClass({ exports.default = ContentView; -},{"../../flow/utils.js":28,"../../utils.js":31,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ +},{"../../flow/utils.js":29,"../../utils.js":32,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2385,7 +2599,7 @@ var Details = _react2.default.createClass({ exports.default = Details; -},{"../../utils.js":31,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){ +},{"../../utils.js":32,"lodash":"lodash","react":"react"}],12:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2514,7 +2728,7 @@ var FlowView = _react2.default.createClass({ exports.default = FlowView; -},{"../prompt.js":19,"./details.js":10,"./messages.js":12,"./nav.js":13,"react":"react"}],12:[function(require,module,exports){ +},{"../prompt.js":20,"./details.js":11,"./messages.js":13,"./nav.js":14,"react":"react"}],13:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2879,7 +3093,7 @@ var Error = exports.Error = _react2.default.createClass({ } }); -},{"../../actions.js":2,"../../flow/utils.js":28,"../../utils.js":31,"../editor.js":5,"./contentview.js":9,"lodash":"lodash","react":"react","react-dom":"react-dom"}],13:[function(require,module,exports){ +},{"../../actions.js":3,"../../flow/utils.js":29,"../../utils.js":32,"../editor.js":6,"./contentview.js":10,"lodash":"lodash","react":"react","react-dom":"react-dom"}],14:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2960,7 +3174,7 @@ var Nav = _react2.default.createClass({ exports.default = Nav; -},{"../../actions.js":2,"react":"react"}],14:[function(require,module,exports){ +},{"../../actions.js":3,"react":"react"}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -3062,7 +3276,7 @@ function Footer(_ref) { ); } -},{"../utils.js":31,"./common.js":4,"react":"react"}],15:[function(require,module,exports){ +},{"../utils.js":32,"./common.js":5,"react":"react"}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -3605,7 +3819,7 @@ var Header = exports.Header = _react2.default.createClass({ } }); -},{"../actions.js":2,"../filt/filt.js":27,"../utils.js":31,"./common.js":4,"./eventlog":6,"jquery":"jquery","react":"react","react-dom":"react-dom","react-redux":"react-redux"}],16:[function(require,module,exports){ +},{"../actions.js":3,"../filt/filt.js":28,"../utils.js":32,"./common.js":5,"./eventlog":7,"jquery":"jquery","react":"react","react-dom":"react-dom","react-redux":"react-redux"}],17:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -3671,7 +3885,7 @@ exports.default = function (Component) { }(Component), _class.displayName = Component.name, _temp), Component); }; -},{"react":"react","react-dom":"react-dom"}],17:[function(require,module,exports){ +},{"react":"react","react-dom":"react-dom"}],18:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -3750,7 +3964,7 @@ function calcVScroll(opts) { return { start: start, end: end, paddingTop: paddingTop, paddingBottom: paddingBottom }; } -},{}],18:[function(require,module,exports){ +},{}],19:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4021,7 +4235,7 @@ var MainView = _react2.default.createClass({ exports.default = MainView; -},{"../actions.js":2,"../filt/filt.js":27,"../store/view.js":30,"../utils.js":31,"./common.js":4,"./flowtable.js":8,"./flowview/index.js":11,"react":"react"}],19:[function(require,module,exports){ +},{"../actions.js":3,"../filt/filt.js":28,"../store/view.js":31,"../utils.js":32,"./common.js":5,"./flowtable.js":9,"./flowview/index.js":12,"react":"react"}],20:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4157,7 +4371,7 @@ var Prompt = _react2.default.createClass({ exports.default = Prompt; -},{"../utils.js":31,"lodash":"lodash","react":"react","react-dom":"react-dom"}],20:[function(require,module,exports){ +},{"../utils.js":32,"lodash":"lodash","react":"react","react-dom":"react-dom"}],21:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4353,7 +4567,7 @@ var App = exports.App = _react2.default.createElement( ) ); -},{"../store/store.js":29,"../utils.js":31,"./common.js":4,"./eventlog.js":6,"./footer.js":14,"./header.js":15,"./mainview.js":18,"lodash":"lodash","react":"react","react-dom":"react-dom","react-redux":"react-redux","react-router":"react-router"}],21:[function(require,module,exports){ +},{"../store/store.js":30,"../utils.js":32,"./common.js":5,"./eventlog.js":7,"./footer.js":15,"./header.js":16,"./mainview.js":19,"lodash":"lodash","react":"react","react-dom":"react-dom","react-redux":"react-redux","react-router":"react-router"}],22:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4380,11 +4594,12 @@ function Connection(url, dispatch) { ws.onopen = function () { dispatch(websocketActions.connected()); _actions.ConnectionActions.open(); + //TODO: fetch stuff! }; ws.onmessage = function (m) { var message = JSON.parse(m.data); _dispatcher.AppDispatcher.dispatchServerAction(message); - dispatch(websocketActions.receiveMessage(message)); + dispatch(message); }; ws.onerror = function () { _actions.ConnectionActions.error(); @@ -4398,7 +4613,7 @@ function Connection(url, dispatch) { return ws; } -},{"./actions.js":2,"./dispatcher.js":22,"./ducks/websocket":26}],22:[function(require,module,exports){ +},{"./actions.js":3,"./dispatcher.js":23,"./ducks/websocket":27}],23:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4427,7 +4642,7 @@ AppDispatcher.dispatchServerAction = function (action) { this.dispatch(action); }; -},{"flux":"flux"}],23:[function(require,module,exports){ +},{"flux":"flux"}],24:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4439,11 +4654,19 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument exports.default = reducer; exports.toggleEventLogFilter = toggleEventLogFilter; exports.toggleEventLogVisibility = toggleEventLogVisibility; +exports.addLogEntry = addLogEntry; + +var _list = require('./list'); + +var _list2 = _interopRequireDefault(_list); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var TOGGLE_FILTER = 'TOGGLE_EVENTLOG_FILTER'; var TOGGLE_VISIBILITY = 'TOGGLE_EVENTLOG_VISIBILITY'; +var UPDATE_LIST = "UPDATE_EVENTLOG"; var defaultState = { visible: false, @@ -4451,21 +4674,36 @@ var defaultState = { "debug": false, "info": true, "web": true - } + }, + events: (0, _list2.default)(), + filteredEvents: [] }; + function reducer() { var state = arguments.length <= 0 || arguments[0] === undefined ? defaultState : arguments[0]; var action = arguments[1]; switch (action.type) { case TOGGLE_FILTER: + var filter = _extends({}, state.filter, _defineProperty({}, action.filter, !state.filter[action.filter])); return _extends({}, state, { - filter: _extends({}, state.filter, _defineProperty({}, action.filter, !state.filter[action.filter])) + filter: filter, + filteredEvents: state.events.list.filter(function (x) { + return filter[x.level]; + }) }); case TOGGLE_VISIBILITY: return _extends({}, state, { visible: !state.visible }); + case UPDATE_LIST: + var events = (0, _list2.default)(state.events, action); + return _extends({}, state, { + events: events, + filteredEvents: events.list.filter(function (x) { + return state.filter[x.level]; + }) + }); default: return state; } @@ -4477,34 +4715,18 @@ function toggleEventLogFilter(filter) { function toggleEventLogVisibility() { return { type: TOGGLE_VISIBILITY }; } +var id = 0; +function addLogEntry(message) { + var level = arguments.length <= 1 || arguments[1] === undefined ? "web" : arguments[1]; -},{}],24:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = reducer; -var defaultState = { - list: [], - isFetching: false, - updateBeforeFetch: [], - byId: {}, - indexOf: {}, - views: {} -}; - -function reducer() { - var state = arguments.length <= 0 || arguments[0] === undefined ? defaultState : arguments[0]; - var action = arguments[1]; - - switch (action.type) { - default: - return state; - } + return { + type: UPDATE_LIST, + cmd: _list.ADD, + data: { message: message, level: level, id: 'log-' + id++ } + }; } -},{}],25:[function(require,module,exports){ +},{"./list":26}],25:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4521,21 +4743,56 @@ var _websocket = require('./websocket.js'); var _websocket2 = _interopRequireDefault(_websocket); -var _flows = require('./flows.js'); - -var _flows2 = _interopRequireDefault(_flows); - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var rootReducer = (0, _redux.combineReducers)({ eventLog: _eventLog2.default, - flows: _flows2.default, websocket: _websocket2.default }); exports.default = rootReducer; -},{"./eventLog.js":23,"./flows.js":24,"./websocket.js":26,"redux":"redux"}],26:[function(require,module,exports){ +},{"./eventLog.js":24,"./websocket.js":27,"redux":"redux"}],26:[function(require,module,exports){ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +exports.default = getList; + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +var ADD = exports.ADD = 'add'; + +var defaultState = { + list: [], + //isFetching: false, + //updateBeforeFetch: [], + indexOf: {} +}; + +//views: {} +function getList() { + var state = arguments.length <= 0 || arguments[0] === undefined ? defaultState : arguments[0]; + var action = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + switch (action.cmd) { + case ADD: + return { + list: [].concat(_toConsumableArray(state.list), [action.data]), + indexOf: _extends({}, state.indexOf, _defineProperty({}, action.data.id, state.list.length)) + }; + default: + return state; + } +} + +},{}],27:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4544,10 +4801,8 @@ Object.defineProperty(exports, "__esModule", { exports.default = reducer; exports.connected = connected; exports.disconnected = disconnected; -exports.receiveMessage = receiveMessage; var CONNECTED = 'WEBSOCKET_CONNECTED'; var DISCONNECTED = 'WEBSOCKET_DISCONNECTED'; -var RECEIVE_MESSAGE = 'RECEIVE_WEBSOCKET_MESSAGE'; var defaultState = { connected: true @@ -4577,11 +4832,8 @@ function connected() { function disconnected() { return { type: DISCONNECTED }; } -function receiveMessage(message) { - return { type: RECEIVE_MESSAGE, message: message }; -} -},{}],27:[function(require,module,exports){ +},{}],28:[function(require,module,exports){ "use strict"; module.exports = function () { @@ -6485,7 +6737,7 @@ module.exports = function () { }; }(); -},{"../flow/utils.js":28}],28:[function(require,module,exports){ +},{"../flow/utils.js":29}],29:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -6619,7 +6871,7 @@ var parseHttpVersion = exports.parseHttpVersion = function parseHttpVersion(http }); }; -},{"jquery":"jquery","lodash":"lodash"}],29:[function(require,module,exports){ +},{"jquery":"jquery","lodash":"lodash"}],30:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -6806,7 +7058,7 @@ _lodash2.default.extend(EventLogStore.prototype, LiveListStore.prototype, { } }); -},{"../actions.js":2,"../dispatcher.js":22,"events":1,"jquery":"jquery","lodash":"lodash"}],30:[function(require,module,exports){ +},{"../actions.js":3,"../dispatcher.js":23,"events":1,"jquery":"jquery","lodash":"lodash"}],31:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -6936,7 +7188,7 @@ _lodash2.default.extend(StoreView.prototype, _events.EventEmitter.prototype, { } }); -},{"../utils.js":31,"events":1,"lodash":"lodash"}],31:[function(require,module,exports){ +},{"../utils.js":32,"events":1,"lodash":"lodash"}],32:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -7051,7 +7303,7 @@ _jquery2.default.ajaxPrefilter(function (options) { alert(message); }); -},{"./actions.js":2,"jquery":"jquery","lodash":"lodash","react":"react"}]},{},[3]) +},{"./actions.js":3,"jquery":"jquery","lodash":"lodash","react":"react"}]},{},[4]) //# sourceMappingURL=app.js.map diff --git a/web/package.json b/web/package.json index 4b58422c7..b7f6ab2a5 100644 --- a/web/package.json +++ b/web/package.json @@ -26,6 +26,7 @@ "react-redux": "^4.4.5", "react-router": "^2.4.0", "redux": "^3.5.2", + "redux-logger": "^2.6.1", "shallowequal": "^0.2.2" }, "devDependencies": { diff --git a/web/src/js/app.js b/web/src/js/app.js index f631b2c5d..fc99f1d2e 100644 --- a/web/src/js/app.js +++ b/web/src/js/app.js @@ -1,22 +1,25 @@ import React from "react" import {render} from 'react-dom' -import {createStore} from 'redux' +import {applyMiddleware, createStore} from 'redux' import {Provider} from 'react-redux' +import createLogger from 'redux-logger'; import Connection from "./connection" import {App} from "./components/proxyapp.js" -import {EventLogActions} from "./actions.js" import rootReducer from './ducks/index'; +import {addLogEntry} from "./ducks/eventLog"; -let store = createStore(rootReducer); +// logger must be last +const logger = createLogger(); +const store = createStore(rootReducer, applyMiddleware(logger)); + +window.onerror = function (msg) { + store.dispatch(addLogEntry(msg)); +}; document.addEventListener('DOMContentLoaded', () => { window.ws = new Connection("/updates", store.dispatch); - window.onerror = function (msg) { - EventLogActions.add_event(msg); - }; - render( {App}, document.getElementById("mitmproxy") diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js index a2e6a0c1d..0857056c4 100644 --- a/web/src/js/components/eventlog.js +++ b/web/src/js/components/eventlog.js @@ -5,69 +5,57 @@ import shallowEqual from "shallowequal" import {toggleEventLogFilter, toggleEventLogVisibility} from "../ducks/eventLog" import AutoScroll from "./helpers/AutoScroll"; import {calcVScroll} from "./helpers/VirtualScroll" -import {StoreView} from "../store/view.js" import {ToggleButton} from "./common"; -class EventLogContents extends React.Component { +function LogIcon({entry}) { + let icon = {web: "html5", debug: "bug"}[entry.level] || "info"; + return +} - static contextTypes = { - eventStore: React.PropTypes.object.isRequired, - }; +function LogEntry({entry}) { + return
+ + {entry.message} +
; +} + +class EventLogContents extends React.Component { static defaultProps = { rowHeight: 18, }; - constructor(props, context) { - super(props, context); - - this.view = new StoreView( - this.context.eventStore, - entry => this.props.filter[entry.level] - ); + constructor(props) { + super(props); this.heights = {}; - this.state = {entries: this.view.list, vScroll: calcVScroll()}; + this.state = {vScroll: calcVScroll()}; - this.onChange = this.onChange.bind(this); this.onViewportUpdate = this.onViewportUpdate.bind(this); } componentDidMount() { window.addEventListener("resize", this.onViewportUpdate); - this.view.addListener("add", this.onChange); - this.view.addListener("recalculate", this.onChange); this.onViewportUpdate(); } componentWillUnmount() { window.removeEventListener("resize", this.onViewportUpdate); - this.view.removeListener("add", this.onChange); - this.view.removeListener("recalculate", this.onChange); - this.view.close(); } componentDidUpdate() { this.onViewportUpdate(); } - componentWillReceiveProps(nextProps) { - if (nextProps.filter !== this.props.filter) { - this.view.recalculate( - entry => nextProps.filter[entry.level] - ); - } - } - onViewportUpdate() { const viewport = ReactDOM.findDOMNode(this); const vScroll = calcVScroll({ - itemCount: this.state.entries.length, + itemCount: this.props.events.length, rowHeight: this.props.rowHeight, viewportTop: viewport.scrollTop, viewportHeight: viewport.offsetHeight, - itemHeights: this.state.entries.map(entry => this.heights[entry.id]), + itemHeights: this.props.events.map(entry => this.heights[entry.id]), }); if (!shallowEqual(this.state.vScroll, vScroll)) { @@ -75,10 +63,6 @@ class EventLogContents extends React.Component { } } - onChange() { - this.setState({entries: this.view.list}); - } - setHeight(id, ref) { if (ref && !this.heights[id]) { const height = ReactDOM.findDOMNode(ref).offsetHeight; @@ -89,23 +73,18 @@ class EventLogContents extends React.Component { } } - getIcon(level) { - return {web: "html5", debug: "bug"}[level] || "info"; - } - render() { const vScroll = this.state.vScroll; - const entries = this.state.entries.slice(vScroll.start, vScroll.end); + const events = this.props.events + .slice(vScroll.start, vScroll.end) + .map(entry => + + ); return (
                 
- {entries.map((entry, index) => ( -
- - {entry.message} -
- ))} + {events}
); @@ -117,7 +96,7 @@ EventLogContents = AutoScroll(EventLogContents); const EventLogContentsContainer = connect( state => ({ - filter: state.eventLog.filter + events: state.eventLog.filteredEvents }) )(EventLogContents); diff --git a/web/src/js/connection.js b/web/src/js/connection.js index 71d20f466..75c2cf258 100644 --- a/web/src/js/connection.js +++ b/web/src/js/connection.js @@ -1,4 +1,3 @@ - import {ConnectionActions, EventLogActions} from "./actions.js"; import {AppDispatcher} from "./dispatcher.js"; import * as websocketActions from "./ducks/websocket" @@ -12,11 +11,12 @@ export default function Connection(url, dispatch) { ws.onopen = function () { dispatch(websocketActions.connected()); ConnectionActions.open(); + //TODO: fetch stuff! }; ws.onmessage = function (m) { var message = JSON.parse(m.data); AppDispatcher.dispatchServerAction(message); - dispatch(websocketActions.receiveMessage(message)); + dispatch(message); }; ws.onerror = function () { ConnectionActions.error(); diff --git a/web/src/js/ducks/eventLog.js b/web/src/js/ducks/eventLog.js index 5bae252a0..2040711cd 100644 --- a/web/src/js/ducks/eventLog.js +++ b/web/src/js/ducks/eventLog.js @@ -1,5 +1,7 @@ +import getList, {ADD} from "./list" const TOGGLE_FILTER = 'TOGGLE_EVENTLOG_FILTER' const TOGGLE_VISIBILITY = 'TOGGLE_EVENTLOG_VISIBILITY' +const UPDATE_LIST = "UPDATE_EVENTLOG" const defaultState = { @@ -8,23 +10,35 @@ const defaultState = { "debug": false, "info": true, "web": true - } + }, + events: getList(), + filteredEvents: [], } + export default function reducer(state = defaultState, action) { switch (action.type) { case TOGGLE_FILTER: + const filter = { + ...state.filter, + [action.filter]: !state.filter[action.filter] + } return { ...state, - filter: { - ...state.filter, - [action.filter]: !state.filter[action.filter] - } + filter, + filteredEvents: state.events.list.filter(x => filter[x.level]) } case TOGGLE_VISIBILITY: return { ...state, visible: !state.visible } + case UPDATE_LIST: + const events = getList(state.events, action) + return { + ...state, + events, + filteredEvents: events.list.filter(x => state.filter[x.level]) + } default: return state } @@ -36,4 +50,12 @@ export function toggleEventLogFilter(filter) { } export function toggleEventLogVisibility() { return {type: TOGGLE_VISIBILITY} +} +let id = 0; +export function addLogEntry(message, level = "web") { + return { + type: UPDATE_LIST, + cmd: ADD, + data: {message, level, id: `log-${id++}`} + } } \ No newline at end of file diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js deleted file mode 100644 index c4077f7a2..000000000 --- a/web/src/js/ducks/flows.js +++ /dev/null @@ -1,15 +0,0 @@ -const defaultState = { - list: [], - isFetching: false, - updateBeforeFetch: [], - byId: {}, - indexOf: {}, - views: {} -} - -export default function reducer(state = defaultState, action) { - switch (action.type) { - default: - return state - } -} diff --git a/web/src/js/ducks/index.js b/web/src/js/ducks/index.js index 0074bda47..3043344c8 100644 --- a/web/src/js/ducks/index.js +++ b/web/src/js/ducks/index.js @@ -1,11 +1,9 @@ import {combineReducers} from 'redux' import eventLog from './eventLog.js' import websocket from './websocket.js' -import flows from './flows.js' const rootReducer = combineReducers({ eventLog, - flows, websocket, }) diff --git a/web/src/js/ducks/list.js b/web/src/js/ducks/list.js new file mode 100644 index 000000000..0b3771e2c --- /dev/null +++ b/web/src/js/ducks/list.js @@ -0,0 +1,21 @@ +export const ADD = 'add' + +const defaultState = { + list: [], + //isFetching: false, + //updateBeforeFetch: [], + indexOf: {}, + //views: {} +}; + +export default function getList(state = defaultState, action = {}) { + switch (action.cmd) { + case ADD: + return { + list: [...state.list, action.data], + indexOf: {...state.indexOf, [action.data.id]: state.list.length}, + } + default: + return state + } +} \ No newline at end of file diff --git a/web/src/js/ducks/websocket.js b/web/src/js/ducks/websocket.js index 281d1f2cd..3999dbcf1 100644 --- a/web/src/js/ducks/websocket.js +++ b/web/src/js/ducks/websocket.js @@ -1,6 +1,5 @@ const CONNECTED = 'WEBSOCKET_CONNECTED' const DISCONNECTED = 'WEBSOCKET_DISCONNECTED' -const RECEIVE_MESSAGE = 'RECEIVE_WEBSOCKET_MESSAGE' const defaultState = { @@ -28,7 +27,4 @@ export function connected() { } export function disconnected() { return {type: DISCONNECTED} -} -export function receiveMessage(message) { - return {type: RECEIVE_MESSAGE, message} } \ No newline at end of file