From 1b327f34c30e490e6f26ec1ee4e6cfe7b40a47eb Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 3 Jun 2016 18:38:03 -0700 Subject: [PATCH] web: add redux list views --- mitmproxy/web/static/app.js | 194 ++++++++++++++++++++------------- web/src/js/ducks/eventLog.js | 16 ++- web/src/js/ducks/utils/list.js | 6 +- web/src/js/ducks/utils/view.js | 58 ++++++++++ 4 files changed, 191 insertions(+), 83 deletions(-) create mode 100644 web/src/js/ducks/utils/view.js diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index 83fd0c58a..a485bfca0 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -304,7 +304,7 @@ function isUndefined(arg) { Object.defineProperty(exports, "__esModule", { value: true }); -exports.Query = exports.FlowActions = exports.EventLogActions = exports.SettingsActions = exports.ConnectionActions = exports.StoreCmds = exports.ActionTypes = undefined; +exports.Query = exports.FlowActions = exports.SettingsActions = exports.ConnectionActions = exports.StoreCmds = exports.ActionTypes = undefined; var _jquery = require("jquery"); @@ -374,21 +374,6 @@ var SettingsActions = exports.SettingsActions = { } }; -var EventLogActions_event_id = 0; -var EventLogActions = exports.EventLogActions = { - add_event: function add_event(message) { - _dispatcher.AppDispatcher.dispatchViewAction({ - type: ActionTypes.EVENT_STORE, - cmd: StoreCmds.ADD, - data: { - message: message, - level: "web", - id: "viewAction-" + EventLogActions_event_id++ - } - }); - } -}; - var FlowActions = exports.FlowActions = { accept: function accept(flow) { _jquery2.default.post("/flows/" + flow.id + "/accept"); @@ -452,7 +437,7 @@ var Query = exports.Query = { SHOW_EVENTLOG: "e" }; -},{"./dispatcher.js":22,"./utils.js":31,"jquery":"jquery"}],3:[function(require,module,exports){ +},{"./dispatcher.js":22,"./utils.js":32,"jquery":"jquery"}],3:[function(require,module,exports){ 'use strict'; var _react = require('react'); @@ -724,7 +709,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"}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -982,7 +967,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"}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1477,7 +1462,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"}],8:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1769,7 +1754,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":7,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"classnames":"classnames","lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],9:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2123,7 +2108,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"}],10:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2391,7 +2376,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"}],11:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2885,7 +2870,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":2,"../../flow/utils.js":29,"../../utils.js":32,"../editor.js":5,"./contentview.js":9,"lodash":"lodash","react":"react","react-dom":"react-dom"}],13:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -3068,7 +3053,7 @@ function Footer(_ref) { ); } -},{"../utils.js":31,"./common.js":4,"react":"react"}],15:[function(require,module,exports){ +},{"../utils.js":32,"./common.js":4,"react":"react"}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -3643,7 +3628,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":2,"../filt/filt.js":28,"../utils.js":32,"./common.js":4,"./eventlog":6,"jquery":"jquery","react":"react","react-dom":"react-dom","react-redux":"react-redux"}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4059,7 +4044,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":2,"../filt/filt.js":28,"../store/view.js":31,"../utils.js":32,"./common.js":4,"./flowtable.js":8,"./flowview/index.js":11,"react":"react"}],19:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4195,7 +4180,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"}],20:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4259,7 +4244,6 @@ var ProxyAppMain = _react2.default.createClass({ childContextTypes: { flowStore: _react2.default.PropTypes.object.isRequired, - eventStore: _react2.default.PropTypes.object.isRequired, returnFocus: _react2.default.PropTypes.func.isRequired, location: _react2.default.PropTypes.object.isRequired }, @@ -4298,13 +4282,11 @@ var ProxyAppMain = _react2.default.createClass({ getChildContext: function getChildContext() { return { flowStore: this.state.flowStore, - eventStore: this.state.eventStore, returnFocus: this.focus, location: this.props.location }; }, getInitialState: function getInitialState() { - var eventStore = new _store.EventLogStore(); var flowStore = new _store.FlowStore(); var settingsStore = new _store.SettingsStore(); @@ -4313,8 +4295,7 @@ var ProxyAppMain = _react2.default.createClass({ _lodash2.default.extend(settingsStore.dict, {}); return { settings: settingsStore.dict, - flowStore: flowStore, - eventStore: eventStore + flowStore: flowStore }; }, focus: function focus() { @@ -4391,7 +4372,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":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){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4437,17 +4418,17 @@ function Connection(url, dispatch) { }; ws.onerror = function () { _actions.ConnectionActions.error(); - _actions.EventLogActions.add_event("WebSocket connection error."); + dispatch(eventLogActions.addLogEntry("WebSocket connection error.")); }; ws.onclose = function () { _actions.ConnectionActions.close(); - _actions.EventLogActions.add_event("WebSocket connection closed."); - dispatch(websocketActions.disconnected()); + dispatch(eventLogActions.addLogEntry("WebSocket connection closed.")); + dispatch(webSocketActions.disconnected()); }; return ws; } -},{"./actions.js":2,"./dispatcher.js":22,"./ducks/eventLog":23,"./ducks/websocket":26}],22:[function(require,module,exports){ +},{"./actions.js":2,"./dispatcher.js":22,"./ducks/eventLog":23,"./ducks/websocket":27}],22:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -4477,7 +4458,7 @@ AppDispatcher.dispatchServerAction = function (action) { }; },{"flux":"flux"}],23:[function(require,module,exports){ -'use strict'; +"use strict"; Object.defineProperty(exports, "__esModule", { value: true @@ -4491,10 +4472,12 @@ exports.toggleEventLogFilter = toggleEventLogFilter; exports.toggleEventLogVisibility = toggleEventLogVisibility; exports.addLogEntry = addLogEntry; -var _list = require('./utils/list'); +var _list = require("./utils/list"); var _list2 = _interopRequireDefault(_list); +var _view = require("./utils/view"); + 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; } @@ -4509,8 +4492,6 @@ var reduceList = _makeList.reduceList; var addToList = _makeList.addToList; var updateList = _makeList.updateList; var fetchList = _makeList.fetchList; -exports.updateLogEntries = updateList; -exports.fetchLogEntries = fetchList; var defaultState = { @@ -4533,7 +4514,7 @@ function reducer() { var filter = _extends({}, state.filter, _defineProperty({}, action.filter, !state.filter[action.filter])); return _extends({}, state, { filter: filter, - filteredEvents: state.events.list.filter(function (x) { + filteredEvents: (0, _view.updateViewFilter)(state.events.list, function (x) { return filter[x.level]; }) }); @@ -4545,7 +4526,7 @@ function reducer() { var events = reduceList(state.events, action); return _extends({}, state, { events: events, - filteredEvents: events.list.filter(function (x) { + filteredEvents: (0, _view.updateViewList)(state.filteredEvents, events, action, function (x) { return state.filter[x.level]; }) }); @@ -4567,11 +4548,13 @@ function addLogEntry(message) { return addToList({ message: message, level: level, - id: 'log-' + id++ + id: "log-" + id++ }); } +exports.updateLogEntries = updateList; +exports.fetchLogEntries = fetchList; -},{"./utils/list":25}],24:[function(require,module,exports){ +},{"./utils/list":25,"./utils/view":26}],24:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4597,12 +4580,13 @@ var rootReducer = (0, _redux.combineReducers)({ exports.default = rootReducer; -},{"./eventLog.js":23,"./websocket.js":26,"redux":"redux"}],25:[function(require,module,exports){ +},{"./eventLog.js":23,"./websocket.js":27,"redux":"redux"}],25:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +exports.RECEIVE_LIST = exports.REQUEST_LIST = exports.ADD = undefined; 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; }; @@ -4614,9 +4598,9 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope 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 = "ADD"; -var REQUEST_LIST = "REQUEST_LIST"; -var RECEIVE_LIST = "RECEIVE_LIST"; +var ADD = exports.ADD = "ADD"; +var REQUEST_LIST = exports.REQUEST_LIST = "REQUEST_LIST"; +var RECEIVE_LIST = exports.RECEIVE_LIST = "RECEIVE_LIST"; var defaultState = { list: [], @@ -4754,7 +4738,84 @@ function makeList(actionType, fetchURL) { return { reduceList: reduceList, addToList: addToList, updateList: updateList, fetchList: fetchList }; } -},{"../../utils":31}],26:[function(require,module,exports){ +},{"../../utils":32}],26:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.updateViewList = updateViewList; +exports.updateViewFilter = updateViewFilter; + +var _list = require("./list"); + +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 defaultFilterFn = function defaultFilterFn(x) { + return true; +}; +var defaultSortFn = false; + +var makeCompareFn = function makeCompareFn(sortFn) { + var compareFn = function compareFn(a, b) { + var akey = sortFn(a), + bkey = sortFn(b); + if (akey < bkey) { + return -1; + } else if (akey > bkey) { + return 1; + } else { + return 0; + } + }; + if (sortFn.reverse) return function (a, b) { + return compareFn(b, a); + }; + return compareFn; +}; + +var sortedInsert = function sortedInsert(list, sortFn, item) { + var l = [].concat(_toConsumableArray(list), [item]); + var compareFn = makeCompareFn(sortFn); + + // only sort if sorting order is not correct yet + if (sortFn && compareFn(list[list.length - 1], item) > 0) { + // TODO: This is untested + console.debug("sorting view..."); + l.sort(compareFn); + } + return l; +}; + +// for when the list changes +function updateViewList(state, nextList, action) { + var filterFn = arguments.length <= 3 || arguments[3] === undefined ? defaultFilterFn : arguments[3]; + var sortFn = arguments.length <= 4 || arguments[4] === undefined ? defaultSortFn : arguments[4]; + + switch (action.cmd) { + case _list.REQUEST_LIST: + return state; + case _list.RECEIVE_LIST: + return updateViewFilter(nextList.list, filterFn, sortFn); + case _list.ADD: + if (filterFn(action.item)) return sortedInsert(state, sortFn, action.item); + return state; + default: + console.error("Unknown list action: ", action); + return state; + } +} + +function updateViewFilter(list) { + var filterFn = arguments.length <= 1 || arguments[1] === undefined ? defaultFilterFn : arguments[1]; + var sortFn = arguments.length <= 2 || arguments[2] === undefined ? defaultSortFn : arguments[2]; + + var filtered = list.filter(filterFn); + if (sortFn) filtered.sort(makeCompareFn(sortFn)); + return filtered; +} + +},{"./list":25}],27:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { @@ -4795,7 +4856,7 @@ function disconnected() { return { type: DISCONNECTED }; } -},{}],27:[function(require,module,exports){ +},{}],28:[function(require,module,exports){ "use strict"; module.exports = function () { @@ -6699,7 +6760,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", { @@ -6833,7 +6894,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", { @@ -6841,7 +6902,6 @@ Object.defineProperty(exports, "__esModule", { }); exports.FlowStore = FlowStore; exports.SettingsStore = SettingsStore; -exports.EventLogStore = EventLogStore; var _lodash = require("lodash"); @@ -6967,7 +7027,7 @@ _lodash2.default.extend(LiveStoreMixin.prototype, { this._fetchxhr = _jquery2.default.getJSON("/" + this.type).done(function (message) { this.handle_fetch(message.data); }.bind(this)).fail(function () { - _actions.EventLogActions.add_event("Could not fetch " + this.type); + console.error("Could not fetch " + this.type); }.bind(this)); } }, @@ -7003,24 +7063,7 @@ function SettingsStore() { return new LiveDictStore(_actions.ActionTypes.SETTINGS_STORE); } -function EventLogStore() { - LiveListStore.call(this, _actions.ActionTypes.EVENT_STORE); -} -_lodash2.default.extend(EventLogStore.prototype, LiveListStore.prototype, { - fetch: function fetch() { - LiveListStore.prototype.fetch.apply(this, arguments); - - // Make sure to display updates even if fetching all events failed. - // This way, we can send "fetch failed" log messages to the log. - if (this._fetchxhr) { - this._fetchxhr.fail(function () { - this.handle_fetch(null); - }.bind(this)); - } - } -}); - -},{"../actions.js":2,"../dispatcher.js":22,"events":1,"jquery":"jquery","lodash":"lodash"}],30:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":22,"events":1,"jquery":"jquery","lodash":"lodash"}],31:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -7150,7 +7193,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", { @@ -7265,7 +7308,6 @@ _jquery2.default.ajaxPrefilter(function (options) { } var message = jqXHR.responseText; console.error(thrownError, message, arguments); - _actions2.default.EventLogActions.add_event(thrownError + ": " + message); alert(message); }); diff --git a/web/src/js/ducks/eventLog.js b/web/src/js/ducks/eventLog.js index 081a22762..883f537cb 100644 --- a/web/src/js/ducks/eventLog.js +++ b/web/src/js/ducks/eventLog.js @@ -1,4 +1,5 @@ import makeList, {ADD} from "./utils/list" +import {updateViewFilter, updateViewList} from "./utils/view" const TOGGLE_FILTER = 'TOGGLE_EVENTLOG_FILTER' const TOGGLE_VISIBILITY = 'TOGGLE_EVENTLOG_VISIBILITY' @@ -11,7 +12,6 @@ const { fetchList, } = makeList(UPDATE_LOG, "/events"); -export {updateList as updateLogEntries, fetchList as fetchLogEntries} const defaultState = { visible: false, @@ -34,7 +34,10 @@ export default function reducer(state = defaultState, action) { return { ...state, filter, - filteredEvents: state.events.list.filter(x => filter[x.level]) + filteredEvents: updateViewFilter( + state.events.list, + x => filter[x.level] + ) } case TOGGLE_VISIBILITY: return { @@ -46,7 +49,11 @@ export default function reducer(state = defaultState, action) { return { ...state, events, - filteredEvents: events.list.filter(x => state.filter[x.level]) + filteredEvents: updateViewList( + state.filteredEvents, + events, action, + x => state.filter[x.level] + ) } default: return state @@ -67,4 +74,5 @@ export function addLogEntry(message, level = "web") { level, id: `log-${id++}` }) -} \ No newline at end of file +} +export {updateList as updateLogEntries, fetchList as fetchLogEntries} \ No newline at end of file diff --git a/web/src/js/ducks/utils/list.js b/web/src/js/ducks/utils/list.js index 37b2ae3a6..b750982e7 100644 --- a/web/src/js/ducks/utils/list.js +++ b/web/src/js/ducks/utils/list.js @@ -1,8 +1,8 @@ import {fetchApi} from "../../utils"; -const ADD = "ADD" -const REQUEST_LIST = "REQUEST_LIST" -const RECEIVE_LIST = "RECEIVE_LIST" +export const ADD = "ADD" +export const REQUEST_LIST = "REQUEST_LIST" +export const RECEIVE_LIST = "RECEIVE_LIST" const defaultState = { diff --git a/web/src/js/ducks/utils/view.js b/web/src/js/ducks/utils/view.js new file mode 100644 index 000000000..4b265f89c --- /dev/null +++ b/web/src/js/ducks/utils/view.js @@ -0,0 +1,58 @@ +import {ADD, REQUEST_LIST, RECEIVE_LIST} from "./list" + +const defaultFilterFn = x => true +const defaultSortFn = false + +const makeCompareFn = sortFn => { + let compareFn = (a, b) => { + let akey = sortFn(a), + bkey = sortFn(b); + if (akey < bkey) { + return -1; + } else if (akey > bkey) { + return 1; + } else { + return 0; + } + } + if (sortFn.reverse) + return (a, b) => compareFn(b, a) + return compareFn +} + +const sortedInsert = (list, sortFn, item) => { + let l = [...list, item] + let compareFn = makeCompareFn(sortFn) + + // only sort if sorting order is not correct yet + if (sortFn && compareFn(list[list.length - 1], item) > 0) { + // TODO: This is untested + console.debug("sorting view...") + l.sort(compareFn) + } + return l +} + +// for when the list changes +export function updateViewList(state, nextList, action, filterFn = defaultFilterFn, sortFn = defaultSortFn) { + switch (action.cmd) { + case REQUEST_LIST: + return state + case RECEIVE_LIST: + return updateViewFilter(nextList.list, filterFn, sortFn) + case ADD: + if (filterFn(action.item)) + return sortedInsert(state, sortFn, action.item) + return state + default: + console.error("Unknown list action: ", action); + return state + } +} + +export function updateViewFilter(list, filterFn = defaultFilterFn, sortFn = defaultSortFn) { + let filtered = list.filter(filterFn) + if (sortFn) + filtered.sort(makeCompareFn(sortFn)) + return filtered +} \ No newline at end of file